Flutter 1.0 is out!
Tuesday, December 4, 2018
Banner是手机应用最常见的需求之一,https://pub.dartlang.org/flutter中搜索Banner找到两个开源库,
引入项目后,分别存在一些问题,其中banner库,没有提供页码指示器。banner_view在手动快速滑动的过程中,会导致bug。
因此决定结合两个项目的优点进行改进,实现效果如下。
主要的布局是由一个Stack包裹着banner内容视图和Indicator指示器视图
其中指示器视图直接使用的banner_view中的代码。banner内容视图部分,使用的是PageView,pageView有个地方比较坑,pageView需要通过PageController控制页面的跳转,但是通过PageController拿到的当前page页码是double类型,并且会丢失精度,需要进行四舍五入,转换成int类型之后,再作为当前页码使用。
import 'package:flutter/material.dart';
//Created by yangxiaowei at 2018/06/06
//indicator view of banner
class IndicatorWidget extends StatelessWidget {
final Widget indicatorNormal;
final Widget indicatorSelected;
final double indicatorMargin;
final int size;
final int currentIndex;
IndicatorWidget({
Key key,
this.size,
this.currentIndex,
this.indicatorNormal,
this.indicatorSelected,
this.indicatorMargin = 5.0,
}) : assert(indicatorMargin != null),
assert(size != null && size > 0),
assert(currentIndex != null && currentIndex >= 0),
super(key: key);
@override
Widget build(BuildContext context) {
return this._renderIndicator(context);
}
//indicator container
Widget _renderIndicator(BuildContext context) {
Widget smallContainer = new Container(
child: new Row(
mainAxisSize: MainAxisSize.min,
children: _renderIndicatorTag(),
),
);
//default implement
return new Align(
alignment: Alignment.bottomCenter,
child: new Opacity(
opacity: 0.5,
child: new Container(
height: 25,
padding: new EdgeInsets.symmetric(horizontal: 16.0),
color: Colors.black45,
alignment: Alignment.centerRight,
child: smallContainer,
),
),
);
}
//generate every indicator item
List _renderIndicatorTag() {
List indicators = [];
final int len = this.size;
Widget selected = this.indicatorSelected ?? generateIndicatorItem(normal: false);
Widget normal = this.indicatorNormal ?? generateIndicatorItem(normal: true);
for (var index = 0; index < len; index++) {
indicators.add(index == this.currentIndex ? selected : normal);
if (index != len - 1) {
indicators.add(new SizedBox(
width: this.indicatorMargin,
));
}
}
return indicators;
}
Widget generateIndicatorItem({bool normal = true, double indicatorSize = 8.0}) {
return new Container(
width: indicatorSize,
height: indicatorSize,
decoration: new BoxDecoration(
shape: BoxShape.circle,
color: normal ? Colors.white : Colors.red,
),
);
}
}
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_biobank/widget/IndicatorWidget.dart';
///广告banner
///author: yinbiao
///time:2018-12-5
class BannerView extends StatefulWidget {
final int delayTime; //间隔时间秒
final int scrollTime; //滑动耗时毫秒
final double height; //banner高度
final List data; //banner内容
int _index = 0; //当前位置
BannerView({Key key, @required this.data, this.delayTime = 3, this.scrollTime = 200, this.height = 200.0}) : super(key: key);
@override
State createState() {
return new BannerViewState();
}
}
class BannerViewState extends State {
PageController controller = new PageController();
Timer timer;
@override
void initState() {
super.initState();
resetTimer();
}
///重置计时器
void resetTimer() {
clearTimer();
timer = new Timer.periodic(new Duration(seconds: widget.delayTime), (Timer timer) {
if (controller.positions.isNotEmpty) {
///这里controller.page会丢失精度,需要四舍五入
widget._index = controller.page.round() + 1;
controller.animateToPage(widget._index, duration: new Duration(milliseconds: widget.scrollTime), curve: Curves.linear);
setState(() {});
}
});
}
///清除计时器
clearTimer() {
if (timer != null) {
timer.cancel();
timer = null;
}
}
@override
Widget build(BuildContext context) {
return new Stack(
children: [
_buildBanner(),
_renderIndicator(),
],
);
}
Widget _buildBanner() {
return new SizedBox(
width: MediaQuery.of(context).size.width,
height: widget.height,
child: new GestureDetector(
onTapDown: (details) {
clearTimer();
},
onTapUp: (details) {
resetTimer();
},
onTapCancel: () {
resetTimer();
},
child: new PageView.builder(
controller: controller,
physics: const PageScrollPhysics(parent: const ClampingScrollPhysics()),
itemBuilder: (BuildContext context, int index) {
return widget.data[index % widget.data.length];
},
itemCount: 0x7fffffff,
onPageChanged: (index) {
setState(() {
widget._index = index;
});
},
),
),
);
}
/// indicator widget
Widget _renderIndicator() {
return new IndicatorWidget(
size: widget.data.length,
currentIndex: widget._index % widget.data.length,
);
}
@override
void dispose() {
clearTimer();
super.dispose();
}
}
Widget buildBanner() {
return new Container(
alignment: Alignment.center,
height: 200.0,
child: new BannerView(
data: [
buildImage("images/bg_login.png"),
buildImage("images/banner2.png"),
buildImage("images/banner3.png"),
buildImage("images/banner4.png"),
],
));
}
https://github.com/zhangruiyu/BannerView
https://github.com/yangxiaoweihn/BannerView