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之间的关系的主要内容,如果未能解决你的问题,请参考以下文章

android 左滑删除控件,仿ios的左滑

微信小程序列表左滑删除,删除按钮自适应高度,删除后列表归位,同时存在一个左滑元素,目前为止写过最舒服的左滑删除

Android SwipeToDismiss:左滑/右滑删除ListView条目Item

Android之自定义ListView左滑删除Item效果

使用swipelistview向左滑动不动

[Android-2A] -仿IOS微信滑动删除_SwipeListview左滑删除例子