flutter 支持无限翻页的pageview

2,132 阅读1分钟

默认pageview通过builder实现无限滚动时,无法让状态保持,故而手动实现了无限滚动

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

class InfinityPageView extends StatefulWidget {
  InfinityPageView({this.pages});

  final List<Widget> pages;

  @override
  createState() {
    return _InfinityPageView(pages: pages);
  }
}

class _InfinityPageView extends State with SingleTickerProviderStateMixin {
  _InfinityPageView({this.pages});

  final List<Widget> pages;

  double _position = 0;
  double _panEndPosition = 0;
  double _panStartPosition = 0;
  double _animateToPosition = 0;
  double _basePosition = 0;

  AnimationController _controller;

  @override
  void initState() {
    _controller = new AnimationController(
        duration: const Duration(milliseconds: 200), vsync: this);
    _controller.addListener(() {
      double percent = _controller.value;
      double newPosition =
          percent * (_animateToPosition) + _panEndPosition * (1 - percent);
      setState(() {
        _position = newPosition;
      });
    });
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    Size screenSize = MediaQuery.of(context).size;

    int i = -1;

    double leftPercent = (-_position / screenSize.width);

    int ceilIndex = leftPercent.ceil();
    int floorIndex = leftPercent.floor();

    ceilIndex = ceilIndex % pages.length;
    floorIndex = floorIndex % pages.length;

    return GestureDetector(
      onHorizontalDragUpdate: (details) {
        setState(() {
          _position = _position + details.delta.dx;
        });
      },
      onHorizontalDragStart: (details) {
        _panStartPosition = _position;
      },
      onHorizontalDragEnd: (details) {
        //速度
        final v = details.velocity.pixelsPerSecond.dx;
        _panEndPosition = _position;
        // 位移距离
        final positionDiff = _panEndPosition - _panStartPosition;
        final minDiff = screenSize.width / 2;
        // 动画执行到的索引
        final newPositionIndex = (_panStartPosition / screenSize.width).round();
        // 初始页码位置
        _basePosition = newPositionIndex * screenSize.width;
        // 位移超过半页或则速度大于两百,则认为翻页
        if (positionDiff > minDiff || v > 200) {
          _animateToPosition = _basePosition + screenSize.width;
        } else if (positionDiff < -minDiff || v < -200) {
          _animateToPosition = _basePosition - screenSize.width;
        } else {
          _animateToPosition = _basePosition;
        }
        _controller.forward(from: 0);
      },
      child: Stack(
        children: [
          ...pages.map((page) {
            i++; //0,1,2,3....
            double left;

            /// 设屏幕宽度为100,当前位置为x(x为left的值,无区间限制),pages数组长度为3
            /// 当x为屏幕宽度的倍数(-100,0,100..),只需要展示对应的page
            /// 否则:先计算出x跨度的两个page,根据x的值展示到对应位置
            /// 跨度计算:若x为50,则跨度页面的索引分别为2、0
            /// 跨度位置计算:
            /// x=1,pos{2:100-1,0:1}
            /// x=-1,pos{0:-1,1:100-1}

            // 计算当前position的page index, position除以屏幕宽度得到整,再取正整数余数

            left = screenSize.width;
            double usefulPosition = _position % left;

            if (floorIndex == ceilIndex && ceilIndex == i) {
              left = 0;
            } else {
              if (usefulPosition > 0) {
                if (i == floorIndex) {
                  left = usefulPosition - screenSize.width;
                } else if (i == ceilIndex) {
                  left = usefulPosition;
                }
              } else {
                if (i == floorIndex) {
                  left = usefulPosition;
                } else if (i == ceilIndex) {
                  left = screenSize.width + usefulPosition;
                }
              }
            }

            return Positioned(
              left: left,
              top: 0,
              width: screenSize.width,
              height: screenSize.height,
              child: Container(
                color: Color.fromARGB(0, 0, 0, 0),
                child: page,
              ),
            );
          }),
        ],
      ),
    );
  }
}