Flutter仿美团点餐的UI效果
前两天公司项目紧,要我做一个左边列表选择后,展示右边的标签,然后右边的标签可以多选,捋了下逻辑,觉得很简单,于是手撸了代码,代码如下
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class TestMenuPage extends StatefulWidget {
@override
_TestMenuPageState createState() => _TestMenuPageState();
}
class _TestMenuPageState extends State
List
int _leftIndex = 0;
Map
double height;
@override
initState() {
super.initState();
leftList = List.generate(20, (index) => LeftData('$index组', index + 1)).toList();
}
@override
Widget build(BuildContext context) {
int length = 0;
totalItem.forEach((key, value) {
length += value.length;
});
height = MediaQuery.of(context).size.height;
return Scaffold(
appBar: AppBar(
title: Text('测试菜单'),
actions: [
Container(
width: 80,
height: 50,
child: Center(
child: GestureDetector(
onTap: () {
print('所有选中的项目: ${totalItem.toString()}');
},
child: Text(
'完成',
style: TextStyle(
fontSize: 16,
color: Colors.white,
),
),
),
),
)
],
),
body: Column(
// mainAxisSize: MainAxisSize.min,
children: [
if (length != 0)
Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
height: 36,
width: double.infinity,
padding: EdgeInsets.symmetric(horizontal: 16),
alignment: Alignment.centerLeft,
child: Text.rich(
TextSpan(
children: [
TextSpan(
text: '已选择',
style: TextStyle(
fontSize: 16,
color: Color(
0xff282828,
),
),
),
WidgetSpan(
child: Container(
width: 8,
),
),
TextSpan(
text: '擅长治疗疾病最多选择20个',
style: TextStyle(
fontSize: 14,
color: Color(
0xff989898,
),
),
)
],
),
),
),
Container(
width: double.infinity,
padding: EdgeInsets.only(
left: 16,
right: 16,
top: 16,
bottom: 16,
),
// height: 173,
constraints: BoxConstraints(
// minHeight: 50,
maxHeight: 173,
),
child: Scrollbar(
thickness: 2,
child: SingleChildScrollView(
physics: BouncingScrollPhysics(),
child: Wrap(
runSpacing: 13,
spacing: 13,
children: _grow(),
),
),
),
),
],
),
Expanded(
flex: 2,
child: Container(
height: double.infinity,
decoration: BoxDecoration(
border: Border(
top: BorderSide(
color: Color(0xfff0f0f0),
width: 1,
),
),
),
child: Row(
children: [
Container(
width: 100,
height: double.infinity,
child: Scrollbar(
thickness: 2,
child: ListView.builder(
physics: BouncingScrollPhysics(),
shrinkWrap: true,
itemBuilder: (c, index) {
LeftData l = leftList[index];
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
setState(() {
_leftIndex = index;
});
print('_leftIndex: $_leftIndex');
},
child: Container(
height: 44,
width: double.infinity,
color: _leftIndex == index ? Colors.white : Color(0xfff2f2f2),
child: _leftIndex == index
? Stack(
// mainAxisAlignment: MainAxisAlignment.center,
children: [
Positioned(
child: Center(
child: Container(
decoration: BoxDecoration(
border: Border(
left: BorderSide(
color: Color(0xffc33b2a),
width: 2,
),
),
),
height: 15,
width: 2,
margin: EdgeInsets.only(right: 8),
),
),
left: 20,
top: 12,
bottom: 12,
// width: 2,
// height: 15,
),
Positioned(
child: Text(
l.name,
style: TextStyle(
fontSize: 16,
color: Color(0xff282828),
),
),
left: 32,
top: 12,
bottom: 12,
),
],
)
: Stack(
children: [
Positioned(
child: Text(
l.name,
style: TextStyle(
fontSize: 16,
color: Color(0xff282828),
),
),
left: 32,
top: 12,
bottom: 12,
)
],
),
),
);
},
itemCount: leftList.length,
itemExtent: 44,
),
),
),
Expanded(
child: Container(
color: Colors.white,
child: Scrollbar(
thickness: 2,
child: SingleChildScrollView(
physics: BouncingScrollPhysics(),
child: Container(
padding: EdgeInsets.all(10),
child: Wrap(
spacing: 10,
runSpacing: 10,
children: List.generate(
50,
(index) => Row(
mainAxisSize: MainAxisSize.min,
children: [
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
setState(() {
if (length < 20) {
if (totalItem[_leftIndex] == null)
totalItem[_leftIndex] = [];
List
if (!list.contains(index))
list.add(index);
else
list.remove(index);
print('totalItem: ${totalItem.toString()}');
} else {
print('最多只能选择20个');
}
});
_grow();
},
child: totalItem[_leftIndex] != null &&
totalItem[_leftIndex].contains(index)
? Container(
height: 32,
padding: EdgeInsets.symmetric(horizontal: 10),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(
Radius.circular(2),
),
border: Border.all(
color: Color(0xffc33b2a),
width: 1,
),
),
child: Center(
child: Text(
'$_leftIndex组$index项',
style: TextStyle(
color: Color(0xffc33b2a),
fontSize: 16,
),
),
),
)
: Container(
height: 32,
padding: EdgeInsets.symmetric(horizontal: 10),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(
Radius.circular(2),
),
border: Border.all(
color: Color(0xffd0d0d0),
width: 1,
),
),
child: Center(
child: Text(
'$_leftIndex组$index项',
style: TextStyle(
color: Color(0xff282828),
fontSize: 16,
),
),
),
),
),
],
),
),
),
),
),
),
),
)
],
),
),
),
],
),
);
}
List
List
totalItem.forEach((key, value) {
list.addAll(value
.map(
(e) => GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
print('delete: $e');
setState(() {
totalItem[key].remove(e);
});
},
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
height: 32,
padding: EdgeInsets.symmetric(horizontal: 10),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(
Radius.circular(2),
),
border: Border.all(
color: Color(0xffc33b2a),
width: 1,
),
),
child: Center(
child: Row(
children: [
Text(
'$key组$e项',
style: TextStyle(
color: Color(0xffc33b2a),
fontSize: 16,
),
),
Text(
'x',
style: TextStyle(
color: Color(0xffc33b2a),
fontSize: 16,
),
),
],
),
),
),
],
),
),
)
.toList());
});
return list;
}
}
class LeftData {
String name;
int id;
LeftData(this.name, this.id);
}
效果见下图:
不知道怎么弄gif图,就看截图吧,哈哈
逻辑是:第一次进入默认选到第一组,展示第一组的所有标签,然后根据选择的哪一组,从右边标签里筛选出想要的,然后上面的区域展示勾选上的,点击里面的x可以去掉该标签,点击下面的标签列表项也可以取消,上面的区域自适应高度。
写的比较简单,写完这个之后,就把它拿到公司项目里,对接接口了,如果大家用得着,可以拿过去,把接口对上就ok了,