Android 二级列表控件ExpandableListView
简述
在Android 开发中多多少少会碰到需要二级列表,之前已经写过一篇RecyclerView 二级列表 其实现方式是通过根据不同ViewHolder 来显示是一级还是二级列表,想起谷歌官方自己就有自带二级列表控件ExpandableListView,如果不需要复杂效果,建议直接使用官方控件,故有了今天这一篇文章。
老规矩,先上图:
视频录制效果.gif
如图所见滑动出屏幕或者点击checkbox时会出现错位等一些问题也解决了,具体方法请往下浏览(文末附上github 地址)
页面布局
布局很简单,ExpandableListView 加底部一个Button,直接上布局截图,相信各位能看懂
activity_main.png
由于其控件会默认自带箭头(如下图)
默认自带的指示器箭头.png
我们可以通过XML中在ExpandableListView控件加上
android:groupIndicator="@null"
取消掉其自带的指示器箭头,当然除了在xml上,也可通过在代码中,当绑定完控件后调用代码也可实现取消效果
expandableListView.setGroupIndicator(null);
接下来是我们重点要研究的适配器StudentExpandableAdapter,继承并重写了BaseExpandableListAdapter这个类的相关函数,其中注释我已经详细写在代码中,若是不懂或者写错,希望各位可以交流或指出,大家一起加深对其认识。
public class StudentExpandableAdapter extends BaseExpandableListAdapter {
private Context context;
private List<DataEntity> dataEntity;
private CheckBoxListener checkBoxListener;
public StudentExpandableAdapter(Context context, List<DataEntity> dataEntity) {
this.context = context;
this.dataEntity = dataEntity;
}
/**
* 获取组的数目
*
* @return 返回一级列表组的数量
*/
@Override
public int getGroupCount() {
return dataEntity == null ? 0 : dataEntity.size();
}
/**
* 获取指定组中的子节点数量
*
* @param groupPosition 子元素组所在的位置
* @return 返回指定组中的子数量
*/
@Override
public int getChildrenCount(int groupPosition) {
return dataEntity.get(groupPosition).getChildrenDataList().size();
}
/**
* 获取与给定组相关联的对象
*
* @param groupPosition 子元素组所在的位置
* @return 返回指定组的子数据
*/
@Override
public Object getGroup(int groupPosition) {
return dataEntity.get(groupPosition).getTitle();
}
/**
* 获取与给定组中的给定子元素关联的数据
*
* @param groupPosition 子元素组所在的位置
* @param childPosition 子元素的位置
* @return 返回子元素的对象
*/
@Override
public Object getChild(int groupPosition, int childPosition) {
return dataEntity.get(groupPosition).getChildrenDataList().get(childPosition);
}
/**
* 获取组在给定位置的ID(唯一的)
*
* @param groupPosition 子元素组所在的位置
* @return 返回关联组ID
*/
@Override
public long getGroupId(int groupPosition) {
return groupPosition;
}
/**
* 获取给定组中给定子元素的ID(唯一的)
*
* @param groupPosition 子元素组所在的位置
* @param childPosition 子元素的位置
* @return 返回子元素关联的ID
*/
@Override
public long getChildId(int groupPosition, int childPosition) {
return childPosition;
}
/**
* @return 确定id 是否总是指向同一个对象
*/
@Override
public boolean hasStableIds() {
return true;
}
/**
* @return 返回指定组的对应的视图 (一级列表样式)
*/
@Override
public View getGroupView(final int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
ParentHolder parentHolder;
if (convertView == null) {
convertView = LayoutInflater.from(context).inflate(R.layout.parent_item, null);
parentHolder = new ParentHolder();
parentHolder.tvParent = convertView.findViewById(R.id.tv_parent);
parentHolder.img_right = convertView.findViewById(R.id.img_right);
convertView.setTag(parentHolder);
} else {
parentHolder = (ParentHolder) convertView.getTag();
}
parentHolder.tvParent.setText(dataEntity.get(groupPosition).getTitle());
//共用一个右箭头,如果展开则顺时针旋转90°选择,否则不旋转
if (isExpanded) parentHolder.img_right.setRotation(90F);
else parentHolder.img_right.setRotation(0F);
return convertView;
}
/**
* @return 返回指定位置对应子视图的视图
*/
@Override
public View getChildView(final int groupPosition, final int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
final ChildrenHolder childrenHolder;
if (convertView == null) {
convertView = LayoutInflater.from(context).inflate(R.layout.childrens_item, null);
childrenHolder = new ChildrenHolder();
childrenHolder.tvChild = convertView.findViewById(R.id.tv_child);
childrenHolder.checkBox = convertView.findViewById(R.id.checkbox);
convertView.setTag(childrenHolder);
} else {
childrenHolder = (ChildrenHolder) convertView.getTag();
}
//Log.e("666","班级:"+dataEntity.get(groupPosition).getTitle()+" 学生:"+dataEntity.get(groupPosition).getChildrenDataList().get(childPosition).getSubContent()+" isChecked:"+dataEntity.get(groupPosition).getChildrenDataList().get(childPosition).isSelect());
childrenHolder.checkBox.setChecked(dataEntity.get(groupPosition).getChildrenDataList().get(childPosition).isSelect());
childrenHolder.tvChild.setText(dataEntity.get(groupPosition).getChildrenDataList().get(childPosition).getSubContent());
childrenHolder.checkBox.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
boolean isChecked = !dataEntity.get(groupPosition).getChildrenDataList().get(childPosition).isSelect();
dataEntity.get(groupPosition).getChildrenDataList().get(childPosition).setSelect(!isChecked);
Log.e("groupPosition:" + groupPosition, "childPosition:" + childPosition + " isChecked:" + isChecked);
checkBoxListener.checkStateListener(groupPosition, childPosition, isChecked);
}
});
return convertView;
}
/**
* 指定位置的子元素是否可选
*
* @param groupPosition 子元素组所在的位置
* @param childPosition 子元素的位置
* @return 返回是否可选
*/
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;
}
class ParentHolder {
TextView tvParent;
ImageView img_right;
}
class ChildrenHolder {
TextView tvChild;
CheckBox checkBox;
}
/**
* 用于提供对外复选框修改通知接口
*/
public interface CheckBoxListener {
void checkStateListener(int groupPosition, int childPosition, boolean isChecked);
}
public void setCheckBoxListener(CheckBoxListener checkBoxListener) {
this.checkBoxListener = checkBoxListener;
}
/**
* 用于刷新更新后的数据
*/
public void reFreshData(List<DataEntity> dataEntity) {
this.dataEntity = dataEntity;
notifyDataSetChanged();
}
}
父布局使用的xml:
parent_item.png
子布局使用的xml:
childrens_item.png
注意在getGroupView中getGroupView的控件不能设置一些抢占焦点的事件或属性,如点击事件或者在代码布局里设置了focusable属性为true,都会导致无法展开子列表。
getChildView中子视图,checkBox不调用setOnCheckedChangeListener是由于可能会因为选中的组展开触发而导致混乱,这边改为使用setOnClickListener,这是一种折中方案,因为状态的改变不是来自事件onClick(也就是你点击了不一定知道状态是否成功更改),OnCheckChangedListener则是监听CheckBox的状态,成功后回调。
实体类DataEntity 代码如下
public class DataEntity {
private String title;//一级列表内容
private List<ChildrenData> childrenDataList;
public DataEntity(String title, List<ChildrenData> childrenDataList) {
this.title = title;
this.childrenDataList = childrenDataList;
}
public List<ChildrenData> getChildrenDataList() {
return childrenDataList;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public void setChildrenDataList(List<ChildrenData> childrenDataList) {
this.childrenDataList = childrenDataList;
}
public static class ChildrenData{
private String subContent;//子内容
private boolean select;//是否选中
public ChildrenData(String subContent, boolean select) {
this.subContent = subContent;
this.select = select;
}
public String getSubContent() {
return subContent;
}
public void setSubContent(String subContent) {
this.subContent = subContent;
}
public boolean isSelect() {
return select;
}
public void setSelect(boolean select) {
this.select = select;
}
}
}
最后是在我们的主界面中实现代码
public class MainActivity extends AppCompatActivity {
private ExpandableListView expandableListView;
private Button btn_select;
private boolean selectAll;
private List<DataEntity> dataEntityList=new ArrayList<>();
private StudentExpandableAdapter studentExpandableAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
expandableListView =findViewById(R.id.listView);
btn_select=findViewById(R.id.btn_select);
initData();
initAdapter();
setOnClickEvent();
}
private void initData() {
for(int i=0;i<5;i++){
List<DataEntity.ChildrenData> childrenData=new ArrayList<>();
for(int j=0;j<8;j++){
DataEntity.ChildrenData children=new DataEntity.ChildrenData("学生"+(j+1),false);
childrenData.add(children);
}
DataEntity dataEntity=new DataEntity((i+1)+"班",childrenData);
dataEntityList.add(dataEntity);
}
}
private void setOnClickEvent() {
btn_select.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
selectAll=!selectAll;
if(selectAll){
//遍历设置全选
for(int i=0;i<dataEntityList.size();i++){
for(int j=0;j<dataEntityList.get(i).getChildrenDataList().size();j++){
dataEntityList.get(i).getChildrenDataList().get(j).setSelect(true);
}
}
}else {
//遍历设置取消全选
for(int i=0;i<dataEntityList.size();i++){
for(int j=0;j<dataEntityList.get(i).getChildrenDataList().size();j++){
dataEntityList.get(i).getChildrenDataList().get(j).setSelect(false);
}
}
}
studentExpandableAdapter.reFreshData(dataEntityList);
btn_select.setText(selectAll? "取消全选":"全选");
}
});
}
private void initAdapter() {
studentExpandableAdapter=new StudentExpandableAdapter(this,dataEntityList);
expandableListView.setAdapter(studentExpandableAdapter);
studentExpandableAdapter.setCheckBoxListener(new StudentExpandableAdapter.CheckBoxListener() {
@Override
public void checkStateListener(int groupPosition, int childPosition, boolean isChecked) {
Log.e("MainActivity","isChecked:"+isChecked);
dataEntityList.get(groupPosition).getChildrenDataList().get(childPosition).setSelect(isChecked);
studentExpandableAdapter.reFreshData(dataEntityList);
}
});
/**
* 默认展开某个item
* */
//expandableListView.expandGroup(1);
}
}
至此,简单讲完了ExpandableListView 的基础使用,希望能对小伙伴们提供一点帮助。
最后附上该项目的github