【-Flutter 组件-】ListWheelViewport 组件介绍
秉承着
有对象,用对象;没对象,找对象;找不到,造对象
的思想方针,终于将ListWheelViewport
组件跑起来了。以前由于认知的局限,一直没能玩转ListWheelViewport
,如今,确实成长了一些。此组件已收录于 FlutterUnit ,目前收录组件已破 310+,可喜可贺,欢迎 star 。
先看一下 ListWheelViewport
的基本信息:
源码位置: flutter/lib/src/widgets/list_wheel_scroll_view.dart
父类: RenderObjectWidget
相关组件: ListWheelScrollView、CupertinoPicker、CupertinoDatePicker
ListWheelViewport
可以实现如下的滚动视口效果,可能你用过Cupertino
风格的选择器,觉得很类似。不错,它们的底层都有ListWheelViewport
的参与。了解ListWheelViewport
后,其他的都是弟弟。
一、 ListWheelViewport
三个必须属性
属性名 | 类型 | 默认值 | 介绍 |
---|---|---|---|
itemExtent | double | required | 主轴方向 item 尺寸 |
offset | ViewportOffset | required | 视口偏移 |
childDelegate | ListWheelChildDelegate | required | 孩子代理构造器 |
下面先用一个最简的 demo 来测试一下
ListWheelViewport
的使用。上面的三个属性是必须给出的。 其中itemExtent
是最简单的,代表主轴方向 item 尺寸
。如下轮子上下滑滚动
,主轴就是 Y 轴,itemExtent 就表示每个 item 的高度
。
childDelegate
属性是ListWheelChildDelegate
类型的,其为抽象类,实现类有如下三个, 其中:ListWheelChildListDelegate
接受List<Widget>
进行展示。ListWheelChildBuilderDelegate
通过builder
构造器创建 item。ListWheelChildLoopingListDelegate
是可以无限滑动的列表,接受List<Widget>
进行展示。
offset
属性需要传入ViewportOffset
对象,这个对象造是很难造出来。不过凭借着之前的经验知道,这个对象可以通过Scrollable
中获得。在viewportBuilder
属性赋值时,可以回调ViewportOffset
对象。
typedef ViewportBuilder = Widget Function(BuildContext context, ViewportOffset position);
class Scrollable extends StatefulWidget {
const Scrollable({
Key? key,
this.axisDirection = AxisDirection.down,
this.controller,
this.physics,
required this.viewportBuilder, //<---- viewportBuilder
this.incrementCalculator,
this.excludeFromSemantics = false,
this.semanticChildCount,
this.dragStartBehavior = DragStartBehavior.start,
this.restorationId,
使用将这几个要素合在一起,就可以将
ListWheelViewport
用起来。代码如下:
class ListWheelViewportDemo extends StatelessWidget {
final List<Color> data = [
Colors.blue[50], Colors.blue[100], Colors.blue[200],
Colors.blue[300], Colors.blue[400], Colors.blue[500],
Colors.blue[600], Colors.blue[700], Colors.blue[800],
Colors.blue[900], Colors.blue[800], Colors.blue[700],
Colors.blue[600], Colors.blue[500], Colors.blue[400],
Colors.blue[300], Colors.blue[200], Colors.blue[100],
];
@override
Widget build(BuildContext context) {
return Container(
height: 250,
width: 320,
child: Scrollable(
axisDirection: AxisDirection.down,
physics: BouncingScrollPhysics(),
dragStartBehavior: DragStartBehavior.start,
viewportBuilder: (ctx, position) => ListWheelViewport(
itemExtent: 50,
offset: position,
childDelegate: ListWheelChildLoopingListDelegate(
children: data.map((e) => _buildItem(e)).toList()),
)),
);
}
Widget _buildItem(Color color) => Container(
alignment: Alignment.center,
color: color,
child: Text(colorString(color),
style: TextStyle(color: Colors.white, shadows: [
Shadow(color: Colors.black, offset: Offset(.5, .5), blurRadius: 2)
])),
);
String colorString(Color color) =>
"#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}";
}
看下 itemExtent 属性的作用
itemExtent = 80 | itemExtent = 100 |
---|---|
![]() | ![]() |
二、 perspective、squeeze、diameterRatio 效果属性
属性名 | 类型 | 默认值 | 介绍 |
---|---|---|---|
perspective | double | 0.003 | 透视参数 0~0.01 |
squeeze | double | 1.0 | 挤压值 |
diameterRatio | double | 2.0 | 直径分率 |
1. perspective 属性
perspective
是透视
的意思,默认是0.003
,取值范围在0~0.01
之间。
---->[RenderListWheelViewport]----
static const double defaultPerspective = 0.003;
- 这是
perspective:0.01
的效果
- 这是
perspective:0.001
的效果,可见perspective
数值越大,透视效果越强。
2. squeeze 属性
squeeze
是挤压
的意思,默认是1.0
。
- 这是
squeeze:0.8
的效果
- 这是
squeeze:1.5
的效果,可见squeeze
可以控制 item 的松散程度
3. diameterRatio 属性
diameterRatio : 圆柱直径与主轴视口大小比率。
diameterRatio = 2 | diameterRatio = pi/2 |
---|---|
![]() | ![]() |
三、 其他属性
属性名 | 类型 | 默认值 | 介绍 |
---|---|---|---|
magnification | double | 1.0 | 放大比例 |
useMagnifier | bool | false | 是否放大 |
clipBehavior | Clip | Clip.hardEdge | 剪裁行为 |
renderChildrenOutsideViewport | bool | false | 出视野是否渲染 |
offAxisFraction | double | 0.0 | 轴中心偏移比 |
overAndUnderCenterOpacity | double | 1 | 放大器之外的透明度 |
1. 放大效果
该组件自带如下
放大效果
,通过magnification
和useMagnifier
控制。
@override
Widget build(BuildContext context) {
return Container(
height: 250,
width: 320,
child: Scrollable(
axisDirection: AxisDirection.down,
physics: BouncingScrollPhysics(),
dragStartBehavior: DragStartBehavior.start,
viewportBuilder: (ctx, position) => ListWheelViewport(
perspective: 0.008,
squeeze: 1,
diameterRatio: 2,
itemExtent: 50,
useMagnifier: true,
magnification: 2,
offset: position,
childDelegate: ListWheelChildLoopingListDelegate(
children: data.map((e) => _buildItem(e)).toList()),
)),
);
}
2. 出界渲染与裁剪
默认情况下,item 不在视野区域内不会渲染,可以通过
renderChildrenOutsideViewport:true
让其显示,注意此时 clipBehavior 必须为 Clip.none
。效果如下:
@override
Widget build(BuildContext context) {
return Container(
height: 250,
width: 320,
child: Scrollable(
axisDirection: AxisDirection.down,
physics: BouncingScrollPhysics(),
dragStartBehavior: DragStartBehavior.start,
viewportBuilder: (ctx, position) => ListWheelViewport(
perspective: 0.008,
squeeze: 1,
diameterRatio: 2,
renderChildrenOutsideViewport: true,
clipBehavior: Clip.none,
itemExtent: 50,
offset: position,
childDelegate: ListWheelChildLoopingListDelegate(
children: data.map((e) => _buildItem(e)).toList()),
)),
);
}
3. offAxisFraction
和 overAndUnderCenterOpacity
属性
offAxisFraction: 0.2
效果
overAndUnderCenterOpacity:0.4
效果
四、 基于 ListWheelViewport
实现的组件们
1. ListWheelScrollView
组件
底层基于
_FixedExtentScrollable(Scrollable子类)
和ListWheelViewport
实现,此组件除了视口之外,还额外拥有监听滑动 item
的能力。如下,在上面的小圆颜色有下面滚轮滑动时选中色决定。ListWheelViewport
的相关属性,在ListWheelScrollView
中效果是一致的。
class CustomListWheelScrollView extends StatefulWidget {
@override
_CustomListWheelScrollViewState createState() =>
_CustomListWheelScrollViewState();
}
class _CustomListWheelScrollViewState extends State<CustomListWheelScrollView> {
var data = <Color>[
Colors.orange[50], Colors.orange[100], Colors.orange[200],
Colors.orange[300], Colors.orange[400], Colors.orange[500],
Colors.orange[600], Colors.orange[700], Colors.orange[800],
Colors.orange[900] ];
Color _color = Colors.blue;
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
_buildCircle(),
Container(
height: 150,
width: 300,
child: ListWheelScrollView.useDelegate(
childDelegate: ListWheelChildLoopingListDelegate(
children: data.map((e) => _buildItem(e)).toList()),
perspective: 0.006,
itemExtent: 50,
onSelectedItemChanged: (index) {
setState(() => _color = data[index]);
},
),
),
],
);
}
Widget _buildCircle() => Container(
margin: EdgeInsets.only(bottom: 5),
width: 30,
height: 30,
decoration: BoxDecoration(color: _color, shape: BoxShape.circle),
);
Widget _buildItem(Color color) {
return Container(
key: ValueKey(color),
alignment: Alignment.center,
height: 50,
color: color,
child: Text(
colorString(color),
style: TextStyle(color: Colors.white, shadows: [
Shadow(color: Colors.black, offset: Offset(.5, .5), blurRadius: 2)
]),
),
);
}
String colorString(Color color) =>
"#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}";
}
2. CupertinoPicker
组件
CupertinoPicker
的内部源码实现依赖于ListWheelScrollView
。所以最终的效果还是ListWheelViewport
的功劳。
class CustomCupertinoPicker extends StatelessWidget {
final names = [
'Java', 'Kotlin', 'Dart',
'Swift', 'C++', 'Python',
"JavaScript", "PHP", "Go", "Object-c"
];
@override
Widget build(BuildContext context) {
return Container(
height: 150,
width: 300,
child: CupertinoPicker(
backgroundColor: CupertinoColors.systemGrey.withAlpha(33),
diameterRatio: 1,
offAxisFraction: 0.2,
squeeze: 1.5,
itemExtent: 40,
onSelectedItemChanged: (position) {
print('当前条目 ${names[position]}');
},
children: names.map((e) => Center(child: Text(e))).toList()),
);
}
}
3. CupertinoDatePicker
组件
CupertinoDatePicker
内部是基于CupertinoPicker
实现的。
class CustomCupertinoDatePicker extends StatefulWidget {
@override
_CustomCupertinoDatePickerState createState() =>
_CustomCupertinoDatePickerState();
}
class _CustomCupertinoDatePickerState extends State<CustomCupertinoDatePicker> {
DateTime _date = DateTime.now();
@override
Widget build(BuildContext context) {
return Container(
width: 350,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
'当前日期:${_date.toIso8601String()}',
style: TextStyle(color: Colors.grey, fontSize: 16),
),
_buildInfoTitle('CupertinoDatePickerMode.dateAndTime'),
buildPicker(CupertinoDatePickerMode.dateAndTime),
],
),
);
}
Container buildPicker(CupertinoDatePickerMode mode) {
return Container(
margin: EdgeInsets.all(10),
height: 150,
child: CupertinoDatePicker(
mode: mode,
initialDateTime: DateTime.now(),
minimumYear: 2018,
maximumYear: 2030,
use24hFormat: false,
minuteInterval: 1,
backgroundColor: CupertinoColors.white,
onDateTimeChanged: (date) {
print(date);
setState(() => _date = date);
},
),
);
}
Widget _buildInfoTitle(info) {
return Padding(
padding: const EdgeInsets.only(left: 20, top: 20, bottom: 5),
child: Text(
info,
style: TextStyle(
color: Colors.blue, fontSize: 16, fontWeight: FontWeight.bold),
),
);
}
}
4. 小结一下
这样来看
滚轮
相关的组件,追其本源都与ListWheelViewport
相关。所以认识了ListWheelViewport
各属性的意义,则其他衍生出来的组件就更容易理解了。这便是以不变,应万变
。也许某一天,你会遇到自定义某种滚轮效果
,这时候ListWheelViewport
定可住你一臂之力。
【1】ListWheelScrollView 是基于 Scrollable + ListWheelViewport 实现的。
【2】CupertinoPicker 是基于 ListWheelScrollView 实现的。
【3】CupertinoDatePicker 是基于 CupertinoPicker 实现的。
转载自:https://juejin.cn/post/6911099341440548877