Repository: flutter
Issue: Provide an easy way to zoom `ListView`s
Labels: framework,f: scrolling,would be a good package,f: gestures,c: proposal,P3,team-framework,triaged-framework

### Use case

The problem: zooming a list of images is not possible with the default flutter widgets

Why is this the case: `ListView` does not support pinch zoom. Using another widget to handle zooming separately does not work in general because there are competing gestures. See eg. https://github.com/flutter/flutter/issues/15011

Major use cases: 

* PDF viewer
* Comic Viewer

Both these use cases require being able to view the pages/images as a list that can be both zoomed and scrolled at the same time.

### Proposal

flutter should provide an easy way to zoom `ListViews`.

Potential solution: use `FittedBox` to scale the `ListView`

This is the solution I have landed on based on a lot of different attempts. It does not require any changes to ListView and can be used on any scrollable widget who's controller exposes it's ScrollPosition, offset, and  jumpTo(offset). The basic zooming functionality works like this:

```dart

double scale = 1;
FittedBox(
  fit: BoxFit.fill,
  child: SizedBox(
    height: height * scale,
    width: width * scale,
    child: ListView( // another listview we use to handle the horizontal zooming and panning
      controller: horizontalController,
      scrollDirection: Axis.horizontal,
      children: [
        widget.child // widget.child being the list we want to zoom
      ],
    ),
  ),
)

```

here is the basic scale updating logic:

```dart

  double scale = 1;
  double lastScale = 1;
  late double distanceFromOffset;
  late double horizontalDistanceFromOffset;
  late double focalPointDistanceFromBottomFactor;
  late double horizontalFocalPointDistanceFromBottomFactor;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onScaleStart: (ScaleStartDetails details) {
        distanceFromOffset = details.localFocalPoint.dy;
        horizontalDistanceFromOffset = details.localFocalPoint.dx;
        focalPointDistanceFromBottomFactor =
            (height - distanceFromOffset) / distanceFromOffset;
        horizontalFocalPointDistanceFromBottomFactor =
            (width - horizontalDistanceFromOffset) /
                horizontalDistanceFromOffset;
      },
      onScaleUpdate: (ScaleUpdateDetails details){
        double oldHeight = height * scale;
        double oldWidth = width * scale;
        setState(() {
          scale = lastScale / details.scale;
        });
        //vertical offset
        final double newHeight = height * scale;
        controller.jumpTo(controller.offset +
            (oldHeight - newHeight) /
                (1 + focalPointDistanceFromBottomFactor));

        //horizontal offset
        final double newWidth = width * scale;
        horizontalController.jumpTo(horizontalController.offset +
            (oldWidth - newWidth) /
                (1 + horizontalFocalPointDistanceFromBottomFactor));
      },
      onScaleEnd: (ScaleEndDetails details){
        lastScale = scale;
      },
      child: FittedBox(
        fit: BoxFit.fill,
        child: SizedBox(
          height: height * scale,
          width: width * scale,
          child: ListView( // another listview we use to handle the horizontal zooming and panning
            controller: horizontalController,
            scrollDirection: Axis.horizontal,
            children: [
              widget.child // widget.child being the list we want to zoom
            ],
          ),
        ),
      ),
    );
  }

```

And a complete ZoomView widget is available at https://github.com/yakagami/zoom_view/ or https://pub.dev/packages/zoom_view. 

The API looks like this:

```dart

class _MyAppState extends State<MyApp> {
  ScrollController controller = ScrollController();
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ZoomListView(
        child: ListView.builder(
            controller: controller,
            itemCount: 10000,
            itemBuilder: (context, index) {
              return Center(
                  child: Text("text $index")
              );
            }
        ),
      ),
    );
  }
}


```

This issue is to discuss and track different potential solutions to the zoom problem. I have provided one potential solution but of course other solutions should be considered here.

Solutions I have tried: 

combine Listview with InteractiveViewer

The main issue with  InteractiveViewer is that it just does a scale transform of it's child before draw like Transform.Scale. So you can't scroll to the top of the list when you zoom in because as far as the child (ie. the scrollable) is concerned, you reach the top of the list sooner because it doesn't know that you are only looking at eg. 1/2 of the total viewport. Here are some issues that show this: https://github.com/flutter/flutter/issues/86531 https://github.com/flutter/flutter/issues/68073. There are some other issues as well related to InteractiveViewers own drag/pan gestures and velocity. What we gain from using FittedBox to scale the viewport is that we don't have to deal with InteractiveViewer and its custom panning, velocity etc. and also the Scrollables know how big they actually are so you can scroll to the edges of the (inner) viewport. 

Solutions I have not tried:

Creating a custom RenderObject: perhaps this might be better, although I don't know enough about RenderObjects to say if that would help.

Creating a custom Viewport: not sure if this would help either. Would like to hear input about this.

For context, in Android RecyclerView (comparable to ListView.builder) you simply call scaleX() or scaleY() on the view and it will scale easily.

Another possibility is to provide a separate `Scrollable` that implements scrolling as well as zooming and panning as opposed to using the existing `ListView`. While this is could be a solution I'm hesitant to suggest that because ideally whatever solution we find here would be able to support indexed lists like eg. [ScrollablePositionedList](https://pub.dev/packages/scrollable_positioned_list) as both PDF and comic viewers need to be able to jump to pages by index. So if we go with a custom solution it would require supporting panning, zooming, scrolling and indexed jump.

A third possibility is that the Framework should not provide a scrolling-zooming scrollable. If this is the case we should make that position known and perhaps show an unofficial best practice for scrolling and zooming without having to maintain an official API for doing so.

Any thoughts on any point about the topic are welcome.