上一篇介绍了实现flutter listview实现滑动删除的方式一,但是很多时候这种方式跟需求不太一致,下面将介绍第二种方式:实现带删除按钮的滑动删除。
首先还是先看一下实现效果:
那这种效果就比较符合需求了,具体实现看下面代码
首先是每一个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是什么效果呢,看下面的动图
好像没什么区别,也能实现效果,但仔细观察一下就会发现移动的时候蓝色区域明显只是距离右边的距离在改变,而不是整体移动的效果。
二.自动开关:
在Android中可以利用Scroller实现,但在flutter中我没找到Scroller,因此用动画代替。首先是在initState方法中初始化动画AnimationController
初始化动画之后先放着等会用,在水平手势按下的时候,先把自身关闭掉同时回调一个onstart方法供外部调用,那这个作用等会再说。接着在手势更新的时候改变start的值并调用setState,代码中做了最大值和最小值的判断,防止滑动超出限制,之后就是在水平手势结束的时候先把start值赋给动画,让动画从当前开始。如果已经移动了最大值那就不用开启动画了,如果没有的话判断已经滑动超过一半距离的时候,启动动画滑动到最大位置,否则滑动到初始位置
三.滑动某项的时候关闭已经打开的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
首先看看没有实现这一功能时的效果是什么样的
可以看到多个item都可以打开,那要实现关闭其它item这一操作需要使用globalkey,首先定义个key集合,有多少条数据key也就相应有多少个。调用item的时候把key设置给item,然后再onstart回调方法里就通过循环关闭已经打开的item。实现在代码中的注释1 、2点
二、处理删除和修改操作
通过在回调的delete和change方法里实现具体操作即可,这里删除的时候使用showModalBottomSheet弹出个底部框实现询问删除功能,点击修改的时候显示一个toast。
到这里基本上就说完了第二种方式,flutter做这种滑动删除操作还是比Android简单了很多的。