Share dimensions between widgets - dart

In Flutter we can use Themes to share colors and font styles.
https://flutter.io/docs/cookbook/design/themes
Is there an existing best practice that we can use in a similar manner share values such as margins, paddings and widths or heights?
Preferably something that helps stick to the material design guidelines.

Defining custom widgets
The easiest and probably most elegant approach is to define custom widgets, like a MyRaisedButton that internally uses the RaisedButton with the right dimensions.
class MyRaisedButton extends StatelessWidget {
MyRaisedButton({
this.child,
this.onPressed,
});
final Widget child;
final VoidCallback onPressed;
#override
Widget build(BuildContext context) {
return RaisedButton(
padding: ...,
onPressed: onPressed,
child: child
);
}
}
This works surprisingly well in most cases.
However, if you still want to keep your widgets flexible (being able to pass a lot of customization options to the constructor), your overall widget definition quickly gets very long, because you need to forward all the options to the RaisedButton.
In that case, it makes sense to actually share the values throughout the app.
Actually sharing values throughout the app
Of course, this approach is possible too.
Due to Flutter's openness, we can just look at how the Theme is implemented and copy that code to create a custom widget that functions just like a Theme.
Here's a boiled-down version:
#immutable
class MyThemeData {
MyThemeData({
this.myPadding,
this.myColor,
this.myString
});
final Padding myPadding;
final Color myColor;
final String myString;
}
class MyTheme extends StatelessWidget {
MyTheme({
Key key,
#required this.data,
#required this.child
}) : super(key: key);
final MyThemeData data;
final Widget child;
static MyThemeData of(BuildContext context) {
return (context.ancestorWidgetOfExactType(MyTheme) as MyTheme)?.data;
}
#override
Widget build(BuildContext context) => child;
}
Now, you can just wrap the MaterialApp in a MyTheme widget:
MyTheme(
data: MyThemeData(
myPadding: ...,
myColor: ...,
...
),
child: ... (here goes the MaterialApp)
)
Then anywhere in your app, you can write MyTheme.of(context).myPadding.
You can adapt the MyThemeData class to your needs, storing anything you want.

Related

Flutter extends Stepper class

I still new with Dart language.
I want to create a custom Stepper class with extends current Stepper class. The reason why I need to create custom Stepper because I need to override function _buildHorizontal
Current workaround:
import 'package:flutter/material.dart';
class CustomStepper extends Stepper {
CustomStepper({
Key key,
#required Step steps,
ScrollPhysics physics,
StepperType type = StepperType.vertical,
int currentStep = 0,
ValueChanged<int> onStepTapped,
VoidCallback onStepContinue,
VoidCallback onStepCancel,
ControlsWidgetBuilder controlsBuilder,
}) : assert(steps != null),
assert(type != null),
assert(currentStep != null),
super(key: key);
}
My question is, how to override _buildHorizontal in a right way?
You can see here the colors that are being used by default by the Stepper that are being get from the Theme, such as primary color, accent color and so on.
If you want to change it only for that stepper, a more convenient approach is by wrapping it in a Theme and override those properties only for that widget, such as:
Theme(
data: ThemeData(
accentColor: Colors.blueAccent
),
child: Stepper()
)
Otherwise, the best way is to copy the source of the Stepper for your own custom widget and tweak it for your needs.

Flutter: AnimatedContainer - children widgets' properties are NOT animating

I've got a simple AnimatedWidget with one child widget.
AnimatedContainer(
duration: Duration(milliseconds: 2000),
curve: Curves.bounceOut,
decoration: BoxDecoration(
color: Colors.purple,
),
child: FlutterLogo(
size: _boxSize,
),
),
where _boxSize is being animated like so:
void _startAnimation() => setState(() {
_boxSize *= 1.7;
});
AnimatedContainer is not working for child widgets, however. You need to change direct properties of AnimatedContainer for the animation to work.
This is in compliance with documentation:
The [AnimatedContainer] will automatically animate between the old
and new values of properties when they change using the provided curve
and duration. Properties that are null are not animated.
Its child and descendants are not animated.
What is the equivalent of AnimatedContainer which is ALSO ABLE to animate its children?
There are few widgets which will animate the child. You can swap the new flutter logo widget with preferred size using AnimatedSwitcher Widget.
AnimatedSwitcher - This widget will swap the child widget with a new widget.
AnimatedPositioned - It'll change the position of the child from the stack widget whenever the given position changes.
AnimatedAlign - Animated version of align which will change the alignment of the child whenever the given alignment changes.
AnimatedCrossFade - It fades between two children and animate itself between their sizes.
There is no magic widget which would simply recursively animate all children. But I think what you want is an implicitly animated widget. ie. you change the constructor parameters of a widget, and as it changes it animates from one value to the next.
The easiest way is probably the ImplicitlyAnimatedWidget with a AnimatedWidgetBaseState. So for your example to animate a boxSize attribute this could look like:
class AnimatedFlutterLogo extends ImplicitlyAnimatedWidget {
const AnimatedFlutterLogo({Key key, #required this.boxSize, #required Duration duration})
: super(key: key, duration: duration);
final double boxSize;
#override
ImplicitlyAnimatedWidgetState<ImplicitlyAnimatedWidget> createState() => _AnimatedFlutterLogoState();
}
class _AnimatedFlutterLogoState extends AnimatedWidgetBaseState<AnimatedFlutterLogo> {
Tween<double> _boxSize;
#override
void forEachTween(visitor) {
_boxSize = visitor(_boxSize, widget.boxSize, (dynamic value) => Tween<double>(begin: value));
}
#override
Widget build(BuildContext context) {
return Container(
child: FlutterLogo(
size: _boxSize?.evaluate(animation),
),
);
}
}
which is imho already pretty concise, the only real boilerplate is basically the forEachTween(visitor) method which has to create Tween objects for all properties you'd like to animate.

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 display an animated picture in Flutter?

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

Resources