Flutter學習筆記(25)--ListView實現上拉刷新下拉加載


如需轉載,請注明出處:Flutter學習筆記(25)--ListView實現上拉刷新下拉加載

 

前面我們有寫過ListView的使用:Flutter學習筆記(12)--列表組件,當列表的數據非常多時,需要使用長列表,比如淘寶后台的訂單列表,手機通訊錄等,這些列表項數據很多,長列表也是使用ListView作為基礎組件,只不過需要添加一個列表項構造器itemBuilder。Flutter的長列表組件其實相當於Android中的RecyclerView,它會自動為您回收列表元素。在創建ListView.builder時,需要傳入兩個參數,一個列表的初始長度,一個itemBuilder函數。ListVIew還支持基於Sliver的延遲構建模型。

 

基於Sliver的延遲構建模式:

通常可滾動組件的子組件可能會非常多,占用的總高度也會非常大,如果要一次性將子組件全部構建出將會導致性能差的問題出現,為此,Flutter中提出一個Sliver(中文為"薄片"的意思)概念,如果一個可滾動組件支持Sliver模型,那么該滾動組件可以將子組件分成好多個薄片(Sliver),只有當Sliver出現在視口時才會去構建它,這種模型也成為"基於Sliver的延遲構建模型"。可滾動組件中有很多都支持基於Sliver的延遲構建模型,如ListView、GridView,但是也有不支持該模型的,如SingleChildScrollView

 

使用ListVIew.separated給列表項之間添加一個分割組件

import 'package:flutter/material.dart';

void main() => runApp(DemoApp());

class DemoApp extends StatelessWidget {

  //初始化數據源
  final List<String> items = new List<String>.generate(200, (i)=>"Item $i");

  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'SingleChildScrollView Demo',
      home: new Scaffold(
        appBar: AppBar(
          title: new Text('SingleChildScrollView Demo'),
        ),
        body:new ListView.separated(
          //列表滑動到邊界時,顯示iOS的彈出效果
          physics: BouncingScrollPhysics(),
          itemCount: items.length,
          //列表項構造器
          itemBuilder: (context,index){
            return ListTile(title: new Text('${items[index]}'),);
          },
          //分割構造器
          separatorBuilder: (context,index){
            //分割組件
            return new Divider(color: Colors.blue,);
          },
        ),
      ),
    );
  }
}

  • 下拉刷新

 Flutter給我們提供了下拉刷新功能RefreshIndicator的組件,先整體說明一下下面Demo的代碼邏輯,其實很簡單,body返回一個RefreshIndicator組件,在該組件內的子組件是一個ListView,重點說一下RefreshIndicator的下拉回調方法onRefresh,在回調方法內延遲2秒中后將list內容清空,並且重新給list列表添加新的數據。

import 'package:flutter/material.dart';

void main() => runApp(DemoApp());

class DemoApp extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return new _DemoAppState();
  }
}

class _DemoAppState extends State<DemoApp> {
  var _items = new List<String>();
  @override
  void initState() {
    super.initState();
    getData();
  }
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'ListView Demo',
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text('ListView Demo'),
        ),
        body: new RefreshIndicator(
            onRefresh: _onRefresh,
             child: new ListView.separated(
                physics: BouncingScrollPhysics(),
                itemBuilder: (context,index){
                  return ListTile(title:new Text('${_items[index]}'));
                },
                //分割線構造器
                separatorBuilder: (context,index){
                  return new Divider(color: Colors.blue,);
                },
                //_items.length + 1是為了給最后一行的加載loading留出位置
                itemCount: _items.length
            ),
        ),
      ),
    );
  }

  void getData() {
    //初始數據源
    for (int i=0;i<20;i++){
      _items.insert(_items.length, "第${_items.length}條原始數據");
      print(_items[i]);
    }
  }

  Future<void> _onRefresh() async {
    await Future.delayed(Duration(seconds: 2)).then((e){
      setState(() {
        _items.clear();
        for (int i=0;i<20;i++){
          _items.insert(_items.length, "第${_items.length}條下拉刷新后的數據");
        }
      });
    });
  }

  @override
  void dispose() {
    super.dispose();
  }
}

