In practice, I want to implement an optional secondary grouping list, pub.dev plug-in library has many plug-ins with similar functions, and XIAO CAI is going to try to implement a simple secondary grouping list from my own perspective.

  • The list is divided into two levels and collapses by default
  • Both primary and secondary lists can be selected and unselected
  • Secondary list data can be added actively

Group list implementation has many ways, small dishes ready to use the most basic two ListView nested ideas to display, the default is to display the first level of list information, click to expand the operation, display the corresponding second level list;

GroupList

1. Bean data structures

The first step is to prepare the data entity class. The first-level list CategoryBean contains the corresponding second-level list SubCategoryBean, where both entities contain an isChecked field to store whether the current item is selected or not.

class CategoryBean {
  String name;
  String url;
  bool _isChecked = false;
  List<SubCategoryBean> itemList;

  bool get isChecked => _isChecked ?? false;

  set isChecked(bool value) => _isChecked = value;

  CategoryBean({this.name, this.url, this.itemList});
}

class SubCategoryBean {
  String name;
  String url;
  bool _isChecked = false;

  SubCategoryBean({this.name, this.url});

  bool get isChecked => _isChecked ?? false;

  set isChecked(bool value) => _isChecked = value;
}
Copy the code

2. Level-1 list

Side dishes show the first level list, you can confirm whether to use SliverListView according to the use scenario, side dishes test process only use the basic ListView; Single selection box, small dishes used before the custom ACECheckbox; Note that the isChecked state in the level 1 entity Bean should be changed when the ACECheckbox callback is clicked;

