【-Flutter 组件-】ListWheelViewport 组件介绍

2020/12/28 00:36
阅读数 601

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

先看一下 ListWheelViewport 的基本信息:

源码位置:   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 对象。

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

---->[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

@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

@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

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 的功劳。

2020年12月19日19-23-50

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

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 实现的。
复制代码

本文同步分享在 博客“”(JueJin)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
0 评论
0 收藏
0
分享
返回顶部
顶部