-
Notifications
You must be signed in to change notification settings - Fork 29.7k
Description
Use case
Currently, animateTo will be interrupted whenever the animation reaches the edge of the viewport and attempts to overscroll. From the docs:
An animation will be interrupted whenever the user attempts to scroll manually, or whenever another activity is started, or whenever the animation reaches the edge of the viewport and attempts to overscroll. (If the ScrollPosition does not overscroll but instead allows scrolling beyond the extents, then going beyond the extents will not interrupt the animation.)
Sometimes a user may want to continue animating even after hitting overscroll, however. Here is a minimal example of where I am running into this:
import 'dart:ui';
import 'package:flutter/material.dart';
class AnimateToTest extends StatefulWidget {
const AnimateToTest({super.key});
@override
State<AnimateToTest> createState() => _AnimateToTestState();
}
class _AnimateToTestState extends State<AnimateToTest> with SingleTickerProviderStateMixin{
double scale = 1.0;
double oldScale = 0.0;
double newScale = 0.0;
double height = 300;
double width = 300;
ScrollController horizontalController = ScrollController();
ScrollController verticalController = ScrollController();
late AnimationController animationController;
final Duration duration = const Duration(seconds: 1);
@override
void initState() {
animationController = AnimationController(vsync: this);
animationController.duration = duration;
animationController.addListener((){
final scaleStep = lerpDouble(oldScale, newScale, animationController.value);
setState(() {
scale = scaleStep!;
});
});
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: SizedBox(
height: height,
width: width,
child: Column(
children: [
Expanded(
child: FittedBox(
fit: BoxFit.fill,
child: SizedBox(
height: height * scale,
width: width * scale,
child: ListView(
//change this to BouncingScrollPhysics to see expected behavior
physics: const ClampingScrollPhysics(),
controller: horizontalController,
scrollDirection: Axis.horizontal,
children: [
Container(
color: Colors.cyanAccent,
width: width,
child: ListView.builder(
physics: const ClampingScrollPhysics(),
controller: verticalController,
itemBuilder: (BuildContext context, int index){
return Center(child: Text("item $index"));
}
),
)
],
),
),
),
),
],
),
),
),
floatingActionButton: TextButton(
onPressed: (){zoom();},
child: const Text("zoom in"),
),
);
}
void zoom(){
if(animationController.isAnimating){return;}
oldScale = scale;
newScale = scale == 1.0 ? scale = 0.5 : scale = 1.0;
final newVertical = newScale == 1.0 ? 0.0 : height/2;
final newHorizontal = newScale == 1.0 ? 0.0 : width/2;
verticalController.animateTo(newVertical, duration: duration, curve: Curves.linear);
horizontalController.animateTo(newHorizontal, duration: duration, curve: Curves.linear);
animationController.reset();
animationController.forward();
}
}
Dartpad demos
Correct behavior:
incorrect behavior due to interrupted animation:
The above code zooms into a ListView using a few tricks, including animateTo. It is a minimal version of the tap-to-zoom feature in a package I wrote. However, it fails when using ClampingScrollPhysics in the horizontal ListView due to applyBoundaryConditions clamping overscroll values. Using a custom ScrollPhysics that sets applyBoundaryConditions to zero does not help in my specific case either. Using BouncingScrollPhysics is also not an option. (InteractiveViewer does not work either as it does not work for zooming lists, see other issues on that subject.)
Proposal
I would like a way for the animation to continue even after over scroll. If you look at the correct behavior case that uses BouncingScrollPhysics, you can see that the List is actually barely ever overscrolling. In my case it is intended for the animation to play that way. It should be up to the developer what happens when an overscroll takes place. I am willing write a PR for this. I checked in DrivenScrollActivity and ScrollPosition but couldn't find where there animation checks for an overscroll, so some pointers on that would be helpful.
Edit: the check is in fact in DrivenScrollActivity
void _tick() {
if (delegate.setPixels(_controller.value) != 0.0) {
delegate.goIdle();
}
}
Simply disabling this check allows the animation to complete however it looks janky.