目录
- 构造方法以及参数:
- 基本用法
- 无限滚动
- 实现指示器
- 切换动画
- 总结:
构造方法以及参数:
PageView可用于Widget的整屏滑动切换,如当代常用的短视频APP中的上下滑动切换的功能,也可用于横向页面的切换,如APP第一次安装时的引导页面,也可用于开发轮播图功能.
PageView({ | |
Key? key, | |
this.scrollDirection = Axis.horizontal, // 设置滚动方向 垂直 / 水平 | |
this.reverse = false, // 反向滚动 | |
PageController? controller, // 滚动控制类 | |
this.physics, // 滚动逻辑 , 不滚动 / 滚动 / 滚动到边缘是否反弹 | |
this.pageSnapping = true, // 如果设置 false , 则无法进行页面手势捕捉 | |
this.onPageChanged, // 页面切换时回调该函数 | |
List<Widget> children = const <Widget>[], | |
this.dragStartBehavior = DragStartBehavior.start, | |
this.allowImplicitScrolling = false, | |
this.restorationId, | |
this.clipBehavior = Clip.hardEdge, | |
}) : assert(allowImplicitScrolling != null), | |
assert(clipBehavior != null), | |
controller = controller ?? _defaultPageController, | |
childrenDelegate = SliverChildListDelegate(children), | |
super(key: key); |
具体参数说明:
scrollDirection主要是滚动的方向即horizontal(水平)和vertical(垂直)两个,默认是horizontal的
reverse这个就是规定了children(子节点)的排序是否是倒序,默认false。这个参数在ListView也有,一般在做IM工具聊天内容用ListView展示时需要倒序展示的。
controller可以传入一个PageController的实例进去,可以更好的控制PageView的各种动作,可以设置:
- 初始页面(initialPage)、
- 是否保存PageView状态(keepPage)
- 每一个PageView子节点的内容占改视图的比例(viewportFraction)
- 直接调转到指定的PageView的子节点的方法(jumpToPage
- 动画(平滑移动)到指定的PageView的子节点的方法(animateToPage)
- 到下一个PageView的子节点的方法(nextPage)
- 到上一个PageView的子节点的方法(previousPage)
- 从以上可以看出基本是普通轮播图组件的API
physics就是设置滑动效果:
- NeverScrollablePhysics表示设置的不可滚动
- BouncingScrollPhysics表示滚动到底了会有弹回的效果,就是iOS的默认交互
- ClampingScrollPhysics表示滚动到底了就给一个效果,就是Android的默认交互
- FixedExtentScrollPhysics就是ios经典选择时间组件UIDatePicker那种交互。
pageSnapping就是设置是不是整页滚动,默认是true.
dragStartBehavior这个属性是设置认定开始拖动行为的方式,可以选择的是down和start两个,默认是start. down是第一个手指按下认定拖动开始,start是手指拖动才算开始。
allowImplicitScrolling这个属性一般提供给视障人士使用的,默认是fasle
基本用法
PageView控件可以实现一个“图片轮播”的效果,PageView不仅可以水平滑动也可以垂直滑动,简单用法如下:
PageView( | |
children: [ | |
Container(color: Colors.red,), | |
Container(color: Colors.black,), | |
Container(color: Colors.yellow,), | |
], | |
); |
PageView滚动方向默认是水平,可以设置其为垂直方向:
PageView( | |
scrollDirection: Axis.vertical, | |
... | |
) |
PageView配合PageController可以实现非常酷炫的效果,控制每一个Page不占满,
Container( | |
height:200 , | |
child: PageView( | |
scrollDirection: Axis.horizontal, | |
controller: PageController(viewportFraction: 0.9), | |
children: [ | |
Container(color: Colors.red,), | |
Container(color: Colors.black,), | |
Container(color: Colors.yellow,), | |
], | |
), | |
); |
PageController中属性initialPage
表示当前加载第几页,默认第一页。
onPageChanged
属性是页面发生变化时的回调,用法如下:
无限滚动
PageView滚动到最后时希望滚动到第一个页面,这样看起来PageView是无限滚动的:
List<Widget> pageList = [PageView1(), PageView2(), PageView3()]; | |
PageView.builder( | |
itemCount: 10000, | |
itemBuilder: (context, index) { | |
return pageList[index % (pageList.length)]; | |
}, | |
) |
实现指示器
指示器显示总数和当前位置,通过onPageChanged
确定当前页数并更新指示器。
int _currentPageIndex = 0; | |
List<Widget> pageList = [ Container(color: Colors.red,), | |
Container(color: Colors.black,), | |
Container(color: Colors.yellow,),]; | |
_buildPageView(){ | |
return Center( | |
child: Container( | |
height: 230, | |
child: Stack( | |
children: [ | |
PageView.builder(itemBuilder: (context,index){ | |
var val = pageList[index%(pageList.length)]; | |
print(val); | |
return _buildPageViewItem('${val}'); | |
}), | |
Positioned( | |
bottom: 20, | |
left: 0, | |
right: 0, | |
child: Container( | |
child: Row( | |
mainAxisAlignment: MainAxisAlignment.center, | |
children: List.generate(pageList.length, (i){ | |
return Container( | |
margin: EdgeInsets.symmetric(horizontal: 5), | |
width: 10, | |
height: 10, | |
decoration: BoxDecoration( | |
shape: BoxShape.circle, | |
color: _currentPageIndex==i?Colors.blue:Colors.grey | |
), | |
); | |
}).toList(), | |
), | |
) | |
) | |
], | |
), | |
), | |
); | |
} | |
_buildPageViewItem(String txt,{Color color=Colors.red}){ | |
return Container( | |
color: color, | |
alignment: Alignment.center, | |
child: Text(txt,style: TextStyle(color: Colors.white,fontSize: 25),), | |
); | |
} |
切换动画
如此常见的切换效果显然不能体验我们独特的个性,我们需要更炫酷的方式,看下面的效果:
在滑出的时候当前页面逐渐缩小并居中,通过给PageController添加监听获取当前滑动的进度:
_pageController.addListener(() { | |
setState(() { | |
_currPageValue = _pageController.page; | |
}); | |
}); |
全部代码:
/** | |
* @Author wywinstonwy | |
* @Date 2022/10/05 9:50 上午 | |
* @Description: | |
*/ | |
import 'package:demo202112/utils/common_appbar.dart'; | |
import 'package:flutter/material.dart'; | |
class WyPageView1 extends StatefulWidget { | |
const WyPageView1({Key? key}) : super(key: key); | |
@override | |
_WyPageViewState createState() => _WyPageViewState(); | |
} | |
class _WyPageViewState extends State<WyPageView1> { | |
int _currentPageIndex = 0; | |
var imgList = [ | |
'https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=2877516247,37083492&fm=26&gp=0.jpg', | |
'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1582796218195&di=04ce93c4ac826e19067e71f916cec5d8&imgtype=0&src=http%3A%2F%2Fhbimg.b0.upaiyun.com%2F344fda8b47808261c946c81645bff489c008326f15140-koiNr3_fw658' | |
]; | |
late PageController _pageController; | |
var _currPageValue=0; | |
//缩放系数 | |
double _scaleFactor = .8; | |
//view page height | |
double _height = 230.0; | |
@override | |
void initState() { | |
// TODO: implement initState | |
super.initState(); | |
_pageController=PageController(viewportFraction: 0.9); | |
_pageController.addListener(() { | |
setState(() { | |
_currPageValue = _pageController.page; | |
}); | |
}); | |
} | |
@override | |
void dispose() { | |
// TODO: implement dispose | |
super.dispose(); | |
_pageController.dispose(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
appBar: getAppBar('PageView'), | |
body: Container( | |
height: _height, | |
child: PageView.builder( | |
itemBuilder: (context, index) => _buildPageItem(index), | |
itemCount: 10, | |
controller: _pageController, | |
)), | |
); | |
} | |
_buildPageItem(int index) { | |
Matrix4 matrix4 = Matrix4.identity(); | |
if (index == _currPageValue.floor()) { | |
//当前的item | |
double currScale = (1 - (_currPageValue - index) * (1 - _scaleFactor)).toDouble(); | |
var currTrans = _height * (1 - currScale) / 2; | |
matrix4 = Matrix4.diagonal3Values(1.0, currScale, 1.0) | |
..setTranslationRaw(0.0, currTrans, 0.0); | |
} else if (index == _currPageValue.floor() + 1) { | |
//右边的item | |
var currScale = | |
_scaleFactor + (_currPageValue - index + 1) * (1 - _scaleFactor); | |
var currTrans = _height * (1 - currScale) / 2; | |
matrix4 = Matrix4.diagonal3Values(1.0, currScale, 1.0) | |
..setTranslationRaw(0.0, currTrans, 0.0); | |
} else if (index == _currPageValue.floor() - 1) { | |
//左边 | |
var currScale = (1 - (_currPageValue - index) * (1 - _scaleFactor)).toDouble(); | |
var currTrans = _height * (1 - currScale) / 2; | |
matrix4 = Matrix4.diagonal3Values(1.0, currScale, 1.0) | |
..setTranslationRaw(0.0, currTrans, 0.0); | |
} else { | |
//其他,不在屏幕显示的item | |
matrix4 = Matrix4.diagonal3Values(1.0, _scaleFactor, 1.0) | |
..setTranslationRaw(0.0, _height * (1 - _scaleFactor) / 2, 0.0); | |
} | |
return Transform( | |
transform: matrix4, | |
child: Padding( | |
padding: EdgeInsets.symmetric(horizontal: 10), | |
child: Container( | |
decoration: BoxDecoration( | |
borderRadius: BorderRadius.circular(12), | |
image: DecorationImage( | |
image: NetworkImage(imgList[index % 2]), fit: BoxFit.fill), | |
), | |
), | |
), | |
); | |
} | |
} |
gitdemo地址:gitee.com/wywinstonwy…
总结:
相比熟悉Android和IOS开发的同学都会比较熟悉ViewPager,可以在界面上滑动多个界面View的切换。在Flutter中同样有这样的组建那就是PageView,相比于ViewPager它有着更加强大的功能,毕竟Flutter中Widget是一等公民,在实际开发中也是比较实用的组件,可以提升开发效率。