How to get original image size in Dart Image.network? - image

The following code gives Division by null error,
#override
Widget build(BuildContext context) {
Image image = Image.network(data['image-url']);
double widthToHeight = image.width / image.height;
if(widthToHeight <= 0.2) {
return NewsTileSmall(data: data);
} else {
return NewsTileLarge(data: data);
}
}
Best if, I could use a function to set the value of double widthToHeight.

In your build method you need to preload image.
Image image = new Image.network('image-url');
Completer<ui.Image> completer = new Completer<ui.Image>();
image.image
.resolve(new ImageConfiguration())
.addListener(ImageStreamListener(ImageInfo info, bool _) {
completer.complete(info.image));
})
Then you need to use FutureBuilder
FutureBuilder<ui.Image>(
future: completer.future,
builder: (BuildContext context, AsyncSnapshot<ui.Image> snapshot) {
if (snapshot.hasData){
return your image Widget}
else{
return placeholder widget
}
completed build method
import 'dart:ui' as ui;
Image image = Image.network('image url');
final completer = Completer<ui.Image>();
image.image
.resolve(const ImageConfiguration()).
addListener(ImageStreamListener((ImageInfo info, bool syncCall) => completer.complete(info.image)));
return Scaffold(
appBar: AppBar(
title: const Text("Image Dimensions Example"),
),
body: FutureBuilder<ui.Image>(
future: completer.future,
builder: (BuildContext context, AsyncSnapshot<ui.Image> snapshot) {
if (snapshot.hasData) {
return Text(
'${snapshot.data.width}x${snapshot.data.height}',
);
} else {
return const Text('Loading...');
}
},
)
);
});

To get the original size of a network image, you could use the image_pixels library.
Here is how to use it:
Widget buildContent(BuildContext context, String url) {
final width = MediaQuery.of(context).size.width; // This is the screen size width
final height = MediaQuery.of(context).size.height; // This is the screen size height
Image nimg = Image.network(url); // Preload the network image
return Stack(children: [
Center(
child: Image(
image: nimg.image,
fit: BoxFit.fill,
width: width,
height: height,
)
),
// Wrap the widget that need to access the original image size in ImagePixels
ImagePixels(
imageProvider: nimg.image,
builder: (context, img) {
return Positioned(
// Place a widget on top of the image, it's position related to the original image size
top: 92.0 * (height/img.height),
left: 137.0 * (width/img.width),
child: InkWell(
onTap: () {
print("On tap");
},
child:
Container(
width: 50,
height: 50,
color: Colors.red,
),
)
);
},
)
]);
}

Related

How do I convert an Image Instance to a File with a path in Flutter?

