How to display an animated picture in Flutter? - dart

I want to display an animated picture, whatever its format, in Flutter. The fact is that currently there seems to be only one solution available, video_loader. This works only on full screen, but it doesn't fit my use case.
Any idea on how I could sort this out?

Now, Image widget Supports GIF. (April 18)
For Ex.
new Image(image: new AssetImage("assets/ajax-loader.gif"))

You can split the frames into separate images using https://ezgif.com/split and add them as assets in your pubspec.yaml.
Then use an Animation<int> to select the correct frame to display. Make sure to set gaplessPlayback: true to avoid flickering.
For example, the following code displays the frames of an animated GIF that was created by Guillaume Kurkdjian.
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
theme: new ThemeData.dark(),
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
State createState() => new MyHomePageState();
}
class MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
AnimationController _controller;
Animation<int> _animation;
#override
void initState() {
_controller = new AnimationController(vsync: this, duration: const Duration(seconds: 5))
..repeat();
_animation = new IntTween(begin: 0, end: 116).animate(_controller);
}
Widget build(BuildContext context) {
return new Scaffold(
body: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new AnimatedBuilder(
animation: _animation,
builder: (BuildContext context, Widget child) {
String frame = _animation.value.toString().padLeft(3, '0');
return new Image.asset(
'assets/frame_${frame}_delay-0.04s.png',
gaplessPlayback: true,
);
},
),
new Text('Image: Guillaume Kurkdjian', style: new TextStyle(fontStyle: FontStyle.italic)),
],
),
);
}
}

2021 Update
As of now flutter does supports playing gif files using the Image widget.
Image.asset('assets/logo.gif')
But there's a problem with current way of loading gif in flutter. The gif plays in a loop and you can't stop the gif after playing it once. There are other ways of showing animated pictures using Rive and Lottie and both of them comes with a pretty well maintained flutter package that gives lots of features out of the box.
Workaround :
Convert your gif to mp4 (Gif to mp4)
Convert mp4 to Lottie json (Mp4 to Json)
Upload your Lottie json to lottiefiles.com or add to your assets folder
Use Lottie package to load your animation
Example from Lottie package docs :
import 'package:flutter/material.dart';
import 'package:lottie/lottie.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: ListView(
children: [
// Load a Lottie file from your assets
Lottie.asset('assets/LottieLogo1.json'),
// Load a Lottie file from a remote url
Lottie.network(
'https://raw.githubusercontent.com/xvrh/lottie-flutter/master/example/assets/Mobilo/A.json'),
// Load an animation and its images from a zip file
Lottie.asset('assets/lottiefiles/angel.zip'),
],
),
),
);
}
}
For sure this is not the most ideal way of loading a gif as this is just a workaround. You can simply use an Image widget if you're not doing much with your gif. But if you will use Lottie then you get lots of things that you can do with your gif with much more control.

In order to run gif animation only once, there are 2 solutions.
First solution.
List<int> listGifDuration = [0,0,22,26,31,27,30,29,29,23,29,24,25,27,33,33,29,29];
List<int> listGifDurationDs = [0,0,1,0,0,0,0,0,0,0,0,0,0,0,5,2,5,0];
List<double> listGifFrames = [0,0,315,389,310,294,435,423,425,331,425,360,365,395,309,384,426,435];
strgif = "assets/gif/motion-all.gif"
fetchGif(AssetImage(strgif)).then((value) {
controller= GifController(vsync: this,duration: Duration(seconds: listGifDuration[widget.storyid]));
controller.addListener(() => setState(() {}));
TickerFuture tickerFuture = controller.repeat(min:0,max:listGifFrames[widget.storyid],period:Duration(seconds: listGifDuration[widget.storyid]));
tickerFuture.timeout(Duration(seconds: listGifDuration[widget.storyid]), onTimeout: () {
controller.forward(from: 0);
controller.stop(canceled: true);
});
});
2nd solution.
Convert the property of the gif file from the infinite loop to 1 loop.
Please use following link to convert gif file looping count.
https://ezgif.com/loop-count
and then
child: new Image.asset(strgif),

