前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Flutter 组件 | 手牵手,一起走 CompositedTransformFollower 与 CompositedTransformTarget

Flutter 组件 | 手牵手,一起走 CompositedTransformFollower 与 CompositedTransformTarget

作者头像
张风捷特烈
发布2022-03-08 15:21:02
1.4K1
发布2022-03-08 15:21:02
举报

一、缘起

代码语言:javascript
复制
CompositedTransformTarget 合成变换目标
CompositedTransformFollower 合成变换伴随者

这两个组件已加入 FlutterUnit, 可更新查看,顺便 star ~。

  1. Slider 组件开始说

其实之前这两个组件我一直都不知道它们是干嘛用的,直到有一天我在看 Slider 的源码时发现了他俩。我们都知道,当 Slider 组件设置了 labeldivisions 时,在拖动的过程中会弹出 Overlay 提示框。

当我们使用 TransformSlider 进行旋转变换,可以发现Overlay 浮层也进行了相应的旋转变换。如果在不知道 CompositedTransformTargetCompositedTransformFollower 组件之前,也许你会以为这个变换是在 Slider 源码中算出来作用在 Overlay 浮层上,其实不然。

代码语言:javascript
复制
Transform(
  transform: Matrix4.rotationZ(-15/180*pi),
  alignment: Alignment.center,
  child: Slider(
    ...
  ),
),
2.两者在 slider 源码中的使用

CompositedTransformFollower 是伴随者,可以看成跟屁虫。在 Slider 组件中 Overlay 对应的组件的外层包裹了 CompositedTransformFollower,表示其身份是一个伴随者。

Slider 核心构建组件 _SliderRenderObjectWidget 的外层包裹了 CompositedTransformTarget。标志着其为被跟随的目标。两者通过 _layerLink 订立连接的契约,从而达到 变换与共 的效果。

用这两个组件有什么好处呢?其实很明显。在不知道这两个组件之前,我们是如何确定 Overlay 的位置呢?一个字:。通过 Positioned 组件和组件位置信息得到确切位置,对于一些静态的Overlay 框,也许可行。但是如果伴随滑动旋转缩放时,那必须通过计算来更新 Overlay 的位置,这显然是非常麻烦的。

二、自己试验一下

Slider 作为框架的源码组件,是比较复杂的,不是所有人都有耐心一点点分享。为了方便演示这两者的使用,我特意准备了几个精简的演示案例。

1.最简使用

现在要实现如下效果:点击时,在组件的左上角显示一个 Overlay 提示信息,再点击则隐藏。如果先不看下面的实现,你可能会想到使用 Positioned 组件,通过RenderBox算出组件的左上角的 绝对坐标来放置 Overlay。这样算起来是比较麻烦的,而且有些人估计也不知道怎么算。

整体逻辑很简单,测试demo中目标组件是Image,上层包裹了 CompositedTransformTarget 。在点击时,执行 _toggleOverlay 方法来切换 Overlay 的显隐情况。

代码语言:javascript
复制
class TipBox extends StatefulWidget {
  TipBox({Key key}) : super(key: key);
  @override
  _TipBoxState createState() => _TipBoxState();
}

class _TipBoxState extends State<TipBox> {
  final LayerLink layerLink = LayerLink();
  OverlayEntry _overlayEntry;
  bool show = false;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: _toggleOverlay,
      child: CompositedTransformTarget(
        link: layerLink,
        child: Image.asset(
          "assets/images/icon_head.webp",
          width: 80, height: 80,
        ),
      ),
    );
  }

  void _toggleOverlay() {
    if (!show) {
      _showOverlay();
    } else {
      _hideOverlay();
    }
    show = !show;
  }

  void _showOverlay() {
    _overlayEntry = _createOverlayEntry();
    Overlay.of(context).insert(_overlayEntry);
  }

  void _hideOverlay() {
    _overlayEntry?.remove();
  }
}

伴随组件是浮窗内容,上层包裹了 CompositedTransformFollower 。由于默认清空下 OverlayEntry 的约束条件会强行撑满全屏,可以使用 UnconstrainedBox 来解除约束。通过两个组件的伴随,实现了在不通过计算的情况下,使 Overlay 停留在目标组件的左上角。

代码语言:javascript
复制
OverlayEntry _createOverlayEntry() => OverlayEntry(
    builder: (BuildContext context) => UnconstrainedBox(
        child: CompositedTransformFollower(
          link: layerLink,
          child: Material(
            child: Container(
              alignment: Alignment.center,
                decoration: BoxDecoration(
                    color: Colors.blue,
                    borderRadius: BorderRadius.circular(5)),
                padding: const EdgeInsets.all(10),
                width: 50,
                child: const Text("toly",style: TextStyle(color: Colors.white),)),
          ),
        ),
      ),
  );
