Question:

When using scroll components such as Flutter’s ListView for history or logging, you need to locate the content at the bottom (default is at the top) immediately after updating the content.

Looking for solutions:

  1. The ListView has a reverse property, which allows it to always display new content if it’s inverted.

But the problem is that when the content is small, it is displayed directly at the bottom, so it looks strange.

  1. The ListView’s ScrollController itself provides methods for scrolling pixels, calculating viewport height and total content height, and then calling the Controller’s jumpTo or animateTo

This is a temporary solution, need to measure the height of the current viewport, and then write in the code, the height of the content is calculated according to the number of child elements, the content should not be too different, equal height is easy to control

It works fine on Windows, but on Apple devices, due to the nature of physical scrolling, it can be quite awkward

  1. Find a new Controller can control the FixedExtentScrollController scroll to the currently selected component

It turned out to be a misunderstanding that the controller was intended for a new component, ListWheelScrollView, which is a gear scroll selection component with the checked item always in the middle

Final plan:

This property. At last, through the print ScrollController position, found that it is in the running ScrollPositionWithSingleContext instances, this example can be directly obtained viewport height to the scroll component, a scrollable parameters such as the scope.

Demo code

import 'package:flutter/material.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 Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo For ListView')); }}class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  ScrollController controller;

  @override
  void initState() {
    super.initState();
    controller = ScrollController();
  }

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
    Future.delayed(Duration(milliseconds: 16)).then((value) =>
        controller.animateTo(
            controller.position.maxScrollExtent,
            duration: Duration(milliseconds: 200),
            curve: Curves.easeOutQuart));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Container(
          width: 150,
          height: 300,
          decoration: BoxDecoration(
              border: Border.all(color: Colors.black26, width: 0.5)),
          child: ListView(
            controller: controller,
            padding: EdgeInsets.all(10),
            children: List<Widget>.generate(
                _counter, (index) => Text('This is row $index')),
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.); }}Copy the code

One thing to note here is that you cannot scroll immediately when adding a new element (the code uses a delay of 16ms, or one frame), otherwise you will not be able to scroll to the end. Since adding an element is a data operation, updating to the interface needs to wait for the next build. If the new element has an entry animation, the delay of calling the scroll should be delayed until the animation ends, otherwise the maxScrollExtend obtained is inaccurate.