Flutter 仿寫微信通訊錄頁面
這是我參與11月更文挑戰的第6天,活動詳情檢視:2021最後一次更文挑戰
針對通訊錄頁面我們整體上可以分為列表部分跟右邊索引條部分,因為索引條是在列表的上面,所以這個頁面的 body
部分我們採用 Stack
部件。下面我們可以來按照每個部分來詳細介紹下實現思路。
列表部分實現
列表部分整體我們可以分為頂部 4 個固定的 cell
跟底部的聯絡人 cell
,所以這一塊我們定義了兩個資料來源 _headerData
跟 _listDatas
,但是 cell
我們用的是同一個 cell
,cell
對應的模型我們用的也是同一個,只是對引數進行的區分,cell
的引數也是一樣。但是我們還可以看到,聯絡人部分相同首字母的頭部會有一個索引頭,在 iOS
中我們可以用組頭檢視來實現,但是 Flutter
中沒有組的概念,所以我們就在 cell
中來新增這個索引頭檢視,只是通過邏輯跟引數來判斷是否展示索引頭檢視部分。下面我們看一下關鍵程式碼部分的實現。
列表部分
``` Container( color: CahtThemColor, child: ListView.builder(itemBuilder: _itemForRow, itemCount: _headerData.length + _listDatas.length,) ),
Widget _itemForRow(BuildContext context, int index) { if (index < _headerData.length) { return _FriendCell(imageAssets: _headerData[index].imageAssets, name: _headerData[index].name); } index = index - _headerData.length; // 判斷是否顯示組名稱 return _FriendCell( imageUrl: _listDatas[index].imageUrl, name: _listDatas[index].name, groupTitle: (index > 0 && _listDatas[index].indexLetter == _listDatas[index -1].indexLetter) ? '' : _listDatas[index].indexLetter); } ```
列表部分我們用 Container
包了一層,方便後期的改動。_itemForRow
方法部分我們通過判斷 index
是否大於 _headerData.length
來傳不同的引數對 cell
進行初始化,針對每組相同首字母的 cell
我們通過判斷當前模型與下一個模型的索引字元 indexLetter
是否相等來判斷是否展示索引頭檢視。
cell 部分的實現
class _FriendCell extends StatelessWidget {
_FriendCell({this.imageUrl = '', this.name = '', this.groupTitle = '', this.imageAssets = ''});
final String imageUrl;
final String name;
final String groupTitle;
final String imageAssets;
@override
Widget build(BuildContext context) {
return Column(
children: [
groupTitle.length > 0 ? Container(
height: 30,
color: CahtThemColor,
alignment: Alignment.centerLeft,
padding: EdgeInsets.only(left: 15),
child: groupTitle.length > 0 ? Text(groupTitle, style: TextStyle(color: Colors.grey),) : null,
) : Container(), //組頭
Container(
color: Colors.white,
child: Row(
children: [
Container(
margin: EdgeInsets.all(10),
width: 34,
height: 34,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6.0),
image: imageUrl.length > 0
? DecorationImage(
image: NetworkImage(imageUrl)
)
: DecorationImage(
image: AssetImage(imageAssets),
)
),
),//圖片
Container(
// color: Colors.red,
width: screenWidth(context) - 54,
child: Column(
children: [
Container(
alignment: Alignment.centerLeft,
height: 54,
child: Text(
name,
style: TextStyle(fontSize: 18),
),
),
Container(
height: 0.5,
color: CahtThemColor,
), //下劃線
],
),
),//暱稱
],
),
), //cell的內容
],
);
}
}
如果一個類我們只是想在當前檔案中使用可以通過下劃線的方式來定義。cell
的整體我們分為上下兩部分,組頭跟內容部分。所以整體我們使用 Column
部件來實現。
模型部分的實現
class Friends {
final String imageUrl;
final String name;
final String indexLetter;
final String imageAssets;
Friends({this.imageUrl = '', this.name = '', this.indexLetter = '', this.imageAssets = ''});
}
索引檢視的實現
索引檢視部分當我們點選的時候顏色會變成黑色透明的,所以整體是有狀態的,繼承於 StatefulWidget
。因為索引條是懸浮在檢視的右邊居中,所以整體採用 Positioned
部件,整體高度我們這裡設定為整個螢幕的二分之一,寬度為 30,距離右邊為 0,距離頂部為螢幕的八分之一。
索引檢視內容部分
for (int i = 0; i < INDEX_WORDS.length; i++) {
_words.add(
Expanded(child: Text(INDEX_WORDS[i],
style: TextStyle(fontSize: 10, color: _textColor),))
);
}
child: Container(
color: _bkColor,
child: Column(
children: _words,
),
),
INDEX_WORDS
陣列存放的是索引字元,我們在 build
中方法中遍歷陣列,把每個字元對應的部件加入到 _words
中。索引條中的部件是上下排列並且等分的,索引每個索引字元對應的部件採用 Expanded
,整體採用 Column
部件。
索引條點選改變狀態顏色
child: GestureDetector(
// 索引條點選
onVerticalDragDown: (DragDownDetails details){
setState(() {
_bkColor = Color.fromRGBO(1, 1, 1, 0.4);
_textColor = Colors.white;
});
},
// 索引條點選取消
onVerticalDragEnd: (DragEndDetails details){
setState(() {
_bkColor = Color.fromRGBO(1, 1, 1, 0.0);
_textColor = Colors.black;
});
},
當點選索引條的時候需要改變背景顏色跟文字顏色,索引我們定義了兩個變數 _bkColor
跟 _textColor
,當索引條被點選跟取消點選的時候呼叫 setState
方法,並且修改這兩個變數的值。
索引條拖拽
//獲取選中的 item 文字
String _getIndex(BuildContext context, Offset globalPosition) {
//拿到點選小部件的盒子,也就是索引條
RenderBox box = context.findRenderObject() as RenderBox;
// 拿到 y 值, globalToLocal 當前位置距離索引條左上角 (0, 0) 位置的距離 (x, y)
double y = box.globalToLocal(globalPosition).dy;
//算出字元的高度
var itemHeight = screenHight(context) / 2 / INDEX_WORDS.length;
//算出第幾個 item
int index = (y ~/ itemHeight).clamp(0, INDEX_WORDS.length - 1);
return INDEX_WORDS[index];
}
onVerticalDragUpdate: (DragUpdateDetails details){
print(_getIndex(context, details.globalPosition));
},
當我們上下拖拽索引條的時候,需要獲取到點選位置對應的索引字元,我們通過 context.findRenderObject() as RenderBox
能拿到當前的部件,也就是索引條,然後通過 box.globalToLocal
可以得到當前觸控位置距離索引條左上角 (0, 0)
位置的距離 (x, y)
,這樣就可以得到了 y
值,因為知道了索引條的高度,索引就可以計算出每個索引字元對應部件的高度,就能得到點選了第幾個索引,也就能通過 INDEX_WORDS[index]
得到點選的字元。最後需要通過 clamp
對 index
做個取值範圍的判斷。
全域性變數的抽取
這裡我們定義了一個 const
檔案,對全域性的變數程序了抽取。
- Swift - LeetCode - 學生出勤記錄 I
- Swift - LeetCode - 字串中的第一個唯一字元
- Swift - LeetCode - 猜數字大小
- Swift - LeetCode - 翻轉二叉樹
- Swift - LeetCode - 二叉樹的後序遍歷
- Swift - LeetCode - 二叉樹的前序遍歷
- Swift String、Moya 原始碼解析及高階函式
- Flutter 中 key 的原理及作用
- Flutter 生命週期及渲染原理
- Flutter 仿寫微信搜尋頁
- Flutter 網路請求類封裝及搜尋框實現
- Flutter 佈局聊天列表頁及網路資料處理
- Flutter 通訊錄索引條完善及聊天資料配置
- Flutter 仿寫微信通訊錄頁面
- Flutter 仿寫微信發現、我的頁面
- Flutter 專案搭建及工程配置
- 常用 Widget 部件介紹及 Flutter 佈局方式
- Flutter 之 Widget 部件體驗
- Dart 基礎語法
- 記憶體管理-弱引用分析