2.伴随者的相对位置

可能有人会问,可以控制 Overlay 停留的位置吗,实现偏移或者对齐。对于伴随者 可以设置 offset(偏移)targetAnchor(目标锚点)followerAnchor(伴随者锚点) 来控制停留的位置。

比如下面,通过设置对齐方式偏移可以实现:Overlay 置于对应组件的左侧。

代码语言:javascript
复制
followerAnchor: Alignment.centerLeft,
targetAnchor: Alignment.centerRight,
offset: Offset(5,0),

也可以放置在对应组件上方,其实有了对齐方式,你想放在哪都行。由于是相对位置,就省去了很多不必要的计算。

代码语言:javascript
复制
followerAnchor: Alignment.bottomCenter,
targetAnchor: Alignment.topCenter,
offset: Offset(0,-5),

结合我的 Wrapper 组件(wrapper: ^1.0.1),这样就可以很轻松实现点击弹出浮框的效果。

代码语言:javascript
复制
OverlayEntry _createOverlayEntry() => OverlayEntry(
    builder: (BuildContext context) => UnconstrainedBox(
      child: CompositedTransformFollower(
            link: layerLink,
            followerAnchor: Alignment.bottomCenter,
            targetAnchor: Alignment.topCenter,
            offset: Offset(0,-5),
            child: Material(
              child: Container(
                width: 150,
                child: Wrapper(
                  color: Colors.white,
                  spineType: SpineType.bottom,
                  elevation: 1,
                    offset:70,
                  shadowColor: Colors.grey.withAlpha(88),
                  child: Text("张风捷特烈 " * 5),
                ),
              ),
            ),
          ),
    ));

通过设置对其方位,很容易控制位置。如果通过传统的 Positioned 组件,想换个位置,还需要一通计算。用着两个哥们,就非常方便。

像这种图标点击的 Overlay 提示栏,使用着两个哥们就很好定位。

3.变换的一致性

如果说有杠精说:不用这俩,我就喜欢算,怎么啦。 但变换一致性的保存是之前很难办到的。这里我将滑动也是为一种变换,滑动本质上也是一种平移变换

  • 旋转:
代码语言:javascript
复制
Transform(
  transform: Matrix4.rotationZ(-15/180*pi),
  alignment: Alignment.center,
  child: TipBox(),
),
  • 缩放:
代码语言:javascript
复制
Transform(
  transform: Matrix4.diagonal3Values(0.5,0.5,1),
  alignment: Alignment.center,
  child: TipBox(),
),
  • 斜切:
代码语言:javascript
复制
Transform(
  transform: Matrix4.skewX(15/180*pi),
  alignment: Alignment.center,
  child: TipBox(),
),
  • 平移:
代码语言:javascript
复制
Transform(
  transform: Matrix4.translationValues(150,0,0),
  alignment: Alignment.center,
  child: TipBox(),
),

很明显,CompositedTransformFollower 会伴随 CompositedTransformTarget 进行变换,这样无论在滑动、缩放、旋转等操作中,两者都会保持相对的绑定关系,无需计算,皆大欢喜。

三、CompositedTransformFollower 只能用于 Overlay 吗

先给个答案,并非。如下,在 Stack 中,两个普通的组件也可以保持绑定关系。但话说回来,这样做并没有什么意义。普通组件间的对其布局很完善,FlexWrap 都可以,并不需要这两个哥们插一脚。而 Overlay 作为一个 孤魂野鬼,需要有个绳把它拴着。

代码语言:javascript
复制
class TipBox extends StatelessWidget{
  final LayerLink layerLink = LayerLink();

  @override
  Widget build(BuildContext context) {
    return  Stack(
        children: [
          CompositedTransformTarget(
            link: layerLink,
            child: Image.asset(
              "assets/images/icon_head.webp",
              width: 50,
              height: 50,
            ),
          ),

          CompositedTransformFollower(
            link: layerLink,
            followerAnchor: Alignment.topLeft,
            targetAnchor: Alignment.topRight,
            offset: Offset(5,0),
            child: Material(
              child: Container(
                width: 150,
                child: Wrapper(
                  color: Color(0xff95EC69),
                  spineType: SpineType.left,
                  elevation: 1,
                  offset:20,
                  shadowColor: Colors.grey.withAlpha(88),
                  child: Text("张风捷特烈 " * 5),
                ),
              ),
            ),
          ),
        ],
    );
  }
}

那本文就到这里,谢谢观看~

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021/04/02 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、缘起
  • 二、自己试验一下
  • 三、CompositedTransformFollower 只能用于 Overlay 吗
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档