Flutter教程-自定义无限滚动轮播器infinity_slider

转载于 https://juejin.im/post/5d6c973ae51d45620064bb92

简介

通过组合PageView方式,实现一个自定义的infinity_slider小部件,完整代码 https://github.com/herghost000/flutter_infinity_slider

  1. 支持无限滚动
  2. 支持定时器滚动
  3. 支持曲线滚动

步骤

1)创建一个有状态的小部件

新建infinity_slider.dart文件,添加下方代码

import 'package:flutter/material.dart';

class InfinitySlider extends StatefulWidget {
  @override
  _InfinitySliderState createState() => _InfinitySliderState();
}

class _InfinitySliderState extends State<InfinitySlider> {
  @override
  Widget build(BuildContext context) {
    return Container();
  }
}
2)添加可配置参数
  1. 要知道PageView小部件是没有设置可无限滚动属性的
  2. 我们有一个需要滑动且长度为5的小部件列表items
  3. PageView.builder有个itemCount字段,当我们构建PageView时,如果没有设置itemCount的话则PageViewitemBuilder会从0-(PageController.initialPage)-infinity 构建滑块,这时items会有溢出的危险。
  4. 你想到了什么?假如我们有一个小部件列表items,我们可以通过一个函数计算出当前的items真实下标,这时items就可以向右进行无限滑动而不用担心items下标的溢出,计算函数代码如下
int _calcIndex(int input, int source) {
  final int result = input % source;
  return result < 0 ? source + result : result;
}
/**
 * _getRealIndex
 *
 * @param {int} position - 真实下标
 * @param {int} base - 初始真实下标
 * @return {int} length 滑块列表长度
 *
 * @since 1.0.0
 */
int _getRealIndex(int position, int base, int length) {
  final int offset = position - base;
  return _calcIndex(offset, length);
}
  1. 有了这个计算函数,让我们来写一写相关代码
import 'package:flutter/material.dart';
import 'dart:async';

int kRealPage = 100;

int _calcIndex(int input, int source) {
  final int result = input % source;
  return result < 0 ? source + result : result;
}

int _getRealIndex(int position, int base, int length) {
  final int offset = position - base;
  return _calcIndex(offset, length);
}

typedef void UpdatePageCallback(int index);

class InfinitySlider extends StatefulWidget {
  final int initialPage;
  final List<Widget> items;
  final double height;
  final PageController pageController;

  InfinitySlider({
    Key key,
    @required this.items,
    this.height: 120,
    this.initialPage: 0,
  })  : pageController = new PageController(
    initialPage: kRealPage + initialPage,
  ),
        assert(items != null),
        assert(items.length > 0),
        assert(initialPage != null),
        super(key: key);

  @override
  _InfinitySliderState createState() => _InfinitySliderState();
}

class _InfinitySliderState extends State<InfinitySlider> {

  @override
  Widget build(BuildContext context) {
    return Container(
      height: widget.height,
      child: PageView.builder(
        itemBuilder: (BuildContext context, int i) {
          final int index = _getRealIndex(i, kRealPage, widget.items.length);
          return widget.items[index];
        },
        controller: widget.pageController,
      ),
    );
  }
}

3)解决向左无限滚动
  1. 在上面代码中,kRealPage被我们设置为100,假如遇到一个无聊的人往左滑动了100次,这时就不能再次往左侧滑动
  2. 我们可以将kRealPage设置为100000000,虽然不会有那么无聊的人往左滑动一亿次,但是我们要解决这个问题
  3. 让我们监听PageController滑动事件
import 'package:flutter/material.dart';
import 'dart:async';

int kRealPage = 100;

int _calcIndex(int input, int source) {
  final int result = input % source;
  return result < 0 ? source + result : result;
}

int _getRealIndex(int position, int base, int length) {
  final int offset = position - base;
  return _calcIndex(offset, length);
}

typedef void UpdatePageCallback(int index);

class InfinitySlider extends StatefulWidget {
  final int initialPage;
  final List<Widget> items;
  final double height;
  final PageController pageController;

  InfinitySlider({
    Key key,
    @required this.items,
    this.height: 120,
    this.initialPage: 0,
  })  : pageController = new PageController(
    initialPage: kRealPage + initialPage,
  ),
        assert(items != null),
        assert(items.length > 0),
        assert(initialPage != null),
        super(key: key);

