1. 前语

最近要完成一个小需求,涵盖了许多知识点,比方手势、动画、布局等。挺有意思的,写出来和我们分享一下。如下所示,分为上下两层;当左右滑时,上层会随偏移量而平移,从而让上层发生滑动手势显隐的作用:

标题
Flutter 知识集锦 | 基于 Flow 实现滑动显隐层
Flutter 知识集锦 | 基于 Flow 实现滑动显隐层

这儿上层经过不透明度 0.2 的蓝色示意,实际运用时能够改为透明色。许多直播间的浮层就是这种交互逻辑,经过右滑来隐藏浮层。

直播 右滑中
Flutter 知识集锦 | 基于 Flow 实现滑动显隐层
Flutter 知识集锦 | 基于 Flow 实现滑动显隐层

2. 完成思路

思路其实十分简略,监听横向滑动的手势事情,依据偏移量让上层组件进行偏移。当放手时,依据偏移量是否到达宽度的一半,运用动画进行移出或者封闭。

Flutter 知识集锦 | 基于 Flow 实现滑动显隐层

偏移的完成方式有许多,但需要自由地进行布局和矩阵改换、透明度,并且需要支持动画的改变,Flow 组件是一个十分不错的选择。 Flow 组件能够经过署理类对子组件进行自定义布局,灵活性极强;如果是 CustomPaint制作之王 能够制作万物,那么 Flow 就是 布局之王,能够摆放万物。三年前写过一篇介绍 Flow 运用的文章: 《【Flutter高档玩法- Flow 】我的方位我做主》 。 本文就不对 Flow 的基础运用进行介绍了。


别的,在滑动过程中需要注意约束偏移量,使偏移量在 0~size.width 之内;当放手时,经过动画控制器来驱动动画,运用补间让偏移量运动到 0 (翻开) 或 size.width(封闭) 。当封闭时,在右下角展现一个按钮用于点击展开:

Flutter 知识集锦 | 基于 Flow 实现滑动显隐层


3. 布局的代码完成

Flow 组件布局最重要的是完成 FlowDelegate,在其间的 paintChildren 办法中完成布局的逻辑。和 CustomPainter 相似,FlowDelegate 的完成类也能够经过 super 结构为 repaint 入参设置可监听对象。可监听对象的改变会触发 paintChildren 重新制作:

SwipeFlowDelegate 完成类再结构时传入可监听对象 offsetX,在制作索引为 1 的孩子时,经过 Matrix4 进行偏移。这样只要在手势水平滑动中,更新 offsetX 值即可。别的,能够依据 offsetX.value 是否到达 size.width 知道是否是封闭状态,如果现已封闭,制作按钮。

class SwipeFlowDelegate extends FlowDelegate {
  final ValueListenable<double> offsetX;
  SwipeFlowDelegate(this.offsetX) : super(repaint: offsetX);
  @override
  void paintChildren(FlowPaintingContext context) {
    Size size = context.size;
    context.paintChild(0);
    Matrix4 offsetM4 = Matrix4.translationValues(offsetX.value, 0, 0);
    context.paintChild(1, transform: offsetM4);
    // 偏移量关于父级尺寸
    if (offsetX.value == size.width) {
      Matrix4 m1 = Matrix4.translationValues(size.width / 2 - 30, size.height / 2 - 30, 0);
      context.paintChild(2, transform: m1);
      Matrix4 m2 = Matrix4.translationValues(size.width / 2 - 30, -(size.height / 2 - 50), 0);
      context.paintChild(3, transform: m2);
    }
  }
  @override
  bool shouldRepaint(covariant SwipeFlowDelegate oldDelegate) {
    return oldDelegate.offsetX.value != offsetX.value;
  }
}

