上一篇介紹了實現(xiàn)flutter listview實現(xiàn)滑動刪除的方式一叹洲,但是很多時候這種方式跟需求不太一致,下面將介紹第二種方式:實現(xiàn)帶刪除按鈕的滑動刪除惕橙。
首先還是先看一下實現(xiàn)效果:
那這種效果就比較符合需求了肮韧,具體實現(xiàn)看下面代碼
首先是每一個item的邏輯處理,在看代碼前先分析一下:
1.從動圖中可以想到的布局方式應(yīng)該是stack+position然走,讓具體內(nèi)容遮蓋住要滑動顯示的內(nèi)容即可。
2.從動圖可以看到我們要處理的是item的水平滑動的手勢操作戏挡,當(dāng)滑動超過一定距離的時候讓item自動打開芍瑞。當(dāng)關(guān)閉的時候也是滑動回一定距離的時候自動關(guān)閉。
3.滑動某項的時候關(guān)閉已經(jīng)打開的item褐墅。
4.點擊刪除和修改按鈕時外部實現(xiàn)具體操作拆檬。
好了根據(jù)以上分析洪己,先直接把item的代碼寫上,等下在做具體分析
typedef ClickDelete =Function(int position);//定義刪除方法
typedef ClickChange =Function(int position);//定義修改方法
class RemoveItem extends StatefulWidget {
final Result result;//數(shù)據(jù)bean
final GlobalKey<RemoveItemState> moveKey;
final VoidCallback onStart;//開始滑動回調(diào)
final ClickDelete delete;
final ClickDelete change;
final int position;//操作position
final Widget child;//具體顯示內(nèi)容
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;//是否是打開狀態(tài)
@override
void initState() {
super.initState();
//初始化動畫竟贯,讓item可以實現(xiàn)自動滑動
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){//滑動開始的時候關(guān)閉打開的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;//滑動結(jié)束的時候給動畫value賦值為當(dāng)前值
if (start==moveMaxLength) isOpen=true;//滑動距離最大的時候即為打開狀態(tài)
else if (start>moveMaxLength/2) {//滑動超過一般距離的時候答捕,啟動動畫滑動到最大位置
controller.animateTo(moveMaxLength);
isOpen=true;
}else if(start<=moveMaxLength/2){//往回滑動超過一般距離的時候,啟動動畫滑動到初始位置
close();
}
},));
}
void close(){
controller.animateTo(0);
isOpen=false;
}
}
現(xiàn)在一一實現(xiàn)上面分析時說到的4點屑那。
一.布局:
布局上有三點分別是詳細(xì)內(nèi)容(需要滑動的item)拱镐、修改和刪除按鈕。這里用的布局方式是stack+position持际。首先是讓要滑動的內(nèi)容占滿item沃琅,然后用position將修改和刪除依次定位在右側(cè)。因為滑動內(nèi)容要跟隨手指移動选酗,所以需要動態(tài)改變其左右距離阵难。根據(jù)上面分析做出了如下布局
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),//滑動內(nèi)容 start動態(tài)改變
],)
這里有一點要注意的是這句代碼:
Positioned(left: -start,right:start,child: widget.child),//滑動內(nèi)容 start動態(tài)改變
即左右都要設(shè)置start,那如果只設(shè)置了right芒填,left設(shè)置為0是什么效果呢,看下面的動圖
好像沒什么區(qū)別空繁,也能實現(xiàn)效果殿衰,但仔細(xì)觀察一下就會發(fā)現(xiàn)移動的時候藍色區(qū)域明顯只是距離右邊的距離在改變,而不是整體移動的效果盛泡。
二.自動開關(guān):
在Android中可以利用Scroller實現(xiàn)闷祥,但在flutter中我沒找到Scroller,因此用動畫代替傲诵。首先是在initState方法中初始化動畫AnimationController
初始化動畫之后先放著等會用凯砍,在水平手勢按下的時候,先把自身關(guān)閉掉同時回調(diào)一個onstart方法供外部調(diào)用拴竹,那這個作用等會再說悟衩。接著在手勢更新的時候改變start的值并調(diào)用setState,代碼中做了最大值和最小值的判斷栓拜,防止滑動超出限制座泳,之后就是在水平手勢結(jié)束的時候先把start值賦給動畫,讓動畫從當(dāng)前開始幕与。如果已經(jīng)移動了最大值那就不用開啟動畫了挑势,如果沒有的話判斷已經(jīng)滑動超過一半距離的時候,啟動動畫滑動到最大位置啦鸣,否則滑動到初始位置
三.滑動某項的時候關(guān)閉已經(jīng)打開的item
這個操作就需要在外部進行控制了潮饱,在Android中要進行控制是很方便的,在flutter中就有點惡心了诫给。好在flutter提供了key香拉,至于key是干嘛的就以后在分析了啦扬。通過給每一個item設(shè)置GlobalKey,并在外部通過GlobalKey調(diào)用關(guān)閉方法就可以做到了缕溉,具體實現(xiàn)可以查看代碼考传。
四.點擊刪除和修改按鈕時外部實現(xiàn)具體操作
這里我先定義了兩個方法,通過方法傳遞position來告知外部點擊的是哪個position证鸥,然后做具體操作僚楞。
typedef ClickDelete =Function(int position);//定義刪除方法
typedef ClickChange =Function(int position);//定義修改方法
然后點擊刪除或者修改按鈕的時候調(diào)用相應(yīng)方法即可。
到這里item的邏輯就分析完了枉层,接著看怎么調(diào)用item并實現(xiàn)相應(yīng)操作泉褐,同樣的先看一下完整代碼
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設(shè)置key,點擊其它item的時候鸟蜡,打開的item關(guān)閉
@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.設(shè)置movekey
listKey.forEach((bankKey){//2.循環(huán)關(guān)閉其他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("刪除后將無法看到該條記錄膜赃,請謹(jǐn)慎操作",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(() {});
}
}
外部調(diào)用代碼主要需要實現(xiàn)的是
1.按下item的時候關(guān)閉已經(jīng)打開的item
2.處理刪除和修改操作
一、關(guān)閉其它打開的item
首先看看沒有實現(xiàn)這一功能時的效果是什么樣的
可以看到多個item都可以打開揉忘,那要實現(xiàn)關(guān)閉其它item這一操作需要使用globalkey跳座,首先定義個key集合,有多少條數(shù)據(jù)key也就相應(yīng)有多少個泣矛。調(diào)用item的時候把key設(shè)置給item疲眷,然后再onstart回調(diào)方法里就通過循環(huán)關(guān)閉已經(jīng)打開的item。實現(xiàn)在代碼中的注釋1 您朽、2點
二狂丝、處理刪除和修改操作
通過在回調(diào)的delete和change方法里實現(xiàn)具體操作即可,這里刪除的時候使用showModalBottomSheet彈出個底部框?qū)崿F(xiàn)詢問刪除功能哗总,點擊修改的時候顯示一個toast几颜。
到這里基本上就說完了第二種方式,flutter做這種滑動刪除操作還是比Android簡單了很多的讯屈。