Flutter Key的原理和使用(五) 需要key的例項:可拖動改變順序的Listview

__white發表於2021-08-31

這是我參與8月更文挑戰的31天,活動詳情檢視:8月更文挑戰

Flutter Key的原理和使用 (一) 沒有Key會發生什麼 (juejin.cn)

Flutter Key的原理和使用 (二) Widget 和 Element 的對應關係 (juejin.cn)

Flutter Key的原理和使用(三) LocalKey的三種型別 (juejin.cn)

Flutter Key的原理和使用(四) GlobalKey 的用法 - 掘金 (juejin.cn)

Flutter Key的原理和使用(五) 需要key的例項:可拖動改變順序的Listview - 掘金 (juejin.cn)

之前呢,我們介紹了flutter中的幾種key,它們相應的原理和使用方式, 這次就來複習一下,看看一個需要用到key的例項.

ReorderableListView

你可能使用過ListView元件,它可以調整,顯示和滾動專案列表. 但是不能做的一件事就是在列表中移動專案.幸運的是,有ReorderableListView.

ReorderableListView是不個不常用的ListView元件.它是一個使用者可以通過拖動來重新排序其專案的列表元件. 有了它,我們可以通過長按專案來在ListView的滾動方向移動它,將其放在新的位置.

它有一個要求: 所有列表項都必須有一個Key 並且必須實現onReorder方法,它是使用者在重新排序列表時呼叫的回撥方法,看一下沒有實現這個回撥的效果:

ReorderableListView(
  children: [
    Box(Colors.blue, key: ValueKey(1)),
    Box(Colors.green, key: ValueKey(2)),
    Box(Colors.red, key: _globalKey),
  ],
  onReorder: (oldIndex, newIndex) {
    print('從位置$oldIndex移動到$newIndex');
  },
)
複製程式碼

a.gif

可以看到雖然我們交換了紅綠兩個box,但是鬆手之後UI並沒有變化,就是因為沒有沒有實現onReorder.

onReorder: (oldIndex, newIndex) , 分別有兩個引數,oldIndex和newIndex, 它們代表的含義就是,拖動的widget拖動前和拖動後的index. 看一下列印結果:

從位置2移動到1

說明是從第2個位置移動到了第1個位置(最開始是第0的位置). 每當發生這樣改變的時候,我們就需要把list元件修改. 修改一下我們的程式碼,移除舊位置的widget,在新的位置插入它:

final boxList = [
  Box(Colors.blue, key: ValueKey(1)),
  Box(Colors.green, key: ValueKey(2)),
  Box(Colors.red, key: ValueKey(3)),
];

Widget listWidget() {
  return ReorderableListView(
    children: boxList,
    onReorder: (oldIndex, newIndex) {
      print('從位置$oldIndex移動到$newIndex');
      final box = boxList.removeAt(oldIndex);
      boxList.insert(newIndex, box);
    },
  );
}
複製程式碼

asd.gif

這樣就達到了我們移動widget的目的. 但其實將widget從上向下移動的時候,有一個問題,我再移動一下大家看一下:

asdasd.gif

相應的列印: 從位置0移動到2. 簡單看一下原因:

indexwidget
0box1
1box2
2box2

我們剛才移動的順序是將box1移動到box2的後面 , 體現在這裡就是從0移動到box2後面,也就是說是移動到了2的位置,因為box2的位置是1嘛. 不知道大家有沒有理解. 當然了,如果從起始位置移動到最後,就會出現陣列越界的報錯:

════════ Exception caught by animation library ═════════════════════════════════════════════════════
The following RangeError was thrown while notifying status listeners for AnimationController:
Invalid value: Not in inclusive range 0..2: 3

When the exception was thrown, this was the stack: 
#0      List.insert (dart:core-patch/growable_array.dart:11:7)
#1      _MyHomePageState.listWidget.<anonymous closure> (package:flutter_key/home_page.dart:53:17)
#2      SliverReorderableListState._dropCompleted (package:flutter/src/widgets/reorderable_list.dart:646:24)
#3      _DragInfo._dropCompleted (package:flutter/src/widgets/reorderable_list.dart:1163:22)
#4      _DragInfo.startDrag.<anonymous closure> (package:flutter/src/widgets/reorderable_list.dart:1134:9)
...
The AnimationController notifying status listeners was: AnimationController#aca1a(⏮ 0.000; paused; DISPOSED)
════════════════════════════════════════════════════════════════════════════════════════════════════
 
複製程式碼

所以在向下移動的時候,我們要額外處理一下,新的index要減1,之後再進行刪除和插入操作:

if(newIndex>oldIndex){
  newIndex --;
}
複製程式碼

橫向列表,就是向右的操作要處理

ReorderableListView的缺點

在拖了幾下之後,發現了幾個缺點:

  • 長按才能觸發拖動,容易誤觸.
  • ReorderableListView仍然是一個Listview,就是說它是會滾動的,當列表很長可以滾動的時候,會有很多誤操作.
  • 一維的ListView,我們都知道listview只能在滾動方向來滑動,ReorderableListView也是一樣,不能上下左右來回拖動.

其實我們可以通過自己來實現一個這樣的元件.
拖動的話,我們可以通過Draggable來實現,它是一個支援拖拽的widget.
為了避免滾動.可以使用ColumnRow.

之後有時間, 會實現一個支援拖動的列表元件,來更好的實現這個效果.

相關文章