从这儿能够看出,FlowDelegate 的最大优势是能够自定义孩子的制作与否,还能够在制作时经过 Matrix4 对孩子进行矩阵改换,还有可选参数能够控制透明度。接下来运用 Flow 组件时,供给 SwipeFlowDelegate ,并在 children 列表中顺次放入子组件。其间前两个组件由外界传入,分别是底组件和上层组件,这样组件的布局就完成了,接下来监听事情,更新 factor 即可:

final ValueNotifier<double> factor = ValueNotifier(0);
Flow(
  delegate: SwipeFlowDelegate(factor),
  children: [
    widget.content,
    widget.overflow,
    GestureDetector(
        onTap: open,
        child: const Icon(Icons.menu_open_outlined, color: Colors.white)),
    GestureDetector(
        onTap: () {
          Navigator.of(context).pop();
        },
        child: const Icon(Icons.close, color: Colors.white))
  ],
)

4. 手势的监听

这儿手势的处理是十分简略的,经过 GestureDetector 监听水平拖拽事情。在 onHorizontalDragUpdate 中依据拖拽的偏移量更新 factor 的值,其间经过 .clamp(0, widget.width) 能够约束偏移量的取值区间。

@override
Widget build(BuildContext context) {
  return GestureDetector(
      behavior: HitTestBehavior.opaque,
      onHorizontalDragUpdate: _onHorizontalDragUpdate,
      onHorizontalDragEnd: _onHorizontalDragEnd,
      child: SizedBox(
        height: MediaQuery.of(context).size.height,
        width: widget.width,
        child: Flow( delegate:// 同上,略...
      );
}
void _onHorizontalDragUpdate(DragUpdateDetails details) {
  double cur = factor.value + details.delta.dx;
  factor.value = cur.clamp(0, widget.width);
}
void _onHorizontalDragEnd(DragEndDetails details) {
  if (factor.value > widget.width / 2) {
    close();
  } else {
    open();
  }
}

最后在 _onHorizontalDragEnd 回调中,依据当前偏移量是否大于一般宽度,决议封闭仍是翻开。期间过程运用动画进行偏移量的过渡改变。


5. 动画的运用

动画的运用,主要是经过 AnimationController 动画控制器来驱动数值的改变;在放手时 Tween 创立补间动画器,监听动画器数值的改变更新偏移量。这样偏移量就能够在指定时间内,在两个值之间渐变,从而发生动画作用。比方抬手时,open 办法是让偏移量从当前方位改变到 0 :

class _ScrollHideWrapperState extends State<ScrollHideWrapper> with SingleTickerProviderStateMixin {
  late AnimationController _ctrl;
  final ValueNotifier<double> factor = ValueNotifier(0);
  @override
  void initState() {
    super.initState();
    _ctrl = AnimationController(
      duration: const Duration(milliseconds: 200),
      vsync: this,
    );
  }
  @override
  Widget build(BuildContext context) {
    // 略同...
  }
  // 动画封闭
  Future<void> close() async {
    Animation<double> anim = Tween<double>(begin: factor.value, end: widget.width).animate(_ctrl);
    anim.addListener(() => factor.value = anim.value);
    await _ctrl.forward(from: 0);
  }
  // 动画翻开
  Future<void> open() async {
    Animation<double> anim = Tween<double>(begin: factor.value, end: 0).animate(_ctrl);
    anim.addListener(() => factor.value = anim.value);
    await _ctrl.forward(from: 0);
  }
}

如果想让动画的改变非匀速,能够运用 Curve 来控制动画曲线。这样,基于 Flow 完成的自定义布局,就能够依据手势和动画,完成特定的交互功能。从这儿能够看出 Flow 自定义布局的灵活性十分强,许多疑难杂症,都能够运用它来完成。

比方企业微信中:侧滑展现左栏,并且上层不会全部消失,经过 Flow 来自定义布局就很容易完成。我们能够基于本文,自己完成一下作为操练。那本文就到这儿,谢谢观看 ~

标题 封闭
Flutter 知识集锦 | 基于 Flow 实现滑动显隐层
Flutter 知识集锦 | 基于 Flow 实现滑动显隐层