自定義searchBar 搜尋本地資料庫好友資訊高亮顯示匹配並多選傳送
先看下檔案分類吧
receive_share_intent.dart 請忽略
採用MVC實現程式碼分層
- logic: controller層處理邏輯
- view: UI控制元件
- state: 資料層
- widget:控制元件拆分
先看下view層
class ForwardMsgPage extends StatelessWidget {
final Message forwardMsg;
/// 注入controller 和 state
final ForwardMsgLogic logic = Get.put(ForwardMsgLogic());
final ForwardMsgState state = Get.find<ForwardMsgLogic>().state;
ForwardMsgPage({Key key, this.forwardMsg}) : super(key: key);
@override
Widget build(BuildContext context) {
return forwardBuildBg(context, backFunc: () {
Get.delete<ForwardMsgLogic>();
Navigator.pop(context);
}, children: [
ZYSearchWidget(
hintText: '搜尋',
onSearch: logic.onSearch,
onCancel: () => logic.onSearchCancle(),
onClear: () => logic.onSearchClear(),
onChanged: logic.onSearchValueChange
// onEditingComplete: () =>logic.onSearchValueChange(''),
),
buildUserAndGroupList(
/// 滑動取消鍵盤
gesturePanDownCallback: () {
FocusScope.of(context).requestFocus(FocusNode());
},
children: [
/// 聊天列表
Obx(() => Visibility(
visible: state.searchQuery.value.isEmpty,
child: ForwardMsgRecentCon(
data: state.conList,
itemClick: (value) => showAlert(context,
determine: () =>
logic.sendMsg(forwardMsg, value, context)),
))),
/// 聯絡人 群組列表
Obx(() => Visibility(
visible: state.searchQuery.value.isNotEmpty,
child: ForwardMsgSearchResult(
state: state,
userItemClick: (userInfo) => logic.userItemClick(userInfo),
groupItemClick: (groupInfo) => logic.groupItemClick(groupInfo),
// itemClick: (value) => showAlert(context,
// determine: () => logic.sendMsg(forwardMsg, value, context)),
))),
]),
/// 聯絡人 群組列表
Obx(() => Visibility(
visible: state.showSend.value,
child: ForwardChooseResult(
state: state,
closeClick: (item)=>logic.closeBtnClick(item),
confirmSendClick:()=> logic.confirmSendClick(forwardMsg,completeHandler: (){
Get.delete<ForwardMsgLogic>();
Navigator.pop(context);
}),
)))
]);
}
}
複製程式碼
Obx 根據響應欄位 searchQuery 和 showSend 隱藏顯示 state類定義了可響應欄位
typedef ForwardUserItemBuilder = Widget Function(UserInfo item);
typedef ForwardGroupItemBuilder = Widget Function(GroupInfoUserList item);
typedef ForwardChooseItemBuilder = Widget Function(dynamic item);
class ForwardMsgState {
///最近會話資料來源
List conList = [];
/// 聯絡人列表
List<SearchUserInfo> userList = [];
/// 群列表
List<SearchGroupInfo> groupList = [];
/// 是否顯示最近聊天
// RxBool showRecentList = true.obs;
/// 搜尋內容
RxString searchQuery = ''.obs;
/// 選中的使用者列表
List selectUserList = [];
/// 選中的群組列表
List selectGroupList = [];
/// 使用者列表 + 群組列表
List selectTotalList = [];
/// 展示多選
RxBool showSend = false.obs;
}
複製程式碼
controller層
class ForwardMsgLogic extends GetxController {
final state = ForwardMsgState();
int forwardTotalCount = 0;
@override
void onReady() {
// TODO: implement onReady
super.onReady();
///防DDos - 每當使用者停止輸入1秒時呼叫
debounce(state.searchQuery, (value) => loadDataFormDB(value));
updateConversationList();
}
updateConversationList() async {
List list = await RongIMClient.getConversationList(
[RCConversationType.Private, RCConversationType.Group]);
state.conList.addAll(list);
update(['conList']);
}
void recentConItemClick(Conversation conversation) {}
void onSearch(msg) {
loadDataFormDB(msg);
}
複製程式碼
onReady方法獲取最近聊天列表資料,get提供了防抖
搜尋後獲取本地資料庫使用者資訊並匹配使用者
void loadDataFormDB(query) async {
print('query----rebuild');
state.userList.clear();
state.groupList.clear();
List<userdb.UserInfo> userlist =
await DbManager.instance.getUserInfoWithUserName(query);
if (userlist != null && userlist.isNotEmpty) {
List<SearchUserInfo> searchUserList = [];
userlist.forEach((element) {
SearchUserInfo searchUserInfo = SearchUserInfo.formUserInfo(element);
// 根據已選擇的是否包含 初始化可選狀態
state.selectUserList.forEach((element) {
if (element.id == searchUserInfo.id) {
searchUserInfo.checked.value = true;
}
});
searchUserList.add(searchUserInfo);
});
state.userList.assignAll(searchUserList);
}
update(['userList']);
List<GroupInfoUserList> grouplist =
await DbManager.instance.getGroupInfoWithGroupName(query);
if (grouplist != null && grouplist.isNotEmpty) {
List<SearchGroupInfo> searchGroupList = [];
grouplist.forEach((element) {
SearchGroupInfo searchGroupInfo =
SearchGroupInfo.formGroupInfo(element);
// 根據已選擇的是否包含 初始化可選狀態
state.selectGroupList.forEach((element) {
if (element.id == searchGroupInfo.id) {
searchGroupInfo.checked.value = true;
}
});
searchGroupList.add(searchGroupInfo);
});
state.groupList.assignAll(searchGroupList);
}
update(['groupList']);
}
複製程式碼
手動呼叫 update(['userList']); 通過制定tag 做到指定重新整理widget
///聯絡人搜尋結果
Widget _searchUserResult({ForwardUserItemBuilder itemBuilder}) {
return GetBuilder<ForwardMsgLogic>(
id: 'userList',
builder: (controller) {
return controller.state.userList.length > 0
? ListView.builder(
padding: EdgeInsets.zero,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: controller.state.userList.length,
itemBuilder: (context, index) => itemBuilder(controller.state.userList[index])
)
: Container();
});
}
/// 搜尋群組結果
Widget _buildGroupList({ForwardGroupItemBuilder itemBuilder}){
return GetBuilder<ForwardMsgLogic>(
id: 'groupList',
builder: (controller) {
return controller.state.groupList.length > 0
? ListView.builder(
// padding: EdgeInsets.zero,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: controller.state.groupList.length,
itemBuilder: (context, index) => itemBuilder(controller.state.groupList[index]))
: Container();
});
}
複製程式碼
通過 設定 GetBuilder的id對應update方法,GetBuilder 可以理解為statefulWidget
多選功能通過設定物件的checked為可觀察 RxBool checked = false.obs;
class SearchUserInfo extends userdb.UserInfo {
RxBool checked = false.obs;
SearchUserInfo.formUserInfo(userdb.UserInfo userInfo) {
this.companyName = userInfo.companyName;
this.id = userInfo.id;
this.name = userInfo.name;
}
}
/// 使用者列表可選框
Widget _buildUserListCheckBox(SearchUserInfo userInfo){
return Padding(
padding: const EdgeInsets.only(right: 17),
child: Obx(()=>Image.asset(
userInfo.checked.value?'assets/images/contact_info_selected.png':'assets/images/contact_info_unselected.png',height: 24,width: 24,fit:BoxFit.fill ,)),
);
}
複製程式碼
點選後更新物件屬性的響應屬性
state.userList.forEach((element) {
if (element.id == item.id) {
element.checked.value = false;
}
});
複製程式碼
通過checked.value 設定 注意 .obs 屬性對應 obx控制元件觀察。
搜尋高亮匹配 程式碼如下
Widget _splitUserNameRichText(String userName) {
print(userName);
List<TextSpan> spans = [];
List<String> strs = userName?.split(state.searchQuery.value)??[];
for (int i = 0; i < strs.length; i++) {
if ((i % 2) == 1) {
spans.add(TextSpan(
text: state.searchQuery.value, style: _highlightUserNameStyle));
}
String val = strs[i];
if (val != '' && val.length > 0) {
spans.add(TextSpan(text: val, style: _normalUserNameStyle));
}
}
return RichText(text: TextSpan(children: spans));
}
複製程式碼
介面佈局防止套娃採用widget分組的方式便於定位程式碼
Widget build(BuildContext context) {
print('ForwardMsgSearchResult---rebuild');
return _buildBg(children: [
/// 聯絡人
_searchUserResult(itemBuilder: (item) {
return _buildUserItemBg([
/// 內容
_buildItemContent(children: [
/// 核取方塊
_buildUserListCheckBox(item),
/// 頭像
WidgetUtil.buildUserPortraitWithParm(item.portraitUrl,item.name),
/// 使用者名稱字
_buildUserItemName(item),
/// 公司名
_buildUserCompanyName(item)
]),
/// 下劃線
_buildBottomLine()
],item: item);
}),
/// 分隔符
_buildSeparator(),
/// 群組結果
_buildSearchGroupBg(children: [
/// 群組標題
_buildGroupTitle(),
/// 群組item
_buildGroupList(itemBuilder: (item){
return _buildGroupItemBg(
groupInfo: item,
children: [
/// 群組內容
_buildGroupContent(children: [
/// 核取方塊
_buildGroupListCheckBox(item),
/// 群頭像
_buildGroupItemPortrait(item),
_buildGroupNameAndUserName(children: [
/// 群名稱
_buildGroupItemGroupName(item),
/// 包含的群成員
_buildGroupMember(item)
])
]),
/// 下劃線
_buildBottomLine()
]);
})
]),
]);
}
複製程式碼
難點在於三個資料來源同步問題,底部展示多選列表和userList,groupList 同步
/// 聯絡人點選
void userItemClick(SearchUserInfo item) {
item.checked.value = !item.checked.value;
if (item.checked.value) {
bool exist = state.selectUserList.any((element) => element.id == item.id);
if (!exist) {
state.selectUserList.add(item);
state.selectTotalList.add(item);
}
} else {
state.selectUserList.removeWhere((element) => element.id == item.id);
state.selectTotalList.removeWhere((element) => element.id == item.id);
}
print('leon----selectUserList---${state.selectUserList.length}');
_showSend();
}
void _showSend() {
update(['chooseSend', 'totalCount']);
state.showSend.value = state.selectTotalList.isNotEmpty;
}
複製程式碼
觀察showSend隱藏顯示底部多選框 visible: state.showSend.value,
/// 多選列表
Obx(() => Visibility(
visible: state.showSend.value,
child: ForwardChooseResult(
state: state,
closeClick: (item)=>logic.closeBtnClick(item),
confirmSendClick:()=> logic.confirmSendClick(forwardMsg,completeHandler: (){
Get.delete<ForwardMsgLogic>();
Navigator.pop(context);
}),
)))
複製程式碼