原文在這裏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。如今該你本身動手了。