安卓开发,通用ExpandableListView三级选择列表。
效果
实现逻辑
ExpandableListView实现三级列表的原理很简单。
就是在第一个ExpandableListView的adapter中的getChildView对convertView再创建一个ExpandableListView,从而实现三级列表。
也就是第一个ExpandableListView的二级其实是二三级容器。一个嵌套关系。
难点
ExpandableListView并不是非常聪明,直接嵌套会出现问题。
一二级的ExpandableListView必须是正常的ExpandableListView,而二三级想要正常显示,必须是重写过onMeasure的CustomExpandableListView,否则会因为高度问题而显示一行导致展示异常。
CustomExpandableListView
import android.content.Context;
import android.util.AttributeSet;
import android.widget.ExpandableListView;
public class CustomExpandableListView extends ExpandableListView {
public CustomExpandableListView(Context context) {
super(context);
}
public CustomExpandableListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomExpandableListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 解决显示不全的问题
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2
, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
}
页面代码
首先我们先弄一个最外层的ExpandableListView
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:ignore="MissingConstraints">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.sunwayworld.monit.common.view.CustomExpandableListView
android:id="@+id/expandablelistview"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
然后我们需要三级的三个选择布局代码
item_expand1
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingLeft="@dimen/x150"
android:paddingTop="@dimen/x6"
android:paddingRight="@dimen/x100"
android:gravity="left|center_vertical"
android:paddingBottom="@dimen/x10">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/ctv_group"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:checkMark="?android:attr/listChoiceIndicatorMultiple"
android:textColor="@color/color_black_90"
android:text="12345"
android:textSize="@dimen/lfont"/>
<androidx.appcompat.widget.AppCompatCheckedTextView
android:id="@+id/check_group"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:checkMark="?android:attr/listChoiceIndicatorMultiple"
android:textColor="@color/color_black_90"
android:text="全选"
android:gravity="center_vertical"
android:textSize="@dimen/xxfont"/>
</LinearLayout>
item_expand2
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingLeft="@dimen/x180"
android:paddingTop="@dimen/x6"
android:paddingRight="@dimen/x120"
android:gravity="left|center_vertical"
android:paddingBottom="@dimen/x10">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/ctv_group"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:checkMark="?android:attr/listChoiceIndicatorMultiple"
android:textColor="@color/color_black_90"
android:text="12345"
android:textSize="@dimen/lfont"/>
<androidx.appcompat.widget.AppCompatCheckedTextView
android:id="@+id/check_group"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:checkMark="?android:attr/listChoiceIndicatorMultiple"
android:textColor="@color/color_black_90"
android:text="全选"
android:gravity="center_vertical"
android:textSize="@dimen/xxfont"/>
</LinearLayout>
item_expand3
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="@dimen/x210"
android:paddingTop="@dimen/x6"
android:paddingRight="@dimen/x140"
android:gravity="left|center_vertical"
android:paddingBottom="@dimen/x6">
<androidx.appcompat.widget.AppCompatCheckedTextView
android:id="@+id/ctv_child"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checkMark="?android:attr/listChoiceIndicatorMultiple"
android:textColor="@color/color_black_90"
android:text="12345"
android:gravity="center_vertical"
android:textSize="@dimen/xxfont"/>
</LinearLayout>
adapter代码
然后我们需要两个adapter分是管理一二级和二三级的。
ExpandFatherAdapter
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.ExpandableListAdapter;
import android.widget.ExpandableListView;
import androidx.appcompat.widget.AppCompatCheckedTextView;
import androidx.appcompat.widget.AppCompatTextView;
import java.util.ArrayList;
import java.util.List;
/**
* author:byx
* 通用三级列表之父级adapter
*/
public class ExpandFatherAdapter<A,B,C> extends BaseExpandableListAdapter {
private List<ExpandFather<A,B,C>> expandFatherList;
private Context context;
public ExpandFatherAdapter(Context context, List<ExpandFather<A,B,C>> expandDataList) {
this.context = context;
this.expandFatherList = expandDataList;
}
@Override
public int getGroupCount() {
return expandFatherList == null ? 0 : expandFatherList.size();
}
@Override
public int getChildrenCount(int groupPosition) {
// if (expandFatherList == null){
// return 0;
// } else {
// if (expandFatherList.get(groupPosition).getTwoList() == null){
// return 0;
// } else {
// return expandFatherList.get(groupPosition).getTwoList().size();
// }
// }
//这里必须是1,因为嵌套结构,这里的child被用来装二三级这一个整体了。
return 1;
}
@Override
public Object getGroup(int groupPosition) {
return expandFatherList.get(groupPosition);
}
@Override
public Object getChild(int groupPosition, int childPosition) {
return expandFatherList.get(groupPosition).getTwoList().get(childPosition);
}
@Override
public long getGroupId(int groupPosition) {
return groupPosition;
}
@Override
public long getChildId(int groupPosition, int childPosition) {
return childPosition;
}
@Override
public boolean hasStableIds() {
return false;
}
private class GroupViewHolder {
AppCompatTextView tv_group;
AppCompatCheckedTextView check_group;
public void setTv_group(String str){
tv_group.setText(str);
}
public void setCheck_group(boolean isCheck){
check_group.setChecked(isCheck);
}
}
private class ChildViewHolder{
CustomExpandableListView expandablelistview;
}
@Override
public View getGroupView(final int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
GroupViewHolder groupViewHolder;
if (convertView == null){
convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_expand1,parent,false);
groupViewHolder = new GroupViewHolder();
groupViewHolder.tv_group = (AppCompatTextView)convertView.findViewById(R.id.ctv_group);
groupViewHolder.check_group = convertView.findViewById(R.id.check_group);
convertView.setTag(groupViewHolder);
}else {
groupViewHolder = (GroupViewHolder)convertView.getTag();
}
final Object obj = expandFatherList.get(groupPosition).getOne();
groupViewHolder.setTv_group(expandFatherList.get(groupPosition).getTitle());
groupViewHolder.setCheck_group(expandFatherList.get(groupPosition).isCheck());
groupViewHolder.check_group.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
((AppCompatCheckedTextView)v).toggle();
expandFatherList.get(groupPosition).setCheck(((AppCompatCheckedTextView)v).isChecked());
setChildsChecked(groupPosition,((AppCompatCheckedTextView)v).isChecked());
notifyDataSetChanged();
}
});
return convertView;
}
public void setChildsChecked(int groupPosition, boolean isCheck){
List<ExpandChild<B,C>> expandChildList = expandFatherList.get(groupPosition).getTwoList();
for (ExpandChild item : expandChildList){
item.setCheck(isCheck);
List<Boolean> selectList = item.getThreeChecks();
for (int i = 0; i < selectList.size(); i++) {
selectList.set(i, isCheck);
}
}
}
@Override
public View getChildView(final int groupPosition, final int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
if (convertView == null){
convertView = new CustomExpandableListView(context);
}
CustomExpandableListView expandableListView = (CustomExpandableListView) convertView;
ExpandChildAdapter expandChildAdapter = new ExpandChildAdapter(context, expandFatherList.get(groupPosition).getTwoList());
expandableListView.setAdapter(expandChildAdapter);
if (expandFatherList.get(groupPosition).getTwoList().get(childPosition).getThreeList().size() == 0) {
expandableListView.setGroupIndicator(null);
}
// setExpandableListViewHeightBasedOnChildren(expandableListView);
return convertView;
}
public static void setExpandableListViewHeightBasedOnChildren(ExpandableListView listView) {
ExpandableListAdapter listAdapter = listView.getExpandableListAdapter();
if (listAdapter == null) {
return;
}
int totalHeight = 0;
for (int i = 0; i < listAdapter.getGroupCount(); i++) {
View group = listAdapter.getGroupView(i, true, null, listView);
group.measure(0, 0);
totalHeight += group.getMeasuredHeight() + listView.getDividerHeight();
if (listView.isGroupExpanded(i)) {
for (int j = 0; j < listAdapter.getChildrenCount(i); j++) {
View child = listAdapter.getChildView(i, j, false, null, listView);
child.measure(0, 0);
totalHeight += child.getMeasuredHeight() + listView.getDividerHeight();
}
}
}
ViewGroup.LayoutParams params = listView.getLayoutParams();
params.height = totalHeight;
listView.setLayoutParams(params);
}
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {//指定位置上的子元素是否可选中
return true;
}
public List<C> getSelectDatas(){
List<C> list = new ArrayList<>();
for (ExpandFather<A,B,C> item : expandFatherList){
for (ExpandChild<B,C> item2 : item.getTwoList()){
for (int i = 0; i < item2.getThreeList().size(); i++) {
if (item2.getThreeChecks().get(i)){
list.add(item2.getThreeList().get(i));
}
}
}
}
return list;
}
}
ExpandChildAdapter
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import androidx.appcompat.widget.AppCompatCheckedTextView;
import androidx.appcompat.widget.AppCompatTextView;
import java.util.List;
/**
* author:byx
* 通用三级列表之子级adapter
*/
public class ExpandChildAdapter<B,C> extends BaseExpandableListAdapter {
private List<ExpandChild<B,C>> expandChildList;
private Context context;
public ExpandChildAdapter(Context context, List<ExpandChild<B,C>> expandChildList) {
this.expandChildList = expandChildList;
this.context = context;
}
@Override
public int getGroupCount() {
return expandChildList.size();
}
@Override
public int getChildrenCount(int groupPosition) {
return expandChildList.get(groupPosition).getThreeList().size();
}
@Override
public Object getGroup(int groupPosition) {
return expandChildList.get(groupPosition);
}
@Override
public Object getChild(int groupPosition, int childPosition) {
return expandChildList.get(groupPosition).getThreeList().get(childPosition);
}
@Override
public long getGroupId(int groupPosition) {
return groupPosition;
}
@Override
public long getChildId(int groupPosition, int childPosition) {
return childPosition;
}
@Override
public boolean hasStableIds() {
return false;
}
private class GroupViewHolder {
AppCompatTextView tv_group;
AppCompatCheckedTextView check_group;
public void setTv_group(String str){
tv_group.setText(str);
}
public void setCheck_group(boolean isCheck){
check_group.setChecked(isCheck);
}
}
private class ChildViewHolder {
AppCompatCheckedTextView ctv_child;
public void setCtv_child(boolean isCheck){
ctv_child.setChecked(isCheck);
}
}
@Override
public View getGroupView(final int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
GroupViewHolder groupViewHolder;
if (convertView == null){
convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_expand2,parent,false);
groupViewHolder = new GroupViewHolder();
groupViewHolder.tv_group = (AppCompatTextView)convertView.findViewById(R.id.ctv_group);
groupViewHolder.check_group = convertView.findViewById(R.id.check_group);
convertView.setTag(groupViewHolder);
}else {
groupViewHolder = (GroupViewHolder)convertView.getTag();
}
final ExpandChild<B,C> expandChild = expandChildList.get(groupPosition);
groupViewHolder.setTv_group(expandChild.getTitle());
groupViewHolder.setCheck_group(expandChild.isCheck());
groupViewHolder.check_group.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
((AppCompatCheckedTextView)v).toggle();
expandChild.setCheck(((AppCompatCheckedTextView)v).isChecked());
setChildsChecked(groupPosition,((AppCompatCheckedTextView)v).isChecked());
notifyDataSetChanged();
}
});
return convertView;
}
public void setChildsChecked(int groupPosition, boolean isCheck){
List<Boolean> selectList = expandChildList.get(groupPosition).getThreeChecks();
for (int i = 0; i < selectList.size(); i++) {
selectList.set(i, isCheck);
}
}
@Override
public View getChildView(final int groupPosition, final int childPosition, final boolean isLastChild, View convertView, ViewGroup parent) {
ChildViewHolder childViewHolder;
if (convertView == null){
convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_expand3,parent,false);
childViewHolder = new ChildViewHolder();
childViewHolder.ctv_child = (AppCompatCheckedTextView)convertView.findViewById(R.id.ctv_child);
convertView.setTag(childViewHolder);
}else {
childViewHolder = (ChildViewHolder) convertView.getTag();
}
final Object obj = expandChildList.get(groupPosition).getThreeList().get(childPosition);
childViewHolder.ctv_child.setText((String)expandChildList.get(groupPosition).getTitles().get(childPosition));
childViewHolder.ctv_child.setChecked((boolean)expandChildList.get(groupPosition).getThreeChecks().get(childPosition));
childViewHolder.ctv_child.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
((AppCompatCheckedTextView)v).toggle();
List<Boolean> selectList = expandChildList.get(groupPosition).getThreeChecks();
selectList.set(childPosition, isLastChild);
}
});
return convertView;
}
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;
}
}
通用参数设置
使用泛型,让数据通用起来,adapter这种只要善用泛型和接口,就能实现通用万用。
一二级数据类
import java.util.List;
public class ExpandFather<A,B,C> {
/**
* 一级实体
*/
private A one;
/**
* 当前一级实体下的二三级数据
*/
private List<ExpandChild<B,C>> twoList;
/**
* 一级展示字段
*/
private String title;
/**
* 一级是否被选中
*/
private boolean isCheck = false;
public ExpandFather(A one, String title) {
this.one = one;
this.title = title;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public A getOne() {
return one;
}
public void setOne(A one) {
this.one = one;
}
public List<ExpandChild<B, C>> getTwoList() {
return twoList;
}
public void setTwoList(List<ExpandChild<B, C>> twoList) {
this.twoList = twoList;
}
public boolean isCheck() {
return isCheck;
}
public void setCheck(boolean check) {
isCheck = check;
}
}
二三级数据类
import java.util.ArrayList;
import java.util.List;
public class ExpandChild<B,C> {
/**
* 二级实体
*/
private B two;
/**
* 二级展示字段
*/
private String title;
/**
* 二级下的所有三级实体
*/
private List<C> threeList;
/**
* 三级实体是否被选中的集合
*/
private List<Boolean> threeChecks = new ArrayList<>();
/**
* 三级展示字段
*/
private List<String> titles = new ArrayList<>();
/**
* 二级是否被选中
*/
private boolean isCheck = false;
/**
* 二级是否被展开
*/
private boolean isExpand = false;
public ExpandChild(B two, String title) {
this.two = two;
this.title = title;
}
public boolean isExpand() {
return isExpand;
}
public void setExpand(boolean expand) {
isExpand = expand;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public List<String> getTitles() {
return titles;
}
public void setTitles(String str) {
// this.titles = titles;
titles.add(str);
}
public B getTwo() {
return two;
}
public void setTwo(B two) {
this.two = two;
}
public List<C> getThreeList() {
return threeList;
}
public void setThreeList(List<C> threeList) {
this.threeList = threeList;
}
public boolean isCheck() {
return isCheck;
}
public void setCheck(boolean check) {
isCheck = check;
}
public List<Boolean> getThreeChecks() {
return threeChecks;
}
public void setThreeChecks(boolean isCheck) {
// this.threeChecks = threeChecks;
threeChecks.add(isCheck);
}
}
使用
示例
private ExpandFatherAdapter<A, B, C> expandFatherAdapter;
for(Object item : List){
ExpandFather expandFather = new ExpandFather(item, "一级标题");
List<ExpandChild> expandChildList = new ArrayList<>();
for(Object item2 : List2){
ExpandChild expandChild = new ExpandChild(item2, "二级标题");
//某些条件获取到List3
expandChild.setThreeList(List3);
for (Object item3 : List3){
expandChild.setTitles("三级标题");
//选中标记先置否,有需求默认选也可以在这设置
expandChild.setThreeChecks(false);
}
if (!List3.isEmpty()){
expandChildList.add(expandChild);
}
if (!expandChildList.isEmpty()){
expandFather.setTwoList(expandChildList);
expandFatherList.add(expandFather);
}
}
}
expandFatherAdapter = new ExpandFatherAdapter(context, expandFatherList);
expandablelistview.setAdapter(expandFatherAdapter);
if (expandFatherList.size() > 0) {
expandablelistview.expandGroup(0);
}
结尾
这个代码虽然写着通用,用起来也不繁琐,但是adapter中的一些逻辑处理还不完全到位。还有完善和修改的空间。布局也还有多元化的空间。可以按照自己的喜好来。
代码中的包名,或者R等一些带包名引用已去掉,需要自己手动再引用一下。
两个需要注意的地方,一个就是一二级必须用原expand,二三级必须重写,一个就是一二级adapter的getChildrenCount必须是1。