[搬运]flutter如何在Widget上叠加其余overlay widget

原文在这里git

做者简介:Jose,刚大学毕业,现带领团队负责维护Flutter的Material库,
Material是一个帮助团队建设高质量用户体验的设计体系。github

假设你的ui里有一个widget,而且您但愿在该widget的顶部覆盖一个浮动widget。
可能该widget被旋转或应用了其余转换,如何将widget的位置和转换信息传递到浮动widget呢?app

你能够使用CompositedTransformTarget, CompositedTransformWidget、LayerLink、Overlay和OverlayEntry来完成上述操做。less

在Flutter中,overlay容许您将视觉元素插入到overlay的堆栈中,从而将它们显示在其余widget的顶部。使用OverlayEntry将widget插入到overlay中,同时能够使用Positioned和AnimatedPositioned来定位你的widget。当你须要把一个widget浮动在另外一个widget上面时,就能够使用这种方法,而且这些widget均可以复用。ide

在这一篇文章中,开发者想要对现有的TextField增长自动建议功能,咱们固然能够使用Stack来实现这个功能,但这种方法并不友好,具备很强的侵入性,还须要将整个屏幕设计为Stack。如本文所述,建议使用Overlay来实现这种效果,这种场景很是常见。函数

直接使用使用Overlay表现的很直观,但在Flutter中实现起来却有限困难:使用一个builder回调函数来建立OverlayEntry实例,并把它插入到Overlay的堆栈中,同时你还须要持有该OverlayEntry实例的引用,能够方便的对该实例进行更新、删除操做。OverlayEntry的position、transformation依赖的widget必须也要存在于overlay中,不然就会发生冲突。由于overlay的MediaQuery的context不一样于常规的contenxt。调用Overlay以前,在widget中使用padding或margin的时候就会发生相似问题。幸运的是,flutter已经为你处理了这些问题。(这一段还须要润色,要衔接上下文中心思想)post

若是你的overlay entry所依赖的“target”不在overlay堆栈中,那么咱们须要使用CompositedTransformTarget、CompositedTransformFollower和LayerLink将它们粘合在一块儿:ui

  • 用CompositedTransformFollower包装须要浮动的widgetthis

  • CompositedTransformFollower必须是CompositedTransformTarget的子孙接点设计

  • 用CompositedTransformTarget包装你要跟随依赖的“target”

  • 使用LayerLink将CompositedTransformTarget和CompositedTransformFollower粘在一块儿

下图Gif中,蓝色的Container不在overlay中,而绿色的在overlay中。蓝色的Container做为CompositedTransformTarget的child节点,绿色的做为CompostedTransformFollower的child节点,他们经过LayerLink实例链接到一块儿。注意绿色overlay widget是如何知道蓝色widget的bounds数据,由于蓝色widget并不在overlay中:

建议你使用DartPad亲自尝试下。
下面是实例代码(做者Hans Muller):

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(home: Slide()));
}

class Indicator extends StatelessWidget {
  Indicator({ Key key, this.link, this.offset }) : super(key: key);

  final LayerLink link;
  final Offset offset;

  @override
  Widget build(BuildContext context) {
    return CompositedTransformFollower(
      offset: offset,
      link: link,
      child: Container(
        color: Colors.green,
      ),
    );
  }
}

class Slide extends StatefulWidget {
  Slide({ Key key }) : super(key: key);

  @override
  _SlideState createState() => _SlideState();
}

class _SlideState extends State<Slide> {
  final double indicatorWidth = 24.0;
  final double indicatorHeight = 300.0;
  final double slideHeight = 200.0;
  final double slideWidth = 400.0;

  final LayerLink layerLink = LayerLink();
  OverlayEntry overlayEntry;
  Offset indicatorOffset;

  Offset getIndicatorOffset(Offset dragOffset) {
    final double x = (dragOffset.dx - (indicatorWidth / 2.0)).clamp(0.0, slideWidth - indicatorWidth);
    final double y = (slideHeight - indicatorHeight) / 2.0;
    return Offset(x, y);
  }

  void showIndicator(DragStartDetails details) {
    indicatorOffset = getIndicatorOffset(details.localPosition);
    overlayEntry = OverlayEntry(
      builder: (BuildContext context) {
        return Positioned(
          top: 0.0,
          left: 0.0,
          child: SizedBox(
            width: indicatorWidth,
            height: indicatorHeight,
            child: Indicator(
                offset: indicatorOffset,
                link: layerLink
            ),
          ),
        );
      },
    );
    Overlay.of(context).insert(overlayEntry);
  }

