Flutter自定義實(shí)現(xiàn)彈出層的示例代碼
背景
最近要使用Flutter實(shí)現(xiàn)一個(gè)下拉菜單,需求就是,在當(dāng)前組件下點(diǎn)擊,其下方彈出一個(gè)菜單選項(xiàng),如下圖所示:

實(shí)現(xiàn)起來(lái),貌似沒(méi)什么障礙,在Flutter中本身就提供了彈出層PopupMenuButton組件和showMenu方法,于是開(kāi)搞,代碼如下:
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運(yùn)行看結(jié)果:

showMenu位置傳的是左上角,這個(gè)就不貼圖了。
看到效果后,我詫異了,這也不符合我的需求啊,直接把選項(xiàng)給我蓋住了,這還得了,況且位置也不對(duì)啊,怎么搞?還好,無(wú)論使用PopupMenuButton還是showMenu,都給我們提供了位置。
PopupMenuButton設(shè)置位置:
offset: Offset(dx, dy)
showMenu設(shè)置位置:
position: const RelativeRect.fromLTRB(left, top, right, bottom)
使用位置后,我們?cè)倏葱Ч?/p>
dx設(shè)置為0,dy設(shè)置為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,
),
)效果如下圖:

這樣看起來(lái)確實(shí)好多了,但是我的疑問(wèn)就來(lái)了,如果我想實(shí)現(xiàn)在左邊展示呢?在上邊、右邊,甚至左上右上,左下右下呢?通過(guò)坐標(biāo)計(jì)算,確實(shí)能實(shí)現(xiàn),但是計(jì)算起來(lái)麻煩,也不精確,很難作為上上策,再者,這種彈窗方式樣式,在實(shí)際開(kāi)發(fā)中也很難滿(mǎn)足我們的需求。
既然原生的組件無(wú)法滿(mǎn)足我們的需求,怎么搞?只有自定義一個(gè)組件了。
今天的內(nèi)容大致如下:
1、自定義彈出層效果一覽
2、彈出層邏輯實(shí)現(xiàn)
3、使用注意事項(xiàng)
4、源碼
一、自定義彈出層效果一覽
目前自定義的組件,可以在目標(biāo)組件,左、上、右、下,左上、右上,左下、右下八個(gè)方向進(jìn)行精確的彈出,當(dāng)然了,除此之外,也可以動(dòng)態(tài)的展示到自己想要的位置,并且彈出層效果可以自定義,效果是我彈出了一個(gè)黑色矩形,你可以彈出一個(gè)列表,一個(gè)圖片等等。

