Mình có tạo 1 class dùng để hiển thị 1 danh sách nào đó và để chọn - lọc dữ liệu
Trước tiên bạn cần import thư viện này vào project của bạn trước
[Thư viện diacritic](https://pub.dev/packages/diacritic
import 'package:diacritic/diacritic.dart';
import 'package:flutter/material.dart';
typedef GetTitle<T> = String Function(T data);
typedef ChoiceDialogTitleBuilder<T> = Widget Function(T data);
typedef OnAccept<T> = void Function(List<T>);
typedef CanCheckItem<T> = bool Function(T data);
// ignore: must_be_immutable
class ChoiceDialog<T> extends StatefulWidget {
BuildContext context;
List<T> searchedData = [];
List<T> rootData = [];
List<T>? selectedObject = [];
List<T>? backupSelectedObject = [];
String? title;
String? hintSearchText;
String choiceButtonText;
Widget? cancelButton;
GetTitle<T>? getTitle;
bool isSingleChoice;
OnAccept<T>? onAccept;
ChoiceDialogTitleBuilder<T>? itemBuilder;
bool isShowCheckAllWidget = false;
CanCheckItem<T>? isCanCheckItem;
ChoiceDialog(this.context, this.searchedData,
{Key? key,
List<T>? selectedObject,
this.title,
this.choiceButtonText = "Chọn",
@required this.getTitle,
this.isSingleChoice = true,
this.onAccept,
this.isShowCheckAllWidget = false,
this.hintSearchText,
this.isCanCheckItem,
this.itemBuilder})
: super(key: key) {
rootData = searchedData;
if (isNotNullOrEmpty(selectedObject)) {
this.selectedObject = selectedObject;
backupSelectedObject?.addAll(selectedObject!);
}
}
Future<List<T>> showChoiceDialog() async {
return await pushPage(context, this);
}
@override
State<StatefulWidget> createState() {
return _ChoiceDialogState<T>();
}
}
class _ChoiceDialogState<T> extends State<ChoiceDialog<T>> {
List<T> searchedData = [];
List<T> rootData = [];
List<T> selectedObject = [];
List<T> backupSelectedObject = [];
String? title;
String? hintSearchText;
String? choiceButtonText;
GetTitle<T>? getTitle;
bool? isSingleChoice;
OnAccept<T>? onAccept;
ChoiceDialogTitleBuilder<T>? itemBuilder;
CanCheckItem<T>? isCanCheckItem;
bool isCheckAll = false;
@override
void initState() {
super.initState();
searchedData = widget.searchedData;
rootData = widget.rootData;
selectedObject.addAll(widget.selectedObject ?? []);
title = widget.title;
hintSearchText = widget.hintSearchText;
choiceButtonText = widget.choiceButtonText;
getTitle = widget.getTitle;
isSingleChoice = widget.isSingleChoice;
onAccept = widget.onAccept;
itemBuilder = widget.itemBuilder;
backupSelectedObject = [];
backupSelectedObject.addAll(selectedObject);
isCheckAll = searchedData.length == selectedObject.length;
isCanCheckItem = widget.isCanCheckItem;
}
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () {
Navigator.pop(context, backupSelectedObject);
return Future.value(false);
},
child: Scaffold(
appBar: AppBar(
title: Text(title ?? ""),
actions: [
Visibility(
visible: widget.isShowCheckAllWidget,
child: IconButton(
onPressed: () {
setState(
() {
isCheckAll = !isCheckAll;
if (isCheckAll) {
selectedObject.addAll(searchedData);
} else {
selectedObject.clear();
}
},
);
},
icon: Padding(
padding: const EdgeInsets.only(right: 16),
child: getIcon(0, isCheckAll: isCheckAll)),
),
),
],
),
body: SafeArea(
child: Column(
children: [
Visibility(
visible: hintSearchText != null,
child: Container(
margin:
const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(8)),
color: getColor('EFF0F5')),
child: TextField(
onChanged: (value) {
var text = removeDiacritics(value).toLowerCase();
searchedData = rootData.where((element) {
String title = getTitle!(element);
if (title == null) return false;
return removeDiacritics(title)
.toLowerCase()
.contains(text);
}).toList();
setState(() {});
},
decoration: InputDecoration(
contentPadding: const EdgeInsets.all(8),
prefixIconConstraints:
const BoxConstraints(maxHeight: 32, maxWidth: 32),
isDense: true,
border: InputBorder.none,
hintText: hintSearchText,
prefixIcon: const Padding(
padding: EdgeInsets.symmetric(horizontal: 8),
child: Icon(
Icons.search,
size: 20,
),
),
),
),
),
),
Container(
color: getColor('EFF0F5'),
height: 1,
margin: EdgeInsets.zero,
),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: _getWidgetContent())),
Container(
color: getColor('EFF0F5'),
height: 12,
margin: const EdgeInsets.symmetric(vertical: 12),
),
saveButton(
titleButton: choiceButtonText ?? "Xong",
onClickButton: () async {
Navigator.pop(context, selectedObject);
if (onAccept != null) {
onAccept!(selectedObject);
}
},
),
saveButton(
colorBackground: Colors.grey,
titleButton: "Hủy",
onClickButton: () async {
Navigator.pop(context, backupSelectedObject);
},
)
],
),
),
),
);
}
Widget saveButton({
required String titleButton,
required VoidCallback onClickButton,
Color? colorBackground,
}) {
return GestureDetector(
onTap: onClickButton,
child: Container(
margin: const EdgeInsets.only(left: 16, right: 16, bottom: 8),
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 16),
alignment: Alignment.center,
decoration: BoxDecoration(
color: colorBackground ?? Colors.blue,
borderRadius: BorderRadius.circular(5000),
),
child: Text(
titleButton,
style: TextStyle(color: Colors.white, fontSize: 18),
),
),
);
}
Widget _getWidgetContent() {
return isNullOrEmpty(searchedData)
? const Center(
child: Text("Không có kết quả tìm kiếm"),
)
: ListView.builder(
itemCount: searchedData.length,
itemBuilder: (context, index) {
var model = searchedData[index];
return InkWell(
onTap: () {
if (isCanCheckItem != null &&
isCanCheckItem!(model) == false) {
return;
}
if (isSingleChoice == true) {
if (selectedObject.contains(model)) {
selectedObject.remove(model);
} else {
selectedObject.clear();
selectedObject.add(model);
}
} else {
if (selectedObject.contains(model)) {
selectedObject.remove(model);
} else {
selectedObject.add(model);
}
isCheckAll = selectedObject.length == searchedData.length;
setState(() {});
}
setState(() {});
},
child: Container(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: itemBuilder != null
? itemBuilder!(model)
: Text(getTitle!(model)),
),
getIcon(index)
],
),
),
);
},
);
}
Widget getIcon(int index, {bool? isCheckAll}) {
bool isSelected =
isCheckAll ?? selectedObject.contains(searchedData[index]);
IconData iconData;
if (isSingleChoice == true) {
iconData = isSelected
? Icons.radio_button_checked_outlined
: Icons.radio_button_off;
} else {
iconData = isSelected
? Icons.check_box_outlined
: Icons.check_box_outline_blank_rounded;
}
Color color = isSelected ? Colors.blue : Colors.grey;
return Icon(
iconData,
color: color,
);
}
}
Color getColor(String hex, {int? alpha}) {
hex = "0xff" + hex;
return Color(int.parse(hex));
}
bool isNullOrEmpty(dynamic object) {
if (object == null) return true;
if (object == "null") return true;
if (object is String) {
return object.isEmpty;
}
if (object is List) {
return object.length == 0;
}
if (object is Map) {
return object.length == 0;
}
if (object is Set) {
return object.length == 0;
}
return false;
}
bool isNotNullOrEmpty(dynamic object) {
return !isNullOrEmpty(object);
}
Future<T?> pushPage<T>(BuildContext context, Widget page) {
return Navigator.of(context).push<T>(MaterialPageRoute(
builder: (context) => page,
settings: RouteSettings(arguments: page.toStringShort())));
}
Vậy cách sử dụng class trên như thế nào
ChoiceDialog choiceDialog = ChoiceDialog<int>(
context,
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
selectedObject: [1],
isSingleChoice: false,
title: "Chọn phòng",
hintSearchText: "Nhập tên phòng",
getTitle: (data) => "$data",
choiceButtonText: "Chọn phòng ban",
isShowCheckAllWidget: true,
isCanCheckItem: (selected) {
print('isCanCheckItem.build $selected');
if (selected == 2) {
return false;
}
return true;
},
itemBuilder: (data) {
return Text(
data.toString() ?? "",
style: TextStyle(fontSize: 14),
);
},
onAccept: (selected) {
print('_DemoDialogState.build $selected');
},
);
choiceDialog.showChoiceDialog();
Trước tiên cần truyền vào 1 kiểu dữ liệu: Có thể sử dụng bất kỳ kiểu dữ liệu nào
Bắt buộc cần truyền context và list
title: tiêu đề Appbar
isSingleChoice: true là chọn 1, false là chọn nhiều
hintSearchText: có thể tìm kiếm dữ liệu trong danh sách
getTitle: dữ liệu cần dc hiển thị ở danh sách là trường nào
selectedObject: cần truyền 1 danh sách dữ liệu đã chọn
choiceButtonText: tên của button lưu
isShowCheckAllWidget: true là hiện thị checkbox , false là ẩn, chức năng dùng để chọn - bỏ tất cả dữ liệu
isCanCheckItem: điều kiện để không cho chọn 1 dữ liệu nào đó trong list
itemBuilder: mỗi item hiển thị như nào đều code UI ở đây
onAccept: khi chọn dữ liệu xong thì truyền dữ liệu đã chọn ra ngoài
Ví dụ code trên của mình
- Kiểu dữ liệu là int
- list là [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
- getTitle: hiển thị dữ liệu ra mỗi item
- selectedObject dữ liệu trước đó mình đã chọn, khi chọn lại nó sẽ hiển thị đã chọn lên
- isCanCheckItem: mình đang đặt điều kiện là nếu dữ liệu = 2 sẽ không cho chọn và các dữ liệu khác sẽ được chọn
- itemBuilder: đây là giao diện mỗi item của mình
- onAccept: khi mình chọn dữ liệu xong sẽ truyền ra ngoài
)
Bình luận