Flutter: 轮播组件

实现一个轮播

在PC端实现轮播一般都是直接使用JavaScript库进行实现.但在Flutter上想要实现一个轮播,可能只有自己写一下了.

轮播的特点:

1. 可以自动播放;
2. 图片可以左右滑动(使用Flutter自带的PageView初始只能向右滑动,不能向左);
3. 可以定义自动播放的间隔时间.

大概就这么几个特点,可以实现个简单的例子.

image-3203

功能部分

目前仅实现简单功能:
1. 可以自定义网络图片或本地图片(可以混用本地图片和网络图片),
2. 自定义图片切换时间,
3. 自定义是否开启自动轮播.

上述几条可以满足一个简单轮播的需要,如果需要更多功能.可以在此基础上增加.

image-3204

源码

完整代码: Gitee仓库


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
/// 轮播组件
///
///  目前仅实现简单功能:
///   1. 可以自定义网络图片或本地图片(可以混用本地图片和网络图片),
///   2. 自定义图片切换时间,
///   3. 自定义是否开启自动轮播.
///
///  调用示例:
///
///  ```dart
///  static const List<String> _images = [
///   'assets/images/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg',
///   'assets/images/19aa98b1fcb2781c4fba33d850549jpeg.jpeg',
///   'https://fuss10.elemecdn.com/0/6f/e35ff375812e6b0020b6b4e8f9583jpeg.jpeg',
///   'https://fuss10.elemecdn.com/9/bb/e27858e973f5d7d3904835f46abbdjpeg.jpeg',
///   'https://fuss10.elemecdn.com/d/e6/c4d93a3805b3ce3f323f7974e6f78jpeg.jpeg',
///   'https://fuss10.elemecdn.com/3/28/bbf893f792f03a54408b3b7a7ebf0jpeg.jpeg',
///   'https://fuss10.elemecdn.com/2/11/6535bcfb26e4c79b48ddde44f4b6fjpeg.jpeg',
///   'https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg',
///   'https://fuss10.elemecdn.com/1/34/19aa98b1fcb2781c4fba33d850549jpeg.jpeg',
///   'https://fuss10.elemecdn.com/0/6f/e35ff375812e6b0020b6b4e8f9583jpeg.jpeg'
///  ];
///
/// ......
///
/// SizedBox(
//            width: double.infinity,
//            height: 200,
//            child: Carousel(
//              _images,
//              autoPlayer: false,
//            ),
//          ),
///
/// ```
///
///
///
///
class Carousel extends StatefulWidget {
  Carousel(
    this.imagesList, {
    Key key,
    this.autoPlayer = true,
    this.seconds = 5,
  })  : assert(imagesList.isNotEmpty),
        assert(seconds > 2),
        super(key: key);

  final int _maxPage = 2000000000;

  /// 轮播切换间隔秒数.
  final int seconds;

  /// 是否自动播放
  final bool autoPlayer;

  final List<String> imagesList;

  @override
  _CarouselState createState() => new _CarouselState();
}

class _CarouselState extends State<Carousel> {

  PageController _pageController;
  Timer _timer;

  var _currIndex = 0;

  @override
  void initState() {
    super.initState();
    if (widget.autoPlayer) {
      startTimeout();
    }
    _pageController = new PageController(initialPage: widget._maxPage);
  }

  @override
  void dispose() {
    super.dispose();
    _pageController.dispose();
    if (_timer != null) {
      _timer.cancel();
    }

  }

  void startTimeout() {
    var timeout = Duration(seconds: widget.seconds);
    Timer.periodic(timeout, (Timer timer) {
      var tempIndex = _currIndex + 1;
      if (tempIndex >= widget.imagesList.length) {
        tempIndex = 0;
      }
      setState(() {
        _timer = timer;
        _currIndex = tempIndex;
      });
    });
  }

  List<Widget> getBottomIcon() {
    var forLength = widget.imagesList.length;
    var res = List<Widget>();
    for (var i = 0; i < forLength; i++) {
      if (_currIndex == i) {
        res.add(GestureDetector(
          /// 选中时的图标,可以更换为图片.
          child: Icon(Icons.brightness_1),
          onTap: () {
            setState(() {
              _currIndex = i;
            });
          },
        ));
      } else {
        res.add(GestureDetector(
          /// 未选中时的图标,可以更换为图片.
          child: Icon(Icons.panorama_fish_eye),
          onTap: () {
            setState(() {
              _currIndex = i;
            });
          },
        ));
      }
    }
    return res;
  }