這里需要注意的是,onRefresh回調方法要增加async....await,不然會出現下拉刷新的loading不會消失的問題:

  Future<void> _onRefresh() async {
    await Future.delayed(Duration(seconds: 2)).then((e){
      setState(() {
        _items.clear();
        for (int i=0;i<20;i++){
          _items.insert(_items.length, "第${_items.length}條下拉刷新后的數據");
        }
      });
    });
  }

 

  • 上拉加載

 先縷一下實現的思路,我們想要實現的效果是每頁20條內容,共5頁的內容,1-4頁末尾數據后要展示加載新數據的loading,到第5頁末尾數據展示“我是有底線的”,因此,我們的itemCount就要是itemCount: _items.length + 1.而不是itemCount: _items.length + 1,這是因為要在最后留出來loading的位置,接下來就是要處理構建LisvtView里面的每一條item,如果當前item的索引是列表數據的最后一條數據,並且不是最后一頁的話,展示loading,如果當前item的索引是列表數據的最后一條數據,並且是最后一頁的話,展示“我是有底線的”,如果當前item的索引不是列表數據的最后一條,則展示下一條數據的內容。最后要處理的就是當頁面滑動到最后了,要怎么獲取新的數據。前面我們在寫頁面滑動的部分有講到過controller屬性(此屬性接收一個ScrollController對象,ScrollController的主要作用是控制滾動位置和監聽滾動事件),我們現在需要做的就是通過ListView的controller控制器來判斷頁面是否滑動到了最底部,如果滑動到了最底部,則獲取新的數據並插入到list里面,最后通過setState通知頁面重新構建。

 

import 'package:flutter/material.dart';

void main() => runApp(DemoApp());

class DemoApp extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return new _DemoAppState();
  }
}

class _DemoAppState extends State<DemoApp> {
  ScrollController _controller = new ScrollController();
  var _items = new List<String>();
  var _mPage = 0;
  @override
  void initState() {
    super.initState();
    getData();
    //給_controller添加監聽
    _controller.addListener((){
      //判斷是否滑動到了頁面的最底部
      if(_controller.position.pixels == _controller.position.maxScrollExtent){
        //如果不是最后一頁數據,則生成新的數據添加到list里面
        if(_mPage < 4){
          _retrieveData();
        }
      }
    });
  }
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'ListView Demo',
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text('ListView Demo'),
        ),
        body: new RefreshIndicator(
            onRefresh: _onRefresh,
             child: new ListView.separated(
                controller: _controller,
                physics: BouncingScrollPhysics(),
                itemBuilder: (context,index){
                  //判斷是否構建到了最后一條item
                  if(index == _items.length){
                    //判斷是不是最后一頁
                    if(_mPage < 4){
                      //不是最后一頁,返回一個loading窗
                      return new Container(
                        padding: EdgeInsets.all(16.0),
                        alignment: Alignment.center,
                        child: SizedBox(
                          width: 24.0,
                          height: 24.0,
                          child: CircularProgressIndicator(strokeWidth: 2.0,),
                        ),
                      );
                    }else{
                      //是最后一頁,顯示我是有底線的
                      return new Container(
                        padding: EdgeInsets.all(16.0),
                        alignment: Alignment.center,
                        child: new Text('我是有底線的!!!',style:TextStyle(color: Colors.blue),),
                      );
                    }
                  }else{
                    return ListTile(title:new Text('${_items[index]}'));
                  }
                },
                //分割線構造器
                separatorBuilder: (context,index){
                  return new Divider(color: Colors.blue,);
                },
                //_items.length + 1是為了給最后一行的加載loading留出位置
                itemCount: _items.length + 1
            ),
        ),
      ),
    );
  }

  void getData() {
    //初始數據源
    for (int i=0;i<20;i++){
      _items.insert(_items.length, "第${_items.length}條原始數據");
      print(_items[i]);
    }
  }

  void _retrieveData() {
    //上拉加載新的數據
    _mPage++;
    Future.delayed(Duration(seconds: 2)).then((e){
      for (int i=0;i<20;i++){
        _items.insert(_items.length, "這是新加載的第${_items.length}條數據");
      }
      setState(() {
      });
    });
  }

  Future<void> _onRefresh() async {
    await Future.delayed(Duration(seconds: 2)).then((e){
      setState(() {
        _mPage = 0;
        _items.clear();
        for (int i=0;i<20;i++){
          _items.insert(_items.length, "第${_items.length}條下拉刷新后的數據");
        }
      });
    });
  }

  @override
  void dispose() {
    //移除監聽,防止內存泄漏
    _controller.dispose();
    super.dispose();
  }
}

 

 

 

 

 

以上就是今天下拉刷新和上拉加載的全部內容了,如果有錯誤的地方或者有任何疑問,歡迎留言!!!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM