Dart4Flutter - 拾遺01 - flutter-dart環境搭建
Flutter 例項 - 從本地到Flutter通訊 - Event Channels
本教程完成一個有載入更多的ListView,最終效果如下圖所示:
開始
首先我們只在列表中展示10個整數。
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<int> items = List.generate(10, (i) => i); // 產生資料
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(
title: Text("Infinite ListView"),
),
body: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(title: new Text("Number $index"));
},
),
);
}
}
複製程式碼
動態新增資料
首先我們模擬一個http請求,假設我們通過傳遞from和to引數,然後返回他們之間的數。我們將新增延遲時間,這樣看起來更像是網路載入。具體程式碼如下所示:
/// from - 包括, to - 不包括
/// 通過這個模擬http請求
Future<List<int>> fakeRequest(int from, int to) async {
// 如果對Future不熟悉,可以參考 https://juejin.im/post/5b2c67a351882574a756f2eb
return Future.delayed(Duration(seconds: 2), () {
return List.generate(to - from, (i) => i + from);
});
}
複製程式碼
當使用者將列表滾動到最底,我們將會呼叫上面的方法。為了監聽列表是否已經滾動到最底,最簡單的方式就是給列表新增一個ScrollController
,當列表滾動的時候,就會發出一個請求。但是防止頻繁的傳送http請求,我們需要新增一個變數isPerformingRequest
,表示是否有請求正在進行。只有當isPerformingRequest
為false時,才能開始一個新的請求。
class _MyHomePageState extends State<MyHomePage> {
List<int> items = List.generate(10, (i) => i);
ScrollController _scrollController = new ScrollController();
bool isPerformingRequest = false; // 是否有請求正在進行
@override
void initState() {
super.initState();
_scrollController.addListener(() {
if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent) {
_getMoreData();
}
});
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
_getMoreData() async {
if (!isPerformingRequest) { // 判斷是否有請求正在執行
setState(() => isPerformingRequest = true);
List<int> newEntries = await fakeRequest(items.length, items.length + 10);
setState(() {
items.addAll(newEntries);
isPerformingRequest = false;// 下一個請求可以開始了
});
}
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(
title: Text("Infinite ListView"),
),
body: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(title: new Text("Number $index"));
},
controller: _scrollController,
),
);
}
}
複製程式碼
如果你現在執行程式碼,你可以看資料可以動態的載入,但是這個距離我們最終想要的效果還有差距。我們需要新增一些提示,告知使用者資料正在載入中。
進度提示
相關的元件是CircularProgressIndicator
,他被Center、Opacity、Padding所包裹。我們將用Opacity
元件控制CircularProgressIndicator
的顯示,當請求正在進行中時顯示。整個元件的程式碼如下所示:
Widget _buildProgressIndicator() {
return new Padding(
padding: const EdgeInsets.all(8.0),
child: new Center(
child: new Opacity(
opacity: isPerformingRequest ? 1.0 : 0.0,
child: new CircularProgressIndicator(),
),
),
);
}
複製程式碼
最後一件事是將這個元件新增到我們的ListView上:
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(
title: Text("Infinite ListView"),
),
body: ListView.builder(
itemCount: items.length + 1,
itemBuilder: (context, index) {
if (index == items.length) {
return _buildProgressIndicator();
} else {
return ListTile(title: new Text("Number $index"));
}
},
controller: _scrollController,
),
);
}
複製程式碼
最終效果如下:
處理空資料情況
下面是附加內容,處理當請求返回的資料為空時的晴空。我們通過ScrollController
給ListView
一些動畫。
_getMoreData() async {
if (!isPerformingRequest) {
setState(() => isPerformingRequest = true);
List<int> newEntries = await fakeRequest(items.length, items.length); //returns empty list
if (newEntries.isEmpty) {
double edge = 50.0;
double offsetFromBottom = _scrollController.position.maxScrollExtent - _scrollController.position.pixels;
if (offsetFromBottom < edge) {
_scrollController.animateTo(
_scrollController.offset - (edge -offsetFromBottom),
duration: new Duration(milliseconds: 500),
curve: Curves.easeOut);
}
}
setState(() {
items.addAll(newEntries);
isPerformingRequest = false;
});
}
}
複製程式碼
下面是完整的程式碼:
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
theme: new ThemeData(primarySwatch: Colors.blue),
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<int> items = List.generate(10, (i) => i);
ScrollController _scrollController = new ScrollController();
bool isPerformingRequest = false;
@override
void initState() {
super.initState();
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
_getMoreData();
}
});
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
_getMoreData() async {
if (!isPerformingRequest) {
setState(() => isPerformingRequest = true);
List<int> newEntries = await fakeRequest(
items.length, items.length + 10); //returns empty list
if (newEntries.isEmpty) {
double edge = 50.0;
double offsetFromBottom = _scrollController.position.maxScrollExtent -
_scrollController.position.pixels;
if (offsetFromBottom < edge) {
_scrollController.animateTo(
_scrollController.offset - (edge - offsetFromBottom),
duration: new Duration(milliseconds: 500),
curve: Curves.easeOut);
}
}
setState(() {
items.addAll(newEntries);
isPerformingRequest = false;
});
}
}
Widget _buildProgressIndicator() {
return new Padding(
padding: const EdgeInsets.all(8.0),
child: new Center(
child: new Opacity(
opacity: isPerformingRequest ? 1.0 : 0.0,
child: new CircularProgressIndicator(),
),
),
);
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(
title: Text("Infinite ListView"),
),
body: ListView.builder(
itemCount: items.length + 1,
itemBuilder: (context, index) {
if (index == items.length) {
return _buildProgressIndicator();
} else {
return ListTile(title: new Text("Number $index"));
}
},
controller: _scrollController,
),
);
}
}
/// from - inclusive, to - exclusive
Future<List<int>> fakeRequest(int from, int to) async {
return Future.delayed(Duration(seconds: 2), () {
return List.generate(to - from, (i) => i + from);
});
}
複製程式碼
有任何問題,歡迎大家提問