We can use Image widget to load any type of image whether it is a normal image or the gif.
We can load them from our asset as well as from network with the help of Image widget
Image.asset('name');
Image.file(file);
Image.memory(bytes);
Image.network('src');
There are some dependency for load the gif also

Related

Flutter accelerometer/gyroscope sensor lag

I've been trying to implement a gyroscope image viewer using the sensors package, however, the result seems to be very laggy. I have found a similar project on YouTube which is trying to achieve a similar goal, but as you can see in the video the animation is also very laggy.
The following code is simply outputting the data from the event, I notice how the data is being updated lags like 50ms in between updates.
Is there a way to smoothen the animation or update the data faster? Or is this a Flutter limitation?
NOTE:
I have tried --release version as suggested by other posts but the result stays the same.
import 'package:sensors/sensors.dart';
class MyGyro extends StatefulWidget {
final Widget child;
MyGyro({this.child});
#override
_MyGyroState createState() => _MyGyroState();
}
class _MyGyroState extends State<MyGyro> {
double gyroX = 0;
double gyroY = 0;
#override
void initState() {
super.initState();
gyroscopeEvents.listen((GyroscopeEvent event) {
setState(() {
gyroX = ((event.x * 100).round() / 100).clamp(-1.0, 1.0) * -1;
gyroY = ((event.y * 100).round() / 100).clamp(-1.0, 1.0);
});
});
}
#override
Widget build(BuildContext context) {
return Container(
height: 100,
width: 100,
child: Transform.translate(
offset: Offset(gyroY, 0),
child: Container(
child: Center(
child: Column(
children: [Text("X: ${gyroX}"), Text("Y: ${gyroY}"),],
),
),
),
),
);
}
}
I have found that is purely the problem of the sensors package I was using, either they have hard coded a slower interval when listening to the sensor event, or they are just using the default interval by the IOS channel.
So, I have found another package called flutter_sensors which had solved the problem. It's a very simple API to access the sensor events, but it allows you to change the interval.

Text Cursor Handle stays Blue

I tried cursorColor and wrapping the TextField in a Theme where textSelectionHandleColor and textSelectionColor are set to whatever colors, however, the text cursor stays blue.
To be clear, I am talking about the handle. None of the following adjust it for me:
https://github.com/flutter/flutter/issues/14598
https://github.com/flutter/flutter/issues/15571
Sadly, it is currently not possible to change the textSelectionHandleColor of a TextField by modifying its parent Theme. The only Theme that changes the textSelectionHandleColor is the Theme directly inside the MaterialApp (source).
Issue on GitHub: textSelectionHandleColor is not working/changing. #20219
The reason this problem exists is that the handles are rendered inside an Overlay. The Overlay is not a child from the TextField, but instead always a child of the MaterialApp. Here is a failed try from another developer to solve the problem: textSelectionHandleColor taken from parent's context. Fixes #20219
Therefore you can currently only adjust the MaterialApp inside your application:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: Theme.of(context).copyWith(textSelectionHandleColor: Colors.red),
home: Scaffold(
body: Center(
child: TextField(
autofocus: true,
),
),
),
);
}
}

Does using const in the widget tree improve performance?

