Flutter listview如何實現下拉刷新上拉加載更多功能

下拉刷新

在Flutter中系統已經為我們提供瞭google material design的刷新功能 , 樣式與原生Android一樣.

我們可以使用RefreshIndicator組件來實現Flutter中的下拉刷新,下面們還是先來看下如何使用吧

RefreshIndicator

構造方法:

 const RefreshIndicator({
    Key key,
    @required this.child,
    this.displacement: 40.0,      //觸發下拉刷新的距離
    @required this.onRefresh,     //下拉回調方法
    this.color,                   //進度指示器前景色 默認為系統主題色
    this.backgroundColor,         //背景色
    this.notificationPredicate: defaultScrollNotificationPredicate,
  })

然後我們看一下效果以及實現方式:

然後我們看一下代碼:

class _MyHomePageState extends State<MyHomePage> {
  List list = new List(); //列表要展示的數據
 

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    getData();
  }

  /**
   * 初始化list數據 加延時模仿網絡請求
   */
  Future getData() async {
    await Future.delayed(Duration(seconds: 2), () {
      setState(() {
        list = List.generate(15, (i) => '哈嘍,我是原始數據 $i');
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        // Here we take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set our appbar title.
        title: new Text(widget.title),
      ),
      body: RefreshIndicator(
        onRefresh: _onRefresh,
        child: ListView.builder(
          itemBuilder: _renderRow,
          itemCount: list.length,
        ),
      ),
    );
  }

  Widget _renderRow(BuildContext context, int index) {
    return ListTile(
      title: Text(list[index]),
    );
  }

  /**
   * 下拉刷新方法,為list重新賦值
   */
  Future<Null> _onRefresh() async {
    await Future.delayed(Duration(seconds: 3), () {
      print('refresh');
      setState(() {
        list = List.generate(20, (i) => '哈嘍,我是新刷新的 $i');
      });
    });
  }
}

代碼不復雜,我們一步步分析:

MyHomePage 隻是返回一個State,這裡省略瞭.

首先body裡我們返回瞭一個RefreshIndicator,這個組件自帶下拉回調,然後裡面我們包裹瞭一個listview,

然後使用List.generate()方法來創建瞭一個長度為15的List,並把List裡的值賦值給ListView Item中的ListTile。

下拉回調onRefresh 我們返回瞭一個改變list的方法 .

在上面的代碼中我們使用_onRefresh()方法來處理下拉刷新的回調

/**
   * 下拉刷新方法,為list重新賦值
   */
  Future<Null> _onRefresh() async {
    await Future.delayed(Duration(seconds: 3), () {
      print('refresh');
      setState(() {
        list = List.generate(20, (i) => '哈嘍,我是新刷新的 $i');
      });
    });
  }

其中 Future.delayed()方法可以選擇延遲處理任務,這裡我們假設網絡的延遲是3秒.

這樣一個簡單的下拉刷新就實現瞭.

上拉加載更多

對於加載更多的組件在Flutter中是沒有提供的,所以在這裡我們就需要考慮如何實現的。

在ListView中有一個ScrollController屬性,它就是專門來控制ListView滑動事件,在這裡我們可以根據ListView的位置來判斷是否滑動到瞭底部來做加載更多的處理。

在這裡我們可以使用如下代碼來判斷ListView 是否滑動到瞭底部

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    getData();
    _scrollController.addListener(() {
      if (_scrollController.position.pixels ==
          _scrollController.position.maxScrollExtent) {
        print('滑動到瞭最底部');
        _getMore();
      }
    });
  }

_scrollController是我們初始化的ScrollController對象,通過監聽我們可以判斷現在的位置是否是最大的下滑位置來判斷是否下滑到瞭底部。

看一下代碼和效果:

class _MyHomePageState extends State<MyHomePage> {
  List list = new List(); //列表要展示的數據
  ScrollController _scrollController = ScrollController(); //listview的控制器
  int _page = 1; //加載的頁數
  bool isLoading = false; //是否正在加載數據

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    getData();
    _scrollController.addListener(() {
      if (_scrollController.position.pixels ==
          _scrollController.position.maxScrollExtent) {
        print('滑動到瞭最底部');
        _getMore();
      }
    });
  }

  /**
   * 初始化list數據 加延時模仿網絡請求
   */
  Future getData() async {
    await Future.delayed(Duration(seconds: 2), () {
      setState(() {
        list = List.generate(15, (i) => '哈嘍,我是原始數據 $i');
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        // Here we take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set our appbar title.
        title: new Text(widget.title),
      ),
      body: RefreshIndicator(
        onRefresh: _onRefresh,
        child: ListView.builder(
          itemBuilder: _renderRow,
          itemCount: list.length,
          controller: _scrollController,
        ),
      ),
      // This trailing comma makes auto-formatting nicer for build methods.
    );
  }

  Widget _renderRow(BuildContext context, int index) {
    return ListTile(
      title: Text(list[index]),
    );
  }

  /**
   * 下拉刷新方法,為list重新賦值
   */
  Future<Null> _onRefresh() async {
    await Future.delayed(Duration(seconds: 3), () {
      print('refresh');
      setState(() {
        list = List.generate(20, (i) => '哈嘍,我是新刷新的 $i');
      });
    });
  }

  /**
   * 上拉加載更多
   */
  Future _getMore() async {
    if (!isLoading) {
      setState(() {
        isLoading = true;
      });
      await Future.delayed(Duration(seconds: 1), () {
        print('加載更多');
        setState(() {
          list.addAll(List.generate(5, (i) => '第$_page次上拉來的數據'));
          _page++;
          isLoading = false;
        });
      });
    }
  }

  @override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
    _scrollController.dispose();
  }
}

