Skip to content

Allow ScrollPosition's animateTo to continue even after overscroll #159809

@yakagami

Description

@yakagami

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:

https://dartpad.dev/?id=f38ef2b017089ac16f968a1ef0ac0858

incorrect behavior due to interrupted animation:

https://dartpad.dev/?id=f538936db13e79dc2518dcd712086d50

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    c: new featureNothing broken; request for a new capabilityc: proposalA detailed proposal for a change to Flutterf: scrollingViewports, list views, slivers, etc.frameworkflutter/packages/flutter repository. See also f: labels.r: solvedIssue is closed as solvedteam-frameworkOwned by Framework team

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions