[搬運]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

相關文章
相關標籤/搜索