Flutter Sectioned GridView - Header/Category/Group - performance

https://gist.github.com/gabrielemariotti/e81e126227f8a4bb339c
Android has SimpleSectionedListAdapter for RecyclerView.
This is achievable in Flutter using Nested ListView and GridView or CustomScrollView.
The problem is that, the first solution is not as performant as the latter one and the latter one is buggy right now: https://github.com/flutter/flutter/issues/16125
So is there another approach which favors performance?

One way you can approach this is by using flutter_staggered_grid_view plugin to manage dynamic GridView items.
Here's a sample that you can try out.
import 'package:flutter/material.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: const MyStatelessWidget(),
),
);
}
}
class MyStatelessWidget extends StatelessWidget {
const MyStatelessWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
int cursor = 1;
int sectionCursor = 1;
return StaggeredGridView.countBuilder(
crossAxisCount: 4,
itemCount: null,
itemBuilder: (BuildContext context, int index) => Container(
color: (index % 9 == 0) ? Colors.lightBlueAccent : Colors.white,
child: Center(
child: (index % 9 == 0) ? Text('Section ${sectionCursor++}') : Text('Sub item ${cursor++}'),
)),
staggeredTileBuilder: (int index) =>
StaggeredTile.count((index % 9 == 0) ? 4 : 1, 1),
mainAxisSpacing: 4.0,
crossAxisSpacing: 4.0,
);
}
}
Demo

Related

How to get the height and width of a imageProvider in flutter?

