flutter-banner
效果如下
main.dart:
import 'package:flutter/material.dart';
import 'banner_view.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return MaterialApp(
//關閉右上角的debug符號
debugShowCheckedModeBanner: false,
title: 'OpenSource China',
home: Container(
alignment: Alignment.center,
height: 200.0,
child: new BannerView([
Image.asset('assets/pic1.png'),
Image.asset('assets/pic2.png'),
Image.asset('assets/pic3.png'),
Image.asset('assets/pic4.png')
]),
),
);
}
}
banner_view.dart
library banner_view;
import 'dart:async';
import 'package:flutter/material.dart';
import 'indicator/IndicatorWidget.dart';
//indicator container builder
///[indicatorWidget] indicator widget, position the indicator widget into container
typedef Widget IndicatorContainerBuilder(BuildContext context, Widget indicatorWidget);
const String TAG = 'BannerView';
/// Created by yangxiaowei
/// BannerView
class BannerView extends StatefulWidget{
final List<Widget> banners;
//init index
final int initIndex;
//switch interval
final Duration intervalDuration;
//animation duration
final Duration animationDuration;
final IndicatorContainerBuilder indicatorBuilder;
final Widget indicatorNormal;
final Widget indicatorSelected;
//the margin of between indicator items
final double indicatorMargin;
final PageController controller;
//whether cycle rolling
final bool cycleRolling;
//whether auto rolling
final bool autoRolling;
final Curve curve;
final ValueChanged onPageChanged;
final bool log;
BannerView(this.banners, {
Key key,
this.initIndex = 0,
this.intervalDuration = const Duration(seconds: 1),
this.animationDuration = const Duration(milliseconds: 500),
this.indicatorBuilder,
this.indicatorNormal,
this.indicatorSelected,
this.indicatorMargin = 5.0,
this.controller,
this.cycleRolling = true,
this.autoRolling = true,
this.curve = Curves.easeInOut,
this.onPageChanged,
this.log = true,
}):
assert(banners?.isNotEmpty ?? true),
assert(null != indicatorMargin),
assert(null != intervalDuration),
assert(null != animationDuration),
assert(null != cycleRolling),
super(key: key);
@override
_BannerViewState createState() => new _BannerViewState();
}
/// Created by yangxiaowei
class _BannerViewState extends State<BannerView> {
List<Widget> _originBanners = [];
List<Widget> _banners = [];
Duration _duration;
PageController _pageController;
int _currentIndex = 0;
@override
void initState() {
super.initState();
_Logger.debug = widget.log ?? true;
this._isActive = true;
this._originBanners = widget.banners;
this._banners = this._banners..addAll(this._originBanners);
if(widget.cycleRolling) {
Widget first = this._originBanners[0];
Widget last = this._originBanners[this._originBanners.length - 1];
this._banners.insert(0, last);
this._banners.add(first);
this._currentIndex = widget.initIndex + 1;
}else {
this._currentIndex = widget.initIndex;
}
this._duration = widget.intervalDuration;
this._pageController = widget.controller ?? PageController(initialPage: this._currentIndex);
this._nextBannerTask();
}
Timer _timer;
void _nextBannerTaskBy({int milliseconds = 0}) {
if(!mounted) {
return;
}
if(!widget.autoRolling) {
return;
}
this._cancel();
_timer = new Timer(new Duration(milliseconds: _duration.inMilliseconds + milliseconds), () {
this._doChangeIndex();
});
}
void _nextBannerTask() {
this._nextBannerTaskBy(milliseconds: 0);
}
void _cancel() {
_timer?.cancel();
}
void _doChangeIndex({bool increment = true}) {
if(!mounted) {
return;
}
if(increment) {
this._currentIndex++;
}else{
this._currentIndex--;
}
this._currentIndex = this._currentIndex % this._banners.length;
_Logger.d(TAG, "_doChangeIndex $_currentIndex .");
if(0 == this._currentIndex) {
this._pageController.jumpToPage(this._currentIndex + 1);
this._nextBannerTaskBy(milliseconds: -_duration.inMilliseconds);
setState(() {});
}else{
this._pageController.animateToPage(
this._currentIndex,
duration: widget.animationDuration,
curve: widget.curve,
);
}
}
@override
Widget build(BuildContext context) {
return this._generateBody();
}
/// compose the body, banner view and indicator view
Widget _generateBody() {
return new Stack(
children: <Widget>[
this._renderBannerBody(),
this._renderIndicator(),
],
);
}
/// Banner container
Widget _renderBannerBody() {
Widget pageView = new PageView.builder(
itemBuilder: (context, index) {
Widget widget = this._banners[index];
return new GestureDetector(
child: widget,
);
},
controller: this._pageController,
itemCount: this._banners.length,
onPageChanged: (index) {
_Logger.d(TAG, '********** changed index: $index cu: $_currentIndex');
this._currentIndex = index;
this._nextBannerTask();
setState(() {});
if(null != widget.onPageChanged) {
widget.onPageChanged(index);
}
},
physics: new ClampingScrollPhysics(),
);
// return pageView;
return new NotificationListener(
child: pageView,
onNotification: (notification) {
this._handleScrollNotification(notification);
return true;
},
);
}
void _handleScrollNotification(Notification notification) {
void _resetWhenAtEdge(PageMetrics pm) {
if(null == pm || !pm.atEdge) {
return;
}
if(!widget.cycleRolling) {
return;
}
try{
if(this._currentIndex == 0) {
this._pageController.jumpToPage(this._banners.length - 2);
}else if(this._currentIndex == this._banners.length - 1) {
this._pageController.jumpToPage(1);
}
setState(() {});
}catch (e){
_Logger.d(TAG, 'Exception: ${e?.toString()}');
}
}
void _handleUserScroll(UserScrollNotification notification) {
UserScrollNotification sn = notification;
PageMetrics pm = sn.metrics;
var page = pm.page;
var depth = sn.depth;
var left = page == .0 ? .0 : page % (page.round());
if(depth == 0) {
_Logger.d(TAG, '** page: $page , left: $left , atEdge: ${pm.atEdge} , index: $_currentIndex');
if(left == 0) {
setState(() {
_resetWhenAtEdge(pm);
});
}
}
}
if(notification is UserScrollNotification) {
if(_isStartByUser) {
return;
}
if(_isEndByUser) {
_isEndByUser = false;
}else {
_Logger.d(TAG, '######### 手動開始');
_isStartByUser = true;
this._cancel();
}
_handleUserScroll(notification);
}else if(notification is ScrollEndNotification) {
_Logger.d(TAG, '######### ${notification.runtimeType} $_isStartByUser');
if(_isEndByUser) {
return;
}
if(_isStartByUser) {
_Logger.d(TAG, '######### 手動結束');
_isEndByUser = true;
_isStartByUser = false;
} else {
_isEndByUser = false;
}
this._nextBannerTask();
}
}
bool _isEndByUser = false;
bool _isStartByUser = false;
/// indicator widget
Widget _renderIndicator() {
int index = widget.cycleRolling ? this._currentIndex - 1 : this._currentIndex;
index = index <= 0 ? 0 : index;
index = index % _originBanners.length;
return new IndicatorWidget(
size: this._originBanners.length,
currentIndex: index,
indicatorBuilder: this.widget.indicatorBuilder,
indicatorNormal: this.widget.indicatorNormal,
indicatorSelected: this.widget.indicatorSelected,
indicatorMargin: this.widget.indicatorMargin,
);
}
bool _isActive = true;
@override
void deactivate() {
super.deactivate();
_isActive = !_isActive;
if(_isActive) {
_nextBannerTask();
} else {
_cancel();
}
}
@override
void dispose() {
_isActive = false;
_pageController?.dispose();
_cancel();
super.dispose();
}
}
class _Logger {
static bool debug = true;
static void d(String tag, String msg) {
if(debug) {
print('$tag - $msg');
}
}
}
IndicatorWidget.dart
import 'package:flutter/material.dart';
import '../banner_view.dart';
import 'IndicatorUtil.dart';
//Created by yangxiaowei at 2018/06/06
//indicator view of banner
class IndicatorWidget extends StatelessWidget {
final IndicatorContainerBuilder indicatorBuilder;
final Widget indicatorNormal;
final Widget indicatorSelected;
final double indicatorMargin;
final int size;
final int currentIndex;
IndicatorWidget({
Key key,
this.size,
this.currentIndex,
this.indicatorBuilder,
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(
// color: Colors.purple[100],
child: new Row(
mainAxisSize: MainAxisSize.min,
children: _renderIndicatorTag(),
),
);
if(null != this.indicatorBuilder) {
return this.indicatorBuilder(context, smallContainer);
}
//default implement
return new Align(
alignment: Alignment.bottomCenter,
child: new Opacity(
opacity: 0.5,
child: new Container(
height: 40.0,
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 ?? IndicatorUtil.generateIndicatorItem(normal: false);
Widget normal = this.indicatorNormal ?? IndicatorUtil.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;
}
}
IndicatorUtil.dart
import 'package:flutter/material.dart';
//Created by yangxiaowei at 2018/05/16
//generate indicator item widget
class IndicatorUtil {
static 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,
),
);
}
}
參考使用:
add in pubspec.yaml
banner_view: "^1.2.0"
or
banner_view:
git: https://github.com/yangxiaoweihn/BannerView.git
使用方法:
import 'package:banner_view/banner_view.dart';
new Container(
alignment: Alignment.center,
height: 200.0,
child: new BannerView(
[...]
),
);
support properties:
final List<Widget> banners;
//init index
final int initIndex;
//switch interval
final Duration intervalDuration;
//animation duration
final Duration animationDuration;
final IndicatorContainerBuilder indicatorBuilder;
final Widget indicatorNormal;
final Widget indicatorSelected;
//the margin of between indicator items
final double indicatorMargin;
final PageController controller;
//whether cycle rolling
final bool cycleRolling;
//whether auto rolling
final bool autoRolling;
final Curve curve;
final ValueChanged onPageChanged;
final bool log;
參考路徑:https://github.com/yangxiaoweihn/BannerView