在学习flutter练手时,就打算把公司项目翻一个flutter版的,需要用到一个带指示器的PageView(如下图所示),然鹅百度了一圈大多都是类似banner的带原点的指示器且没有动画效果,所以憋了一天半终于自己手撸了一个(刚学太菜了)
话不多说上代码
import 'package:flutter/material.dart';
import 'package:flutter_shenzhou/library/utils/Utils.dart';
class PageViewWidget extends StatefulWidget {
PageViewWidget(
{Key key,
@required this.childWidget,
@required this.titles,
this.initPage = 0,
this.height = 36,
this.width = 40,
this.indicatorColor = Colors.black38,
this.indicatorSelectedColor = Colors.red,
this.backgroundColor = Colors.grey,
this.scrollDirection = Axis.horizontal,
this.indicatorWidth = 80,
this.indicatorHeight = 2,
this.onPageClicked})
: super(key: key);
//PageViewWidget的宽
final double width;
//PageViewWidget的高
final double height;
final List<Widget> childWidget;
final List<String> titles;
final Axis scrollDirection;
final ValueChanged<int> onPageClicked;
//显示的第一页
final int initPage;
//指示器未选中时的颜色
final Color indicatorColor;
//指示器选中时的颜色
final Color indicatorSelectedColor;
//背景色
final Color backgroundColor;
//指示条长度
double indicatorWidth;
//指示条高度
final double indicatorHeight;
@override
_PageViewWidgetState createState() => _PageViewWidgetState();
}
class _PageViewWidgetState extends State<PageViewWidget>
with TickerProviderStateMixin {
int selectedPage = 0;
PageController _controller;
AnimationController controller;
Animation<EdgeInsets> animation;
//根据titles平分屏幕宽度
double itemWidth;
void onPageChanged(int index) {
controller =
AnimationController(duration: Duration(milliseconds: 200), vsync: this);
controller.addListener(() {
setState(() {});
});
if (index > selectedPage) {
// print("x1>>>" +
// (selectedPage * itemWidth + (itemWidth - widget.indicatorWidth) / 2)
// .toString() +
// "x2>>>" +
// ((selectedPage + 1) * itemWidth +
// (itemWidth - widget.indicatorWidth) / 2)
// .toString());
animation = EdgeInsetsTween(
begin: EdgeInsets.only(
left: selectedPage * itemWidth +
(itemWidth - widget.indicatorWidth) / 2),
end: EdgeInsets.only(
left: (selectedPage + (index - selectedPage)) * itemWidth +
(itemWidth - widget.indicatorWidth) / 2))
.animate(controller);
} else {
// print("x3>>>" +
// (selectedPage * itemWidth + (itemWidth - widget.indicatorWidth) / 2)
// .toString() +
// "x4>>>" +
// ((selectedPage - 1) * itemWidth +
// (itemWidth - widget.indicatorWidth) / 2)
// .toString());
animation = EdgeInsetsTween(
begin: EdgeInsets.only(
left: selectedPage * itemWidth +
(itemWidth - widget.indicatorWidth) / 2),
end: EdgeInsets.only(
left: (selectedPage - (selectedPage - index)) * itemWidth +
(itemWidth - widget.indicatorWidth) / 2))
.animate(controller);
}
setState(() {
selectedPage = index;
});
controller.forward();
}
void _onPageClicked() {
widget?.onPageClicked(selectedPage);
}
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
itemWidth = Utils.getScreenWidth(context) / widget.titles.length;
print("lenth>>" + ((itemWidth - widget.indicatorWidth) / 2).toString());
_controller = PageController(
initialPage: widget.initPage,
);
Widget pageView = GestureDetector(
onTap: _onPageClicked,
child: PageView(
children: widget.childWidget,
scrollDirection: widget.scrollDirection,
onPageChanged: onPageChanged,
controller: _controller,
),
);
Widget indicatorWidget = Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
height: 40,
color: widget.backgroundColor,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: widget.titles.map((f) {
int index = widget.titles.indexOf(f);
return Expanded(
child: Center(
child: GestureDetector(
onTap: () {
onPageChanged(index);
_controller.jumpToPage(index);
},
child: Text(
widget.titles[index],
style: TextStyle(
color: index == selectedPage
? widget.indicatorSelectedColor
: widget.indicatorColor,
),
))));
}).toList())),
Container(
//即_getPadding()
padding: _getPadding(),
child: Container(
width: _getIndicatorWidth(),
height: widget.indicatorHeight,
color: widget.indicatorSelectedColor,
),
),
],
),
);
return Container(
width: widget.width,
height: widget.height,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[indicatorWidget, Expanded(child: pageView)],
));
}
EdgeInsetsGeometry _getPadding() {
if (animation == null) {
//边界处理padding不能小于0
if (((itemWidth - widget.indicatorWidth) / 2) < 0) {
return EdgeInsets.only(left: 0);
} else {
return EdgeInsets.only(left: (itemWidth - widget.indicatorWidth) / 2);
}
} else {
//边界处理padding不能小于0
if (animation.value.left < 0) {
return EdgeInsets.only(left: 0);
} else {
return animation.value;
}
}
}
//widget.indicatorWidth不能大于itemWidth
double _getIndicatorWidth(){
if(widget.indicatorWidth > itemWidth){
widget.indicatorWidth = itemWidth;
}
return widget.indicatorWidth;
}
@override
void dispose() {
// TODO: implement dispose
super.dispose();
controller.dispose();
}
}
接下来是如何使用
import 'package:flutter/material.dart';
import 'package:flutter_shenzhou/common/Colors.dart';
import 'package:flutter_shenzhou/library/widget/PageViewWidget.dart';
class HomeContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(appBar:AppBar(title: Text('带指示器的Pageview')),body:PageViewWidget(
width: double.infinity,
height: double.infinity,
backgroundColor: ColorsM.GRAY_1,
titles: ["推荐商品", "推荐美食", "推荐潮牌","推荐景点"],
childWidget: <Widget>[
Center(
child: Container(
color: Colors.red,
),
),
Center(
child: Container(
color: Colors.pink,
),
),
Center(
child: Container(
color: Colors.yellow,
),
),Center(
child: Container(
color: Colors.blue,
),
)
],
) );
}
}
属性 | 说明 | 默认值 | 必填项 |
---|---|---|---|
childWidget | PageView要现实的widget | 无 | * |
titles | 指示器文字列表 | 无 | * |
width | widget的宽 | 100 | |
height | widget的高 | 100 | |
initPage | pageView初始显示第n页 | 0 | |
indicatorColor | 未选中title的颜色 | Colors.black38 | |
indicatorSelectedColor | 选中title的颜色 | Colors.red | |
backgroundColor | 指示器北京颜色 | Colors.grey | |
scrollDirection | pageview的滑动方向(水平/竖直) | Axis.horizontal | |
indicatorWidth | 指示器指示条的宽度 | 80 | |
indicatorHeight | 指示器指示条的高度 | 2 | |
onPageClicked | pageview页面点击回调 | 无 |
大家可以根据自己的需求来适当改动。
*注:当titles过多时会出现换行的情况,Ui体验较差,可以将titles放入listview进行显示(如:今日头条的导航栏效果)