  void updateIndicator(DragUpdateDetails details) {
    indicatorOffset = getIndicatorOffset(details.localPosition);
    overlayEntry.markNeedsBuild();
  }

  void hideIndicator(DragEndDetails details) {
    overlayEntry.remove();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Overlay Indicator')),
      body: Center(
        child: CompositedTransformTarget(
          link: layerLink,
          child: Container(
            width: slideWidth,
            height: slideHeight,
            color: Colors.blue.withOpacity(0.2),
            child: GestureDetector(
              onPanStart: showIndicator,
              onPanUpdate: updateIndicator,
              onPanEnd: hideIndicator,
            ),
          ),
        ),
      ),
    );
  }
}

上面例子展现了如何将浮动widget和它依赖的“target”粘合到一块儿使用。接下来咱们要对CompositedTransformTarget作一些Transformation来展现这些widget的真正强大之处。你会注意到绿色的overlay widget也会自动被施加这些Transformation,这一切都得益于LayerLink。

下面的gif图中,蓝色的container依然是不在overlay,绿色在。这一次咱们对CompositedTransformTarget(即蓝色container)作了一个旋转的Transformation,如你所见,尽管CompositeTransformFollower位于overlay中,它依然感知获得“target”的位置和被施加的transformations。

建议你使用DartPad亲自尝试下。
下面是实例代码(做者Hans Muller):

import 'dart:math' as math;

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(home: Slide()));
}

class Indicator extends StatelessWidget {
  Indicator({ Key key, this.link, this.offset }) : super(key: key);

  final LayerLink link;
  final Offset offset;

  @override
  Widget build(BuildContext context) {
    return CompositedTransformFollower(
      offset: offset,
      link: link,
      child: Container(
        color: Colors.green,
      ),
    );
  }
}

class Slide extends StatefulWidget {
  Slide({ Key key }) : super(key: key);

  @override
  _SlideState createState() => _SlideState();
}

class _SlideState extends State<Slide> {
  final double indicatorWidth = 24.0;
  final double indicatorHeight = 300.0;
  final double slideHeight = 200.0;
  final double slideWidth = 400.0;

  final LayerLink layerLink = LayerLink();
  OverlayEntry overlayEntry;
  Offset indicatorOffset;

  Offset getIndicatorOffset(Offset dragOffset) {
    final double x = (dragOffset.dx - (indicatorWidth / 2.0)).clamp(0.0, slideWidth - indicatorWidth);
    final double y = (slideHeight - indicatorHeight) / 2.0;
    return Offset(x, y);
  }

  void showIndicator(DragStartDetails details) {
    indicatorOffset = getIndicatorOffset(details.localPosition);
    overlayEntry = OverlayEntry(
      builder: (BuildContext context) {
        return Positioned(
          top: 0.0,
          left: 0.0,
          child: SizedBox(
            width: indicatorWidth,
            height: indicatorHeight,
            child: Indicator(
                offset: indicatorOffset,
                link: layerLink
            ),
          ),
        );
      },
    );
    Overlay.of(context).insert(overlayEntry);
  }

  void updateIndicator(DragUpdateDetails details) {
    indicatorOffset = getIndicatorOffset(details.localPosition);
    overlayEntry.markNeedsBuild();
  }

  void hideIndicator(DragEndDetails details) {
    overlayEntry.remove();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Overlay Indicator')),
      body: Transform.rotate(
        angle: -math.pi / 12.0,
        child: Center(
          child: CompositedTransformTarget(
            link: layerLink,
            child: Container(
              width: slideWidth,
              height: slideHeight,
              color: Colors.blue.withOpacity(0.2),
              child: GestureDetector(
                onPanStart: showIndicator,
                onPanUpdate: updateIndicator,
                onPanEnd: hideIndicator,
              ),
            ),
          ),
        ),
      ),
    );
  }
}

总结

当你要实现一个widget浮动在另外一个widget之上时,使用overlay,而后链接这些widget,很是简单易用。本文中,你学会了如何使用LayerLink来粘合这些widget。如今该你本身动手了。

想了解更多关于Jose,能够访问他的GitHub、LinkedIn
YouTubeInstagram

相关文章
相关标签/搜索