I need to take a picture, convert the file to an image to crop, and then convert the image back to a file to then run into a tflite model (currently just displaying an image on another screen).
As it stands I am using a simple camera app (https://flutter.dev/docs/cookbook/plugins/picture-using-camera?source=post_page---------------------------)
and stacking a container on the preview screen to use as a viewfinder. I Use the rect_getter package to get the container coordinates for the copyCrop() function from the Image package.
Attempting to convert my file to an image (so the copyCrop() function can be run) and then back to a file (cropSaveFile.path) to later be used in a tflite model is resulting in an error: The following FileSystemException was thrown resolving an image codec:
��GFD�oom����������������� etc.
final image = await _controller.takePicture();
////////////////////////////////////////////
final xpath = image.path;
final bytes = await File(xpath).readAsBytes();
final img.Image? newImage = img.decodeImage(bytes);
////////////////////////////////////////////
img.Image crop =
img.copyCrop(newImage!, _proX, _proY, _proW, _proH);
print('Crop: $crop');
final newBytes = crop.getBytes();
final File cropSaveFile = File.fromRawPath(newBytes);
I'm not sure what kind of file I'm really getting back. It is unreadable. Any Ideas? The full code to run is below:
import 'dart:async';
import 'dart:io';
import 'package:universal_io/io.dart';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:rect_getter/rect_getter.dart';
import 'package:image/image.dart' as img;
import 'package:path_provider/path_provider.dart';
Future<void> main() async {
// Ensure that plugin services are initialized so that `availableCameras()`
// can be called before `runApp()`
WidgetsFlutterBinding.ensureInitialized();
// Obtain a list of the available cameras on the device.
final cameras = await availableCameras();
// Get a specific camera from the list of available cameras.
final firstCamera = cameras.first;
runApp(
MaterialApp(
theme: ThemeData.dark(),
home: TakePictureScreen(
// Pass the appropriate camera to the TakePictureScreen widget.
camera: firstCamera,
),
),
);
}
// A screen that allows users to take a picture using a given camera.
class TakePictureScreen extends StatefulWidget {
const TakePictureScreen({
Key? key,
required this.camera,
}) : super(key: key);
final CameraDescription camera;
#override
TakePictureScreenState createState() => TakePictureScreenState();
}
class TakePictureScreenState extends State<TakePictureScreen> {
late CameraController _controller;
late Future<void> _initializeControllerFuture;
var ContainerKey = RectGetter.createGlobalKey();
// Coordinates for rectangle
late int _proX;
late int _proY;
late int _proW;
late int _proH;
#override
void initState() {
super.initState();
// To display the current output from the Camera,
// create a CameraController.
_controller = CameraController(
// Get a specific camera from the list of available cameras.
widget.camera,
// Define the resolution to use.
ResolutionPreset.medium,
);
// Next, initialize the controller. This returns a Future.
_initializeControllerFuture = _controller.initialize();
}
#override
void dispose() {
// Dispose of the controller when the widget is disposed.
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Take a picture')),
// You must wait until the controller is initialized before displaying the
// camera preview. Use a FutureBuilder to display a loading spinner until the
// controller has finished initializing.
body: FutureBuilder<void>(
future: _initializeControllerFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
// If the Future is complete, display the preview.
return Column(
children: [
AspectRatio(
aspectRatio: 1 / _controller.value.aspectRatio,
child: Stack(
children: [
CameraPreview(_controller),
Padding(
padding: const EdgeInsets.fromLTRB(
50.0,
8.0,
16.0,
8.0,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
key: ContainerKey,
height: 175,
width: 175,
decoration: BoxDecoration(
border: Border.all(
width: 10,
color: Colors.yellow,
),
borderRadius: BorderRadius.circular(10.0),
),
),
Text(
'Place Image in Box Above',
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
),
),
],
),
),
],
),
),
],
);
} else {
// Otherwise, display a loading indicator.
return const Center(child: CircularProgressIndicator());
}
},
),
floatingActionButton: FloatingActionButton(
// Provide an onPressed callback.
onPressed: () async {
// Take the Picture in a try / catch block. If anything goes wrong,
// catch the error.
try {
// Ensure that the camera is initialized.
await _initializeControllerFuture;
_controller.setFlashMode(FlashMode.off);
///////////////////////////////////////////
Rect? imageRect = RectGetter.getRectFromKey(ContainerKey);
setState(() {
_proX = imageRect!.left.toInt();
_proY = imageRect.top.toInt();
_proW = imageRect.right.toInt();
_proH = imageRect.bottom.toInt();
});
print(_proX);
print(_proY);
print(_proW);
print(_proH);
///////////////////////////////////////////
// Attempt to take a picture and get the file `image`
// where it was saved.
final image = await _controller.takePicture();
////////////////////////////////////////////
final xpath = image.path;
final bytes = await File(xpath).readAsBytes();
final img.Image? newImage = img.decodeImage(bytes);
////////////////////////////////////////////
img.Image crop =
img.copyCrop(newImage!, _proX, _proY, _proW, _proH);
print('Crop: $crop');
final newBytes = crop.getBytes();
final File cropSaveFile = File.fromRawPath(newBytes);
// If the picture was taken, display it on a new screen.
await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => DisplayPictureScreen(
// Pass the automatically generated path to
// the DisplayPictureScreen widget.
imagePath: cropSaveFile.path,
),
),
);
} catch (e) {
// If an error occurs, log the error to the console.
print(e);
}
},
child: const Icon(Icons.camera_alt),
),
);
}
}
// A widget that displays the picture taken by the user.
class DisplayPictureScreen extends StatelessWidget {
final String imagePath;
const DisplayPictureScreen({Key? key, required this.imagePath})
: super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Display the Picture')),
// The image is stored as a file on the device. Use the `Image.file`
// constructor with the given path to display the image.
body: Image.file(File(imagePath)),
);
}
}
In the following code
final image = await _controller.takePicture();
final xpath = image.path;
//xpath store the path of the file captured by the app
just use
final File file = File(image.path);
//which is a jpg file for verification just console print the image.path and check for the extension.
btw I didn't get why you need a jpeg file because under the hood in the code snippet shared by you, you are already working on a jpeg file the only difference is that you are reading it from device memory as ByteString for processing it to crop and then generating a file again for the ByteString.
Edit: Future me found the following package much better than the code below... https://pub.dev/packages/mask_for_camera_view
This works for me.
////////////////////////////////////////////
final xpath = image.path;
final bytes = await File(xpath).readAsBytes();
final img.Image? newImage = img.decodeImage(bytes);
////////////////////////////////////////////
img.Image crop = img.copyCrop(newImage!, _proY, _proY, 175, 175);
final jpg = img.encodeJpg(crop);
File cropSaveFile = File(xpath);
cropSaveFile.writeAsBytes(jpg);
If anybody is interested in full code to crop an image from the camera based on a viewfinder container it is below.
import 'dart:io';
import 'package:universal_io/io.dart';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:rect_getter/rect_getter.dart';
import 'package:image/image.dart' as img;
Future<void> main() async {
// Ensure that plugin services are initialized so that `availableCameras()`
// can be called before `runApp()`
WidgetsFlutterBinding.ensureInitialized();
// Obtain a list of the available cameras on the device.
final cameras = await availableCameras();
// Get a specific camera from the list of available cameras.
final firstCamera = cameras.first;
runApp(
MaterialApp(
theme: ThemeData.dark(),
home: TakePictureScreen(
// Pass the appropriate camera to the TakePictureScreen widget.
camera: firstCamera,
),
),
);
}
// A screen that allows users to take a picture using a given camera.
class TakePictureScreen extends StatefulWidget {
const TakePictureScreen({
Key? key,
required this.camera,
}) : super(key: key);
final CameraDescription camera;
#override
TakePictureScreenState createState() => TakePictureScreenState();
}
class TakePictureScreenState extends State<TakePictureScreen> {
late CameraController _controller;
late Future<void> _initializeControllerFuture;
var ContainerKey = RectGetter.createGlobalKey();
// Coordinates for rectangle
late int _proY;
#override
void initState() {
super.initState();
// To display the current output from the Camera,
// create a CameraController.
_controller = CameraController(
// Get a specific camera from the list of available cameras.
widget.camera,
// Define the resolution to use.
ResolutionPreset.medium,
);
// Next, initialize the controller. This returns a Future.
_initializeControllerFuture = _controller.initialize();
}
#override
void dispose() {
// Dispose of the controller when the widget is disposed.
_controller.dispose();
super.dispose();
}
Future<File> writeImageWidgetToFile(
img.Image crop, String croppedImagePath) async {
final imgByteData = await crop.getBytes();
final buffer = imgByteData.buffer;
return File(croppedImagePath).writeAsBytes(buffer.asUint8List(
imgByteData.offsetInBytes, imgByteData.lengthInBytes));
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Take a picture')),
// You must wait until the controller is initialized before displaying the
// camera preview. Use a FutureBuilder to display a loading spinner until the
// controller has finished initializing.
body: FutureBuilder<void>(
future: _initializeControllerFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
// If the Future is complete, display the preview.
return Column(
children: [
AspectRatio(
aspectRatio: 1 / _controller.value.aspectRatio,
child: Stack(
children: [
CameraPreview(_controller),
Padding(
padding: const EdgeInsets.fromLTRB(
16.0,
8.0,
16.0,
8.0,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
key: ContainerKey,
height: 175,
width: 175,
decoration: BoxDecoration(
border: Border.all(
width: 10,
color: Colors.yellow,
),
borderRadius: BorderRadius.circular(10.0),
),
),
Text(
'Place Image in Box Above',
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
),
),
],
),
),
],
),
),
],
);
} else {
// Otherwise, display a loading indicator.
return const Center(child: CircularProgressIndicator());
}
},
),
floatingActionButton: FloatingActionButton(
// Provide an onPressed callback.
onPressed: () async {
// Take the Picture in a try / catch block. If anything goes wrong,
// catch the error.
try {
// Ensure that the camera is initialized.
await _initializeControllerFuture;
_controller.setFlashMode(FlashMode.off);
///////////////////////////////////////////
Rect? imageRect = RectGetter.getRectFromKey(ContainerKey);
setState(() {
_proY = imageRect!.top.toInt();
});
print('Top Left Corner of Rect: $_proY');
///////////////////////////////////////////
// Attempt to take a picture and get the file `image`
// where it was saved.
final image = await _controller.takePicture();
////////////////////////////////////////////
final xpath = image.path;
final bytes = await File(xpath).readAsBytes();
final img.Image? newImage = img.decodeImage(bytes);
////////////////////////////////////////////
img.Image crop = img.copyCrop(newImage!, _proY, _proY, 175, 175);
final jpg = img.encodeJpg(crop);
File cropSaveFile = File(xpath);
cropSaveFile.writeAsBytes(jpg);
// If the picture was taken, display it on a new screen.
await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => DisplayPictureScreen(
// Pass the automatically generated path to
// the DisplayPictureScreen widget.
imagePath: cropSaveFile.path,
),
),
);
} catch (e) {
// If an error occurs, log the error to the console.
print(e);
}
},
child: const Icon(Icons.camera_alt),
),
);
}
}
// A widget that displays the picture taken by the user.
class DisplayPictureScreen extends StatelessWidget {
final String imagePath;
const DisplayPictureScreen({Key? key, required this.imagePath})
: super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Display the Picture')),
// The image is stored as a file on the device. Use the `Image.file`
// constructor with the given path to display the image.
body: Image.file(File(imagePath)),
);
}
}

