赞
踩
1、Overlay 是什么
在官网上关于 Overlay 的介绍是:
简单翻译一下:
Overlay 这个 widget 是一个 基于 Stack 管理的 Widget,可以独立的使用。
Overlay 通过把独立的 widget 插入到 overlay 的 stack 里面来实现让这个 widget 显示到其他 widget 的上面。Overlay 是通过 OverlayEntry 来管理 widget 的显示层次的(不能直接使用 widget)。
可以直接创建一个 Overlay 对象来使用,但是更一般的,都是使用 WidgetsApp 或者 MaterialApp 里面的 Navigator 创建了 Overlay,Navigator 使用这个 overlay 来管理路由显示效果(我们可以通过这个 overlay 来实现全局弹窗)。
2、Overlay 使用步骤
- Overlay entry=new OverlayEntry(
- builder:(){
- //自己的 widget
- });
- 复制代码
- Overlay.of(context).insert(overlayEntry);
- 复制代码
- entry.remove();
- 复制代码
-
- //通过 Overlay 实现 Toast
- class Toast {
- static void show({@required BuildContext context, @required String message}) {
- //1、创建 overlayEntry
- OverlayEntry overlayEntry = new OverlayEntry(builder: (context) {
- return new Positioned(
- top: MediaQuery.of(context).size.height * 0.8,
- child: new Material(
- child: new Container(
- width: MediaQuery.of(context).size.width,
- alignment: Alignment.center,
- child: new Center(
- child: new Card(
- child: new Padding(
- padding: EdgeInsets.all(8),
- child: new Text(message),
- ),
- color: Colors.grey.withOpacity(0.6),
- ),
- ),
- ),
- ));
- });
- //插入到 Overlay中显示 OverlayEntry
- Overlay.of(context).insert(overlayEntry);
- //延时两秒,移除 OverlayEntry
- new Future.delayed(Duration(seconds: 2)).then((value) {
- overlayEntry.remove();
- }
- );
- }
- 复制代码
使用:
- RaisedButton(
- child: Text('SUBMIT'),
- onPressed: () {
- Toast.show(context: context, message: "信息已经提交!");
- },
- )
- 复制代码
假设我们有一个信息输入界面,如下:
我们希望用户在输入框输入的时候,可以在下方根据用户的输入,当输入框获取焦点时,来展示一个 list 来辅助用户输入(这个可以根据自己的需求来调整),如下:
当然为了实现这个功能,完全可以重写界面,通过 Stack + Position 来完成,但是这样可能需要重构代码,增加界面复杂度,而且可扩展性差,可以通过 Overlay 的方式来实现这个功能。
1、通过 Overlay 来显示悬浮窗
我们需要实现的是,当 TextFormField 获取焦点的时候,就弹出弹窗,当失去焦点的时候,就移除这个弹窗。定义这个弹窗控件如下:
- class CountriesField extends StatefulWidget {
- @override
- _CountriesFieldState createState() => _CountriesFieldState();
- }
-
- class _CountriesFieldState extends State<CountriesField> {
-
- final FocusNode _focusNode = FocusNode();
-
- OverlayEntry _overlayEntry;
-
- @override
- void initState() {
- _focusNode.addListener(() {
- if (_focusNode.hasFocus) {
-
- this._overlayEntry = this._createOverlayEntry();
- Overlay.of(context).insert(this._overlayEntry);
-
- } else {
- this._overlayEntry.remove();
- }
- });
- }
-
- OverlayEntry _createOverlayEntry() {
-
- RenderBox renderBox = context.findRenderObject();
- var size = renderBox.size;
- var offset = renderBox.localToGlobal(Offset.zero);
-
- return OverlayEntry(
- builder: (context) => Positioned(
- left: offset.dx,
- top: offset.dy + size.height + 5.0,
- width: size.width,
- child: Material(
- elevation: 4.0,
- child: ListView(
- padding: EdgeInsets.zero,
- shrinkWrap: true,
- children: <Widget>[
- ListTile(
- title: Text('Syria'),
- ),
- ListTile(
- title: Text('Lebanon'),
- )
- ],
- ),
- ),
- )
- );
- }
-
- @override
- Widget build(BuildContext context) {
- return TextFormField(
- focusNode: this._focusNode,
- decoration: InputDecoration(
- labelText: 'Country'
- ),
- );
- }
- }
- 复制代码
但是,当使用这个自定义控件时,会出现一个问题,就是当 ListView 足够长,我们滚动 ListView 时,这个弹窗的位置是固定不变的。
2、弹窗跟随滚动
我们需要做到是,我们的全局弹窗可以跟随 TextField 滚动,Flutter 提供了两个组件:CompositedTransformFollower 和 CompositedTransformTarget 。
我们将一个 follower 和 一个 target 链接在一起(为这两个 widget 指定相同的 LayerLink),这样当 target 滚动的时候,follower 就会跟随滚动。
在我们这个示例里,弹窗相当于 Follower,而 TextField 是 Target 。我们用 CompositedTransformFollower 和 CompositedTransformTarget 分别包裹弹窗和 TextField,代码如下:
- class CountriesField extends StatefulWidget {
- @override
- _CountriesFieldState createState() => _CountriesFieldState();
- }
- class _CountriesFieldState extends State<CountriesField> {
- final FocusNode _focusNode = FocusNode();
- OverlayEntry _overlayEntry;
- final LayerLink _layerLink = LayerLink();
- @override
- void initState() {
- _focusNode.addListener(() {
- if (_focusNode.hasFocus) {
- this._overlayEntry = this._createOverlayEntry();
- Overlay.of(context).insert(this._overlayEntry);
- } else {
- this._overlayEntry.remove();
- }
- });
- }
- OverlayEntry _createOverlayEntry() {
- RenderBox renderBox = context.findRenderObject();
- var size = renderBox.size;
- return OverlayEntry(
- builder: (context) => Positioned(
- width: size.width,
- child: CompositedTransformFollower(
- link: this._layerLink,
- showWhenUnlinked: false,
- offset: Offset(0.0, size.height + 5.0),
- child: Material(
- elevation: 4.0,
- child: ListView(
- padding: EdgeInsets.zero,
- shrinkWrap: true,
- children: <Widget>[
- ListTile(
- title: Text('Syria'),
- onTap: () {
- print('Syria Tapped');
- },
- ),
- ListTile(
- title: Text('Lebanon'),
- onTap: () {
- print('Lebanon Tapped');
- },
- )
- ],
- ),
- ),
- ),
- )
- );
- }
- @override
- Widget build(BuildContext context) {
- return CompositedTransformTarget(
- link: this._layerLink,
- child: TextFormField(
- focusNode: this._focusNode,
- decoration: InputDecoration(
- labelText: 'Country'
- ),
- ),
- );
- }
- }
- 复制代码
效果 :
参考: Using Overlay to display floating widgets
代码: github
欢迎关注「Flutter 编程开发」微信公众号 。
作者:Flutter编程开发
链接:https://juejin.cn/post/6844904115764658189
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。