flutter listview实现滑动删除——方式二

上一篇介绍了实现flutter listview实现滑动删除的方式一,但是很多时候这种方式跟需求不太一致,下面将介绍第二种方式:实现带删除按钮的滑动删除。

首先还是先看一下实现效果:


view.gif

那这种效果就比较符合需求了,具体实现看下面代码

首先是每一个item的逻辑处理,在看代码前先分析一下:
1.从动图中可以想到的布局方式应该是stack+position,让具体内容遮盖住要滑动显示的内容即可。
2.从动图可以看到我们要处理的是item的水平滑动的手势操作,当滑动超过一定距离的时候让item自动打开。当关闭的时候也是滑动回一定距离的时候自动关闭。
3.滑动某项的时候关闭已经打开的item。
4.点击删除和修改按钮时外部实现具体操作。

好了根据以上分析,先直接把item的代码写上,等下在做具体分析

typedef ClickDelete =Function(int position);//定义删除方法
typedef ClickChange =Function(int position);//定义修改方法


class RemoveItem extends StatefulWidget  {
  final Result result;//数据bean
  final GlobalKey<RemoveItemState> moveKey;
  final VoidCallback onStart;//开始滑动回调
  final ClickDelete delete;
  final ClickDelete change;

  final int position;//操作position

  final Widget child;//具体显示内容

  RemoveItem(this.result,this.position,this.child,{@required this.moveKey,this.onStart,this.delete,this.change}):super(key:moveKey);
  @override
  RemoveItemState createState() => RemoveItemState();
}

class RemoveItemState extends State<RemoveItem> with SingleTickerProviderStateMixin{


 // Animation<double> animation;
  AnimationController controller;

  double moveMaxLength=160;//滑动最大距离
  double start=0;

  bool isOpen=false;//是否是打开状态


  @override
  void initState() {
    super.initState();
//初始化动画,让item可以实现自动滑动
    controller = new AnimationController( lowerBound: 0,
        upperBound: moveMaxLength,duration: const Duration(milliseconds: 300), vsync: this)
      ..addListener((){
        start=controller.value;
        setState(() {});
      });

  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Container(height:115,padding: EdgeInsets.only(left: 15,right: 15,top: 15),width:MediaQuery.of(context).size.width,child:GestureDetector(
      child:Stack(children: <Widget>[
        Positioned(right: 80,child:InkWell(onTap: (){widget.change(widget.position); },child:Container(width: 80,height:100,alignment: Alignment.center,color: Colors.grey, child: Text("修改",style: TextStyle(color: Colors.white),),),),),
        Align(alignment: Alignment.centerRight,child: InkWell(onTap: (){widget.delete(widget.position); },child: Container(width: 80,alignment: Alignment.center,color: Colors.red,child: Text("删除",style: TextStyle(color: Colors.white)),),),),
        Positioned(left: -start,right:start,child: widget.child),

      ],),onHorizontalDragDown: (DragDownDetails details){//滑动开始的时候关闭打开的item
      close();
      return widget.onStart();
    },
      onHorizontalDragUpdate: (DragUpdateDetails details){//滑动中更新滑动距离
        setState(() {
          start-=details.delta.dx;
          if (start<=0) {//限制最小滑动距离
            start=0;
          }

          if(start>=moveMaxLength){//限制最大滑动距离
            start=moveMaxLength;
          }
        });
      },onHorizontalDragEnd: (DragEndDetails details){
      controller.value=start;//滑动结束的时候给动画value赋值为当前值
      if (start==moveMaxLength) isOpen=true;//滑动距离最大的时候即为打开状态
      else if (start>moveMaxLength/2) {//滑动超过一般距离的时候,启动动画滑动到最大位置
        controller.animateTo(moveMaxLength);
        isOpen=true;
      }else if(start<=moveMaxLength/2){//往回滑动超过一般距离的时候,启动动画滑动到初始位置
        close();
      }

    },));
  }

  void close(){
    controller.animateTo(0);
    isOpen=false;

  }


}

现在一一实现上面分析时说到的4点。

一.布局:

布局上有三点分别是详细内容(需要滑动的item)、修改和删除按钮。这里用的布局方式是stack+position。首先是让要滑动的内容占满item,然后用position将修改和删除依次定位在右侧。因为滑动内容要跟随手指移动,所以需要动态改变其左右距离。根据上面分析做出了如下布局

 child:Stack(children: <Widget>[
        Positioned(right: 80,child:InkWell(onTap: (){widget.change(widget.position); },child:Container(width: 80,height:100,alignment: Alignment.center,color: Colors.grey, child: Text("修改",style: TextStyle(color: Colors.white),),),),),
        Align(alignment: Alignment.centerRight,child: InkWell(onTap: (){widget.delete(widget.position); },child: Container(width: 80,alignment: Alignment.center,color: Colors.red,child: Text("删除",style: TextStyle(color: Colors.white)),),),),
        Positioned(left: -start,right:start,child: widget.child),//滑动内容  start动态改变

      ],)

这里有一点要注意的是这句代码:
Positioned(left: -start,right:start,child: widget.child),//滑动内容 start动态改变
即左右都要设置start,那如果只设置了right,left设置为0是什么效果呢,看下面的动图

view3.gif

好像没什么区别,也能实现效果,但仔细观察一下就会发现移动的时候蓝色区域明显只是距离右边的距离在改变,而不是整体移动的效果。
单一.png