Flutter make image with GestureDetectors also Draggable

My goal is to have an image that I can zoom & move around inside a CustomClipperImage and it should also be Draggable!
Right now I can scale the image in its Clip and this looks like this:
Screenvideo
This is the code for it:
child: Container(
height: _containetWidth,
width: _containetWidth,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10.0),
border: Border.all(color: Colors.white, width: 5),
),
child: GestureDetector(
onTap: () => print("tapped"),
onScaleStart: (details) {
_startingFocalPoint.value = details.focalPoint;
_previousOffset.value = _offset.value;
_previousZoom.value = _zoom.value;
},
onScaleUpdate: (details) {
_zoom.value = _previousZoom.value * details.scale;
final Offset normalizedOffset =
(_startingFocalPoint.value - _previousOffset.value) /
_previousZoom.value;
_offset.value =
details.focalPoint - normalizedOffset * _zoom.value;
},
child: Stack(
children: [
ClipPath(
clipper: CustomClipperImage(),
child: Transform(
transform: Matrix4.identity()
..translate(_offset.value.dx, _offset.value.dy)
..scale(_zoom.value),
child: Image.asset('assets/images/example.jpg',
width: _containetWidth,
height: _containetWidth,
fit: BoxFit.fill),
),
),
CustomPaint(
painter: MyPainter(),
child: Container(
width: _containetWidth, height: _containetWidth),
),
],
),
),
),
But I can not make it Draggable... I tried wrapping the whole Container or also just the Image.asset inside Draggable but when doing this, scaling stops working and Draggable is not working either.
What is the best way to achieve this? I couldn't find anything on this... Let me know if you need more details!
The problem you have is a conflict between:
zooming and dragging the image inside the custom ClipPath
dragging the images between two custom ClipPath
The solution I propose is to use drag handles to swap the images
!!! SPOILER : It does not work (yet) !!!
To implement this drag-n-drop with custom ClipPath, we need the support of HitTestBehavior.deferToChild on DragTarget.
The good news is... It's already available in Flutter master channel! [ref]
So, if you can wait a bit for it to be released in stable, here is my solution:
The main idea is to have the zoomable images as DragTargets and for each image a drag handle as Draggable.
I added a layer of State Management to keep the zoom level and offset when swapping the images.
I also improved the zoomable feature to ensure that the image always covers the full ClipPath.
Full source code (250 lines)
import 'dart:math' show min, max;
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
part '66474773.drag.freezed.dart';
void main() {
runApp(
ProviderScope(
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
home: HomePage(),
),
),
);
}
class HomePage extends HookWidget {
#override
Widget build(BuildContext context) {
final images = useProvider(imagesProvider.state);
final _width = MediaQuery.of(context).size.shortestSide * .8;
void swapImages() => context.read(imagesProvider).swap();
return Scaffold(
backgroundColor: Colors.black87,
body: Padding(
padding: const EdgeInsets.all(24.0),
child: Container(
height: _width,
width: _width,
child: Stack(
children: [
DragTarget<VerticalDirection>(
hitTestBehavior: HitTestBehavior.deferToChild,
onWillAccept: (direction) =>
direction == VerticalDirection.up,
onAccept: (_) => swapImages(),
builder: (_, __, ___) => _Zoomable(
key: GlobalKey(),
width: _width,
pathFn: topPathFn,
imageId: 0,
),
),
DragTarget<VerticalDirection>(
hitTestBehavior: HitTestBehavior.deferToChild,
onWillAccept: (direction) =>
direction == VerticalDirection.down,
onAccept: (_) => swapImages(),
builder: (_, __, ___) => _Zoomable(
key: GlobalKey(),
width: _width,
pathFn: bottomPathFn,
imageId: 1,
),
),
Positioned.fill(
child: Align(
alignment: Alignment.topLeft,
child: _DragHandle(
direction: VerticalDirection.down,
imgAssetPath: images[0].assetPath,
),
),
),
Positioned.fill(
child: Align(
alignment: Alignment.bottomRight,
child: _DragHandle(
direction: VerticalDirection.up,
imgAssetPath: images[1].assetPath,
),
),
),
],
)),
),
);
}
}
class _DragHandle extends StatelessWidget {
final VerticalDirection direction;
final String imgAssetPath;
const _DragHandle({Key key, this.direction, this.imgAssetPath})
: super(key: key);
#override
Widget build(BuildContext context) {
return Draggable<VerticalDirection>(
data: direction,
child: Container(
decoration: BoxDecoration(
color: Colors.grey.shade200,
border: Border.all(color: Colors.grey.shade700),
),
child: Icon(Icons.open_with),
),
childWhenDragging: Container(),
feedback: Image.asset(imgAssetPath, width: 80),
);
}
}
class _Zoomable extends HookWidget {
final double width;
final Path Function(Size) pathFn;
final int imageId;
const _Zoomable({
Key key,
this.width,
this.pathFn,
this.imageId,
}) : super(key: key);
#override
Widget build(BuildContext context) {
final image =
useProvider(imagesProvider.state.select((state) => state[imageId]));
final _startingFocalPoint = useState(Offset.zero);
final _previousOffset = useState<Offset>(null);
final _offset = useState(image.offset);
final _previousZoom = useState<double>(null);
final _zoom = useState(image.zoom);
return CustomPaint(
painter: MyPainter(pathFn: pathFn),
child: GestureDetector(
onTap: () {}, // onScaleUpdate not triggered if onTap is not defined
onScaleStart: (details) {
_startingFocalPoint.value = details.focalPoint;
_previousOffset.value = _offset.value;
_previousZoom.value = _zoom.value;
},
onScaleUpdate: (details) {
_zoom.value = max(1, _previousZoom.value * details.scale);
final newOffset = details.focalPoint -
(_startingFocalPoint.value - _previousOffset.value) *
details.scale;
_offset.value = Offset(
min(0, max(-width * (_zoom.value - 1), newOffset.dx)),
min(0, max(-width * (_zoom.value - 1), newOffset.dy)),
);
},
onScaleEnd: (_) => context.read(imagesProvider).update(
imageId, image.copyWith(zoom: _zoom.value, offset: _offset.value)),
child: ClipPath(
clipper: MyClipper(pathFn: pathFn),
child: Transform(
transform: Matrix4.identity()
..translate(_offset.value.dx, _offset.value.dy)
..scale(_zoom.value),
child: Image.asset(
image.assetPath,
width: width,
height: width,
fit: BoxFit.fill,
),
),
),
),
);
}
}
Path bottomPathFn(Size size) => Path()
..moveTo(size.width, 0)
..lineTo(0, size.height)
..lineTo(size.height, size.height)
..close();
Path topPathFn(Size size) => Path()
..moveTo(size.width, 0)
..lineTo(0, size.height)
..lineTo(0, 0)
..close();
class MyClipper extends CustomClipper<Path> {
final Path Function(Size) pathFn;
MyClipper({this.pathFn});
#override
getClip(Size size) => pathFn(size);
#override
bool shouldReclip(CustomClipper oldClipper) {
return false;
}
}
class MyPainter extends CustomPainter {
final Path Function(Size) pathFn;
Path _path;
MyPainter({this.pathFn});
#override
void paint(Canvas canvas, Size size) {
_path = pathFn(size);
final paint = Paint()
..color = Colors.white
..strokeWidth = 4.0
..style = PaintingStyle.stroke;
canvas.drawPath(_path, paint);
}
#override
bool hitTest(Offset position) {
return _path?.contains(position);
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
final imagesProvider =
StateNotifierProvider<ImagesNotifier>((ref) => ImagesNotifier([
ZoomedImage(assetPath: 'images/abstract.jpg'),
ZoomedImage(assetPath: 'images/abstract2.jpg'),
]));
class ImagesNotifier extends StateNotifier<List<ZoomedImage>> {
ImagesNotifier(List<ZoomedImage> state) : super(state);
void swap() {
state = state.reversed.toList();
}
void update(int id, ZoomedImage updatedImage) {
state = [...state]..[id] = updatedImage;
}
}
#freezed
abstract class ZoomedImage with _$ZoomedImage {
const factory ZoomedImage({
String assetPath,
#Default(1.0) double zoom,
#Default(Offset.zero) Offset offset,
}) = _ZoomedImage;
}

How to scroll or jump to position of PageView.builder or PageController in Flutter?

Issue: Unable to scroll to POSITION after loading the Pageviews using PageController *
like ViewPager scroll to specific page in Android
Widget _buildCarousel(BuildContext context, int selectedIndex) {
PageController controller = PageController(viewportFraction: 1, keepPage: true);
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
SizedBox(
// you may want to use an aspect ratio here for tablet support
height: 400.0,
width: 240,
child: PageView.builder(
itemCount: assetImageList.length,
controller: controller,
itemBuilder: (BuildContext context, int itemIndex) {
return _buildCarouselItem(context, selectedIndex, itemIndex);
},
),
)
],
);
}
Finally found the answer. Just set the initialPage: mSelectedPosition attribute like:
child: PageView.builder(
itemCount: mTemplateModelList.length,
controller: PageController(initialPage: mSelectedPosition, keepPage: true, viewportFraction: 1),
itemBuilder: (BuildContext context, int itemIndex) {
return _buildCarouselItem(context, selectedIndex, itemIndex);
},
),
OR if you want to scroll the page after the button is clicked then, you can use jumpTo() method using PageController which is clearly mentioned below by another user: #android.
Currently there's 2 options to handle your request:
PageView.builder(
controller: _pageController,
itemCount: _list.length,
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
_pageController.jumpToPage(index); // for regular jump
_pageController.animateToPage(_position, curve: Curves.decelerate, duration: Duration(milliseconds: 300)); // for animated jump. Requires a curve and a duration
},
child: Container();
);
}
),
You can use jumpTo() method to scroll position for PageView.
I have create one changePageViewPostion() method in below example:
import 'package:flutter/material.dart';
class MyPageView extends StatefulWidget {
createState() {
return StateKeeper();
}
}
class StateKeeper extends State<MyPageView> {
PageController controller = PageController(viewportFraction: 1, keepPage: true);
var currentPageValue = 0.0;
var mItemCount = 10;
#override
void initState() {
// TODO: implement initState
super.initState();
controller.addListener(() {
setState(() {
currentPageValue = controller.page;
});
});
}
void changePageViewPostion(int whichPage) {
if(controller != null){
whichPage = whichPage + 1; // because position will start from 0
double jumpPosition = MediaQuery.of(context).size.width / 2;
double orgPosition = MediaQuery.of(context).size.width / 2;
for(int i=0; i<mItemCount; i++){
controller.jumpTo(jumpPosition);
if(i==whichPage){
break;
}
jumpPosition = jumpPosition + orgPosition;
}
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('PageView position change'),
),
body: PageView.builder(
controller: controller,
itemBuilder: (context, position) {
return Container(
color: position % 2 == 0 ? Colors.blue : Colors.pink,
child: Column(
children: <Widget>[
Center(
child: Text(
"Page " + (position + 1).toString(),
style: TextStyle(color: Colors.white, fontSize: 22.0),
),
),
Align(
alignment: FractionalOffset.bottomCenter,
child: Padding(padding: EdgeInsets.only(bottom: 20),
child: FloatingActionButton(
elevation: 0.0,
child: new Icon(Icons.check),
backgroundColor: new Color(0xFFE57373),
onPressed: (){
changePageViewPostion(5);
}
),),
),
],
),
);
},
itemCount: mItemCount,
)
);
}
}
We can get current position with controller as below:
controller.addListener(() {
setState(() {
currentPageValue = controller.page.toInt();
print((currentPageValue + 1).toString());
});
});
Hope it helps :)
If you just want to scroll to the next page using a button, you can simply use the following method.
//Create a PageController variable
late PageController _pageController;
//Initialize the variable in the init method.
#override
void initState() {
_pageController = PageController(
initialPage: _activePage, keepPage: true, viewportFraction: 1);
super.initState();
}
//Use this nextPage() method in the onPressed() method.
onPressed: () {
setState(() {
_activePage < 2
? _activePage++
: Navigator.pushReplacementNamed(
context, LoginScreen.id);
});
_pageController.nextPage(
duration: const Duration(milliseconds: 300),
curve: Curves.decelerate,
);
}

