AndroidFlutter UI

Flutter循环滑动的PageView

2020-12-02  本文已影响0人  说点儿什么吧

序言

Android原生里一般会使用ViewPager来实现Banner区域,当然Flutter中的PageView也可以实现类似的效果,今天就来撸一把循环滑动的PageView。

在Android中想要实现循环滑动的ViewPager,最常用的方法是,在原数据源的基础上,通过前后补位来操作:即准备新的数据集合list , 第一个位置插入原数据中的最后一个元素、最后一个位置插入原数据中的第一个元素,Flutter中PageView实现循环滑动的方法如出一辙,如下图所示:

在这里插入图片描述

在用户滑动过程中,当(2)被选中后,无动画切换到2的位置;当(0)被选中后,此时无动画切换到0的位置。即可实现循环滑动的PageView。

准备新的数据源

这里需要解释下,如果只有一个数据的话,不考虑循环滑动

  /// 初始化Page
  /// 准备一个新的数据源list
  /// 在原数据data的基础上,前后各添加一个view  data[data.length-1]、data[0]
  void _initWidget() {
    currentIndex = widget.controller.initialPage;
    if (widget.children == null || widget.children.isEmpty) return;
    if (widget.children.length == 1) {
      _children.addAll(widget.children);
    } else {
      _children.add(widget.children[widget.children.length - 1]);
      _children.addAll(widget.children);
      _children.add(widget.children[0]);
    }
  }

当用户在滑动到新位置的Page后,会触发PageView的回调监听onPageChanged(int index),参数即为新选中的Page索引,此时我们需要及时将页面切换到正确的位置

/// Page切换后的回调,及时修复索引
  _onPageChanged(int index) async {
    if (index == 0) {
      // 当前选中的是第一个位置,自动选中倒数第二个位置
      currentIndex = _children.length - 2;
      await Future.delayed(Duration(milliseconds: 400));
      widget.controller?.get()?.jumpToPage(currentIndex);
      realPosition = currentIndex - 1;
    } else if (index == _children.length - 1) {
      // 当前选中的是倒数第一个位置,自动选中第二个索引
      currentIndex = 1;
      await Future.delayed(Duration(milliseconds: 400));
      widget.controller?.get()?.jumpToPage(currentIndex);
      realPosition = 0;
    } else {
      currentIndex = index;
      realPosition = index - 1;
      if (realPosition < 0) realPosition = 0;
    }
      setState(() {});
  }

你可能会发现在调用jumpToPage之前为什么延迟了400毫秒,这里做一个短暂的延迟是因为PageView在切换页面后如果立即jumpToPage会出现卡顿的现象,做短暂延迟可以规避这个问题。

定时切换

目前已经实现了PageView的循环滑动,那么现在我们加一个定时器,每隔2s自动切换下一个页面。

/// 创建定时器
  void createTimer() {
    if (widget.isTimer) {
      cancelTimer();
      _timer = Timer.periodic(widget.delay, (timer) => _scrollPage());
    }
  }

/// 定时切换PageView的页面
  void _scrollPage() {
    ++currentIndex;
    var next = currentIndex % _children?.length;
    widget.controller?.get()?.animateToPage(
          next,
          duration: widget.duration,
          curve: Curves.ease,
        );
  }

/// 开始定时滑动
  void _start() {
    if (!widget.isTimer) return;
    if (!isActive) return;
    if (_children.length <= 1) return;
    createTimer();
  }

/// 停止定时滑动
  void _stop() {
    if (!widget.isTimer) return;
    cancelTimer();
  }

/// 取消定时器
  void cancelTimer() {
    _timer?.cancel();
  }

滑动冲突

到这里就实现了可以定时自动循环滑动的PageView,但是看下实际效果你会发现,当用户在滑动过程中,定时器还在进行,此时就需要取消定时器,当用户手指离开后再开启定时器自动轮播。

所以这里你可以给PageView包裹一层NotificationListener来监听用户滑动

@override
  Widget build(BuildContext context) => NotificationListener(
        onNotification: (notification) => _onNotification(notification),
        child: PageView(
          scrollDirection: widget.scrollDirection,
          reverse: widget.reverse,
          controller: widget.controller?.get(),
          physics: widget.physics,
          pageSnapping: widget.pageSnapping,
          onPageChanged: (index) => _onPageChanged(index),
          children: _children,
          dragStartBehavior: widget.dragStartBehavior,
          allowImplicitScrolling: widget.allowImplicitScrolling,
          restorationId: widget.restorationId,
          clipBehavior: widget.clipBehavior,
        ),
      );
/// Page滑动监听
  _onNotification(notification) {
    if (notification is ScrollStartNotification) {
      isEnd = false;
    } else if (notification is UserScrollNotification) {
      // 用户滑动时回调顺序:start - user , end - user
      if (isEnd) {
        isUserGesture = false;
        _start();
        return;
      }
      isUserGesture = true;
      _stop();
    } else if (notification is ScrollEndNotification) {
      isEnd = true;
      if (isUserGesture) {
        _start();
      }
    }
  }

值得注意的是,自动滑动和用户滑动都会触发start、end事件,但是用户滑动时会触发user事件,滑动时回调顺序:start - user 、 end - user,所以只需要在user事件回调中判断是否手指离开了,即可区分用户滑动和页面滑动,实现用户滑动状态下暂停定时器,用户手指离开后启动定时器。

看下最终的实现效果,代码里时加了页面圆点指示器的,可以参考代码自定义配置。

插件地址:

Github
pub.dev

UI
上一篇下一篇

猜你喜欢

热点阅读