【Flutter】開發之進階Widget(五)

歡子發表於2019-06-26

前言

在上一篇中,我們學習了controller、點選事件onPressedGestureDetectorTabBarPageView聯動的使用,這一篇,我們來說說ListView的上拉重新整理、下拉載入和輪播圖。

重新整理控制元件

下拉重新整理

官方為我們提供了RefreshIndicator,主要有colorbackgroundColordisplacementonRefresh等屬性

        RefreshIndicator(
        //重新整理進度條顏色
        color: Colors.black45,
        //背景色
        backgroundColor: Colors.blue,
        //觸發下拉重新整理的距離 預設40
        displacement: 40,
        //下拉回撥方法,方法需要有async和await關鍵字,沒有await,重新整理圖示立馬消失,沒有async,重新整理圖示不會消失
        onRefresh: refresh,
        child: ListView.separated(
          itemBuilder: ((context, index) {
            return MoveItem();
          }),
          separatorBuilder: (context, index) {
            return Divider(
              color: Colors.black45,
              height: 10,
            );
          },
          itemCount: count,
        ),
      ),
複製程式碼
  int count = 2;

  Future refresh() async {
    await Future.delayed(Duration(seconds: 3), () {
      setState(() {
        count = 10;
      });
    });
  }
複製程式碼

效果圖如下

20190625_174848.gif

上拉載入

上拉載入的話,需要使用ListViewcontroller屬性

final ScrollController _scrollController = new ScrollController();

 @override
  void initState() {
    ///增加滑動監聽
    _scrollController.addListener(() {
      ///判斷當前滑動位置是不是到達底部,觸發載入更多回撥
      if (_scrollController.position.pixels ==
          _scrollController.position.maxScrollExtent) {
        setState(() {
          count += 5;
        });
      }
    });

    super.initState();
  }

  @override
  void dispose() {
    super.dispose();
    _scrollController.dispose();
  }
複製程式碼

然後將_scrollController設定給ListView

當我們執行時卻發現,不僅不能上拉載入,連下拉重新整理也失效了!

這是因為,RefreshIndicatorScrollController有相容性問題,當然官方也給出瞭解決辦法,給ListView新增如下程式碼: physics: const AlwaysScrollableScrollPhysics()

效果圖如下

20190625_175404.gif

雖然說功能實現了,但是感覺效果有點怪怪的,新的item出現前應該有個過渡。 思路如下,當觸發上拉載入時,給ListView新增一個載入中的item,當載入完成後,再移除。 先定義個變數,bool loadMore = false; 當觸發上拉載入時,設定為true

  @override
  void initState() {
    ///增加滑動監聽
    _scrollController.addListener(() {
      ///判斷當前滑動位置是不是到達底部,觸發載入更多回撥
      if (_scrollController.position.pixels ==
          _scrollController.position.maxScrollExtent) {
        setState(() {
          loadMore = true;
        });
        getMore();
      }
    });
    super.initState();
  }
複製程式碼

載入完成時,設定為false

  Future getMore() async {
    await Future.delayed(Duration(seconds: 3), () {
      setState(() {
        loadMore = false;
        count += 5;
      });
    });
  }
複製程式碼

這時,item就不能是固定的了

  Widget getItem(int index) {
    if (loadMore && index == count) {
      return LoadMoreItem();
    } else {
      return MoveItem();
    }
  }
複製程式碼

包括count

  int getItemCount() {
    if (loadMore) {
      return count + 1;
    } else {
      return count;
    }
  }
複製程式碼

效果圖如下

20190625_174934.gif

完整程式碼如下

class ListViewDemo extends StatefulWidget {
  @override
  _ReListViewDemoState createState() => _ReListViewDemoState();
}

class _ReListViewDemoState extends State<ListViewDemo> {
  int count = 2;
  final ScrollController _scrollController = new ScrollController();

  bool loadMore = false;

  Future refresh() async {
    await Future.delayed(Duration(seconds: 3), () {
      setState(() {
        count = 10;
      });
    });
  }

  Future getMore() async {
    await Future.delayed(Duration(seconds: 3), () {
      setState(() {
        loadMore = false;
        count += 5;
      });
    });
  }

  @override
  void initState() {
    ///增加滑動監聽
    _scrollController.addListener(() {
      ///判斷當前滑動位置是不是到達底部,觸發載入更多回撥
      if (_scrollController.position.pixels ==
          _scrollController.position.maxScrollExtent) {
        setState(() {
          loadMore = true;
        });
        getMore();
      }
    });
    super.initState();
  }

