Flutter从ListView的左滑删除,简单理解KeyWidget与Element之间的关系
Posted 安静的Sunny
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flutter从ListView的左滑删除,简单理解KeyWidget与Element之间的关系相关的知识,希望对你有一定的参考价值。
关于Flutter学习的相关Demo源码,已经上传至Github.
有兴趣的同学,欢迎Star或Flow。我会不及时更新上传相关知识点源码
https://github.com/SunnyLy/flutter_sunny_demo
一、原由
最近在学习Flutter中ListView组件的使用。一看到ListView很自然的就会想到以下操作点:
- 下拉刷新;
- 上拉加载更多;
- 添加头部、底部等;
- Item滑动(左滑删除)
在做的过程中,我就先仿微信,选择实现左滑删除。想实现的效果如下:
ListView中的Item可以向左滑动,然后展示出操作项。当再次去滑动其他Item项时,要求先关闭已经打开的item。这里面就涉及到ListView中各Item的联动。
那怎么在我操作当前Item的时候,去遍历检测并关闭其他Item呢?
Flutter中ListView的创建我就不贴出了,下面就简单说下左滑Item的实现。
$1.1、布局控件选用
由于是左滑时,显示出隐藏的操作项。因此可以考虑选用Stack层叠布局。操作项放在最下层,Item内容放在最上层。而最上层由于会随着滑动而移动距离。因此最上层的内容用Positioned来包裹,而它的位置则由一个变量来替代。
child: Stack(
children: <Widget>[
Align(//最下层
alignment: Alignment.centerRight,
child: InkWell(
onTap: ()
ToastUtil.show("删除成功");
,
child: Container(
width: 80.0,
height: 200.0,
alignment: Alignment.center,
color: Colors.red,
child: Icon(
Icons.delete,
color: Colors.white,
),
),
),
),
Positioned(//最上层
child: widget.visible,
height: 200,
left: -offset,//offset.表示在X轴上滑动的总量(累加值),
right: offset,
),
],
)
由于向左滑动得监听手势滑动,因此还得在Stack外套一个手势监听控件:GestureDetector
child:GestureDetector(
child:Stack(
children:<Widget>[
.....
],
),
onHorizontalDragDown:(DragDownDetails downDetails)
//水平方向上按下时触发。
,
onHorizontalDragUpdate:(DragUpdateDetails updateDetails)
//水平方向上滑动时回调,随着手势滑动一直回调。
,
onHorizontalDragEnd:(DragEndDetails endDetails)
//水平方向上滑动结束时回调
,
)
$1.2、回调
当手指按下时,要想让其他子item也知道,我们可以通过回调的方法来实现。在当前Widget的构造函数中,加入一个回调函数参数。必须由父Widget传递进来,然后在父Widget里面定义相关处理方法。
/// @Annotation <p>可滑动删除的Item</p>
/// 用动画来实现
/// @Auth Sunny
/// @date 2020/5/19
/// @Version V1.0.0
class RemovableItem extends StatefulWidget
RemovableItem(
Key key,
.....///这里加入其他自己想要传入的参数
this.onActionDown)
: super(key: moveItemKey);
.....
VoidCallback onActionDown; //手指按下的回调
@override
RemovableItemState createState() => RemovableItemState();
class RemovableItemState extends State<RemovableItem>
with SingleTickerProviderStateMixin
@override
Widget build(BuildContext context)
return Container(
child:GestureDetector(
....
onHorizontalDragDown:(DragDownDetails downDetails)
//水平方向上按下时触发。
....
return widget.onActionDown();//把当前的这个事件通过回调函数传出去
,
),);
二、Key与GlobalKey
上面第一点,只是实现了如果把当前的触摸事件传递出去。但并为能达到控制其他Item的目的。
在我们new一个Widget时,其构造方法中的第一个参数为Key,但我们一般都没用。官方在framework.dart中给出的解释如下:
/// Controls how one widget replaces another widget in the tree.
///
/// If the [runtimeType] and [key] properties of the two widgets are
/// [operator==], respectively, then the new widget replaces the old widget by
/// updating the underlying element (i.e., by calling [Element.update] with the
/// new widget). Otherwise, the old element is removed from the tree, the new
/// widget is inflated into an element, and the new element is inserted into the
/// tree.
///
/// In addition, using a [GlobalKey] as the widget's [key] allows the element
/// to be moved around the tree (changing parent) without losing state. When a
/// new widget is found (its key and type do not match a previous widget in
/// the same location), but there was a widget with that same global key
/// elsewhere in the tree in the previous frame, then that widget's element is
/// moved to the new location.
///
/// Generally, a widget that is the only child of another widget does not need
/// an explicit key.
///
/// See also:
///
/// * The discussions at [Key] and [GlobalKey].
final Key key;
翻译过来通俗的说就是:
key是用来表示Widget唯一的属性之一。一般只要新创建的Widget与树中的另一个Widget的这个key一样,那么系统就会通过刷新底层元素(Element),来达到替换视图树中这个旧Widget的目的。但是这个Key是不能保存Widget状态的。而要想在整个视图树中不丢失状态的移除Widget(什么意思呢?可以把状态理解为配置,即你可以把Widget这个控件从树中移除,但是这个控件相关的配置还是会保存),可以用全局的Key,即GlobalKey(当新的视屏帧中新的Widget创建且全局的key与上一帧中旧的Widget一样时,新的Widget就会在取出旧的Widget的配置并取代它。
上面这个是我个人的理解。英语好的初学者,建议直接看官方文档。
因此由上面可知,要想达到控制其他Item关闭的目的,我们得利用这个GlobalKey。具体用法如下(详细源码我已经上传到github):
$2.1、在父Widget中初始化GlobalKey列表
//利用全局的Key
static List<GlobalKey<RemovableItemState>> childItemStates = [];
//简单的利用Key,做为唯一标记
// static List<Key> childItemStates = [];
@override
void initState()
super.initState();
initChildItemStates();
void initChildItemStates()
childItemStates.clear();
for (int i = 0; i < _itemContentList.length; i++)
GlobalKey<RemovableItemState> removeGK = GlobalKey(debugLabel: "$i");
childItemStates.add(removeGK);
// Key removeKey = Key("$i");
// childItemStates.add(removeKey);
setState(() );
$2.2、将Key与Widget绑定
在子Widget的构造方法中,添加接入GlobalKey的参数,然后再通过super传给Widget的构造方法。如下:
class RemovableItem extends StatefulWidget
RemovableItem(
Key key,
....
this.moveItemKey,
...)
: super(key: moveItemKey);//这个是重点。把传进来的key再回给super
三、Widget与Element
其实在看Key时,就从源码上初步的可以了解到Widget与Element的关系。
因此这里我也只是简单的说下个人的理解,没有很深入。
Widget就像是一个配置文件。在Flutter的渲染机制中,视图树上的挂载点其实为Element。所有的添加,删除,刷新操作其实都是针对Element。而最终Element要想在屏幕上显示出来,就必须要有Widget的填充,即读取它的配置(比如:怎么排列、背景颜色、字体等等)。
以上是关于Flutter从ListView的左滑删除,简单理解KeyWidget与Element之间的关系的主要内容,如果未能解决你的问题,请参考以下文章
微信小程序列表左滑删除,删除按钮自适应高度,删除后列表归位,同时存在一个左滑元素,目前为止写过最舒服的左滑删除