主题列表:juejin, github, smartblue, cyanosis, channing-cyan, fancy, hydrogen, condensed-night-purple, greenwillow, v-green, vue-pro, healer-readable, mk-cute, jzman, geek-black, awesome-green

贡献主题:https://github.com/xitu/juejin-markdown-themes

theme: condensed-night-purple

highlight:

秉承着 有对象,用对象;没对象,找对象;找不到,造对象 的思想方针,终于将 ListWheelViewport 组件跑起来了。以前由于认知的局限,一直没能玩转ListWheelViewport,如今,确实成长了一些。此组件已收录于 FlutterUnit ,目前收录组件已破 310+,可喜可贺,欢迎 star 。

先看一下 ListWheelViewport 的基本信息:

dart 源码位置: flutter/lib/src/widgets/list_wheel_scroll_view.dart 父类: RenderObjectWidget 相关组件: ListWheelScrollView、CupertinoPicker、CupertinoDatePicker

ListWheelViewport 可以实现如下的滚动视口效果,可能你用过 Cupertino 风格的选择器,觉得很类似。不错,它们的底层都有 ListWheelViewport 的参与。了解 ListWheelViewport后,其他的都是弟弟。

2020年12月19日14-25-39

2020年12月19日16-20-18


一、 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> 进行展示。

image-20201219135526757

offset属性需要传入 ViewportOffset 对象,这个对象造是很难造出来。不过凭借着之前的经验知道,这个对象可以通过 Scrollable 中获得。在 viewportBuilder 属性赋值时,可以回调 ViewportOffset 对象。

```dart 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 用起来。代码如下:

```dart class ListWheelViewportDemo extends StatelessWidget { final List 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 | | ---- | ---- | | image-20201219141001629 |image-20201219141041934 |


二、 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 之间。

dart ---->[RenderListWheelViewport]---- static const double defaultPerspective = 0.003;

  • 这是 perspective:0.01 的效果

2020年12月19日14-25-39

  • 这是 perspective:0.001 的效果,可见 perspective 数值越大,透视效果越强。

2020年12月19日14-22-33


2. squeeze 属性

squeeze挤压的意思,默认是 1.0

  • 这是squeeze:0.8 的效果

2020年12月19日14-31-20

  • 这是squeeze:1.5 的效果,可见 squeeze 可以控制 item 的松散程度

2020年12月19日14-55-38


3. diameterRatio 属性

diameterRatio : 圆柱直径与主轴视口大小比率。

| diameterRatio = 2 | diameterRatio = pi/2 | | ---- | ---- | | image-20201219155954632 |image-20201219160026558 |


三、 其他属性

| 属性名 | 类型 | 默认值 | 介绍 | | ------------- | ------ | ------ | -------- | | magnification | double | 1.0 | 放大比例 | | useMagnifier | bool | false | 是否放大 | | clipBehavior | Clip | Clip.hardEdge | 剪裁行为 | | renderChildrenOutsideViewport | bool | false | 出视野是否渲染 | | offAxisFraction | double | 0.0 | 轴中心偏移比 | | overAndUnderCenterOpacity | double | 1 | 放大器之外的透明度 |


1. 放大效果

该组件自带如下 放大效果,通过 magnificationuseMagnifier 控制。

2020年12月19日16-20-18

dart @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 。效果如下:

2020年12月19日16-39-21

dart @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. offAxisFractionoverAndUnderCenterOpacity 属性

offAxisFraction: 0.2 效果

2020年12月19日17-19-04

overAndUnderCenterOpacity:0.4 效果

2020年12月19日17-47-14


四、 基于 ListWheelViewport 实现的组件们
1. ListWheelScrollView 组件

底层基于 _FixedExtentScrollable(Scrollable子类)ListWheelViewport 实现,此组件除了视口之外,还额外拥有监听滑动 item 的能力。如下,在上面的小圆颜色有下面滚轮滑动时选中色决定。ListWheelViewport 的相关属性,在 ListWheelScrollView 中效果是一致的。

2020年12月19日17-55-47

```dart class CustomListWheelScrollView extends StatefulWidget { @override _CustomListWheelScrollViewState createState() => _CustomListWheelScrollViewState(); }

class _CustomListWheelScrollViewState extends State { var data = [ 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: [ _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 的功劳。

2020年12月19日19-23-50

```dart 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 实现的。

2020年12月19日19-33-55

```dart class CustomCupertinoDatePicker extends StatefulWidget { @override _CustomCupertinoDatePickerState createState() => _CustomCupertinoDatePickerState(); }

class _CustomCupertinoDatePickerState extends State { DateTime _date = DateTime.now();

@override Widget build(BuildContext context) { return Container( width: 350, child: Column( mainAxisSize: MainAxisSize.min, children: [ 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 定可住你一臂之力。

shell 【1】ListWheelScrollView 是基于 Scrollable + ListWheelViewport 实现的。 【2】CupertinoPicker 是基于 ListWheelScrollView 实现的。 【3】CupertinoDatePicker 是基于 CupertinoPicker 实现的。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