二、彈出層邏輯實(shí)現(xiàn)
1、懸浮在其他頂部小部件之上
為了更好的展示彈出效果,和不影響UI層的相關(guān)邏輯,針對(duì)彈出層,我們可以懸浮在內(nèi)容層之上,做透明處理即可,這里使用到了Overlay對(duì)象,它是一個(gè)類(lèi)似懸浮小彈窗,如Toast,安卓的PopupWindow效果。
相關(guān)代碼如下,創(chuàng)建OverlayEntry,并插入到Overlay中,這樣就可以把OverlayEntry中構(gòu)建的小部件疊加懸浮在其他頂部小部件之上。
OverlayState overlayState = Overlay.of(key.currentContext!); OverlayEntry _overlayEntry = OverlayEntry(); overlayState.insert(_overlayEntry!);
2、獲取彈出目標(biāo)組件的左上右下
所謂目標(biāo)組件,就是,你想要在哪個(gè)組件(左上右下)進(jìn)行彈出,確定了目標(biāo)組件之后,為了使彈出層,精確的展示在目標(biāo)組件的方位,需要拿到目標(biāo)組件的位置,也就是左上右下的位置,這里使用到了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)建記錄位置對(duì)象,用來(lái)標(biāo)記左上右下。
///組件對(duì)象,標(biāo)記左上右下
class WidgetSize {
double left;
double top;
double right;
double bottom;
WidgetSize(this.left, this.top, this.right, this.bottom);
}3、設(shè)置彈出層的位置
彈出層位置,這里利用到了Positioned組件,控制其left和top位置,基本上和PopupMenuButton類(lèi)似,無(wú)非就是自己實(shí)現(xiàn)了位置的測(cè)量而已。
首先根據(jù)傳遞的屬性WindowDirection,確定要設(shè)置的方位。
具體各個(gè)方位計(jì)算如下:
目標(biāo)組件下邊:
top坐標(biāo):目標(biāo)組件的底部坐標(biāo)+邊距
left坐標(biāo):目標(biāo)組件的右部坐標(biāo)-彈出層的寬度/2-目標(biāo)組件寬度/2
目標(biāo)組件左邊:
top坐標(biāo):目標(biāo)組件的底部坐標(biāo)-彈出層的高度/2-目標(biāo)組件的高度/2
left坐標(biāo):目標(biāo)組件的左邊坐標(biāo)-彈出層的寬度-邊距
目標(biāo)組件上邊:
top坐標(biāo):目標(biāo)組件的上邊坐標(biāo)-彈出層的高度-邊距
left坐標(biāo):目標(biāo)組件的右部坐標(biāo)-彈出層的寬度/2-目標(biāo)組件寬度/2
目標(biāo)組件右邊:
top坐標(biāo):目標(biāo)組件的底部坐標(biāo)-彈出層的高度/2-目標(biāo)組件的高度/2
left坐標(biāo):目標(biāo)組件的右邊坐標(biāo)+邊距
目標(biāo)組件左上:
top坐標(biāo):目標(biāo)組件的底部坐標(biāo)-彈出層的高度-目標(biāo)組件的高度-邊距
left坐標(biāo):目標(biāo)組件的左邊坐標(biāo)-彈出層的寬度-邊距
目標(biāo)組件右上:
top坐標(biāo):目標(biāo)組件的底部坐標(biāo)-彈出層的高度-目標(biāo)組件的高度-邊距
left坐標(biāo):目標(biāo)組件的左邊坐標(biāo)+邊距
目標(biāo)組件左下:
top坐標(biāo):目標(biāo)組件的底部坐標(biāo)+邊距
left坐標(biāo):目標(biāo)組件的左邊坐標(biāo)-彈出層的寬度-邊距
目標(biāo)組件右下:
top坐標(biāo):目標(biāo)組件+邊距
left坐標(biāo):目標(biāo)組件右邊的坐標(biāo)+邊距
var size = getWidgetSize(key); //獲取在目標(biāo)組件的位置
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: //取消 自己測(cè)量位置
widgetTop = top;
widgetLeft = left;
break;
}三、使用注意事項(xiàng)
1、為了能夠精確的設(shè)置彈出層的位置,其彈出層的寬度和高度是必須要傳遞的,也就是childWidth和childHeight屬性。
2、如果想自己設(shè)置位置,可以不傳childWidth和childHeight,設(shè)置direction為WindowDirection.none,并且left和top坐標(biāo)需要傳遞。
3、margin屬性設(shè)置彈出層距離目標(biāo)組件的距離。
四、源碼
源碼地址
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ù)介紹
| 屬性 | 類(lèi)型 | 概述 |
|---|---|---|
| key | GlobalKey | 目標(biāo)組件的key |
| child | Widget | 彈出層 |
| childWidth | double | 彈出層的寬 |
| childHeight | double | 彈出層的高 |
| direction | WindowDirection | 位置:left//左top//上right//右bottom//下topLeft, //左上角topRight, //右上角bottomLeft, //左下bottomRight, //右下none//取消位置,自己定義 |
| left | double | 相對(duì)于屏幕的左側(cè)坐標(biāo) |
| top | double | 相對(duì)于屏幕的頂部坐標(biāo) |
| margin | double | 彈出層距離目標(biāo)組件的距離 |
到此這篇關(guān)于Flutter自定義實(shí)現(xiàn)彈出層的示例代碼的文章就介紹到這了,更多相關(guān)Flutter彈出層內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android之自定義實(shí)現(xiàn)BaseAdapter(通用適配器三)
這篇文章主要為大家詳細(xì)介紹了Android之自定義實(shí)現(xiàn)BaseAdapter通用適配器第三篇,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-12-12
Android基于Sensor感應(yīng)器獲取重力感應(yīng)加速度的方法
這篇文章主要介紹了Android基于Sensor感應(yīng)器獲取重力感應(yīng)加速度的方法,涉及Android使用Sensor類(lèi)實(shí)現(xiàn)感應(yīng)重力變化的功能,需要的朋友可以參考下2015-12-12
Android實(shí)現(xiàn)3D翻轉(zhuǎn)動(dòng)畫(huà)效果
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)3D翻轉(zhuǎn)動(dòng)畫(huà)效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12
Android 改變圖標(biāo)原有顏色和搜索框的實(shí)例代碼
讓Android也能有iOS那么方便的圖片色調(diào)轉(zhuǎn)換,就像同一個(gè)圖標(biāo),但是有多個(gè)地方使用,并且顏色不一樣,就可以用這個(gè)方法了。 本文實(shí)現(xiàn)TextView圖片和文字居中,鍵盤(pán)搜索功能,具體實(shí)現(xiàn)代碼大家跟隨腳本之家小編看看吧2017-09-09
Android高亮引導(dǎo)控件的實(shí)現(xiàn)代碼
這篇文章主要介紹了Android高亮引導(dǎo)控件的實(shí)現(xiàn)代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-07-07
Android點(diǎn)擊Button實(shí)現(xiàn)功能的幾種方法總結(jié)
當(dāng)Button有多個(gè)或者Button的使用次數(shù)很多時(shí),我們需要采用綁定監(jiān)聽(tīng)器的做法,其實(shí),綁定監(jiān)聽(tīng)器也有幾種方法,不過(guò),我在這里就不一一列舉了,畢竟那些方法在實(shí)際的應(yīng)用中也不常見(jiàn)2013-10-10
Android中TabLayout結(jié)合ViewPager實(shí)現(xiàn)頁(yè)面切換
這篇文章主要為大家詳細(xì)介紹了Android中TabLayout結(jié)合ViewPager實(shí)現(xiàn)頁(yè)面切換效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-12-12
Android Studio導(dǎo)入jar包過(guò)程詳解
這篇文章主要介紹了Android Studio導(dǎo)入jar包過(guò)程詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-11-11
flutter 中監(jiān)聽(tīng)滑動(dòng)事件
這篇文章主要介紹了flutter 中監(jiān)聽(tīng)滑動(dòng)事件,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11

