Flutter listview下拉刷新 上拉加載更多

這是我參與8月更文挑戰的第4天,活動詳情查看:8月更文挑戰markdown

下拉刷新

在Flutter中系統已經爲咱們提供了google material design的刷新功能 , 樣式與原生Android同樣. 咱們可使用RefreshIndicator組件來實現Flutter中的下拉刷新,下面們仍是先來看下如何使用吧網絡

RefreshIndicator

構造方法:app

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

而後咱們看一下效果以及實現方式: 這裏寫圖片描述async

而後咱們看一下代碼:ide

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()方法來處理下拉刷新的回調post

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

其中 Future.delayed()方法能夠選擇延遲處理任務,這裏咱們假設網絡的延遲是3秒. 這樣一個簡單的下拉刷新就實現了.ui

上拉加載更多

對於加載更多的組件在Flutter中是沒有提供的,因此在這裏咱們就須要考慮如何實現的。this

在ListView中有一個ScrollController屬性,它就是專門來控制ListView滑動事件,在這裏咱們能夠根據ListView的位置來判斷是否滑動到了底部來作加載更多的處理。google

在這裏咱們可使用以下代碼來判斷ListView 是否滑動到了底部spa

@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: zhuoyuan93@gmail.com
 *
 */
 
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所處的位置來處理加載更多顯示效果

相關文章
相關標籤/搜索