前言
普通的基础组件自然不能满足我们的日常开发需求,所以小T带大家了解Flutter开发中的常用组件
本篇将介绍常用的组件。分为上中下三篇
结构图:
本小结内容:
1.ListView (详解)
2.GridView
3.PageView (用处超多哦~)
好好看好好学
ListView:
1.ListView
1) 普通ListView (适用于列表中含有少量元素的场景)
2) ListView.builder (适用于子 Widget 较多,且视觉效果呈现某种规律性的场景)
3) ListView.separated (适用于子 Widget 较多,且视觉效果呈现某种规律性、每个子 Widget 之间需要分割线的场景)
在 Flutter 中,ListView 可以沿一个方向(垂直或水平方向)来排列其所有子 Widget
1.普通ListView
一个很简单的列表
让我们来看看源码:
ListView({
Axis scrollDirection = Axis.vertical,
ScrollController controller,
ScrollPhysics physics,
bool shrinkWrap = false,
EdgeInsetsGeometry padding,
this.itemExtent,
double cacheExtent,
List<Widget> children = const <Widget>[],
})
源码详解:
scrollDirection
: 列表的滚动方向,可选值有Axis
的horizontal
和vertical
,可以看到默认是垂直方向上滚动;controller
: 控制器,与列表滚动相关,比如监听列表的滚动事件;physics
: 列表滚动至边缘后继续拖动的物理效果,Android
与iOS
效果不同。Android
会呈现出一个波纹状(对应ClampingScrollPhysics
),而iOS
上有一个回弹的弹性效果(对应BouncingScrollPhysics
)。如果你想不同的平台上呈现各自的效果可以使用AlwaysScrollableScrollPhysics
,它会根据不同平台自动选用各自的物理效果。如果你想禁用在边缘的拖动效果,那可以使用NeverScrollableScrollPhysics
;shrinkWrap
: 该属性将决定列表的长度是否仅包裹其内容的长度。当ListView
嵌在一个无限长的容器组件中时,shrinkWrap
必须为true,否则Flutter
会给出警告;padding
: 列表内边距;itemExtent
: 子元素长度。当列表中的每一项长度是固定的情况下可以指定该值,有助于提高列表的性能(因为它可以帮助ListView
在未实际渲染子元素之前就计算出每一项元素的位置);cacheExtent
: 预渲染区域长度,ListView
会在其可视区域的两边留一个cacheExtent
长度的区域作为预渲染区域(对于ListView.build
或ListView.separated
构造函数创建的列表,不在可视区域和预渲染区域内的子元素不会被创建或会被销毁);children
: 容纳子元素的组件数组。
效果图代码:
ListView(
children: [
ListTile(
leading: Icon(
Icons.home,
color: Colors.cyan, // 图标颜色
),
title: Text("首页"),
selected: true, // 设置状态为选中状态
),
ListTile(
leading: Icon(
Icons.add_shopping_cart,
color: Colors.black54,
),
title: Text("购物车"),
),
ListTile(
leading: Icon(
Icons.account_circle,
color: Colors.black54,
),
title: Text("我的"),
)
],
)
可以看到,默认构造函数
的用法非常之简单,直接把子元素组件放在children
数组中就可以了。但是潜在的问题前面也已经解释过,对于长列表
这种应用场景还是应该用ListView.build
构造函数性能会更好~
2. ListView.builder
它的源码和ListView相比较就多了
itemCount
: 列表中元素的数量;itemBuilder
: 子元素的渲染方法,允许自定义子元素组件(等同于rn
中FlatList
组件的renderItem
属性)。
适合在多数据的列表下使用
让我们来体验它的魅力吧:
先模仿一下真实的Json数据:
LinkedHashMap<String, dynamic> builderTest = {
"data": {
"0": {"userId": "1", "userName": "阿T One"},
"1": {"userId": "2", "userName": "阿T Two"},
"2": {"userId": "3", "userName": "阿T Three"},
},
"code": "200",
"msg": "success"
} as LinkedHashMap<String, dynamic>;
实现:
ListView.builder(
itemCount: builderTest["data"].length, //data数据中的长度
itemBuilder: (context, int index) { //index是自增的~
return Column(
children: [
Container(
margin: EdgeInsets.only(top: 5),
alignment: Alignment.center,
child: Text("用户名" +
builderTest["data"][index.toString()]["userName"]),
),
],
);
})
非常适合开发的日常使用
3.ListView.separated
绝大多数列表类的需求我们都可以用ListView.builder
构造函数来解决问题,不过有的列表子项之间需要分割线
,此时我们可以用Flutter
提供的另一个构造函数ListView.separated
来创建列表。
就多了个分割线,还是ListView.builder香~
GridView:
常见的GridView:
GridView
默认构造函数可以类比于ListView
默认构造函数,适用于有限个数子元素的场景,因为GridView
组件会一次性全部渲染children
中的子元素组件;GridView.builder
构造函数可以类比于ListView.builder
构造函数,适用于长列表的场景,因为GridView
组件会根据子元素是否出现在屏幕内而动态创建销毁,减少内存消耗,更高效渲染;GridView.count
构造函数是GrdiView
使用SliverGridDelegateWithFixedCrossAxisCount
的简写(语法糖),效果完全一致;GridView.extent
构造函数式GridView
使用SliverGridDelegateWithMaxCrossAxisExtent
的简写(语法糖),效果完全一致。
咱们这个只讲解最常用的:GridView.bulider
和ListView.builder一样,这个组件适用于:
当子widget比较多时,我们可以通过GridView.builder
来动态创建子widget
GridView.builder
必须指定的参数有两个:
GridView.builder(
...
@required SliverGridDelegate gridDelegate,
@required IndexedWidgetBuilder itemBuilder,
)
其中itemBuilder
为子widget构建器
效果图:
实现:(上面的Json数据不要忘记写哦~)
GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, //每行三列
childAspectRatio: 1.0 //显示区域宽高相等
),
itemCount: builderTest["data"].length,
itemBuilder: (context, index) {
return Column(
children: [
Container(
color: Colors.amberAccent,
margin: EdgeInsets.only(top: 100,right: 5,left: 5),
alignment: Alignment.center,
child: Text("用户名" +
builderTest["data"][index.toString()]["userName"]),
),
],
);
})
咱也可以自己改进这个组件,造轮子哈哈~
PageView:
PageView可用于Widget的整屏滑动切换,如当代常用的短视频APP中的上下滑动切换的功能,也可用于横向页面的切换,如APP第一次安装时的引导页面,也可用于开发轮播图功能
例:微信的界面可以左右切换,可以用PageView实现
先让我们看看PageView的源码:
我们来实现一个简单的轮播图:
可以左右切换~
PageView({
Key? key,
this.scrollDirection = Axis.horizontal, //滚动方向
this.reverse = false, //是否反向,这个反向根阅读习惯有关
PageController? controller, //控制器 !!
this.physics, //滚动的物理效果
this.pageSnapping = true, //自定义滚动行为时,设置为false,滚动起来就向list一样
this.onPageChanged, //页面切换回调
List<Widget> children = const <Widget>[],
this.dragStartBehavior = DragStartBehavior.start,//影响DragStartDetails返回offset的方式
this.allowImplicitScrolling = false,//在头部或者尾部继续滚动时,手势事件是否传给上层widget消费
})
代码实现:
第一步:定义需要的参数:
PageController _pageController;
Timer _timer; //骚操作所使用的,不需要可以注释
int _index = 0;
List bannerList = [
"https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=2675667394,3836121343&fm=26&gp=0.jpg",
"https://img2.baidu.com/it/u=2228201159,3924578095&fm=26&fmt=auto&gp=0.jpg",
"https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2975272371,772428775&fm=26&gp=0.jpg",
"https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3644428647,2654791241&fm=26&gp=0.jpg",
];
第二步:初始化:
@override
void initState() {
super.initState();
_pageController = PageController(
initialPage: _index, //默认在第几个
viewportFraction: 1, // 占屏幕多少,1为占满整个屏幕
keepPage: true, //是否保存当前 Page 的状态,如果保存,下次回复保存的那个 page,initialPage被忽略,
//如果为 false 。下次总是从 initialPage 开始。
);
// _timer = Timer.periodic(const Duration(seconds: 2), (timer) {
// _index++;
// _pageController.animateToPage(
// _index % 3, //跳转到的位置
// duration: Duration(milliseconds: 16), //跳转的间隔时间
// curve: Curves.fastOutSlowIn, //跳转动画
// );
// });
}
第三步:主体代码:
return Scaffold(
appBar: AppBar(
leading: null,
automaticallyImplyLeading: false,
title: Text("title"),
),
body: PageView(
scrollDirection: Axis.horizontal,
reverse: false,
controller: _pageController,
physics: BouncingScrollPhysics(),
pageSnapping: true,
onPageChanged: (index) {
print('当前是第$index图片');
},
children: <Widget>[
Image.network(
bannerList[0],
width: MediaQuery.of(context).size.width,
),
Image.network(
bannerList[1],
width: MediaQuery.of(context).size.width,
),
Image.network(
bannerList[2],
width: MediaQuery.of(context).size.width,
),
Image.network(
bannerList[3],
width: MediaQuery.of(context).size.width,
)
],
),
);
第四步:销毁处理
@override
void dispose() {
_pageController.dispose();
// _timer.cancel();
super.dispose();
}
这样就可以啦~
骚操作:让它自己动起来
初始化时加个定时器:
_timer = Timer.periodic(const Duration(seconds: 2), (timer) {
_index++;
_pageController.animateToPage(
_index % 3, //跳转到的位置
duration: Duration(milliseconds: 16), //跳转的间隔时间
curve: Curves.fastOutSlowIn, //跳转动画
);
});
然后在销毁做进一步处理
_timer.cancel();
这样就是一个自己会动的轮播图啦~
同学们在日常生活中要根据实际情况灵活运用哦~
欢迎留言纠正 ~