整体.png

二.自动开关:

在Android中可以利用Scroller实现,但在flutter中我没找到Scroller,因此用动画代替。首先是在initState方法中初始化动画AnimationController


初始化动画之后先放着等会用,在水平手势按下的时候,先把自身关闭掉同时回调一个onstart方法供外部调用,那这个作用等会再说。接着在手势更新的时候改变start的值并调用setState,代码中做了最大值和最小值的判断,防止滑动超出限制,之后就是在水平手势结束的时候先把start值赋给动画,让动画从当前开始。如果已经移动了最大值那就不用开启动画了,如果没有的话判断已经滑动超过一半距离的时候,启动动画滑动到最大位置,否则滑动到初始位置


3.png

三.滑动某项的时候关闭已经打开的item

这个操作就需要在外部进行控制了,在Android中要进行控制是很方便的,在flutter中就有点恶心了。好在flutter提供了key,至于key是干嘛的就以后在分析了。通过给每一个item设置GlobalKey,并在外部通过GlobalKey调用关闭方法就可以做到了,具体实现可以查看代码。

四.点击删除和修改按钮时外部实现具体操作

这里我先定义了两个方法,通过方法传递position来告知外部点击的是哪个position,然后做具体操作。

typedef ClickDelete =Function(int position);//定义删除方法
typedef ClickChange =Function(int position);//定义修改方法

然后点击删除或者修改按钮的时候调用相应方法即可。

到这里item的逻辑就分析完了,接着看怎么调用item并实现相应操作,同样的先看一下完整代码

class ListRemovePage extends StatefulWidget {
  @override
  _ListRemovePageState createState() => _ListRemovePageState();
}

class _ListRemovePageState extends State<ListRemovePage> {


  List<Result> listBank=new List();
  int positionNow=0;

  List<GlobalKey<RemoveItemState>> listKey=[];//通过给各个item设置key,点击其它item的时候,打开的item关闭

  @override
  void initState(){
    super.initState();
    initList();
  }

  void initList(){
    listBank=List.generate(10, (index){
      Result result=new Result("title $index","detail $index");
      return result;
    });
    updateView(listBank);
  }



  void updateView(List<Result> list){
    listKey.clear();
    listKey.addAll(setKey(list.length));
    setState(() {});
  }

  List<GlobalKey<RemoveItemState>> setKey(int length){
    var list=<GlobalKey<RemoveItemState>>[];
    for (int i = 0; i < length; i++) {
      var key=GlobalKey<RemoveItemState>();
      list.add(key);
    }
    return list;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(appBar: AppBar(title: Text("List滑动删除"),centerTitle: true,elevation:0,),
      body:ListView.builder(itemCount:listBank.length,itemBuilder: (BuildContext context, int index){
        return RemoveItem(listBank[index],index,new RemoveWidget(listBank[index]),moveKey: listKey[index],onStart:(){//1.设置movekey
          listKey.forEach((bankKey){//2.循环关闭其他item
            if (bankKey!=listKey[index]) {
              bankKey.currentState?.close();
            }
          });
        },delete: (position){
          positionNow=position;
          showLoginDialog();
        },change: (position){
          Toast.toast(context,msg: "你点击了修改 $position");
        },);
      }),
    );
  }


  void showLoginDialog() {

    showModalBottomSheet(context: context, builder: (context){
      return Container(height: 170,color: Colors.white,child: Column(children: <Widget>[
        SizedBox(height: 20,),
        Text("删除后将无法看到该条记录,请谨慎操作",style: TextStyle(color: Colors.grey,fontSize: 14),),
        SizedBox(height: 1,),
        Container(height: 50,width:double.infinity,margin:EdgeInsets.only(left: 15,right: 15),
          child:  FlatButton(onPressed:(){
            Navigator.of(context).pop();
            _deleteBank();
            Toast.toast(context,msg: "你点击了删除 $positionNow");
          },
            child: Text("删除",style: TextStyle(fontSize: 16,color:Colors.blue),),),),
        SizedBox(height: 10,),
        Container(height: 50,width:double.infinity,margin:EdgeInsets.only(left: 15,right: 15),
          child:  FlatButton(onPressed:(){Navigator.of(context).pop();}, child: Text("取消",style: TextStyle(fontSize: 14),),),),
      ],),);
    });
  }

  void _deleteBank(){
    listKey.removeAt(positionNow);
    listBank.removeAt(positionNow);
    setState(() {});
  }

}

外部调用代码主要需要实现的是
1.按下item的时候关闭已经打开的item
2.处理删除和修改操作

一、关闭其它打开的item

首先看看没有实现这一功能时的效果是什么样的


4.png

可以看到多个item都可以打开,那要实现关闭其它item这一操作需要使用globalkey,首先定义个key集合,有多少条数据key也就相应有多少个。调用item的时候把key设置给item,然后再onstart回调方法里就通过循环关闭已经打开的item。实现在代码中的注释1 、2点

二、处理删除和修改操作

通过在回调的delete和change方法里实现具体操作即可,这里删除的时候使用showModalBottomSheet弹出个底部框实现询问删除功能,点击修改的时候显示一个toast。

到这里基本上就说完了第二种方式,flutter做这种滑动删除操作还是比Android简单了很多的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容