In Flutter, How to get the width and height of a imageProvider?
In the example below, I need to get the width of the imageProvider. So that I can calculate the minScale for the PhotoView widget in photo_view package.
import 'package:flutter/material.dart';
import 'package:photo_view/photo_view.dart';
class ImageViewerScreen extends StatelessWidget {
final ImageProvider imageProvider;
ImageViewerScreen({#required this.imageProvider});
#override
Widget build(BuildContext context) {
final double _screenWidth = MediaQuery.of(context).size.width;
final double _imageWidth =
imageProvider.width; //Here is the question: how to get the width of this imageProvider?
final double _minScale = _screenWidth / _imageWidth;
return Scaffold(
appBar: AppBar(),
body: Container(
child: PhotoView(
imageProvider: imageProvider,
minScale: _minScale,
)),
);
}
}

How to animate AnimatedAlign by Curve in Flutter

I want bubble to move smoothly between edges of the screen tracking random curves.
This is the code I have
class BubbleAnimator extends StatefulWidget {
const BubbleAnimator({Key key}) : super(key: key);
#override
_BubbleAnimatorState createState() => _BubbleAnimatorState();
}
class _BubbleAnimatorState extends State<BubbleAnimator> {
double x;
double y;
#override
void initState() {
super.initState();
x = math.Random().nextDouble()*2-1;
y = math.Random().nextDouble()*2-1;
}
#override
Widget build(BuildContext context) {
return Bubble(
x: x,
y: y,
);
}
}
class Bubble extends StatelessWidget {
final double x;
final double y;
const Bubble({Key key, this.x, this.y}) : super(key: key);
#override
Widget build(BuildContext context) {
return AnimatedAlign(
duration: Duration(milliseconds: 300),
alignment: Alignment(x, y),
child: GestureDetector(
onTap: () {
print("tapped");
},
child: AnimatedContainer(
duration: Duration(milliseconds: 300),
decoration:
BoxDecoration(shape: BoxShape.circle, color: Colors.blue),
height: 30,
width: 30,
)),
);
}
}
How could I make my BubbleAnimator to move by curve?
For example, let my bubble shows up in the middle of the screen. Bubble then moves toward random edge of screen by surfing some smooth curve. Hitting the edge bubble then changes it's direction to some other edge tracking another unpredictable curve.
AnimatedAlign like any other Animated Widget (AnimatedContainer, etc) needs to be inside a StatefulWidget.
Your x and y values can't be final as they need to change to animate your Alignment.
Either move your BubbleWidget logic inside your BubbleAnimator or move everything to Bubble and convert that one into a StatefulWidget.
You'd need to call setState every time you change your x and y variables.

How can I scale/zoom in and out the widgets with only one finger using matrix gesture detector?

I am trying to scaling/zooming the widgets by tapping and dragging from certain points of widgets by only one finger using matrix gesture detector.With two finger it is working but i need it done with one finger.How can I implement it? My code is:
class MyTransformWidget extends StatefulWidget {
final Widget transformWidget;
MyTransformWidget({this.transformWidget});
#override
_MyTransformWidgetState createState() => _MyTransformWidgetState();
}
class _MyTransformWidgetState extends State<MyTransformWidget> {
#override
Widget build(BuildContext context) {
final ValueNotifier<Matrix4> notifier = ValueNotifier(Matrix4.identity());
return MatrixGestureDetector(
onMatrixUpdate: (m, tm, sm, rm) {
notifier.value = m;
},
child: AnimatedBuilder(
animation: notifier,
child: widget.transformWidget,
builder: (ctx, child) {
return Transform(transform: notifier.value, child: child);
},
),
shouldRotate: false,
shouldScale: true,
shouldTranslate: false,
);
}
}

What would be the proper way to update animation values in a Flutter animation?

So I'm trying to create an animation in Flutter that requires a different outcome every time the user presses a button.
I've implemented the following code according to the Flutter Animations tutorial and created a function to update it.
class _RoulettePageWidgetState extends State<RoulettePageWidget>
with SingleTickerProviderStateMixin {
Animation<double> _animation;
Tween<double> _tween;
AnimationController _animationController;
int position = 0;
#override
void initState() {
super.initState();
_animationController =
AnimationController(duration: Duration(seconds: 2), vsync: this);
_tween = Tween(begin: 0.0, end: 100.0);
_animation = _tween.animate(_animationController)
..addListener(() {
setState(() {});
});
}
void setNewPosition(int newPosition) {
_tween = Tween(
begin: 0.0,
end: math.pi*2/25*newPosition);
_animationController.reset();
_tween.animate(_animationController);
_animationController.forward();
}
#override
Widget build(BuildContext context) {
return Container(
child: Column(
children: <Widget>[
Center(
child: Transform.rotate(
angle: _animationController.value,
child: Icon(
Icons.arrow_upward,
size: 250.0,
),
)),
Expanded(
child: Container(),
),
RaisedButton(
child: Text('SPIN'),
onPressed: () {
setState(() {
setNewPosition(math.Random().nextInt(25));
});
},
)
],
)
);
}
}
As you can see I'm updating the _tween's begin: and end: but this doesn't seem to change the animation.
So what should I be doing to create a 'different' animation every time the users presses the button?
The general idea is to make the animations build upon each other with a random new value so for example:
first spin: 0 -> 10
second spin: 10 -> 13
third spin: 13 -> 18
... etc
So I wondered if I could update the animation, or should I create a new animation every time?
Another thing I could think of was tracking the positions and use the same animation every time (0.0 -> 100.0) to act as a percentage of the transfer.
So instead of creating a new animation from 10 -> 15 I would be doing something like:
currentValue = 10 + (15-10)/100*_animationController.value
I'm going to skip your code a bit, and focus on what you're really asking:
The general idea is to make the animations build upon each other with a random new value so for example:
first spin: 0 -> 10
second spin: 10 -> 13
third spin: 13 -> 18
... etc
With an explicit animation like this, there are three objects you are interested in:
a controller, which is a special kind of Animation that simply generates values linearly from its lower to its upper bound (both doubles, typically 0.0 and 1.0). You can control the flow of the animation - send it running forward, reverse it, stop it, or reset it.
a tween, which isn't an Animation but rather an Animatable. A tween defines the interpolation between two values, which don't even have to be numbers. It implements a transform method under the hood that takes in the current value of an animation and spits out the actual value you want to work with: another number, a color, a linear gradient, even a whole widget. This is what you should use to generate your angles of rotation.
an animation, which is the animation whose value you're actually going to work with (so this is where you'd grab values to build with). You get this by giving your tween a parent Animation to transform - this might be your controller directly but can also be some other sort of animation you've built on it (like a CurvedAnimation, which would give you easing or bouncy/elastic curves and so on). Flutter's animations are highly composable that way.
Your code is failing largely because you're not actually using the top-level animation you created in your build method and you're creating a new tween and animation every time you call setNewPosition. You can use the same tween and animation for multiple animation "cycles" - simply change the begin and end properties of the existing tween and it bubbles up to the animation. That ends up something like this:
class _RoulettePageWidgetState extends State<RoulettePageWidget>
with SingleTickerProviderStateMixin {
Animation<double> _animation;
Tween<double> _tween;
AnimationController _animationController;
math.Random _random = math.Random();
int position = 0;
double getRandomAngle() {
return math.pi * 2 / 25 * _random.nextInt(25);
}
#override
void initState() {
super.initState();
_animationController =
AnimationController(duration: Duration(seconds: 2), vsync: this);
_tween = Tween(begin: 0.0, end: getRandomAngle());
_animation = _tween.animate(_animationController)
..addListener(() {
setState(() {});
});
}
void setNewPosition() {
_tween.begin = _tween.end;
_animationController.reset();
_tween.end = getRandomAngle();
_animationController.forward();
}
#override
Widget build(BuildContext context) {
return Container(
child: Column(
children: <Widget>[
Center(
child: Transform.rotate(
angle: _animation.value,
child: Icon(
Icons.arrow_upward,
size: 250.0,
),
)),
Expanded(
child: Container(),
),
RaisedButton(
child: Text('SPIN'),
onPressed: setNewPosition,
)
],
)
);
}
}
Hope that helps!
While working, in no situation will you actually want to make these animations within your layout as explained by #filleduchaos.
This is under optimized, as you're rebuilding far more than you should for the animation. And it's a pain to write yourself.
You'll want to use the AnimatedWidget family for this. They are divided into two
kinds:
XXTransition
AnimatedXX
The first is a low layer that consumes an Animation and listens to it so that you don't need to do that ugly :
..addListener(() {
setState(() {});
});
The second handles the remaining pieces: AnimationController, TickerProvider and Tween.
This makes using animations much easier as it's almost entirely automatical.
In your case a rotation example would be as followed:
class RotationExample extends StatefulWidget {
final Widget child;
const RotationExample({
Key key,
this.child,
}) : super(key: key);
#override
RotationExampleState createState() {
return new RotationExampleState();
}
}
class RotationExampleState extends State<RotationExample> {
final _random = math.Random();
double rad = 0.0;
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: _rotate,
child: AnimatedTransform(
duration: const Duration(seconds: 1),
alignment: Alignment.center,
transform: Matrix4.rotationZ(rad),
child: Container(
color: Colors.red,
height: 42.0,
width: 42.0,
),
),
);
}
void _rotate() {
setState(() {
rad = math.pi * 2 / 25 * _random.nextInt(25);
});
}
}
Easier right?
The irony is that Flutter forgot to provide an AnimatedTransform (even although we have many others !). But no worries, I made it for you!
The AnimatedTransform implementation is as followed :
class AnimatedTransform extends ImplicitlyAnimatedWidget {
final Matrix4 transform;
final AlignmentGeometry alignment;
final bool transformHitTests;
final Offset origin;
final Widget child;
const AnimatedTransform({
Key key,
#required this.transform,
#required Duration duration,
this.alignment,
this.transformHitTests = true,
this.origin,
this.child,
Curve curve = Curves.linear,
}) : assert(transform != null),
assert(duration != null),
super(
key: key,
duration: duration,
curve: curve,
);
#override
_AnimatedTransformState createState() => _AnimatedTransformState();
}
class _AnimatedTransformState
extends AnimatedWidgetBaseState<AnimatedTransform> {
Matrix4Tween _transform;
#override
void forEachTween(TweenVisitor<dynamic> visitor) {
_transform = visitor(_transform, widget.transform,
(dynamic value) => Matrix4Tween(begin: value));
}
#override
Widget build(BuildContext context) {
return Transform(
alignment: widget.alignment,
transform: _transform.evaluate(animation),
transformHitTests: widget.transformHitTests,
origin: widget.origin,
child: widget.child,
);
}
}
I will submit a pull request so that in the future you won't need this bit of code.
If you want to reverse your animation with a different path (go/back way). Try this.
In your setNewPosition function, just define new begin/end value for _tween.
void setNewPosition() {
_tween.begin = 0; //new begin int value
_tween.end = 1; //new end int value
_animationController.reverse();
}

