問題:
在使用Flutter的ListView等滾動元件做歷史記錄或日誌記錄時,需要在更新內容後立即定位到內容底部(預設是在頂部的)。
尋找解決方案:
- ListView有一個reverse屬性,如果把內容倒置,倒是可以總顯示新內容。
但問題是內容少的時候,是直接靠底部顯示的,這樣看起來比較怪。
- ListView的ScrollController本身提供了畫素滾動的方法,可以計算視口高度和內容總高度,然後呼叫Controller的jumpTo或animateTo
這是一個臨時解決方案,需要測量出當前視口的高度,然後寫在程式碼裡,內容的高度根據子元素數目計算,需要子元素內容沒太大差異,等高的情況就好控制
在Windows下效果尚可,但到蘋果裝置上由於物理滾動的特性,會很跳脫
- 找到一個新Controller可以控制滾動到當前選中的元件FixedExtentScrollController
原來是場誤會,這個控制器是專門給新元件ListWheelScrollView用的,這是一個齒輪滾動選擇的元件,被選中項總是在中間
最終方案:
最後通過print ScrollController.position這個屬性,發現它在執行中其實是ScrollPositionWithSingleContext的例項,這個例項可以直接獲取到滾動元件的視口高度,可滾動範圍等引數。
演示程式碼
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.
);
}
}
複製程式碼
這裡要注意的一點是,在加入新元素中呼叫滾動,是不能立即滾動的(程式碼中採用了延遲16ms,一幀的時間),否則就會出現滾動不到底的情況。因為加入元素是一個資料操作,要更新到介面需要等待下一次build,如果這個新元素還有入場動畫,那麼呼叫滾動的延遲最好delay到動畫結束,否則獲取的maxScrollExtend是不準確的。