聊天列表是一個很扣細節的場景,在之前的 《Flutter 實現完美的雙向聊天列表效果,滑動列表的知識點》 里,通過 CustomScrollView
和配置它的 center
從而解決了數據更新時的列表跳動問題,但是這時候又有網友提出了新的問題:
如下動圖所示,可以看到雖然列表在添加新數據后雖然沒有發生跳動,但是在列表數據長度足夠的情況下,頂部會有一篇空白。
如下代碼所示,這個問題的起因正是在解決跳動問題而增加的 center
,因為列表是 reverse
,并且紅色的 SliverList
長度只有 3 條,高度不夠導致頂部留空白。
CustomScrollView(
controller: scroller,
reverse: true,
center: centerKey,
slivers: [
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
var item = newData[index];
if (item.type == "Right")
return renderRightItem(item);
else
return renderLeftItem(item);
},
childCount: newData.length,
),
),
SliverPadding(
padding: EdgeInsets.zero,
key: centerKey,
),
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
var item = loadMoreData[index];
if (item.type == "Right")
return renderRightItem(item);
else
return renderLeftItem(item);
},
childCount: loadMoreData.length,
),
),
],
)
如下圖結合圖片理解更形象:
-
center
其實就是列表的起始錨點,我們把錨點給了SliverPadding
,而因為列表是reverse
,所以起始位置是在屏幕下方; - 紅色的 old 數據
SliverList
,在代碼里是處于center
的下方,而因為reverse
所以它實際就是黃色的部分; - 所以雖然綠色的
SliverList
雖然新增了數據,但是從center
往上的高度還是不夠,所以就出現了黃色SliverList
頂部空白的問題;
結合這個問題,這里可以發現關鍵的點就在于 reverse
,而對比微信和QQ的聊天列表需求,在沒有數據時,消息數據應該是從頂部開始,所以這時候就需要我們調整列表實現,參考微信/QQ 的實現模式。
如下代碼所以,這里針對新交互場景做了優化調整:
- 去除
CustomScrollView
的reverse
; - 對調兩個
SliverList
的位置,把加載 old 數據的SliverList
放到center
的前面;
CustomScrollView(
controller: scroller,
center: centerKey,
slivers: [
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
var item = loadMoreData[index];
if (item.type == "Right")
return renderRightItem(item);
else
return renderLeftItem(item);
},
childCount: loadMoreData.length,
),
),
SliverPadding(
padding: EdgeInsets.zero,
key: centerKey,
),
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
var item = newData[index];
if (item.type == "Right")
return renderRightItem(item);
else
return renderLeftItem(item);
},
childCount: newData.length,
),
),
],
)
是不是很簡單,就這?運行后也如下圖所示,可以看到運行后的代碼不會再有空白的情況,也沒有新增數據跳動的情況,雙向滑動也正常,那你知道為什么嗎?
如下圖所示,調整后從結構上變成了右邊的邏輯:
- 數據起始錨點在頁面頂部,所以不會存在頂部留空問題;
- 在
center
下面的SliverList
按照正向排序正常顯示,用于顯示新數據; - 在
center
上面的SliverList
列表會被變成以center
為起點反向順序顯示,用于加載舊數據;
當然,這里有一點需要注意的局就是:起始進來時加載的第一頁數據應該是用綠色的正向 SliverList
,因為起始點在頂部,如果不用下面綠色的正向 SliverList
,就會導致第一次數據看不到的情況。
這時候就有人可能會說,如果是下圖所示場景,只加載舊數據,不加載新數據,那不就出現底部留空了嗎?
是的,我們其實是把頂部留空的問題轉移到了底部,但是這個問題在實際業務場景是不成立,進入聊天列表首先就需要先加載滿一頁的數據,所以:
- 如果 old 數據本來就不夠,例如例子里只有3條,那也就不會有加載更多 old 數據的場景,所以不會產生滑動;
- 如果 old 數據足夠,那默認就足以撐滿列表;
而隨著 new 數據的增加,頁面也會被填滿從而可以正?;瑒硬⑶页錆M,所以從這個實現上看會更加合理。
那有人可能會說,就這?還有什么可以優化的小技巧? 比如增加判斷列表是否處于底部,決定在接受到新數據時是否滑動到最新消息。
實現這個優化也很簡單,首先我們可以嵌套一個 NotificationListener
, 在這里我們主要是獲取 notification.metrics.extentAfter
這個參數。
NotificationListener(
onNotification: (notification) {
if (notification is ScrollNotification) {
if (notification.metrics is PageMetrics) {
return false;
}
if (notification.metrics is FixedScrollMetrics) {
if (notification.metrics.axisDirection == AxisDirection.left ||
notification.metrics.axisDirection == AxisDirection.right) {
return false;
}
}
///取到這個值
extentAfter = notification.metrics.extentAfter;
}
return false;
},
)
這里的
if
判斷,只是為了規避其他控件的影響,比如列表里的PageView
或者TextFiled
的影響。
那 extentAfter
參數的作用是什么? 事實上在 FixedScrollMetrics
里有 extentBefore
、 extentInside
和 extentAfter
三個參數,它們的關系類似下圖所示:
一般情況下:
-
extentInside
就是視圖窗口大?。?/li> -
extentBefore
就是前面還可以滑動距離; -
extentAfter
就是后面還可以滑動距離;
所以我們只需要判斷 extentAfter
是否為 0 ,就可以判斷列表是不是處于底部 ,從而針對場景首先不同的業務邏輯,例如下圖所示,針對列表是否處于底部,在接收到新數據時是直接跳到最新數據,還是彈出提示用讓用戶點擊跳轉。
if (extentAfter == 0) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text("你目前位于最底部,自動跳轉新消息item"),
duration: Duration(milliseconds: 1000),
));
Future.delayed(Duration(milliseconds: 200), () {
scroller.jumpTo(scroller.position.maxScrollExtent);
});
} else {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: InkWell(
onTap: () {
scroller.jumpTo(scroller.position.maxScrollExtent);
},
child:Text("點擊我自動跳轉新消息item")
),
duration: Duration(milliseconds: 1000),
));
}
所以從聊天列表的場景上看,實現一個聊天列表并不難,但是需要優化的細節可能會很多,如果你在這方面還有什么問題,歡迎評論交流。
實例代碼可見:https://github.com/CarGuo/gsy_flutter_demo/blob/master/lib/widget/chat_list_scroll_demo_page_2.dart