滑動到底部的時候,我們執行加載更多的方法,給list數據多加5條,這次我們把延遲改到瞭1秒:

/**
   * 上拉加載更多
   */
  Future _getMore() async {
    if (!isLoading) {
      setState(() {
        isLoading = true;
      });
      await Future.delayed(Duration(seconds: 1), () {
        print('加載更多');
        setState(() {
          list.addAll(List.generate(5, (i) => '第$_page次上拉來的數據'));
          _page++;
          isLoading = false;
        });
      });
    }
  }

是的,看著上面的效果我們已經實現瞭下拉加載更多,但是因為我們是滑動到底部觸發的,如果在正在請求的過程中多次下拉就會造成多次加載更多的情況,所以我們還得對這個做下處理為瞭避免多次觸發,我們加瞭一個isLoading,在上拉方法執行的過程中不會再次執行.

可以看到,我們僅僅在上面代碼的基礎上加上瞭一個isLoading的變量,當這個變量的值為true時,就不會觸發加載更多的操作。

而因為是網絡請求,可能需要分頁,所以我們加瞭個page參數來查看是第幾次觸發上拉加載.

因為我們加瞭個監聽,在組件卸載掉的時候記得移除這個監聽,所以:

  @override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
    _scrollController.dispose();
  }

這個一定不要忘記,養成好習慣,每次加瞭監聽都跑到這個方法裡移除掉.

這樣,我們一個簡單的上拉加載更多的功能就實現瞭.

但是還有個問題,沒有用戶交互啊,加載的時候要有個提示,於是我們嘗試上拉的時候展示一個加載中的組件給用戶:

首先我們創建加載更多時顯示的Vidget

/**
   * 加載更多時顯示的組件,給用戶提示
   */
  Widget _getMoreWidget() {
    return Center(
      child: Padding(
        padding: EdgeInsets.all(10.0),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: <Widget>[
            Text(
              '加載中...     ',
              style: TextStyle(fontSize: 16.0),
            ),
            CircularProgressIndicator(strokeWidth: 1.0,)
          ],
        ),
      ),
    );
  }

然後我們在listview的itemcount那裡把count+1,相當於我們給listview加瞭個尾部的組件.

 body: RefreshIndicator(
        onRefresh: _onRefresh,
        child: ListView.builder(
          itemBuilder: _renderRow,
          itemCount: list.length + 1,   //這裡!這裡!這裡!
          controller: _scrollController,
        ),

看一下效果是否滿意:

嗯,基本符合要求,感覺那個刷新圖標加的有點醜,畫蛇添足瞭,不過功能都是ok瞭的.

當然, 大傢可以根據自己的需要去自己實現想要的樣式

看一下全部的代碼:

/*
 * Created by 李卓原 on 2018/9/13.
 * email: [email protected]
 *
 */
 
class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  List list = new List(); //列表要展示的數據
  ScrollController _scrollController = ScrollController(); //listview的控制器
  int _page = 1; //加載的頁數
  bool isLoading = false; //是否正在加載數據

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    getData();
    _scrollController.addListener(() {
      if (_scrollController.position.pixels ==
          _scrollController.position.maxScrollExtent) {
        print('滑動到瞭最底部');
        _getMore();
      }
    });
  }

  /**
   * 初始化list數據 加延時模仿網絡請求
   */
  Future getData() async {
    await Future.delayed(Duration(seconds: 2), () {
      setState(() {
        list = List.generate(15, (i) => '哈嘍,我是原始數據 $i');
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        // Here we take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set our appbar title.
        title: new Text(widget.title),
      ),
      body: RefreshIndicator(
        onRefresh: _onRefresh,
        child: ListView.builder(
          itemBuilder: _renderRow,
          itemCount: list.length + 1,
          controller: _scrollController,
        ),
      ),
      // This trailing comma makes auto-formatting nicer for build methods.
    );
  }

  Widget _renderRow(BuildContext context, int index) {
    if (index < list.length) {
      return ListTile(
        title: Text(list[index]),
      );
    }
    return _getMoreWidget();
  }

  /**
   * 下拉刷新方法,為list重新賦值
   */
  Future<Null> _onRefresh() async {
    await Future.delayed(Duration(seconds: 3), () {
      print('refresh');
      setState(() {
        list = List.generate(20, (i) => '哈嘍,我是新刷新的 $i');
      });
    });
  }

  /**
   * 上拉加載更多
   */
  Future _getMore() async {
    if (!isLoading) {
      setState(() {
        isLoading = true;
      });
      await Future.delayed(Duration(seconds: 1), () {
        print('加載更多');
        setState(() {
          list.addAll(List.generate(5, (i) => '第$_page次上拉來的數據'));
          _page++;
          isLoading = false;
        });
      });
    }
  }

  /**
   * 加載更多時顯示的組件,給用戶提示
   */
  Widget _getMoreWidget() {
    return Center(
      child: Padding(
        padding: EdgeInsets.all(10.0),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: <Widget>[
            Text(
              '加載中...',
              style: TextStyle(fontSize: 16.0),
            ),
            CircularProgressIndicator(
              strokeWidth: 1.0,
            )
          ],
        ),
      ),
    );
  }

  @override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
    _scrollController.dispose();
  }
}

總結:

  • RefreshIndicator可以顯示下拉刷新
  • 使用ScrollController可以監聽滑動事件,判斷當前view所處的位置
  • 可以根據item所處的位置來處理加載更多顯示效果

到此這篇關於Flutter listview如何實現下拉刷新上拉加載更多功能的文章就介紹到這瞭,更多相關Flutter listview下拉刷新上拉加載更多內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: