【Flutter 专题】97 仿网易新闻标签选择器
和尚前段时间刚学习了 Draggable + DragTarget 实现基本的拖拽效果,现在尝试以此为基础仿照网易新闻客户端实现一个简单的标签选择器;
预期功能
- 标签选项器中单个标签可以拖拽换位;
- 【编辑】状态下可以删除单个标签;
- 可随时添加新的标签位;
- 拖拽过程中添加动画效果(后期优化);
案例尝试
和尚简单罗列了一下预期功能,其中拖拽动画和尚还未尝试,先把其他的功能实现;
1. 单个拖拽标签
标签需要拖拽,和尚将 DragTarget 作为 Draggable 的子 Widget 嵌套应用;主要实现三个回调,分别为是否接收 Draggable 状态的 onWillAccept 回调,接收 Draggable 的 onAccept 回调和取消接收状态的 onLeave 回调;
_itemDragableWid(list, index) {
return Draggable(
data: index,
childWhenDragging: Container(),
dragAnchor: DragAnchor.child,
feedback: _itemClipWid(list, index, true),
child: DragTarget(onWillAccept: (data) {
print("Draggable onWillAccept data --> $data");
return data != null;
}, onAccept: (data) {
print("Draggable onAccept data --> $data");
setState(() {
final temp = list[data];
list.remove(temp);
list.insert(index, temp);
});
}, onLeave: (data) {
print("Draggable onLeave data --> $data");
}, builder: (context, candidateData, rejectedData) {
return _itemClipWid(list, index, false);
}));
}
和尚绘制了一个圆角标签 item,其中【删除/添加 icon】根据列表类型判断;和尚还设置了在拖拽过程中与未拖拽标签颜色大小的区分;
和尚在测试过程中拖动时文字会变大且有下划线,主要是主题设置问题,和尚在外层嵌套一个 Material Widget 来避免文字样式变化;
但与此同时会带来新的问题,和尚设置的圆角 Container 的四个角在拖动过程中有白色背景,其原因是设置 Material 嵌套后,默认背景色为白色,于是和尚设置 Material 背景色为透明,设置 Container BoxDecoration 背景色为白色即可;
_itemClipWid(list, index, isFeedBack) {
return Material(
color: Colors.transparent,
child: Container(
width: (MediaQuery.of(context).size.width - 40) / 4,
child: Padding(
padding: EdgeInsets.symmetric(vertical: isFeedBack ? 8.0 : 4.0),
child: Center(child:Row(mainAxisSize: MainAxisSize.min, children: <Widget>[
(isEdit && list == mList) ? Icon(Icons.clear, size: 12.0, color: Colors.grey) : Container(),
(list == recList) ? Icon(Icons.add, size: 12.0, color: Colors.grey) : Container(),
Text(list[index], style: TextStyle(color: isFeedBack ? Colors.red : Colors.black))
]))),
decoration: BoxDecoration(
border: Border.all(
color: isFeedBack ? Colors.red : Colors.black54,
width: 0.5),
color: Colors.white70,
borderRadius: BorderRadius.all(Radius.circular(50.0)))));
}
2. 网格列表
网格列表就是最常用的 GridView;和尚设置两个 GridView 分别存储【我的栏目】和【推荐栏目】;其中标签 item 的点击事件和拖拽事件并不冲突;
和尚测试过程中删除或加入单个标签时会错位,其原因是和尚 list.remove(list[index]); recList.add(list[index]); 这样 list 在第一次 remove 时就已经改变了数量,再次 add 时当前 index 对应的标签已经更换;于是和尚设置一个临时变量 temp 来避免此类情况;
_comGridView(list) {
return Padding(
padding: EdgeInsets.only(left: 14.0, right: 14.0),
child: GridView.builder(
physics: ScrollPhysics(),
primary: false, shrinkWrap: true,
scrollDirection: Axis.vertical,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4, mainAxisSpacing: 8.0, crossAxisSpacing: 8.0, childAspectRatio: 2.6),
itemCount: list.length,
itemBuilder: (context, index) {
return GestureDetector(
child: _itemDragableWid(list, index),
onTap: () {
Toast.show(list[index], context, duration: Toast.LENGTH_SHORT, gravity: Toast.BOTTOM);
if (list == recList) {
final temp = list[index];
recList.remove(temp);
list.remove(temp);
mList.add(temp);
setState(() {});
} else {
final temp = list[index];
mList.remove(temp);
list.remove(temp);
recList.add(temp);
setState(() {});
}
});
}));
}
3. 编辑状态
和尚添加了【编辑/完成】两种业务逻辑,在【编辑】状态可以【删除】标签;
和尚预期的想法是只允许【我的栏目】中进行拖拽更新,不允许【推荐栏目】内和与【我的栏目】互相拖拽;因为和尚是采用 Draggable + DragTarget 嵌套,所以在拖拽过程中会执行两次 onWillAccept 判断,此时不能确定是由哪个标签 item 起始的,导致列表刷新异常;于是和尚设置了一个临时数组,分别存放起始和终止 onWillAccept 回调时是哪个 DataList,只有在【我的栏目】内才允许 onAccept 接收回调;
_titleRightWid(isRec) {
if (isRec)
return Container();
else
return GestureDetector(
child: Container(
child: Padding(
padding: EdgeInsets.symmetric(vertical: 3.0, horizontal: 14.0),
child: Text(!isEdit ? '编辑' : '完成', textAlign: TextAlign.right, style: TextStyle(color: Colors.red))),
decoration: BoxDecoration(
border: Border.all(color: Colors.red, width: 0.5),
borderRadius: BorderRadius.all(Radius.circular(50.0)))),
onTap: () {
setState(() => isEdit = !isEdit);
Toast.show(!isEdit ? '编辑' : '完成', context, duration: Toast.LENGTH_SHORT, gravity: Toast.BOTTOM);
});
}
_tempState(list) {
if (tempList != null) {
if (tempList.length == 2) {
tempList = [];
tempList.add((list == mList) ? 0 : 1);
} else if (tempList.length == 1) {
tempList.add((list == mList) ? 0 : 1);
} else {
tempList.add((list == mList) ? 0 : 1);
}
} else {
tempList = [];
tempList.add((list == mList) ? 0 : 1);
}
}
- 【LeetCode 136】 关关的刷题日记32 Single Number
- 1599: [Usaco2008 Oct]笨重的石子
- 【LeetCode 136】 关关的刷题日记33 Intersection of Two
- 1218: [HNOI2003]激光炸弹
- Java多线程高并发学习笔记(一)——Thread&Runnable
- 1257: [CQOI2007]余数之和sum
- 【LeetCode 136】 关关的刷题日记34 Intersection of Two Arrays II
- 1724: [Usaco2006 Nov]Fence Repair 切割木板
- 【LeetCode 202】 关关的刷题日记35 – Leetcode 202. Happy Number
- JAVA多线程高并发学习笔记(三)——Callable、Future和FutureTask
- 算法模板——Trie树
- JavaScript Alert 函数执行顺序问题
- 算法模板——计算几何1(图形面积)
- 算法模板——线段树6(二维线段树:区域加法+区域求和)(求助phile)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Linux MySQL忘记root密码解决方案
- 如何使用iostat查看linux硬盘IO性能
- linux搭建NFS文件共享服务器的步骤详解
- Windows和Linux实现远程桌面连接
- Ubuntu19.10开启ssh服务(详细过程)
- Ubuntu 18.04更换apt-get源的方法
- 浅谈Linux环境变量文件介绍
- linux创建线程之pthread_create的具体使用
- Ubuntu20.04安装搜狗输入法的详细步骤
- linux系统安装msf的过程详解
- Linux删除系统自带版本Python过程详解
- Linux时间子系统之时间的表示示例详解
- 如何在Linux中修改tomcat端口号
- centos7修改系统语言为简体中文的实现
- Linux 通过Rsync+Inotify实现本、异地远程数据实时同步功能