  @override
  Widget build(BuildContext context) {
    var width = MediaQuery.of(context).size.width;
    return Scaffold(
      body: PageView.builder(
          controller: _pageController,
          onPageChanged: (int index) {
            var renderIndex = index - widget._maxPage;
            renderIndex = renderIndex % widget.imagesList.length;
            if (renderIndex < 0) {
              renderIndex += widget.imagesList.length;
            }
            setState(() {
              _currIndex = renderIndex;
            });
          },
          itemBuilder: (BuildContext context, int index) {
            var renderIndex = _currIndex - widget._maxPage;
            renderIndex = renderIndex % widget.imagesList.length;
            if (renderIndex < 0) {
              renderIndex += widget.imagesList.length;
            }
            return Container(
                child: Stack(children: <Widget>[
              Center(
                child: widget.imagesList[_currIndex].startsWith("https://") ||
                        widget.imagesList[_currIndex].startsWith("http://")
                    ? CachedNetworkImage(
                        imageUrl: widget.imagesList[_currIndex],
                        fit: BoxFit.fill,
                        width: width,
                        height: double.infinity,
                        placeholder: (context, url) =>
                            CircularProgressIndicator(),
                        errorWidget: (context, url, error) => Icon(Icons.error),
                      )
                    : FadeInImage(
                        image: AssetImage(widget.imagesList[_currIndex]),
                        placeholder: AssetImage("assets/images/loading.gif"),
                        fit: BoxFit.fill,
                        width: width,
                        height: double.infinity,
                      ),
              ),
              Positioned(
                  left: 2.0,
                  right: 2.0,
                  bottom: 10.0,
                  child: Container(

                      /// padding: EdgeInsets.all(5.0),
                      child: widget.imagesList.length > 10
                          ? SingleChildScrollView(
                              scrollDirection: Axis.horizontal,
                              child: Row(
                                mainAxisAlignment: MainAxisAlignment.center,
                                crossAxisAlignment: CrossAxisAlignment.start,
                                children: getBottomIcon(),
                              ),
                            )
                          : Row(
                              mainAxisAlignment: MainAxisAlignment.center,
                              crossAxisAlignment: CrossAxisAlignment.start,
                              children: getBottomIcon(),
                            )))
            ]));
          }),
    );
  }
}

Flutter: 无限滚动

为什么需要无限滚动

之前做过一个横向图片展示的页面,发现就需要无限滚动(可以往左滑,也可以往右滑).第一时间首先想到的是用PageView,但是发现PageView不支持往左滑动,只能往右滑.这明显限制了功能的实用性.

于是在网上参考了一些教程之后,有了下面的代码.

image-3198

原理

原理其实很简单,就是初始化的时候给PageView设置一个特别大的值.这样无论往左或者往右都可以滑动.通过滑动的值,计算实际的下标,进行展示对应的数据即可.

在线运行效果

源码

源码如下:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import 'package:flutter/material.dart';

/// run to dartpad.dev or dartpad.cn
void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) => MaterialApp(
        home: HomePage(),
      );
}

int maxPage = 2000000000;

class HomePage extends StatelessWidget {
  static const List&lt;Color&gt; _colors = [Colors.blue, Colors.green, Colors.red];

  final pageController = new PageController(initialPage: maxPage);

  @override
  Widget build(BuildContext context) => Scaffold(
        body: PageView.builder(
          controller: pageController,
          itemBuilder: (BuildContext context, int index) {
            var renderIndex = index - maxPage;
            renderIndex = renderIndex % _colors.length;
            if (renderIndex < 0) {
              renderIndex += _colors.length;
            }
            return Container(
              color:_colors[index % _colors.length],
                child: Center(
                    child: Text("text $renderIndex, default Index: $index",
                        style: TextStyle(
                            fontSize: 36.0,
                            color: Colors.white))));
          },
        ),
      );
}