When creating a widget tree, will inserting const before static widgets improve performance?
ie
child: const Text('This is some text');
vs
child: Text('This is some text');
I know that, with Dart 2, const is optional and will be inserted automatically is some places. Is this one of those situations? If it isn't, will using const reduce memory usage/improve performance?
Thanks for your answers!
It is a small performance improvement, but it can add up in larger apps or apps where the view is rebuilt often for example because of animations.
const reduces the required work for the Garbage Collector.
You can enable some linter rules in analysis_options.yaml that tell you when you should add const because it's not inferred but would be possible like
http://dart-lang.github.io/linter/lints/prefer_const_constructors.html
http://dart-lang.github.io/linter/lints/prefer_const_declarations.html
http://dart-lang.github.io/linter/lints/prefer_const_literals_to_create_immutables.html
or that reminds you when you use const but it is inferred anyway
http://dart-lang.github.io/linter/lints/unnecessary_const.html
See also https://www.dartlang.org/guides/language/analysis-options
In the case of Flutter, the real gain with const is not having less instantiation.
Flutter has a special treatment for when the instance of a widget doesn't change: it doesn't rebuild them.
Consider the following:
Foo(
child: const Bar(
child: Baz()
),
)
In the case of build method being called again (setState, parent rebuild, Inheritedwidget...), then due to the const for Bar subtree, only Foo will see its build method called.
Bar will never get rebuilt because of its parent, because Flutter knows that since the widget instance didn't change, there's nothing to update.
Update:
I noticed I received an upvote lately, and I must say that I'm not confident in my tests below but that's all I got. So someone better run a better test.
I've ran some test to see if it makes a difference.
The tests are heavily based on the ones done in this article.
For the tests, there are 300 containers with text inside moving randomly on the screen. Something you wouldn't see in a day to day app.
For my results there is no difference in frame per second and there is no difference in memory usage except that the Garbage collector seems to run more often when not using const. Again, the FPS were about the same.
Imo, the performance boost is negligible and sounds like preemptive optimization. However there is no deeply nested chain of widgets in the test, but I don't see how that would make a difference. The article above seems to say there is a small one, but that's not what my tests show.
I've a card like this (this is the const version):
import 'package:flutter/material.dart';
class MyCard extends StatelessWidget {
const MyCard();
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
margin: const EdgeInsets.all(8.0),
height: 100,
width: 100,
color: Colors.red,
child: const Text('Hi'),
),
);
}
}
that's rendered 300 times and moving on the screen randomly.
This is the widget that makes them move
import 'package:flutter/material.dart';
import 'dart:math';
import 'dart:async';
import './my-card.dart';
class MovingContainer extends StatefulWidget {
#override
_MovingContainerState createState() => _MovingContainerState();
}
class _MovingContainerState extends State<MovingContainer> {
final Random _random = Random();
final Duration _duration = const Duration(milliseconds: 1000);
Timer _timer;
double _top = 0;
double _left = 0;
#override
void initState() {
super.initState();
initMove();
}
void initMove() {
_timer = Timer.periodic(
_duration,
(timer) {
move();
},
);
}
void move() {
final Size size = MediaQuery.of(context).size;
setState(() {
_top = _random.nextInt(size.height.toInt() - 100).toDouble();
_left = _random.nextInt(size.width.toInt() - 100).toDouble();
});
}
#override
void dispose() {
_timer.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return AnimatedPositioned(
top: _top,
left: _left,
child: const MyCard(),
duration: _duration,
);
}
}
Note: I'm new to flutter, and so are many others because it's a relatively new framework. Therefor my tests could very well be wrong, don't take it as gospel. Also don't take it as gospel when you read an article titled << Number One Perf gain on Flutter >>. I've yet to see actual proof there is a perf gain.
When we use setState() Flutter calls the build method and rebuilds
every widget tree inside it. The best way to avoid this is by using
const costructors.
Use const constructors whenever possible when building your own
widgets or using Flutter widgets. This helps Flutter to rebuild only
widgets that should be updated.
So if you have a StatefulWidget and you are using setState((){}) to
update that widget and you have widgets like:
class _MyWidgetState extends State<MyWidget> {
String title = "Title";
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Column(
children: <Widget>[
const Text("Text 1"),
const Padding(
padding: const EdgeInsets.all(8.0),
child: const Text("Another Text widget"),
),
const Text("Text 3"),
],
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {
setState(() => title = 'New Title');
},
),
);
}
}

How to reload a FileImage in Flutter

