I have a scrollable AnimatedList and I want whenever a new item is added to the end of the list, it would scroll to the end.
I try to apply this code form ListView but it doesn't work.
import 'package:flutter/material.dart';
class TestPage extends StatefulWidget {
#override
_TestPageState createState() => _TestPageState();
}
class _TestPageState extends State<TestPage> {
List<String> list = ["a", "a", "a", "a", "a", "a", "a"];
final ScrollController _listScrollController = new ScrollController();
final GlobalKey<AnimatedListState> _listKey = GlobalKey();
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: AnimatedList(
key: _listKey,
controller: _listScrollController,
initialItemCount: list.length,
itemBuilder: (BuildContext context, int index, Animation animation) {
return FadeTransition(
opacity: animation,
child: Container(
width: itemSize,
height: itemSize,
child: Text(list[index]),
),
);
},
),
),
floatingActionButton: FloatingActionButton(onPressed: _addNewItem),
);
}
_addNewItem() {
list.add(list.length.toString());
_listKey.currentState.insertItem(
list.length - 1,
duration: Duration(seconds: 1),
);
_listScrollController.animateTo(
_listScrollController.position.maxScrollExtent, // wrong value (this value is before add new item)
duration: const Duration(milliseconds: 250),
curve: Curves.ease,
);
}
}
So I need to change the scroll code a little bit.
const double itemSize = 40.0;
_listScrollController.animateTo(
_listScrollController.position.maxScrollExtent + itemSize,
duration: const Duration(milliseconds: 250),
curve: Curves.ease,
);
But it need itemSize to hardcode(itemSize) or use BuildLayout, RenderBox to determine new item size. Do you guys have any better solutions?
The issue is you were animating the stuff before adding the item. You were taking a duration of 1s to add item, use this timer that will run after 1s.
Timer(
Duration(milliseconds: 1100),
() {
_listScrollController.animateTo(
_listScrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 250),
curve: Curves.ease,
);
},
);
Replace yours with mine.
_addNewItem() {
list.add(list.length.toString());
_listKey.currentState.insertItem(
list.length - 1,
duration: Duration(milliseconds: 200),
);
Timer(
Duration(milliseconds: 220),
() {
_listScrollController.animateTo(
_listScrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 500),
curve: Curves.ease,
);
},
);
}
Output:
Related
I have a main widget called DashboardWidget. Inside it, I have a Scaffold with BottomNavigationBar and a FloatingActionButton:
Now, I want to make a widget that would be dragged from the bottom by:
Swiping up with the finger.
Pressing on FloatingActionButton.
In other words, I want to expand the BottomNavigationBar.
Here's a design concept in case I was unclear.
The problem is, I'm not sure where to start to implement that. I've thought about removing the BottomNavigationBar and create a custom widget that can be expanded, but I'm not sure if it's possible either.
Output:
I used a different approach and did it without AnimationController, GlobalKey etc, the logic code is very short (_handleClick).
I only used 4 variables, simple and short!
void main() => runApp(MaterialApp(home: HomePage()));
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
static double _minHeight = 80, _maxHeight = 600;
Offset _offset = Offset(0, _minHeight);
bool _isOpen = false;
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xFFF6F6F6),
appBar: AppBar(backgroundColor: Color(0xFFF6F6F6), elevation: 0),
body: Stack(
alignment: Alignment.bottomCenter,
children: <Widget>[
Align(
alignment: Alignment.topLeft,
child: FlatButton(
onPressed: _handleClick,
splashColor: Colors.transparent,
textColor: Colors.grey,
child: Text(_isOpen ? "Back" : ""),
),
),
Align(child: FlutterLogo(size: 300)),
GestureDetector(
onPanUpdate: (details) {
_offset = Offset(0, _offset.dy - details.delta.dy);
if (_offset.dy < _HomePageState._minHeight) {
_offset = Offset(0, _HomePageState._minHeight);
_isOpen = false;
} else if (_offset.dy > _HomePageState._maxHeight) {
_offset = Offset(0, _HomePageState._maxHeight);
_isOpen = true;
}
setState(() {});
},
child: AnimatedContainer(
duration: Duration.zero,
curve: Curves.easeOut,
height: _offset.dy,
alignment: Alignment.center,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(30),
topRight: Radius.circular(30),
),
boxShadow: [BoxShadow(color: Colors.grey.withOpacity(0.5), spreadRadius: 5, blurRadius: 10)]),
child: Text("This is my Bottom sheet"),
),
),
Positioned(
bottom: 2 * _HomePageState._minHeight - _offset.dy - 28, // 56 is the height of FAB so we use here half of it.
child: FloatingActionButton(
child: Icon(_isOpen ? Icons.keyboard_arrow_down : Icons.add),
onPressed: _handleClick,
),
),
],
),
);
}
// first it opens the sheet and when called again it closes.
void _handleClick() {
_isOpen = !_isOpen;
Timer.periodic(Duration(milliseconds: 5), (timer) {
if (_isOpen) {
double value = _offset.dy + 10; // we increment the height of the Container by 10 every 5ms
_offset = Offset(0, value);
if (_offset.dy > _maxHeight) {
_offset = Offset(0, _maxHeight); // makes sure it does't go above maxHeight
timer.cancel();
}
} else {
double value = _offset.dy - 10; // we decrement the height by 10 here
_offset = Offset(0, value);
if (_offset.dy < _minHeight) {
_offset = Offset(0, _minHeight); // makes sure it doesn't go beyond minHeight
timer.cancel();
}
}
setState(() {});
});
}
}
You can use the BottomSheet class.
Here is a Medium-tutorial for using that, here is a youtube-tutorial using it and here is the documentation for the class.
The only difference from the tutorials is that you have to add an extra call method for showBottomSheet from your FloatingActionButton when it is touched.
Bonus: here is the Material Design page on how to use it.
You can check this code, it is a complete example of how to start implementing this kind of UI, take it with a grain of salt.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:rxdart/rxdart.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Orination Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
bool _isOpen;
double _dragStart;
double _hieght;
double _maxHight;
double _currentPosition;
GlobalKey _cardKey;
AnimationController _controller;
Animation<double> _cardAnimation;
#override
void initState() {
_isOpen = false;
_hieght = 50.0;
_cardKey = GlobalKey();
_controller =
AnimationController(vsync: this, duration: Duration(milliseconds: 700));
_cardAnimation = Tween(begin: _hieght, end: _maxHight).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut)
);
_controller.addListener(() {
setState(() {
_hieght = _cardAnimation.value;
});
});
super.initState();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 0.0,
backgroundColor: Colors.transparent,
titleSpacing: 0.0,
title: _isOpen
? MaterialButton(
child: Text(
"Back",
style: TextStyle(color: Colors.red),
),
onPressed: () {
_isOpen = false;
_cardAnimation = Tween(begin: _hieght, end: 50.0).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut)
);
_controller.forward(from: 0.0);
},
)
: Text(""),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.keyboard_arrow_up),
onPressed: () {
final RenderBox renderBoxCard = _cardKey.currentContext
.findRenderObject();
_maxHight = renderBoxCard.size.height;
_cardAnimation = Tween(begin: _hieght, end: _maxHight).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut)
);
_controller.forward(from: 0.0);
_isOpen = true;
}),
body: Stack(
key: _cardKey,
alignment: Alignment.bottomCenter,
children: <Widget>[
Container(
width: double.infinity,
height: double.infinity,
color: Colors.black12,
),
GestureDetector(
onPanStart: _onPanStart,
onPanUpdate: _onPanUpdate,
onPanEnd: _onPanEnd,
child:Material(
borderRadius: BorderRadius.only(
topRight: Radius.circular(16.0),
topLeft: Radius.circular(16.0),
),
elevation: 60.0,
color: Colors.white,
// shadowColor: Colors.,
child: Container(
height: _hieght,
child: Center(
child: Text("Hello, You can drag up"),
),
),
),
),
],
),
);
}
void _onPanStart(DragStartDetails details) {
_dragStart = details.globalPosition.dy;
_currentPosition = _hieght;
}
void _onPanUpdate(DragUpdateDetails details) {
final RenderBox renderBoxCard = _cardKey.currentContext.findRenderObject();
_maxHight = renderBoxCard.size.height;
final hieght = _currentPosition - details.globalPosition.dy + _dragStart;
print(
"_currentPosition = $_currentPosition _hieght = $_hieght hieght = $hieght");
if (hieght <= _maxHight && hieght >= 50.0) {
setState(() {
_hieght = _currentPosition - details.globalPosition.dy + _dragStart;
});
}
}
void _onPanEnd(DragEndDetails details) {
_currentPosition = _hieght;
if (_hieght <= 60.0) {
setState(() {
_isOpen = false;
});
} else {
setState(() {
_isOpen = true;
});
}
}
}
Edit: I modified the code by using Material Widget instead of A container with shadow for better performance,If you have any issue, please let me know .
I am trying to move the container on the screen by giving begin and end offset like from Offset(0.0,0.0) to Offset(400.0,300.0). I am using Slide Transition to animate the container I am using Tween<Offset>(begin: const Offset(3.0, 4.0), end: Offset(0.0, 0.0)) to move it on the screen I want to pass these Offset(400.0,300.0) and animate it.
Here is my code
class MoveContainer extends StatefulWidget {
MoveContainer({Key key, }) : super(key: key);
#override
State<StatefulWidget> createState() {
return new _MyMoveContainer();
}
}
class _MyMoveContainer extends State<MoveContainer>
with TickerProviderStateMixin {
GlobalKey _globalKey = new GlobalKey();
AnimationController _controller;
Animation<Offset> _offset;
Offset local;
#override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 3),
);
_offset =
Tween<Offset>(begin: const Offset(3.0, 4.0), end: Offset(0.0, 0.0))
.animate(_controller);
_offset.addListener(() {
setState(() {});
});
_controller.forward();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return SlideTransition(
position: _offset,
child: GestureDetector(
onPanStart: (start) {
RenderBox getBox = context.findRenderObject();
local = getBox.localToGlobal(start.globalPosition);
print('point are $local');
},
child: Container(
color: Colors.cyan,
height: 200.0,
width: 200.0,
child: Text("hello ")),
),
);
}
}
Probably this question is not actual for the author. (Asked 7 months ago).
But maybe my answer will help someone else.
Usually Slide Transition is used for transitions between pages. That is why, one unit of position value here is the size of one page. When you put there Offset(400.0,300.0) it's equal 400 screen right, and 300 pages down.
For your case it better to use AnimatedPositioned Widget.
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
backgroundColor: Colors.blue,
body: MoveContainer(),
),
);
}
}
class MoveContainer extends StatefulWidget {
#override
_MoveContainerState createState() => _MoveContainerState();
}
class _MoveContainerState extends State<MoveContainer> {
Offset offset = Offset.zero;
final double height = 200;
final double width = 200;
#override
Widget build(BuildContext context) {
return GestureDetector(
onPanStart: (details) {
RenderBox getBox = context.findRenderObject();
setState(() {
offset = getBox.localToGlobal(details.globalPosition);
});
},
child: Stack(
children: <Widget>[
AnimatedPositioned(
duration: Duration(milliseconds: 300),
top: offset.dy - (height / 2),
left: offset.dx - (width / 2),
child: Container(
color: Colors.cyan,
height: height,
width: width,
child: Text("hello "),
),
),
],
),
);
}
}
I am using Transform widget in my flutter code to rotate the screen
Offset _offset = Offset.zero;
return new Transform(
transform: Matrix4.identity()
..setEntry(3, 2, 0.001)
..rotateX(0.01 * _offset.dy)
..rotateY(-0.01 * _offset.dx)
..rotateZ(- 0.01 * _offset.),
alignment: FractionalOffset.center,
child: new Scaffold(
appBar: AppBar(
title: Text("The 3D Matrix"),
),
body: GestureDetector(
onPanUpdate: (details) => setState(() => _offset += details.delta),
onDoubleTap: () => setState(() => _offset = Offset.zero),
child: Content())
),);
Now what I want is to spin the widget along z-axis with certain velocity and slow down it's speed to zero after few seconds.
May be I need to use the Animation Controller. How can we achieve this state?
Right now I achieved this much:
Simply add an AnimationController to your page widget. Then wrap your Transform into a AnimatedBuilder
And when you need to start the animation, call animationController.forward().
class MyHome extends StatefulWidget {
#override
_MyHomeState createState() => _MyHomeState();
}
class _MyHomeState extends State<MyHome> with SingleTickerProviderStateMixin {
AnimationController animationController;
#override
void initState() {
animationController = new AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
);
super.initState();
}
#override
Widget build(BuildContext context) {
return new AnimatedBuilder(
animation: animationController,
builder: (context, child) {
return new Transform(
transform: Matrix4.identity()
..setEntry(3, 2, 0.001)
..rotateZ(animationController.value * 45.0),
child: child,
);
},
child: new Scaffold(
appBar: AppBar(
title: Text("The 3D Matrix"),
),
body: new Center(
child: new RaisedButton(
onPressed: () => animationController.forward(),
child: new Text("Start anim"),
),
),
),
);
}
}
I'm trying to create an animation in a CustomPainter in which the animation starts from the bottom to up, but it's starting at the top.
When clicking on the FloatActionButton the rectangle should rise to the maximum height of the screen, and when tap again go back to the minimum size.
I can get the size of the screen but I'm not able to insert this animation from the bottom to up. can you help me?
import 'package:flutter/material.dart';
import 'dart:ui' as ui;
void main() {
runApp(new MaterialApp(home: new HomePage()));
}
class HomePage extends StatefulWidget {
#override
HomePageState createState() => new HomePageState();
}
class HomePageState extends State<HomePage> with TickerProviderStateMixin {
AnimationController _controller;
Animation<double> _animation;
bool upDown = true;
#override
void initState() {
_controller = new AnimationController(
vsync: this,
duration: const Duration(milliseconds: 180),
);
_animation = new CurvedAnimation(
parent: _controller,
curve: new Interval(0.0, 1.0, curve: Curves.linear),
);
}
#override
Widget build(BuildContext context) {
final ui.Size logicalSize = MediaQuery.of(context).size;
final double _width = logicalSize.width;
final double _height = logicalSize.height;
void _up(){
setState((){
if(upDown) {
upDown = false;
_controller.forward(from: 0.0);
} else {
upDown = true;
_controller.reverse(from: 1.0);
}
});
}
return new Scaffold(
body: new Stack(
children: <Widget>[
new Positioned(
bottom: 0.0,
child: new AnimatedBuilder(
animation: _animation,
builder: (BuildContext context, Widget child) {
return new Container(
height: _height,
child: new CustomPaint(
painter: new Sky(_width, _height * _animation.value),
//child: new Text('$_height '+ _animation.value.toString()),
),
);
},
),
),
new Positioned(
bottom: 16.0,
right: 16.0,
child: new FloatingActionButton(
backgroundColor: new Color(0xFFE57373),
child: new Icon(Icons.add),
onPressed: (){
_up();
},
)
)
]
)
);
}
}
class Sky extends CustomPainter {
final double _width;
double _rectHeight;
Sky(this._width, this._rectHeight);
#override
void paint(Canvas canvas, Size size) {
canvas.drawRect(
new Rect.fromLTRB(
0.0, 0.0, this._width, _rectHeight
),
new Paint()..color = new Color(0xFF0099FF),
);
}
#override
bool shouldRepaint(Sky oldDelegate) {
return _width != oldDelegate._width || _rectHeight != oldDelegate._rectHeight;
}
}
You can use an AnimatedBuilder to do this. Also, make sure that you provide a working implementation of shouldRepaint. Your upDown member variable should be a member of the State rather than part of the build function.
import 'package:flutter/material.dart';
import 'dart:ui' as ui;
void main() {
runApp(new MaterialApp(home: new HomePage()));
}
class HomePage extends StatefulWidget {
#override
HomePageState createState() => new HomePageState();
}
class HomePageState extends State<HomePage> with TickerProviderStateMixin {
AnimationController _controller;
Animation<double> _animation;
bool upDown = true;
#override
void initState() {
_controller = new AnimationController(
vsync: this,
duration: const Duration(milliseconds: 180),
);
_animation = new CurvedAnimation(
parent: _controller,
curve: new Interval(0.0, 1.0, curve: Curves.linear),
);
}
#override
Widget build(BuildContext context) {
final ui.Size logicalSize = MediaQuery.of(context).size;
final double _width = logicalSize.width;
final double _height = logicalSize.height;
void _up(){
setState((){
if(upDown) {
upDown = false;
_controller.forward(from: 0.0);
} else {
upDown = true;
_controller.reverse(from: 1.0);
}
});
}
return new Scaffold(
body: new Stack(
children: <Widget>[
new Positioned(
bottom: 0.0,
child: new AnimatedBuilder(
animation: _animation,
builder: (BuildContext context, Widget child) {
return new Container(
height: _height,
child: new CustomPaint(
painter: new Sky(_width, _height * _animation.value),
),
);
},
),
),
new Positioned(
bottom: 16.0,
right: 16.0,
child: new FloatingActionButton(
backgroundColor: new Color(0xFFE57373),
child: new Icon(Icons.add),
onPressed: (){
_up();
},
)
)
]
)
);
}
}
class Sky extends CustomPainter {
final double _width;
double _rectHeight;
Sky(this._width, this._rectHeight);
#override
void paint(Canvas canvas, Size size) {
canvas.drawRect(
new Rect.fromLTRB(
0.0, size.height - _rectHeight, this._width, size.height
),
new Paint()..color = new Color(0xFF0099FF),
);
}
#override
bool shouldRepaint(Sky oldDelegate) {
return _width != oldDelegate._width || _rectHeight != oldDelegate._rectHeight;
}
}
I'd like to create a number counter that animates from a starting value to an end value. I've looked into using a Timer but can't seem to animate/update state properly. Including the decimal value would be great, but a simple integer animation is fine.
Number counter that needs to animate
double _mileCounter = 643.6;
_animateMileCounter() {
Duration duration = new Duration(milliseconds: 300);
return new Timer(duration, _updateMileCounter);
}
_updateMileCounter() {
setState(() {
_mileCounter += 1;
});
}
How would I increment the counter X number of times (with animation)? Similar to how a car's odometer increments.
For anyone still looking, you can use ImplicitlyAnimatedWidget.
Here is an example of an int counter. Works analogously for doubles.
class AnimatedCount extends ImplicitlyAnimatedWidget {
final int count;
AnimatedCount({
Key key,
#required this.count,
#required Duration duration,
Curve curve = Curves.linear
}) : super(duration: duration, curve: curve, key: key);
#override
ImplicitlyAnimatedWidgetState<ImplicitlyAnimatedWidget> createState() => _AnimatedCountState();
}
class _AnimatedCountState extends AnimatedWidgetBaseState<AnimatedCount> {
IntTween _count;
#override
Widget build(BuildContext context) {
return new Text(_count.evaluate(animation).toString());
}
#override
void forEachTween(TweenVisitor visitor) {
_count = visitor(_count, widget.count, (dynamic value) => new IntTween(begin: value));
}
}
Just rebuild the widget with a new value and it automatically animates there.
You should use an AnimationController with an AnimatedBuilder to rebuild your text when the controller changes. Here's an example that increments the miles when the floating action button is pressed (double.toStringAsFixed to get the decimal to show), with a curve on the animation speed:
import 'dart:math';
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(primarySwatch: Colors.purple),
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
createState() => new MyHomePageState();
}
class MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
AnimationController _controller;
Animation<double> _animation;
double _miles = 0.0;
#override initState() {
_controller = new AnimationController(
vsync: this,
duration: const Duration(milliseconds: 1500),
);
_animation = _controller;
super.initState();
}
#override
Widget build(BuildContext context) {
TextTheme textTheme = Theme.of(context).textTheme;
return new Scaffold(
body: new Material(
color: const Color.fromRGBO(246, 251, 8, 1.0),
child: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
new AnimatedBuilder(
animation: _animation,
builder: (BuildContext context, Widget child) {
return new Text(
_animation.value.toStringAsFixed(1),
style: textTheme.display4.copyWith(fontStyle: FontStyle.italic),
);
},
),
new Text(
"MILES",
style: textTheme.display1.copyWith(fontStyle: FontStyle.italic),
)
],
),
),
),
floatingActionButton: new FloatingActionButton(
child: new Icon(Icons.directions_run),
onPressed: () {
Random rng = new Random();
setState(() {
_miles += rng.nextInt(20) + 0.3;
_animation = new Tween<double>(
begin: _animation.value,
end: _miles,
).animate(new CurvedAnimation(
curve: Curves.fastOutSlowIn,
parent: _controller,
));
});
_controller.forward(from: 0.0);
}
),
);
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
}
You can use Countup package.
Countup(
begin: 0,
end: 7500,
duration: Duration(seconds: 3),
separator: ',',
style: TextStyle(
fontSize: 36,
),
)
https://pub.dev/packages/countup
You can simply use this plugin countup: ^0.1.3
import 'package:countup/countup.dart';
Countup(
begin: 100,
end: 8000,
duration: Duration(seconds: 3),
separator: ',',
style: TextStyle(
fontSize: 36,
fontweight : Fontweight.bold,
),
),