Return Scaffold(appBar: appBar (title: Text(' grouping list ')), body: ListView.builder(itemCount: widget.listData.length, itemBuilder: (context, index) { return GroupItemWidget(widget.listData[index]); })); class _GroupItemWidgetState extends State<GroupItemWidget> { bool _isExpand = false; @override Widget build(BuildContext context) { return InkWell( child: Column(children: <Widget>[ Divider(height: Symmetric (horizontal: 15.0, vertical: 10.0), child: Symmetric (horizontal: 15.0, vertical: 10.0) Row(children: <Widget>[ Icon(_isExpand ? Icons.arrow_drop_down : Icons.arrow_right, color: Color.blue), _userIcon(false), SizedBox(width: 5.0), Expanded(Child: Text('${widget.bean.name}', style: TextStyle(fontSize: 16.0)), _rightCheckBox(widget.bean, 0)])), _subCategoryList(widget.bean)]), onTap: () { _isExpand = ! _isExpand; setState(() {}); }); } _userIcon(isCircle) { double size = isCircle ? 40.0, 45.0; return PhysicalModel( color: Colors.transparent, shape: isCircle ? Rectangle: BoxShape.rectangle: BoxShape.rectangle: clipBehavior, Elevation: 2.0, borderRadius: BorderRadius. All (Radius. Circular (20.0)), Child: Container(width: size, height: size, child: image. asset(isCircle? 'images/icon_qq.png' : 'images/icon_hzw01.jpg'))); } _rightCheckBox(bean, type, {subIndex}) { bool _isChecked = type == 0 ? bean.isChecked : bean.itemList[subIndex].isChecked; return ACECheckbox( value: _isChecked, type: ACECheckBoxType.circle, unCheckColor: Colors.blue, onChanged: (value) { setState(() => _isChecked = value); if (type == 0) { bean.isChecked = _isChecked; List.generate(bean.itemList.length, (index) => bean.itemList[index].isChecked = _isChecked); }}); }}Copy the code

3. Secondary lists

When clicking on a level 1 list item, the level 2 list is displayed. It’s really just adding a new list data to the first level item, visually expanding the second level list; When clicking item again, replace the newly added Container with an empty Container layout.

_subCategoryList(CategoryBean bean) { Widget _widget; if (! _isExpand || bean == null || bean.itemList == null || bean.itemList.length == 0) { _widget = Container(); } else { _widget = ListView.builder( itemCount: bean.itemList.length, itemBuilder: (context, index) => Row(children: <Widget>[ Flexible(child: _subCategoryItem(bean, index)) ])); } return _widget; } _subCategoryItem(CategoryBean bean, index) {return Column(children: <Widget>[Divider(height: 0.5, color: Symmetric (Horizontal: 15.0, vertical: 10.0), Child: Row(children: <Widget>[SizedBox(width: 40.0), _userIcon(true), SizedBox(width: 5.0), Expanded(child: Text(bean.itemList[index].name ?? 'SubName')), _rightCheckBox(bean, 1, subIndex: index) ])) ]); }Copy the code

4. ACECheckbox Select & Deselect

The most important thing to deal with is the selected and unselected status of list items. The first tip is to add an isChecked state to the entity Bean to record the current checked state.

When the primary List is selected, all elements in the secondary List are also selected regardless of expansion or collapse. The small dish changes the isChecked state by going through the secondary List through list. generate.

When the item part of the second-level list is selected, the corresponding first-level list is unselected. At the same time, if all items in the second-level list are selected, the corresponding first-level list must also be selected. Small dishes adjust the isChecked state corresponding to the first-level item by iterating and judging the number of selected items in the second-level list.

_rightCheckBox(bean, type, {subIndex}) { bool _isChecked = type == 0 ? bean.isChecked : bean.itemList[subIndex].isChecked; return ACECheckbox( value: _isChecked, type: ACECheckBoxType.circle, unCheckColor: Colors.blue, onChanged: (value) { setState(() => _isChecked = value); if (type == 0) { bean.isChecked = _isChecked; List.generate(bean.itemList.length, (index) => bean.itemList[index].isChecked = _isChecked); } else { bean.itemList[subIndex].isChecked = _isChecked; int checkedSize = 0; List.generate(bean.itemList.length, (index) { if (bean.itemList[index].isChecked == false) { bean.isChecked = false; } else { checkedSize += 1; } if (checkedSize == bean.itemList.length) { bean.isChecked = true; }}); }}); }Copy the code

5. Dynamically add data

The expected goal of the small dish is that the first data only shows the first level list data, and the second level list data will be requested only when the first level list item is clicked, and dynamically added to the data list; The advantage is to reduce data requests and simplify data formats; By default, the secondary list data is not set in group 5, which is dynamically added when clicked;

Return InkWell(child: Column(children: <Widget>[Divider(height: 0.5, color: color.blue), Padding(Padding: EdgeInsets. Symmetric (horizontal: 15.0, vertical: 10.0), Child: Row(children: <Widget>[ Icon(_isExpand ? Icons.arrow_drop_down : Icons.arrow_right, color: Color.blue), _userIcon(false), SizedBox(width: 5.0), Expanded(Child: Text('${widget.bean.name}', style: TextStyle(fontSize: 16.0)), _rightCheckBox(widget.bean, 0)])), _subCategoryList(widget.bean)]), onTap: () { _isExpand = ! _isExpand; setState(() {}); If (widget. Beans. Name = = 'group five && (widget. Beans. ItemList = = null | | widget. Beans. ItemList. Length = = 0)) { widget.bean.itemList = [ SubCategoryBean(name: 'O'), SubCategoryBean(name: 'P'), SubCategoryBean(name: 'Q') ]; }});Copy the code

6. Sliding conflicts

The side dish uses two listViews to realize the second-level grouping list, which involves gesture conflict. When the second-level list is expanded, gestures can only be triggered at the first-level list. The second-level list does not slide as a whole and has theme color water ripples up and down. Set primary: false & shrinkWrap: True in ListView.

_widget = ListView.builder(
    primary: false,
    shrinkWrap: true,
    itemCount: bean.itemList.length,
    itemBuilder: (context, index) => Row(children: <Widget>[ Flexible(child: _subCategoryItem(bean, index)) ]));
Copy the code


GroupListPage case source


The exercise of GroupList secondary GroupList is over for the time being, but it should be adjusted according to specific data request and page operation in real application. The above is only a simple test Demo. If there are mistakes, please give more guidance!

Source: Little Monk A Ce