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<Widget> _renderIndicatorTag() {
List<Widget> 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<Widget> 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<StatefulWidget> createState() {
return new BannerViewState();
}
}
class BannerViewState extends State<BannerView> {
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: <Widget>[
_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: <Widget>[
buildImage("images/bg_login.png"),
buildImage("images/banner2.png"),
buildImage("images/banner3.png"),
buildImage("images/banner4.png"),
],
));
}