上一篇文章https://www.jianshu.com/p/29019113e54e
借鉴几篇文章实现下拉框,后面发现维护的时候很痛苦,主要是不容易看懂,尤其是间距,显示的位置,经常会对不齐
,于是想重新写 一篇简单的,通俗易懂,
实现的需求是,头部完全自定义实现,下拉框随意的自定义部件,不需要考虑显示的位置和间距,只要提供部件就行
废话少说先看效果
下拉框.gif
最主要的就是CompositedTransformTarget配合CompositedTransformFollower的使用,他们是通过LayerLink来绑定
第一篇文章的作者是吧头部的每一个item对应一个LayerLink,我觉这做法不好
我的做法是,一个头部不管有几个item,都是一个LayerLink,CompositedTransformFollower起始位置左侧就是0(offset: Offset(0, tabHeight!)),高度自行调节,
当头部onChange,CompositedTransformFollower显示不同的部件即可
这逻辑非常简单吧,至于部件的布局样式,你完全自定义,
有个问题是当你下拉部件选择之后,需要同步给头部显示高亮及箭头上下翻转,这个可以借鉴第一篇的逻辑
最后,代码如下,代码很少,直接拷贝就可以用,当做一个页面打开就可以直接测试效果
import 'package:flutter/material.dart';
class DropMenuWidget extends StatefulWidget {
@override
_DropMenuWidgetState createState() => _DropMenuWidgetState();
}
class _DropMenuWidgetState extends State<DropMenuWidget> {
final LayerLink _layerLink = LayerLink();
final GlobalKey _appBarKey = GlobalKey();
final GlobalKey _tabKey = GlobalKey();
OverlayEntry? _overlayEntry;
@override
void initState() {}
@override
void dispose() {
_hideOverlay();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
key: _appBarKey,
title: const Text('下拉框demo'),
),
body: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
CompositedTransformTarget(
link: _layerLink,
child: Container(
key: _tabKey,
color: Colors.blue,
padding: EdgeInsets.symmetric(horizontal: 15, vertical: 8),
child: Row(
children: [
Expanded(
child: TextButton(
onPressed: () {
_showOverlay(context, '销量');
},
child: Text('销量'),
)),
Expanded(
child: TextButton(
onPressed: () {
_showOverlay(context, '品质');
},
child: Text('品质'),
)),
Expanded(
child: TextButton(
onPressed: () {
_showOverlay(context, '区域');
},
child: Text('区域'),
)),
],
),
),
),
],
),
);
}
void _showOverlay(BuildContext context, String content) {
_hideOverlay();
final overlay = Overlay.of(context);
final screenWidth = MediaQuery.of(context).size.width;
final screenHeight = MediaQuery.of(context).size.height;
final appBarHeight = _appBarKey.currentContext?.size!.height;
final statusBar = MediaQuery.of(context).padding.top;
final tabHeight = _tabKey.currentContext?.size!.height;
_overlayEntry = OverlayEntry(
builder: (context) => Stack(
children: [
// 使用Container作为Mask,mask是第一个部件,位于最底部,这个mask的margin高度,其实可以不用算,自己写一个合适的也可以,前提是OverlayEntry距离顶部高度是固定的
// 如果希望mask覆盖整个屏幕,可以不写margin
GestureDetector(
onTap: _hideOverlay,
child: Container(
margin:
EdgeInsets.only(top: appBarHeight! + statusBar + tabHeight!),
width: screenWidth,
height: screenHeight,
color: Colors.black54, // Semi-transparent mask
),
),
Positioned(
width: screenWidth,
height: 100, // 这个高度不写的话,就是内容的高度
child: CompositedTransformFollower(
link: _layerLink,
showWhenUnlinked: false,
offset: Offset(0, tabHeight!),
child: Material(
elevation: 4.0,
child: Container(
// 这里完全可以根据传入的index,来显示不同的部件,甚至在参数直接传入显示的部件也可以
color: Colors.red,
child: Column(
children: [
Center(
child: Text('$content',style: TextStyle(color: Colors.white,fontWeight:
FontWeight.bold),),
),
TextButton(
onPressed: () {
_hideOverlay();
},
child: Text('点我关闭'))
],
),
),
),
),
)
],
),
);
overlay.insert(_overlayEntry!);
}
void _hideOverlay() {
_overlayEntry?.remove();
_overlayEntry = null;
}
}