Let's say I have an image on the device's disk and load it on the screen providing its path to a FileImage.
I edit that image and save it on the same path expecting calling the setState(() {}) function will reload it. But it doesn't.
I tried clearing the image cache by calling imageCache.clear() function and also imageProvider.evict() but no difference.
If I close that page and open it again, I see the updated images.
I assume the images that are being displayed on the screen are in the memory, if my assumption is correct, how to reload it?
I know I'm a bit late, but I used this workaround:
_tmpImageFile.existsSync() ? Image.memory(
Uint8List.fromList(_tmpImageFile.readAsBytesSync()),
alignment: Alignment.center,
height: 200,
width: 200,
fit: BoxFit.contain,
) : Container(),
This is also working for me:
Image previewImage = Image.file(
File(scenePreviewFilename),
);
And when pressing a button or something just evict the cached image from imageCache like this:
setState(() {
imageCache.evict(previewImage.image, includeLive: true);
});
PS: I had some issues getting this to work(similar to the initial post) because I was generating the image in another thread and the image was loaded before it was generated, resulting in the imageCache appearing not to have an effect ... so make sure you are not in this race situation.
Already tested - Adding the both image cache commands will solve the problem.
imageCache.clear();
imageCache.clearLiveImages();
});
Would changing the key of the Image do the trick?
Set a key to the image
Image.file(
_imageFile,
key: _key,
fit: BoxFit.cover,
),
and change the key every time you change _imageFile, let's set it to timestamp for example
setState(() {
_imageFile = newImageFile;
_key = DateTime.now().millisecondsSinceEpoch;
});
This is a bit ugly workaround, but it works for me. Here we're resetting image cache + force reloading image from disk:
class _MyAppState extends State<MyApp> {
final imageKey = GlobalKey();
bool forceLoad = false;
#override
Widget build(BuildContext context) {
final app = MaterialApp(
home: Scaffold(
body: forceLoad
? Image.memory(currentFile.readAsBytesSync(), key: imageKey)
: Image.file (currentFile, key: imageKey),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.refresh),
onPressed: () async {
await (imageKey.currentWidget as Image).image.evict(); // reset cache for current image
setState(() {
forceLoad = true; // reloading current image from disk
});
},
)
)
);
forceLoad = false;
return app;
}
}
The other answers were missing step 2. Full explanation here.
call imageCache.clear(); and then imageCache.clearLiveImages(); when the new image needs to be reloaded.
Add a value key in the image widget to ensure the new image gets rebuilt
Image(image: FileImage(File(pathName)), key: ValueKey(File(pathName).lengthSync()))
The image should reload after.

Clipping images using png in flutter?

I am trying to achieve effect demonstrated in the image at the bottom of this question, essentially I have orc graphic (square) and a png for dark cloud/smoke that I want to use as a mask for my orc image.
Thus far I found options to clip images using bezier curves but nothing on using other images as a mask. Is this achievable?
You can certainly do it with CustomPainter. Note that there are two different classes in Flutter called Image. The normal one is a Widget; the other one (part of the ui package) is closer to a bitmap. We'll be using the latter. I put two images in my assets folder (cute kitten and background with transparent hole). This shows how to load the graphics from assets, convert them to bitmaps, and how to draw those to a Canvas. End result is kitten showing through hole.
import 'dart:ui' as ui;
import 'dart:typed_data';
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show rootBundle;
class ImageOverlay extends StatefulWidget {
#override
State createState() => new ImageOverlayState();
}
class ImageOverlayState extends State<ImageOverlay> {
ui.Image kitten;
ui.Image hole;
#override
Widget build(BuildContext context) {
return new SizedBox(
width: 500.0,
height: 500.0,
child: new CustomPaint(
painter: new OverlayPainter(hole, kitten),
),
);
}
#override
void initState() {
super.initState();
load('assets/hole.png').then((i) {
setState(() {
hole = i;
});
});
load('assets/kitty.jpg').then((i) {
setState(() {
kitten = i;
});
});
}
Future<ui.Image> load(String asset) async {
ByteData data = await rootBundle.load(asset);
ui.Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List());
ui.FrameInfo fi = await codec.getNextFrame();
return fi.image;
}
}
class OverlayPainter extends CustomPainter {
ui.Image hole;
ui.Image kitten;
OverlayPainter(this.hole, this.kitten);
#override
void paint(Canvas canvas, Size size) {
if (kitten != null) {
canvas.drawImage(kitten, Offset(0.0, 0.0), new Paint());
}
if (hole != null) {
canvas.drawImage(hole, Offset(0.0, 0.0), new Paint());
}
}
#override
bool shouldRepaint(OverlayPainter oldDelegate) {
return hole != oldDelegate.hole || kitten != oldDelegate.kitten;
}
}
When drawing the images to the Canvas you may need to deal with Transforms to scale the images correctly.
I didn't get a chance to try, but have you tried a Stack with two (widget) Images positioned on top of each other?

Resources