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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,755评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,369评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,799评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,910评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,096评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,159评论 3 411
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,917评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,360评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,673评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,814评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,509评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,156评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,123评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,641评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,728评论 2 351

推荐阅读更多精彩内容