最近在做Flutter开发,碰到产品提的新的需求,类似于京东的地址选择器,但是baidu了一堆,都只是定死层级的实现效果。不符合产品需求啊。自己码一个,记录一下。(可以根据需要,封装成通用工具)
写的不好,请大佬轻喷。
效果图
需要注意,这里使用的是
TickerProviderStateMixin
而不是
SingleTickerProviderStateMixin
上代码,注释很清楚。
class EventAreaDialog extends StatefulWidget {
// 这个是我们网络拉取的数据,根据实际你所需要的去变。应该是个树形结构的model
SpaceData spaceData;
// 选中后的callback
EventAreaDialogCallBack eventAreaDialogCallBack;
EventAreaDialog({this.spaceData, this.eventAreaDialogCallBack});
@override
_EventAreaDialogState createState() => _EventAreaDialogState();
}
class _EventAreaDialogState extends State
with TickerProviderStateMixin {
//根据筛选出的数据
Map> datas = new Map();
//选中的model,根据业务需求获取所需要的信息
Map selectDatas = new Map();
//选中的名称
Map selectNameDatas = new Map();
//初始化的tab名称
List _tabs = ["请选择"];
//tab的控制器
TabController _tabController;
//当前所选中的tab位置
int currentTabPos = 0;
@override
void initState() {
super.initState();
//初始化第一层级所需要显示的内容
List list = [];
for (int i = 0; i < widget.spaceData.data.length; i++) {
Data data = widget.spaceData.data[i];
list.add(new Children(data.id, data.children, data.name));
}
//datas第一层级初始化赋值
datas[currentTabPos] = list;
_tabController = new TabController(length: _tabs.length, vsync: this);
}
@override
void dispose() {
super.dispose();
_tabController.dispose();
}
@override
Widget build(BuildContext context) {
return SafeArea(
child: AnimatedPadding(
padding: MediaQuery.of(context).viewInsets, //边距(必要)
duration: const Duration(milliseconds: 100), //时常 (必要)
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20),
),
color: Colors.white,
),
height: 350,
child: Flex(
direction: Axis.vertical,
children: [
Container(
padding: new EdgeInsets.only(left: 10, right: 10),
height: 40,
child: Scaffold(
backgroundColor: Colors.transparent,
appBar: TabBar(
controller: _tabController,
unselectedLabelColor: ColorUtils.TEXT_GRAY,
labelColor: ColorUtils.TEXT_BLUE,
indicatorColor: ColorUtils.TEXT_BLUE,
labelStyle: TextStyle(fontSize: 16),
isScrollable: true,
indicatorWeight: 3,
indicatorSize: TabBarIndicatorSize.label,
indicatorPadding: new EdgeInsets.fromLTRB(8, 0, 8, 0),
onTap: (index) {
if (!mounted) {
return;
}
setState(() {
//获取当前tab有多少
int size = _tabs.length;
//记录当前位置
currentTabPos = index;
//循环处理,将当前位置之后的数据去除
for (int i = size; i > currentTabPos + 1; i--) {
_tabs.removeAt(i - 1);
selectDatas.remove(i - 1);
selectNameDatas.remove(i - 1);
}
//这一步是重新创建tab
_tabController = new TabController(
length: _tabs.length, vsync: this);
//将当前tab移动到选中的位置上
_tabController.animateTo(currentTabPos);
});
},
tabs: _tabs.map((e) => Tab(text: e)).toList(),
),
),
),
Expanded(
flex: 1,
child: TabBarView(
children: _buildPages(),
controller: _tabController,
physics: NeverScrollableScrollPhysics(),
)),
],
),
),
),
);
}
/**
*创建tab界面
*/
List _buildPages() {
List pages = List();
for (int i = 0; i < _tabs.length; i++) {
Widget page = new ListView.builder(
padding: EdgeInsets.only(top: 15),
itemCount: datas[currentTabPos].length,
itemBuilder: (BuildContext context, int index) {
return _getListItem(index);
},
physics: new BouncingScrollPhysics(),
shrinkWrap: true,
);
pages.add(page);
}
return pages;
}
/**
*创建每个界面的item显示
*/
Widget _getListItem(int index) {
Children children = datas[currentTabPos][index];
return GestureDetector(
child: Container(
alignment: Alignment.centerLeft,
color: Colors.white,
height: 50,
padding: new EdgeInsets.only(left: 15),
child: Text(children.name, style: TextStyle(fontSize: 15, color: selectDatas[currentTabPos] == children.id ? ColorUtils.TEXT_BLUE : ColorUtils.TEXT_GRAY),),
),
onTap: () {
if (!mounted) {
return;
}
setState(() {
if (currentTabPos > 0 && index == 0) {
Navigator.pop(context);
return;
}
if (children.children != null && children.children.length > 0) {//这是选中的tab还有下级数据时
_tabs[currentTabPos] = children.name;
currentTabPos++;
//加上一条【暂不选择】的空数据
if(children.children[0].id != "-1") {
children.children.insert(0, new Children("-1", [], "暂不选择"));
}
//修改datas的数据源
datas[currentTabPos] = children.children;
selectDatas[currentTabPos - 1] = children.id;
selectNameDatas[currentTabPos - 1] = children.name;
_tabs.add("请选择");
_tabController =
new TabController(length: _tabs.length, vsync: this);
_tabController.animateTo(currentTabPos);
} else {//这是选中的tab没有下级数据时
_tabs[currentTabPos] = children.name;
selectDatas[currentTabPos] = children.id;
selectNameDatas[currentTabPos] = children.name;
Navigator.pop(context);
}
//将选中的区域id添加到集合,根据自己的业务去甄别
List areaIds = [];
for(int i = 0; i < selectDatas.length; i++) {
areaIds.add(selectDatas[i]);
}
//这个是处理选中的名称拼接,根据自己的业务去甄别
String names = "";
for(int i = 0; i < selectNameDatas.length; i++) {
if (i == selectNameDatas.length - 1) {
names += selectNameDatas[i];
} else {
names += "${selectNameDatas[i]}-";
}
}
//将选中的数据实时回调到需要的地方
widget.eventAreaDialogCallBack.onEventAreaDialogCallBack(names, areaIds);
});
},
);
}
}
最后使用方法
showModalBottomSheet(
isScrollControlled: true,
context: context,
builder: (ctx) => EventAreaDialog(
spaceData: spaceData,
eventAreaDialogCallBack: this,
),backgroundColor: ColorUtils.CLEAR);