Animating widget positions as the screen scrolls in flutter (GIF included)

I am trying to animate two Rows of widgets to collapse into 1 Row of these widgets as one scroll. I am trying to achieve this behavior inside a SliverAppBar.
For clarification, I have included a GIF here for reference. I would like the behavior you see in the app bar, but instead of 1 row to 2, I would like 2 rows becoming 1.
Here is a quick snippet of what I have so far. I wrapped 2 Row widgets that contain 3 shrinkableBox widgets each into a Wrap widget. I dynamically adjust the size of these boxes by hooking into _scrollController.offset and doing some calculations. The rows do move around dynamically but they don't animate and move abruptly instead.
double kExpandedHeight = 300.0;
Widget build(BuildContext context) {
double size = !_scrollController.hasClients || _scrollController.offset == 0 ? 75.0 : 75 - math.min(45.0, (45 / kExpandedHeight * math.min(_scrollController.offset, kExpandedHeight) * 1.5));
return Scaffold(
body: CustomScrollView(
controller: _scrollController,
slivers: <Widget>[
SliverAppBar(
pinned: true,
expandedHeight: kExpandedHeight,
title: new Text(
"Title!",
),
bottom: PreferredSize(child: Wrap(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ShrinkableBox(
onClick: () {
print("tapped");
},
size: size,
),
ShrinkableBox(
onClick: () {
print("tapped");
},
size: size,
),
ShrinkableBox(
onClick: () {
print("tapped");
},
size: size,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ShrinkableBox(
onClick: () {
print("tapped");
},
size: size,
),
ShrinkableBox(
onClick: () {
print("tapped");
},
size: size,
),
ShrinkableBox(
onClick: () {
print("tapped");
},
size: size,
),
],
),
],
), preferredSize: new Size.fromHeight(55),),
)
// ...
// ...Other sliver list content here...
// ...
You could use a Stack together with Positioned widgets to position the ShrinkableBoxes as you need. Since what controls the animation is the scroll offset, you don't need to use animated widgets or an animation controller or something like it. Here's a working example which calculates the positions by linearly interpolating the initial and final position of the boxes (you can get different animation paths by changing the Curves.linear to other curves):
import 'dart:math' as math;
import 'dart:ui';
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(home: Home()));
}
class Home extends StatefulWidget {
#override
State createState() => HomeState();
}
class HomeState extends State<Home> {
static const double kExpandedHeight = 300.0;
static const double kInitialSize = 75.0;
static const double kFinalSize = 30.0;
static const List<Color> kBoxColors = [
Colors.red,
Colors.green,
Colors.yellow,
Colors.purple,
Colors.orange,
Colors.grey,
];
ScrollController _scrollController = new ScrollController();
#override
void initState() {
_scrollController.addListener(() {
setState(() { /* State being set is the Scroll Controller's offset */ });
});
}
#override
void dispose() {
_scrollController.dispose();
}
Widget build(BuildContext context) {
double size = !_scrollController.hasClients || _scrollController.offset == 0
? 75.0
: 75 -
math.min(45.0,
(45 / kExpandedHeight * math.min(_scrollController.offset, kExpandedHeight) * 1.5));
return Scaffold(
body: CustomScrollView(
controller: _scrollController,
slivers: <Widget>[
SliverAppBar(
pinned: true,
expandedHeight: kExpandedHeight,
title: Text("Title!"),
bottom: PreferredSize(
preferredSize: Size.fromHeight(55),
child: buildAppBarBottom(size),
),
),
SliverFixedExtentList(
itemExtent: 50.0,
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return ListTile(title: Text('Item $index'));
},
),
),
],
),
);
}
Widget buildAppBarBottom(double size) {
double t = (size - kInitialSize) / (kFinalSize - kInitialSize);
const double initialContainerHeight = 2 * kInitialSize;
const double finalContainerHeight = kFinalSize;
return Container(
height: lerpDouble(initialContainerHeight, finalContainerHeight, t),
child: LayoutBuilder(
builder: (context, constraints) {
List<Widget> stackChildren = [];
for (int i = 0; i < 6; i++) {
Offset offset = getInterpolatedOffset(i, constraints, t);
stackChildren.add(Positioned(
left: offset.dx,
top: offset.dy,
child: buildSizedBox(size, kBoxColors[i]),
));
}
return Stack(children: stackChildren);
},
),
);
}
Offset getInterpolatedOffset(int index, BoxConstraints constraints, double t) {
Curve curve = Curves.linear;
double curveT = curve.transform(t);
Offset a = getOffset(index, constraints, kInitialSize, 3);
Offset b = getOffset(index, constraints, kFinalSize, 6);
return Offset(
lerpDouble(a.dx, b.dx, curveT),
lerpDouble(a.dy, b.dy, curveT),
);
}
Offset getOffset(int index, BoxConstraints constraints, double size, int columns) {
int x = index % columns;
int y = index ~/ columns;
double horizontalMargin = (constraints.maxWidth - size * columns) / 2;
return Offset(horizontalMargin + x * size, y * size);
}
Widget buildSizedBox(double size, Color color) {
return Container(
height: size,
width: size,
color: color,
);
}
}

