i am building an chat app. I want whenever the chat class open it scrolled to max initially.
i tried the below code but it's wrong
void initState() {
super.initState();
scrollController = ScrollController();
scrollController.animateTo(scrollController.position.maxScrollExtent,
duration: Duration(milliseconds: 10), curve: Curves.easeOut);
}
I'm also building an app with chatting integrated, and I had the same problem. I fixed it by making it addPostFrameCallback when building. This is called everytime the widget is rebuilt.
Widget build(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((_) {
scrollController.jumpTo(positionToScroll);
}
}
You can use animateTo instead of jumpTo and it should still work.
Probably, any scrollable widget is not rendering in initstate yet.
You can try to change the scroll position after your scrollable widget is ready.
Use addPostFrameCallback from SchedulerBinding to force ScrollController to move only when the building process completes, guaranteeing it has an initial position to move from :
Import Scheduler :
import 'package:flutter/scheduler.dart';
Modify your initState() :
void initState() {
super.initState();
scrollController = ScrollController();
SchedulerBinding.instance.addPostFrameCallback((_) => scrollController.animateTo(scrollController.position.maxScrollExtent, duration: Duration(milliseconds: 10), curve: Curves.easeOut));
}
Related
I made a widget 'ExpandableSection' which animates the child content by using a SizeTransition. It also works fine so far but I would like to have an additional parameter like "disableAtFirstBuild" so there is no initial animation but the widget is instantly shown. And only on rebuild the animation should be triggered. It seems like an easy task but I searched multiple hours for a solution without luck. For example I tried to set the animation duration to zero at first, invert a state boolean to save the fact that one build is done and afterwards set the duration to the normal value again. But somehow you cannot change an active controller. Is there a way to do it? Maybe it is quite easy and obvious but it is just not clear to me.
Any help would be much appreciated.
import 'package:flutter/widgets.dart';
class ExpandableSection extends StatefulWidget {
final Widget? child;
final bool? expand, useSliverScrollSafeMode;
final Axis axis;
ExpandableSection({this.expand = false, this.useSliverScrollSafeMode = false, this.axis = Axis.vertical, this.child});
#override
_ExpandableSectionState createState() => _ExpandableSectionState();
}
class _ExpandableSectionState extends State<ExpandableSection> with SingleTickerProviderStateMixin {
late AnimationController expandController;
late Animation<double> animation;
#override
void initState() {
super.initState();
prepareAnimations();
_runExpandCheck();
}
void prepareAnimations() {
expandController = AnimationController(vsync: this, duration: const Duration(milliseconds: 650));
animation = CurvedAnimation(
parent: expandController,
curve: Curves.easeInOutQuart,
);
}
void _runExpandCheck() {
if (widget.expand!) {
expandController.forward();
} else {
expandController.reverse();
}
}
#override
void didUpdateWidget(ExpandableSection oldWidget) {
super.didUpdateWidget(oldWidget);
_runExpandCheck();
}
#override
void dispose() {
expandController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
if (widget.useSliverScrollSafeMode! && !expandController.isAnimating && !widget.expand!) {
return SizedBox.shrink();
} else {
return SizeTransition(axisAlignment: -1, axis: widget.axis, sizeFactor: animation, child: Center(child: widget.child));
}
}
}
Add 'value' to your controller while creating it; like this:
expandController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 650),
value: widget.expand ? 650 : 0,
);
Try stoping the animation controller in initState and then calling .forward() whenever you want it to run.
#override
void initState() {
super.initState();
prepareAnimations();
_runExpandCheck();
expandController.stop();
}
I have created the state currentPage for a carousel of images. My three possible events are increment/decrement and set page which will change the currently viewed page of the carousel. I'm using a timer to increment or reset the state after 5 seconds depending on what the currently viewed page is.
#override
void initState() {
super.initState();
Timer.periodic(const Duration(seconds: 5), (Timer timer) {
if (_currentPage < 2) {
context.read<CarouselBloc>().add(IncrementCurrentPage());
} else {
context.read<CarouselBloc>().add(SetCurrentPage(newPage: 0));
}
_pageController.animateToPage(_currentPage,
duration: const Duration(milliseconds: 300), curve: Curves.easeIn);
});
}
I now want to change the _currentPage variable to the state state.currentPage. But I don't know how to access the state from within my void initState function. I'm wrapping my carousel widget with a BlocProvider to read the CarouselBloc from the context and emit the events depending on the current page.
I hope I can get some help regarding the access of the state.currentPage from within the initState function.
try this:
var page = context.read<CarouselBloc>().state.currentPage
I am working On Tap Bounce feature on my widget. I have referred to these links for information:
Bouncing Button Flutter
Create on tap bounce on flutter
I got this package named as bouncing_widget, which was good enough for the workaround, but there is one limitation to it, i.e., It is not good for scrolling widget. You cannot scroll the page by tapping on the widget which uses this flutter bouncing widget
Somebody has already created a bug for the above widget, and no solution is found. Here is where you can find the issue: Bouncing Widget GitHub Issue
So, I decided to make my own widget which does the job for me. I have made the widget, and it works fine, but there is a limitation, i.e.,
When you scroll the page by holding on the widget, the widget stays there in one fixed position, that means, like it remains in pressed position
Here is my code:
import 'package:flutter/material.dart';
class CLBounceWidget extends StatefulWidget {
final Function onPressed;
final Widget child;
CLBounceWidget({Key key, #required this.onPressed, #required this.child}): super(key:key);
#override
CLBounceWidgetState createState() => CLBounceWidgetState();
}
class CLBounceWidgetState extends State<CLBounceWidget> with SingleTickerProviderStateMixin{
double _scale;
final Duration duration = Duration(milliseconds: 200);
AnimationController _animationController;
//Getting onPressed Calback
VoidCallback get onPressed => widget.onPressed;
#override
void initState() {
_animationController = AnimationController(
vsync: this,
duration: duration,
lowerBound: 0.0,
upperBound: 0.1
)..addListener((){ setState((){}); });
super.initState();
}
#override
void dispose() {
// TODO: implement dispose
_animationController?.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
_scale = 1 - _animationController.value;
return GestureDetector(
onTapDown: _onTapDown,
onTapUp: _onTapUp,
child: Transform.scale(
scale: _scale,
child: widget.child
)
);
}
// Triggering the onPressed callback with a check
void _onTapTrigger(){
if(onPressed != null){
onPressed();
}
}
//Start the animation
_onTapDown(TapDownDetails details){
_animationController.forward();
}
// We revise the animation and notify the user of the event
_onTapUp(TapUpDetails details){
Future.delayed(Duration(milliseconds: 100), (){
_animationController.reverse();
});
//Finally calling the callback function
_onTapTrigger();
}
}
Also: I have tried doing this, without using Future, but with that, Animation only happens when long pressed, just _onTapTrigger() works:
_onTapUp(TapUpDetails details){
_animationController.reverse();
_onTapTrigger();
}
Tried: I have tried using onLongPress, onLongPressEnd, onLongPressMoveUpdate to at least do the _animationController.reverse();, but nothing worked out for me.
I want the widget to stay normal when I scroll the page while holding the widget, like a normal widget performs.
Since, after a long more researches, and hours and hours of toil, I finally found out what I was willing to have in my project. I have created a package on flutter pub.dev to help other developers in need of what the package flutter_bounce has to offer.
You can search for flutter_bounce on the above link, or you can directly go to this: flutter_bounce
To make use of it, you just need to do this:
Bounce(
duration: Duration(milliseconds: 110),
onPressed: (){ YOUR_FUNCTION },
child: YOUR_WIDGET
)
For more details, please feel free to read the README of the link. It has all necessary details. Happy coding :)
Another new package that you guys can try is flutter_bounceable that I've developed. If you guys prefer a much more simple, interactive and customizable bounce widget, then this package is just for you.
Bounceable(
onTap: yourFunction, // set to null if want to disable bounceable
child: YourWidget(),
duration: const Duration(milliseconds: 200), // optional
reverseDuration: const Duration(milliseconds: 200), // optional
curve: Curves.decelerate, // optional
reverseCurve: Curves.decelerate, // optional
);
My app contains a couple of pages. In the appbar I have a selfmade stateful widget with a badge that shows the number of new messages. When I swipe to refresh data the badge will run a small animation if the badge value is changed.
The problem is that the badge value comes from a scoped model. How do I run the animation from the scoped model class. I tried to let the scoped model class hold the animationController as well as a function. It works on the first and second screen. But when I am navigating back to the first page again and pull to refresh. It is like the animationController is in bad state.
Code in the scoped model:
Function _runNotificationAnimation;
set runNotificationAnimation(Function fun) => _runNotificationAnimation = fun;
void _setNotificationCount(int count) {
_notificationCount = count;
if (count > 0 && _runNotificationAnimation != null) {
_runNotificationAnimation();
}
notifyListeners();
}
function that runs the animation
runAnim() {
setState(() {
controller.reset();
controller.forward(from: 0.0);
});
}
Error from flutter:
[VERBOSE-2:shell.cc(184)] Dart Error: Unhandled exception:
NoSuchMethodError: The method 'stop' was called on null.
Receiver: null
Tried calling: stop(canceled: true)
0 Object.noSuchMethod (dart:core/runtime/libobject_patch.dart:50:5)
1 AnimationController.stop (package:flutter/src/animation/animation_controller.dart:650:13)
2 AnimationController.value= (package:flutter/src/animation/animation_controller.dart:349:5)
3 AnimationController.reset (package:flutter/src/animation/animation_controller.dart:370:5)
4 NotificationIconState.runAnim (package:volvopenta/widgets/notificaton_icon.dart:38:16)
5 SettingsModel._setNotificationCount (package:volvopenta/scoped-models/settings-model.dart:57:7)
6 SettingsModel.updateAppData (package:volvopenta/scoped-models/settings-model.dart:185:5)
7 MyMachines.build... (package:volvopenta/pages/fleet.dart:83:27)
8<…>
Since your animation will be built in a stateful Widget, it is better to leave the animationController in that stateful Widget and move only the animation (Tween) in the model class. It is essential that you place the notifyListener(); in the controller.addListerner() and not at the end of the function.
class MyModel extends Model{
Animation animation;
runAnimation(controller) {
animation = Tween(begin: 0,0, end:
400).animate(controller);
controller.forward();
controller.addListener((){
notifyListeners();
});
}
}
You can call this function in your stateful widget as follows:
class _MyScreenState extends State<MyScreen> with
SingleTickerProviderStateMixin{
AnimationController controller;
MyModel myModel = MyModel();
#overide
void initState(){
super.initState();
controller = AnimationController(duration: Duration(seconds: 2), vsync:
this);
myModel.runAnimation(controller);
}
//dispose here
#override
Widget build(Buildcontext context){
return ScopedModel<MyModel>(
model: myModel,
child: Scaffold(
body: Text("Hello", style: TextStyle(fontSize: 13 *
controller.value)),
),
);
}
}
I made a custom transition for my iOS project, and now I want to move the project to Flutter. The transition is fading out the old page, and fading in the new one.
But I cannot achieve this by overriding the PageRoute.
I did some research on this:
There's a similar question
Animate route that is going out / being replaced
From the accepted answer, I know there's a parameter 'secondaryAnimation' which may be useful to achieve it, but after trying to use the code from it, I still cannot animate the old page, all transitions have happened to the new page (the 'child' widget).
Can I get an 'old page' instance from the buildTransition method for animating? Or is there a better way to animate the old page?
Thanks!
I think that secondaryAnimation is used when transitioning to another page. So for your initial route, you'd have to specify the animation of it going away using secondaryAnimation and on your second page you use animation to animate how it appears.
It's a bit awkward that you have to use secondaryAnimation when creating the first route, because it means that it will be used for any transition away from that route. So, with PageRouteBuilder, you can't, for example, let the old page slide to the left when transitioning to page B but slide up when transitioning to page C.
I wrote two classes to achieve animating the old page.
PageSwitcherBuilder is a widget builder to animate old page.
PageSwitcherRoute is a route class to navigate new page.
Examples on DartPad
Here is my script.
class PageSwitcherBuilder extends StatefulWidget {
const PageSwitcherBuilder(
{Key? key,
required this.builder,
this.duration = const Duration(milliseconds: 500),
this.reverseDuration = const Duration(milliseconds: 500)})
: super(key: key);
final Duration duration;
final Duration reverseDuration;
final Widget Function(AnimationController controller) builder;
#override
State<PageSwitcherBuilder> createState() => _PageSwitcherBuilderState();
}
class _PageSwitcherBuilderState extends State<PageSwitcherBuilder>
with TickerProviderStateMixin {
late final AnimationController _controller = AnimationController(
vsync: this,
duration: widget.duration,
reverseDuration: widget.reverseDuration,
);
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return widget.builder(_controller);
}
}
class PageSwitcherRoute extends PageRouteBuilder {
PageSwitcherRoute({
required this.controller,
required Widget page,
}) : super(pageBuilder: (BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
return page;
}) {
willDisposeAnimationController = false;
}
#override
final AnimationController controller;
#override
AnimationController createAnimationController() {
return controller;
}
}
You may also check this ZoomPageTransitionsBuilder class to change default transitions.
Workaround that I've come up with is to observe pushes and pops of routes and animte during these.
Create a Route Wrapper using RouteObserver
Run animation on didPushNext
Reverse animation on didPopNext
Wrap all screens that you want to animate on exit and on reenter.
import 'package:flutter/material.dart';
class ScreenSlideTransition extends StatefulWidget {
const ScreenSlideTransition({super.key, required this.child});
final Widget child;
#override
State<ScreenSlideTransition> createState() => _ScreenSlideTransitionState();
}
class _ScreenSlideTransitionState extends State<ScreenSlideTransition>
with RouteAware, TickerProviderStateMixin {
late final AnimationController _controller;
#override
Widget build(BuildContext context) {
final screenWidth = -MediaQuery.of(context).size.width;
return AnimatedBuilder( // Whatever animation you need goes here
animation: _controller,
builder: (BuildContext context, Widget? child) => Transform.translate(
offset: Offset(screenWidth * _controller.value, 0),
child: widget.child));
}
#override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this, duration: const Duration(milliseconds: 1000));
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
routeObserver.subscribe(this, ModalRoute.of(context)!);
}
#override
void dispose() {
_controller.dispose();
routeObserver.unsubscribe(this);
super.dispose();
}
#override
void didPopNext() {
_controller.reverse();
super.didPopNext();
}
#override
void didPushNext() {
_controller.forward();
super.didPushNext();
}
}
In order to make the above code work make sure to initialise RouteObserver (see docs)
final RouteObserver<ModalRoute<void>> routeObserver = RouteObserver<ModalRoute<void>>();
void main() {
runApp(MaterialApp(
home: Container(),
navigatorObservers: <RouteObserver<ModalRoute<void>>>[ routeObserver ],
));
}
Remember to set the same Duration for AnimationController in ScreenSlideTransition and duration for transitionsBuilder if you want to synchronise in/out animation.