  @override
  _InfinitySliderState createState() => _InfinitySliderState();
}

class _InfinitySliderState extends State<InfinitySlider> {

  @override
  void initState() {
    super.initState();
    widget.pageController.addListener(() {
      if (widget.pageController.page == (kRealPage - widget.items.length) ||
          widget.pageController.page == (kRealPage + widget.items.length)) {
        widget.pageController.position
            .setPixels(MediaQuery.of(context).size.width * kRealPage);
      }
    });
  }
  
  @override
  Widget build(BuildContext context) {
    return Container(
      height: widget.height,
      child: PageView.builder(
        itemBuilder: (BuildContext context, int i) {
          final int index = _getRealIndex(i, kRealPage, widget.items.length);
          return widget.items[index];
        },
        controller: widget.pageController,
      ),
    );
  }
}

4)添加剩余的功能
  1. 完整的代码如下
import 'package:flutter/material.dart';
import 'dart:async';

int kRealPage;
double kAutoMaxPage;
double kAutoMinPage;

int _calcIndex(int input, int source) {
  final int result = input % source;
  return result < 0 ? source + result : result;
}

int _getRealIndex(int position, int base, int length) {
  final int offset = position - base;
  return _calcIndex(offset, length);
}

typedef void UpdatePageCallback(int index);

class InfinitySlider extends StatefulWidget {
  final int initialPage;
  final List<Widget> items;
  final bool autoPlay;
  final Duration interval;
  final Curve transCurve;
  final Duration transDuration;
  final double height;
  final PageController pageController;
  final UpdatePageCallback updateCallback;

  InfinitySlider({
    Key key,
    @required this.items,
    this.height: 120,
    this.initialPage: 0,
    this.autoPlay: true,
    this.interval: const Duration(seconds: 2),
    this.transDuration: const Duration(milliseconds: 800),
    this.transCurve: Curves.fastOutSlowIn,
    this.updateCallback,
  })  : pageController = new PageController(
    initialPage: kRealPage + initialPage,
  ),
        assert(items != null),
        assert(items.length > 0),
        assert(initialPage != null),
        assert(autoPlay != null),
        assert(interval != null),
        assert(transDuration != null),
        assert(transCurve != null),
        super(key: key) {
    kRealPage = items.length;
    kAutoMinPage = 0.0;
    kAutoMaxPage = (items.length * 2).toDouble();
  }

  @override
  _InfinitySliderState createState() => _InfinitySliderState();

  Future<void> nextPage({Duration duration, Curve curve}) {
    return pageController.nextPage(duration: duration, curve: curve);
  }

  Future<void> previousPage({Duration duration, Curve curve}) {
    return pageController.previousPage(duration: duration, curve: curve);
  }
}

class _InfinitySliderState extends State<InfinitySlider> {
  int currentPage;
  Timer timer;

  @override
  void initState() {
    super.initState();
    widget.pageController.addListener(() {
      if (widget.pageController.page == kAutoMinPage ||
          widget.pageController.page == kAutoMaxPage) {
        widget.pageController.position
            .setPixels(MediaQuery.of(context).size.width * kRealPage);
      }
    });
    currentPage = widget.initialPage;
    if (widget.autoPlay) {
      timer = new Timer.periodic(widget.interval, (_) {
        widget.nextPage(
            duration: widget.transDuration, curve: widget.transCurve);
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      height: widget.height,
      child: PageView.builder(
        itemBuilder: (BuildContext context, int i) {
          final int index = _getRealIndex(i, kRealPage, widget.items.length);
          return widget.items[index];
        },
        controller: widget.pageController,
        onPageChanged: (int index) {
          currentPage = _getRealIndex(index, kRealPage, widget.items.length);
          if (widget.updateCallback != null) widget.updateCallback(currentPage);
        },
      ),
    );
  }

  @override
  void dispose() {
    super.dispose();
    timer?.cancel();
  }
}

5)使用这个小部件
InfinitySlider(
    items: [1,2,3,4,5].map((i) {
      return Builder(
        builder: (BuildContext context) {
          return Container(
            width: MediaQuery.of(context).size.width,
            color: Colors.amber
            child: new Text('text $i', style: new TextStyle(fontSize: 16.0),)
          );
        },
      );
    }).toList(),
)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值