Check image is loaded in Image.network widget in flutter

I am new to Flutter. I try to load network images using image.network widget. it's working fine but sometimes it takes time to load. I added tap listener to image.network during tap I need to check image is fully loaded or not based on the result I need to redirect the page. how to check image is loaded or not?
Code:
new Image.network('http://via.placeholder.com/350x150')
Any help will be appreciated, thank you in advance
You may use the loadingBuilder which is inbuilt feature from flutter for Image.Network
I did it as below:
Image.network(imageURL,fit: BoxFit.cover,
loadingBuilder:(BuildContext context, Widget child,ImageChunkEvent loadingProgress) {
if (loadingProgress == null) return child;
return Center(
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null ?
loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes!
: null,
),
);
},
),
for this kind of issues it's good to use the cached_network_image
so you can provide a placeholder when the image is loading and an error widget in case a resource fails to load
String url = "http://via.placeholder.com/350x150";
CachedNetworkImage(
imageUrl: url,
placeholder: (context,url) => CircularProgressIndicator(),
errorWidget: (context,url,error) => new Icon(Icons.error),
),
for ones who do not need to cache the image can use meet_network_image package,
The package basic usage :
MeetNetworkImage(
imageUrl:
"https://random.dog/3f62f2c1-e0cb-4077-8cd9-1ca76bfe98d5.jpg",
loadingBuilder: (context) => Center(
child: CircularProgressIndicator(),
),
errorBuilder: (context, e) => Center(
child: Text('Error appear!'),
),
)
In addition, you can do that by yourself with using a FutureBuilder,
We need to get data with http call that way, we need to import http before import you also need to add pubspec.yaml and run the command flutter packages get
import 'package:http/http.dart' as http;
FutureBuilder(
// Paste your image URL inside the htt.get method as a parameter
future: http.get(
"https://random.dog/3f62f2c1-e0cb-4077-8cd9-1ca76bfe98d5.jpg"),
builder: (BuildContext context, AsyncSnapshot<http.Response> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return Text('Press button to start.');
case ConnectionState.active:
case ConnectionState.waiting:
return CircularProgressIndicator();
case ConnectionState.done:
if (snapshot.hasError)
return Text('Error: ${snapshot.error}');
// when we get the data from the http call, we give the bodyBytes to Image.memory for showing the image
return Image.memory(snapshot.data.bodyBytes);
}
return null; // unreachable
},
);
This way it will start loading, then it will show the loading of the image loading and then the image. Best option if you don't want to use external libs.
Image.network(
imgUrl,
height: 300,
fit: BoxFit.contain,
frameBuilder: (_, image, loadingBuilder, __) {
if (loadingBuilder == null) {
return const SizedBox(
height: 300,
child: Center(child: CircularProgressIndicator()),
);
}
return image;
},
loadingBuilder: (BuildContext context, Widget image, ImageChunkEvent? loadingProgress) {
if (loadingProgress == null) return image;
return SizedBox(
height: 300,
child: Center(
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes!
: null,
),
),
);
},
errorBuilder: (_, __, ___) => Image.asset(
AppImages.withoutPicture,
height: 300,
fit: BoxFit.fitHeight,
),
)
thank you for your comment thats help to resolve the situation that how to know if the image is loaded or not hope that help
I use a StatefulWidget
need a editing depend on your AffichScreen
situation :
-i have an url that i enter
-if url is correct affich the image if not affich an icon
-if empty affich a Text()
-precacheImage check if the url is correct if not give an error and change _loadingimage(bool) to false to affich the icon eror
-i use a NetworkImage to check with precacheImage and before affich use a Image.network
bool _loadingimage;
ImageProvider _image;
Image _imagescreen;
#override
void initState() {
_loadingimage = true;
_imageUrlfocusNode.addListener(_updateImageUrl);
super.initState();
}
#override
void dispose() {
_imageUrlfocusNode.removeListener(_updateImageUrl);
_quantityfocusNode.dispose();
_imageUrlConroller.dispose();
_imageUrlfocusNode.dispose();
super.dispose();
}
void _updateImageUrl() {
setState(() {
_image = NetworkImage(_imageUrlConroller.text);
});
if (!_imageUrlfocusNode.hasFocus) {
if (_imageUrlConroller.text.isNotEmpty) {
setState(() {
loadimage();
});
}
}
}
void loadimage() {
_loadingimage = true;
precacheImage(_image, context, onError: (e, stackTrace) {
// log.fine('Image ${widget.url} failed to load with error $e.');
print('error $e');
setState(() {
_loadingimage = false;
print(_loadingimage);
});
});
if (_loadingimage == true) {
_imagescreen = Image.network(
_imageUrlConroller.text,
fit: BoxFit.fill,
);
}
}
Container(
width: 100,
height: 100,
margin: EdgeInsets.only(top: 13, right: 11),
decoration: BoxDecoration(
border: Border.all(
width: 1,
color: Colors.grey,
),
),
child:_imageUrlConroller.text.isEmpty
? Text('enter an url')
: !_loadingimage
? Container(
child: Icon(Icons.add_a_photo),
)
: Container(
child: _imagescreen,
),
),

Resources