美文网首页Flutter圈子FlutterFlutter
Flutter - 简单的BannerView(2019.12.

Flutter - 简单的BannerView(2019.12.

作者: Cosecant | 来源:发表于2019-12-15 00:46 被阅读0次

循环逻辑,按照网上说的,例如:有3张图,那么排版出来的应该是这样的:20120。为什么的是这样的呢?首位相连,在BannerView滚动过程中调整Page的索引位置。注意读取数据时一定要注意头尾应该取的数据,以及真实索引的位置。

QQ截图20191229125412.png

下面是一个简单的实现过程,支持自动循环:

import 'dart:async';

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

typedef BannerViewItemBuilder = Widget Function(
    BuildContext context, int postion, int realPosition, dynamic itemData);

class BannerView<T> extends StatefulWidget {
  BannerView({
    Key key,
    double width,
    double height = 210,
    @required this.dataSource,
    @required BannerViewItemBuilder itemBuilder,
    Duration duration,
  })  : assert(height != null && height > 0, '高度不能小于0'),
        assert(dataSource?.isNotEmpty == true, '数据源不能为空'),
        width = width,
        height = height,
        virutalDataSource = dataSource?.isNotEmpty == true
            ? [dataSource[dataSource.length - 1], ...dataSource, dataSource[0]]
            : throw Exception('数据源不能为空'),
        duration = duration ?? const Duration(seconds: 5),
        itemBuilder = itemBuilder,
        super(key: key);

  final double width;
  final double height;
  final List<String> dataSource;
  final List<String> virutalDataSource;
  final Duration duration;
  final BannerViewItemBuilder itemBuilder;

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

class _BannerViewState extends State<BannerView>
    with SingleTickerProviderStateMixin {
  PageController _pageController;
  int _virtualPosition = 1, _position = 0, _indicatorPosition = 0;
  Timer _timer; //动画定时器

  /// 初始化
  void _initializer() {
    _pageController = PageController(initialPage: 1)
      ..addListener(_onListenScrollOffsetChanged);
    _initAndStartTimer(); //初始化并启动动画定时器
  }

  /// 获取真实的索引位置
  /// + `pagePosition` virtual的Page索引位置
  int _getRealPagePosition(int pagePosition) {
    if (pagePosition == widget.virutalDataSource.length - 1)
      pagePosition = 1;
    else if (pagePosition == 0)
      pagePosition = widget.virutalDataSource.length - 2;
    else
      pagePosition -= 1;
    return pagePosition;
  }

  /// 监听滚动的Offset的变化
  void _onListenScrollOffsetChanged() {
    final Decimal curPageOffset =
        Decimal.parse(_pageController.offset.toString()) /
            Decimal.parse(
                (widget.width ?? MediaQuery.of(context).size.width).toString());
    //设置indicator所在的位置
    setState(() => _indicatorPosition =
        _getRealPagePosition(curPageOffset.toDouble().round()));
    final curPageInt = int.tryParse(curPageOffset.toString());
    if (curPageInt != null) {
      _virtualPosition = curPageInt;
      //设置Page的逻辑位置
      setState(() => _position = _getRealPagePosition(curPageInt));
      if (curPageInt == 0)
        _pageController.jumpToPage(_position);
      else if (curPageInt == widget.virutalDataSource.length - 1)
        _pageController.jumpToPage(_position);
    }
  }

  @override
  void initState() {
    _initializer();
    super.initState();
  }

  @override
  void dispose() {
    _releaseTimer();
    _pageController
      ..removeListener(_onListenScrollOffsetChanged)
      ..dispose();
    super.dispose();
  }

  /// 初始化并启动定时器
  void _initAndStartTimer() {
    _releaseTimer();
    _timer = Timer.periodic(widget.duration, (_) {
      _pageController.animateToPage(++_virtualPosition,
          duration: const Duration(milliseconds: 800),
          curve: Curves.easeInOutQuart);
    });
  }

  /// 释放定时器
  void _releaseTimer() {
    if (_timer != null) _timer.cancel();
    _timer = null;
  }

  @override
  Widget build(BuildContext context) =>
      Stack(alignment: Alignment.bottomCenter, children: [
        GestureDetector(
            onTapDown: (_) => _releaseTimer(),
            onTapUp: (_) => _initAndStartTimer(),
            onTapCancel: () => _initAndStartTimer(),
            child: SizedBox(
                width: widget.width,
                height: widget.height,
                child: PageView.builder(
                  controller: _pageController,
                  pageSnapping: true,
                  physics: BouncingScrollPhysics(),
                  itemCount: widget.virutalDataSource?.length ?? 0,
                  itemBuilder: (context, position) => widget.itemBuilder(
                      context,
                      position,
                      position == widget.virutalDataSource.length - 1
                          ? 0
                          : (position == 0
                              ? widget.virutalDataSource.length - 2
                              : position - 1),
                      widget.virutalDataSource.elementAt(position)),
                ))),
        _buildIndicator()
      ]);

  /// 构建指示器
  Widget _buildIndicator() => Padding(
      padding: const EdgeInsets.only(bottom: 25),
      child: Row(
          mainAxisSize: MainAxisSize.max,
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            for (int i = 0; i < widget.dataSource.length; i++)
              Container(
                  height: 3,
                  width: 15,
                  margin: EdgeInsets.only(left: i == 0 ? 0 : 8),
                  decoration: BoxDecoration(
                      color: _indicatorPosition == i
                          ? Colors.white
                          : Colors.white60,
                      borderRadius: BorderRadius.all(Radius.circular(1000))))
          ]));
}

实例代码:

 BannerView<String>(
                      height: 180,
                      dataSource: [
                        'http://e.hiphotos.baidu.com/image/pic/item/4610b912c8fcc3cef70d70409845d688d53f20f7.jpg',
                        'http://a.hiphotos.baidu.com/image/pic/item/0ff41bd5ad6eddc40189fc4133dbb6fd52663319.jpg',
                        'http://g.hiphotos.baidu.com/image/pic/item/c2cec3fdfc03924590b2a9b58d94a4c27d1e2500.jpg'
                      ],
                      itemBuilder: (_, __, ___, itemData) => CachedNetworkImage(
                          imageUrl: itemData,
                          imageBuilder: (_, imageProvider) => Padding(
                              padding: const EdgeInsets.all(15),
                              child: Container(
                                  height: 180,
                                  decoration: BoxDecoration(
                                      borderRadius:
                                          BorderRadius.all(Radius.circular(10)),
                                      image: DecorationImage(
                                          image: imageProvider,
                                          fit: BoxFit.cover)))),
                          placeholder: (_, __) => SizedBox(height: 180),
                          errorWidget: (_, __, ___) => SizedBox(height: 180)))

相关文章

  • Flutter - 简单的BannerView(2019.12.

    循环逻辑,按照网上说的,例如:有3张图,那么排版出来的应该是这样的:20120。为什么的是这样的呢?首位相连,在B...

  • Banner 怎么实现轮播不同尺寸的图片

    需求: UI设计APP的 BannerView 轮播图的图片每个Item尺寸不同,比如:设计 BannerView...

  • 仿半糖项目Swift 第二天

    今天学到的东西: 1. 完成了 bannerView 轮播的样式,包括 自动轮播广告,如图 bannerview2...

  • iOS 轮播图

    大神连接 BannerCell BannerView 实现

  • bannerView

    利用闲暇时间写了一个banner 原理 scrollview中添加3张图片,每次都是显示中间那张图片,当滚动或者按...

  • bannerView

    bannerView 好久不写文章了,前一段时间遇到要播放视频的banner,也没有找到合适的插件,就自己用swi...

  • 修改轮播图分页控制小点点

    _bannerView.currentPageDotImage=[UIImage imageNamed:@"p...

  • 2019.12. 28

    今天跟一个玩游戏的小伙儿聊了聊玩游戏的乐趣。其实这个发心是因为儿子吕子豪。 前几天在吉林看到儿子,短暂的吃饭聊天中...

  • Flutter 和 Android 混合开发(flutterBo

    flutter源码依赖简单demo(名字为onlyone):flutter混合开发中,会有flutter modu...

  • 2019.12.感赏

    12.17 感赏在晨读群里抢到0.36元红包!钱宝宝太爱我了,我就是吸金达人啊!每天都能吸引钱宝宝来到我的账户余额...

网友评论

    本文标题:Flutter - 简单的BannerView(2019.12.

    本文链接:https://www.haomeiwen.com/subject/jfqknctx.html