  Widget getItem(int index) {
    if (loadMore && index == count) {
      return LoadMoreItem();
    } else {
      return MoveItem();
    }
  }

  int getItemCount() {
    if (loadMore) {
      return count + 1;
    } else {
      return count;
    }
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('ListViewDemo'),
        centerTitle: true,
        brightness: Brightness.dark,
      ),
      body: RefreshIndicator(
        //重新整理進度條顏色
        color: Colors.black45,
        //背景色
        backgroundColor: Colors.blue,
        ////觸發下拉重新整理的距離 預設40
        displacement: 40,
        //下拉回撥方法,方法需要有async和await關鍵字,沒有await,重新整理圖示立馬消失,沒有async,重新整理圖示不會消失
        onRefresh: refresh,
        child: ListView.separated(
          itemBuilder: ((context, index) {
            return getItem(index);
          }),
          separatorBuilder: (context, index) {
            return Divider(
              color: Colors.black45,
              height: 10,
            );
          },
          itemCount: getItemCount(),
          controller: _scrollController,
          //保持ListView任何情況都能滾動,解決在RefreshIndicator的相容問題。
          physics: const AlwaysScrollableScrollPhysics(),
        ),
      ),
    );
  }
}
複製程式碼

輪播圖

首先,用到的是PageView,以及PageController,這些之前已經說過,就不在細說

  Widget _pageView() {
    return PageView(
      children: <Widget>[
        Image.network(
          'http://img4.imgtn.bdimg.com/it/u=1621655683,865218969&fm=200&gp=0.jpg',
          height: 150,
          fit: BoxFit.fitWidth,
        ),
        Image.network(
          'http://img1.imgtn.bdimg.com/it/u=1901690610,3955011377&fm=200&gp=0.jpg',
          height: 150,
          fit: BoxFit.fitWidth,
        ),
        Image.network(
          'http://img3.imgtn.bdimg.com/it/u=1546158593,2358526642&fm=200&gp=0.jpg',
          height: 150,
          fit: BoxFit.fitWidth,
        ),
      ],
      controller: pageController,
    );
  }
複製程式碼

其次,需要一個定時器,別忘記取消

  int count = 3;
  int currentPosition = 0;

  @override
  void initState() {
    super.initState();
    _timer = new Timer.periodic(Duration(seconds: 2), (time) {
       //每2秒執行一次
      changePage();
    });
  }

  @override
  void dispose() {
    super.dispose();
    _timer.cancel();
    pageController.dispose();
  }
  void changePage() {
      pageController.animateToPage(currentPosition % count,
          duration: Duration(milliseconds: 200), curve: Curves.fastOutSlowIn);
    currentPosition++;
  }
複製程式碼

效果圖如下

20190625_175123.gif

當我們手動滑動時,currentPosition會錯亂,我們需要對其進行調整,需要用到的是onPageChanged屬性

      onPageChanged: (index) {
        _timer.cancel();
        currentPosition = index;
        _timer = new Timer.periodic(Duration(seconds: 2), (time) {
          changePage();
        });
      },
複製程式碼

當然,這麼常用的控制元件,已經有造好的輪子了 flutter_swiper

首先新增依賴flutter_swiper: ^1.1.6

下面是一些常用屬性

  Widget _swiper() {
    return new Swiper(
      itemBuilder: (BuildContext context, int index) {
        return new Image.network(
          "http://img4.imgtn.bdimg.com/it/u=1621655683,865218969&fm=200&gp=0.jpg",
          fit: BoxFit.fitWidth,
          height: 150,
        );
      },
      itemCount: 3,
      //動畫時間,預設300.0毫秒
      duration: 300,
      //初始位置
      index: 0,
      //無限輪播模式開關
      loop: true,
      //是否自動播放,預設false
      autoplay: true,
      layout: SwiperLayout.DEFAULT,
      //滾動方式
      scrollDirection: Axis.horizontal,
      //點選輪播的事件
      onTap: (index) {},
      //使用者拖拽的時候,是否停止自動播放
      autoplayDisableOnInteraction: true,
      //指示器
      pagination: new SwiperPagination(),
      //左右箭頭
      control: null,
    );
  }
複製程式碼

你的認可,是我堅持更新部落格的動力,如果覺得有用,就請點個贊,謝謝

相關文章