Flutter自定義實現(xiàn)彈出層的示例代碼
背景
最近要使用Flutter實現(xiàn)一個下拉菜單,需求就是,在當前組件下點擊,其下方彈出一個菜單選項,如下圖所示:
實現(xiàn)起來,貌似沒什么障礙,在Flutter中本身就提供了彈出層PopupMenuButton組件和showMenu方法,于是開搞,代碼如下:
PopupMenuButton<String>( initialValue: '下拉菜單一', child: const Text("下拉菜單"), itemBuilder: (context) { return <PopupMenuEntry<String>>[ const PopupMenuItem<String>( value: '下拉菜單一', child: Text('下拉菜單一'), ), const PopupMenuItem<String>( value: '下拉菜單二', child: Text('下拉菜單二'), ), const PopupMenuItem<String>( value: '下拉菜單三', child: Text('下拉菜單三'), ) ]; }, )
直接使用showMenu也行,代碼如下:
showMenu( context: context, position: const RelativeRect.fromLTRB(0, 0, 0, 0), items: <PopupMenuEntry>[ const PopupMenuItem(value: "下拉菜單一",child: Text("下拉菜單一"),), const PopupMenuItem(value: "下拉菜單二",child: Text("下拉菜單二"),), const PopupMenuItem(value: "下拉菜單三",child: Text("下拉菜單三"),), ]);
PopupMenuButton運行看結果:
showMenu位置傳的是左上角,這個就不貼圖了。
看到效果后,我詫異了,這也不符合我的需求啊,直接把選項給我蓋住了,這還得了,況且位置也不對啊,怎么搞?還好,無論使用PopupMenuButton還是showMenu,都給我們提供了位置。
PopupMenuButton設置位置:
offset: Offset(dx, dy)
showMenu設置位置:
position: const RelativeRect.fromLTRB(left, top, right, bottom)
使用位置后,我們再看效果:
dx設置為0,dy設置為50:
PopupMenuButton<String>( initialValue: '下拉菜單一', offset: const Offset(0, 50), itemBuilder: (context) { return <PopupMenuEntry<String>>[ const PopupMenuItem<String>( value: '下拉菜單一', child: Text('下拉菜單一'), ), const PopupMenuItem<String>( value: '下拉菜單二', child: Text('下拉菜單二'), ), const PopupMenuItem<String>( value: '下拉菜單三', child: Text('下拉菜單三'), ) ]; }, child: Text( "下拉菜單", key: _key, ), )
效果如下圖:
這樣看起來確實好多了,但是我的疑問就來了,如果我想實現(xiàn)在左邊展示呢?在上邊、右邊,甚至左上右上,左下右下呢?通過坐標計算,確實能實現(xiàn),但是計算起來麻煩,也不精確,很難作為上上策,再者,這種彈窗方式樣式,在實際開發(fā)中也很難滿足我們的需求。
既然原生的組件無法滿足我們的需求,怎么搞?只有自定義一個組件了。
今天的內(nèi)容大致如下:
1、自定義彈出層效果一覽
2、彈出層邏輯實現(xiàn)
3、使用注意事項
4、源碼
一、自定義彈出層效果一覽
目前自定義的組件,可以在目標組件,左、上、右、下,左上、右上,左下、右下八個方向進行精確的彈出,當然了,除此之外,也可以動態(tài)的展示到自己想要的位置,并且彈出層效果可以自定義,效果是我彈出了一個黑色矩形,你可以彈出一個列表,一個圖片等等。
二、彈出層邏輯實現(xiàn)
1、懸浮在其他頂部小部件之上
為了更好的展示彈出效果,和不影響UI層的相關邏輯,針對彈出層,我們可以懸浮在內(nèi)容層之上,做透明處理即可,這里使用到了Overlay對象,它是一個類似懸浮小彈窗,如Toast,安卓的PopupWindow效果。
相關代碼如下,創(chuàng)建OverlayEntry,并插入到Overlay中,這樣就可以把OverlayEntry中構建的小部件疊加懸浮在其他頂部小部件之上。
OverlayState overlayState = Overlay.of(key.currentContext!); OverlayEntry _overlayEntry = OverlayEntry(); overlayState.insert(_overlayEntry!);
2、獲取彈出目標組件的左上右下
所謂目標組件,就是,你想要在哪個組件(左上右下)進行彈出,確定了目標組件之后,為了使彈出層,精確的展示在目標組件的方位,需要拿到目標組件的位置,也就是左上右下的位置,這里使用到了GlobalKey作為獲取方式,具體的位置信息獲取如下:
///獲取組件的位置 static WidgetSize getWidgetSize(GlobalKey key) { //獲取組件的位置,在左上右下 final RenderBox renderBox = (key.currentContext?.findRenderObject() as RenderBox); final left = renderBox.localToGlobal(Offset.zero).dx; //左邊 final top = renderBox.localToGlobal(Offset(renderBox.size.width, 0)).dy; final bottom = renderBox.localToGlobal(Offset(0, renderBox.size.height)).dy; final right = renderBox .localToGlobal(Offset(renderBox.size.width, renderBox.size.height)) .dx; return WidgetSize(left, top, right, bottom); }
創(chuàng)建記錄位置對象,用來標記左上右下。
///組件對象,標記左上右下 class WidgetSize { double left; double top; double right; double bottom; WidgetSize(this.left, this.top, this.right, this.bottom); }
3、設置彈出層的位置
彈出層位置,這里利用到了Positioned組件,控制其left和top位置,基本上和PopupMenuButton類似,無非就是自己實現(xiàn)了位置的測量而已。
首先根據(jù)傳遞的屬性WindowDirection,確定要設置的方位。
具體各個方位計算如下:
目標組件下邊:
top坐標:目標組件的底部坐標+邊距
left坐標:目標組件的右部坐標-彈出層的寬度/2-目標組件寬度/2
目標組件左邊:
top坐標:目標組件的底部坐標-彈出層的高度/2-目標組件的高度/2
left坐標:目標組件的左邊坐標-彈出層的寬度-邊距
目標組件上邊:
top坐標:目標組件的上邊坐標-彈出層的高度-邊距
left坐標:目標組件的右部坐標-彈出層的寬度/2-目標組件寬度/2
目標組件右邊:
top坐標:目標組件的底部坐標-彈出層的高度/2-目標組件的高度/2
left坐標:目標組件的右邊坐標+邊距
目標組件左上:
top坐標:目標組件的底部坐標-彈出層的高度-目標組件的高度-邊距
left坐標:目標組件的左邊坐標-彈出層的寬度-邊距
目標組件右上:
top坐標:目標組件的底部坐標-彈出層的高度-目標組件的高度-邊距
left坐標:目標組件的左邊坐標+邊距
目標組件左下:
top坐標:目標組件的底部坐標+邊距
left坐標:目標組件的左邊坐標-彈出層的寬度-邊距
目標組件右下:
top坐標:目標組件+邊距
left坐標:目標組件右邊的坐標+邊距
var size = getWidgetSize(key); //獲取在目標組件的位置 double widgetTop = 0.0; double widgetLeft = 0.0; switch (direction) { case WindowDirection.bottom: //下面 widgetTop = size.bottom + margin; widgetLeft = size.right - childWidth / 2 - ((size.right - size.left) / 2); break; case WindowDirection.left: //左面 widgetTop = size.bottom - childHeight / 2 - ((size.bottom - size.top) / 2); widgetLeft = size.left - childWidth - margin; break; case WindowDirection.top: //上面 widgetTop = size.top - childHeight - margin; widgetLeft = size.right - childWidth / 2 - ((size.right - size.left) / 2); break; case WindowDirection.right: //右面 widgetTop = size.bottom - childHeight / 2 - ((size.bottom - size.top) / 2); widgetLeft = size.right + margin; break; case WindowDirection.topLeft: //左上 widgetTop = size.bottom - childHeight - (size.bottom - size.top) - margin; widgetLeft = size.left - childWidth - margin; break; case WindowDirection.topRight: //右上 widgetTop = size.bottom - childHeight - (size.bottom - size.top) - margin; widgetLeft = size.right + margin; break; case WindowDirection.bottomLeft: //左下 widgetTop = size.bottom + margin; widgetLeft = size.left - childWidth - margin; break; case WindowDirection.bottomRight: //右下 widgetTop = size.bottom + margin; widgetLeft = size.right + margin; break; case WindowDirection.none: //取消 自己測量位置 widgetTop = top; widgetLeft = left; break; }
三、使用注意事項
1、為了能夠精確的設置彈出層的位置,其彈出層的寬度和高度是必須要傳遞的,也就是childWidth和childHeight屬性。
2、如果想自己設置位置,可以不傳childWidth和childHeight,設置direction為WindowDirection.none,并且left和top坐標需要傳遞。
3、margin屬性設置彈出層距離目標組件的距離。
四、源碼
源碼地址
github.com/AbnerMing888/flutter_widget/blob/master/lib/utils/popup_window.dart
使用方式
PopupWindow.create( _key, const BaseWidget( width: 100, height: 100, backgroundColor: Colors.black, ), direction: direction, margin: 10, childWidth: 100, childHeight: 100);
參數(shù)介紹
屬性 | 類型 | 概述 |
---|---|---|
key | GlobalKey | 目標組件的key |
child | Widget | 彈出層 |
childWidth | double | 彈出層的寬 |
childHeight | double | 彈出層的高 |
direction | WindowDirection | 位置:left//左top//上right//右bottom//下topLeft, //左上角topRight, //右上角bottomLeft, //左下bottomRight, //右下none//取消位置,自己定義 |
left | double | 相對于屏幕的左側坐標 |
top | double | 相對于屏幕的頂部坐標 |
margin | double | 彈出層距離目標組件的距離 |
到此這篇關于Flutter自定義實現(xiàn)彈出層的示例代碼的文章就介紹到這了,更多相關Flutter彈出層內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Android實現(xiàn)底部彈出PopupWindow背景逐漸變暗效果
這篇文章主要為大家詳細介紹了Android實現(xiàn)底部彈出PopupWindow背景逐漸變暗效果,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-10-10Android Parcelable與Serializable詳解及區(qū)別
這篇文章主要介紹了Android Parcelable與Serializable詳解及區(qū)別的相關資料,需要的朋友可以參考下2017-01-01Android中AsyncTask異步任務使用詳細實例(一)
AsyncTask是Android提供的輕量級的異步類,可以直接繼承AsyncTask,在類中實現(xiàn)異步操作,并提供接口反饋當前異步執(zhí)行的程度(可以通過接口實現(xiàn)UI進度更新),最后反饋執(zhí)行的結果給UI主線程,通過本文給大家介紹Android中AsyncTask異步任務使用詳細實例(一),需要的朋友參考下2016-02-02Github簡單易用的?Android?ViewModel?Retrofit框架
這篇文章主要介紹了Github簡單易用的Android?ViewModel?Retrofit框架,RequestViewMode有自動對LiveData進行緩存管理,每個retrofit api接口復用一個livedata的優(yōu)勢。下文具體詳情,感興趣的小伙伴可以參考一下2022-06-06Android 中RecycleView實現(xiàn)item的點擊事件
這篇文章主要介紹了Android 中RecycleView實現(xiàn)item的點擊事件的相關資料,需要的朋友可以參考下2017-03-03Android Studio開發(fā)之 JNI 篇的簡單示例
本篇文章主要介紹了Android Studio開發(fā)之 JNI 篇的簡單示例,它提供了若干的API實現(xiàn)了Java和其他語言的通信,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-10-10