Is it possible to draw an image with Dart/Flutter?

I'm looking to find a path to generating an image (jpeg or png) from within a flutter application. The image would be composed of circles, lines, text etc.
There does appear to be a means of drawing to the screen using a canvas (https://docs.flutter.io/flutter/dart-ui/Canvas/Canvas.html), however there doesn't appear to be the equivalent for creating an image that could be presented within or sent/used outside the application.
Is there any dart library available for drawing an image? It would seem that it possible given the underlying skia framework. In the Dart-html package there is a CanvasRenderingContext2D.
Edit: Getting something like the following working (as per Richard's suggestions) would be a start:
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:ui';
import 'dart:typed_data';
import 'dart:async';
import 'dart:io';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Image _image;
#override
void initState() {
super.initState();
_image = new Image.network(
'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_120x44dp.png',
);
}
Future<String> get _localPath async {
final directory =
await getApplicationDocumentsDirectory(); //From path_provider package
return directory.path;
}
Future<File> get _localFile async {
final path = await _localPath;
return new File('$path/tempImage.png');
}
Future<File> writeImage(ByteData pngBytes) async {
final file = await _localFile;
// Write the file
file.writeAsBytes(pngBytes.buffer.asUint8List());
return file;
}
_generateImage() {
_generate().then((val) => setState(() {
_image = val;
}));
}
Future<Image> _generate() async {
PictureRecorder recorder = new PictureRecorder();
Canvas c = new Canvas(recorder);
var rect = new Rect.fromLTWH(0.0, 0.0, 100.0, 100.0);
c.clipRect(rect);
final paint = new Paint();
paint.strokeWidth = 2.0;
paint.color = const Color(0xFF333333);
paint.style = PaintingStyle.fill;
final offset = new Offset(50.0, 50.0);
c.drawCircle(offset, 40.0, paint);
var picture = recorder.endRecording();
final pngBytes =
await picture.toImage(100, 100).toByteData(format: ImageByteFormat.png);
//Aim #1. Upade _image with generated image.
var image = Image.memory(pngBytes.buffer.asUint8List());
return image;
//new Image.memory(pngBytes.buffer.asUint8List());
// _image = new Image.network(
// 'https://github.com/flutter/website/blob/master/_includes/code/layout/lakes/images/lake.jpg?raw=true',
// );
//Aim #2. Write image to file system.
//writeImage(pngBytes);
//Make a temporary file (see elsewhere on SO) and writeAsBytes(pngBytes.buffer.asUInt8List())
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
_image,
],
),
),
floatingActionButton: new FloatingActionButton(
onPressed: _generateImage,
tooltip: 'Generate',
child: new Icon(Icons.add),
),
);
}
}
PictureRecorder lets you create a Canvas, use the Canvas drawing methods and provides endRecording() returning a Picture. You can draw this Picture to other Scenes or Canvases, or use .toImage(width, height).toByteData(format) to convert it to PNG (or raw - jpeg isn't supported).
For example:
import 'dart:ui';
import 'dart:typed_data';
....
PictureRecorder recorder = new PictureRecorder();
Canvas c = new Canvas(recorder);
c.drawPaint(paint); // etc
Picture p = recorder.endRecording();
ByteData pngBytes =
await p.toImage(100, 100).toByteData(format: ImageByteFormat.png);
Make sure that you are on flutter 0.4.4, otherwise you may not have the format parameter available.
Having seen your edit, though, I suspect you are really looking for CustomPainter where a Widget gives you a Canvas on which you can draw. Here's an example from a similar question.

Resources