Android中ExpandableListView的使用(一)

Android中ExpandableListView的使用(一)ExpandableListView是可扩展的下拉列表,它的可扩展性在于点击父item可以拉下或收起列表,适用于一些场景的使用

大家好,又见面了,我是你们的朋友全栈君。

相关文章:

Android中ExpandableListView的使用(二)

ExpandableListView是可扩展的下拉列表,它的可扩展性在于点击父item可以拉下或收起列表,适用于一些场景的使用,下面介绍的是在Activity中如何使用,关于它的各种样式的详细解释请见另一篇文章:Android中ExpandableListView常用属性总结

下面介绍它的基本使用方法

先看一下效果:

Android中ExpandableListView的使用(一)

一、最基本的使用

新建一个布局文件expandable_layout.xml,内容很简单,一个LinearLayout里面包含了一个ExpandableListView,别忘了给它加上id:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <ExpandableListView
        android:id="@+id/expandablelistview"
        android:layout_margin="5dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>

然后在activity文件中引入这个layout,获取这个ExpandableListView:

private ExpandableListView listview;
setContentView(R.layout.expandable_layout);
listview = (ExpandableListView) findViewById(R.id.expandablelistview);

为了给ExpandableListView提供数据,需要先初始化数据,这里使用一个Map来存放数据,类型为<String, List<String>>:

private Map<String, List<String>> dataset = new HashMap<>();
private String[] parentList = new String[]{"first", "second", "third"};
private List<String> childrenList1 = new ArrayList<>();
private List<String> childrenList2 = new ArrayList<>();
private List<String> childrenList3 = new ArrayList<>();
private void initialData() {
    childrenList1.add(parentList[0] + "-" + "first");
    childrenList1.add(parentList[0] + "-" + "second");
    childrenList1.add(parentList[0] + "-" + "third");
    childrenList2.add(parentList[1] + "-" + "first");
    childrenList2.add(parentList[1] + "-" + "second");
    childrenList2.add(parentList[1] + "-" + "third");
    childrenList3.add(parentList[2] + "-" + "first");
    childrenList3.add(parentList[2] + "-" + "second");
    childrenList3.add(parentList[2] + "-" + "third");
    dataset.put(parentList[0], childrenList1);
    dataset.put(parentList[1], childrenList2);
    dataset.put(parentList[2], childrenList3);
}

然后需要自己实现一个Adapte类,用于为ExpandableListView提供数据,该类继承了BaseExpandableListAdapter,下面这个是最简单的自定义的类:

private class MyExpandableListViewAdapter extends BaseExpandableListAdapter {

    //  获得某个父项的某个子项
    @Override
    public Object getChild(int parentPos, int childPos) {
        return dataset.get(parentList[parentPos]).get(childPos);
    }

    //  获得父项的数量
    @Override
    public int getGroupCount() {
        return dataset.size();
    }

    //  获得某个父项的子项数目
    @Override
    public int getChildrenCount(int parentPos) {
        return dataset.get(parentList[parentPos]).size();
    }

    //  获得某个父项
    @Override
    public Object getGroup(int parentPos) {
        return dataset.get(parentList[parentPos]);
    }

    //  获得某个父项的id
    @Override
    public long getGroupId(int parentPos) {
        return parentPos;
    }

    //  获得某个父项的某个子项的id
    @Override
    public long getChildId(int parentPos, int childPos) {
        return childPos;
    }

    //  按函数的名字来理解应该是是否具有稳定的id,这个方法目前一直都是返回false,没有去改动过
    @Override
    public boolean hasStableIds() {
        return false;
    }

    //  获得父项显示的view
    @Override
    public View getGroupView(int parentPos, boolean b, View view, ViewGroup viewGroup) {
        return view;
    }

    //  获得子项显示的view
    @Override
    public View getChildView(int parentPos, int childPos, boolean b, View view, ViewGroup viewGroup) {
        return view;
    }

    //  子项是否可选中,如果需要设置子项的点击事件,需要返回true
    @Override
    public boolean isChildSelectable(int i, int i1) {
        return false;
    }
 }

其中注释说明了每个方法的作用,这个adapter的所有数据来源都是刚刚初始化的dataset,因此当这个adapter需要返回父项的数目时,返回的就是dataset的大小,如果需要返回某个父项的某个子项时,通过父项的position,使用map的get方法即可获得。自定义的类中最重要的是下面这两个方法,下面先介绍getGroupView方法:

        //  获得父项显示的view
        @Override
        public View getGroupView(int parentPos, boolean b, View view, ViewGroup viewGroup) {
            if (view == null) {
                LayoutInflater inflater = (LayoutInflater) ExpandableListViewTestActivity
                        .this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                view = inflater.inflate(R.layout.parent_item, null);
            }
            view.setTag(R.layout.parent_item, parentPos);
            view.setTag(R.layout.child_item, -1);
            TextView text = (TextView) view.findViewById(R.id.parent_title);
            text.setText(parentList[parentPos]);
            return view;
        }

这个方法用来指定父项显示的样式,内容和行为,其中使用了parent_item布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:id="@+id/parent_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:textColor="@color/black"
        android:textStyle="bold"
        android:text="这是父item"
        android:layout_margin="5dp"/>
</LinearLayout>

布局很简单,只是一个LinearLayout包含了一个TextView。

getGroupView方法先判断view是否为空,如果view不为空,说明已经加载过一次parent_item布局,因此不需要重复加载以提高效率。如果view为空,那么使用如下方法加载parent_item布局:

if (view == null) {
    LayoutInflater inflater = (LayoutInflater) ExpandableListViewTestActivity
                   .this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    view = inflater.inflate(R.layout.parent_item, null);
}

然后定义父项的内容和行为,这里只需要定义父项的内容,先通过id获得parent_item布局中的TextView,然后通过方法提供的parentPos参数获取到存放在dataset中的内容,调用TextView的setText方法即可设置父项要显示的内容:
TextView text = (TextView) view.findViewById(R.id.parent_title);
text.setText(parentList[parentPos]);

然后将view返回即可。

接下来是getChildView方法:

        //  获得子项显示的view
        @Override
        public View getChildView(int parentPos, int childPos, boolean b, View view, ViewGroup viewGroup) {
            if (view == null) {
                LayoutInflater inflater = (LayoutInflater) ExpandableListViewTestActivity
                        .this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                view = inflater.inflate(R.layout.child_item, null);
            }
            view.setTag(R.layout.parent_item, parentPos);
            view.setTag(R.layout.child_item, childPos);
            TextView text = (TextView) view.findViewById(R.id.child_title);
            text.setText(dataset.get(parentList[parentPos]).get(childPos));
            text.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Toast.makeText(ExpandableListViewTestActivity.this, "点到了内置的textview", Toast.LENGTH_SHORT).show();
                }
            });
            return view;
        }

这个方法用于定义子项的布局,内容和行为,同样需要一个child_item来指定子项的布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:id="@+id/child_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="18sp"
        android:textColor="@color/black"
        android:text="这是子item"
        android:layout_margin="5dp"/>
</LinearLayout>

关于view的处理和父项一样,这里在方法中还给子项显示内容的textview添加了一个点击的监听器。

adapter自定义完之后,通过声明一个Adapter,实例化后调用setAdapter方法即可:

private MyExpandableListViewAdapter adapter;
adapter = new MyExpandableListViewAdapter();
listview.setAdapter(adapter);

至此,最基本的ExpandableListView的使用就可以满足啦

二、稍微复杂一点的使用方法

1、为子项添加点击的监听事件,效果图:

Android中ExpandableListView的使用(一)

只需要调用ExpandableListView的setOnChildClickListener方法即可:

        listview.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
            @Override
            public boolean onChildClick(ExpandableListView expandableListView, View view,
                                        int parentPos, int childPos, long l) {
                Toast.makeText(ExpandableListViewTestActivity.this,
                        dataset.get(parentList[parentPos]).get(childPos), Toast.LENGTH_SHORT).show();
                return true;
            }
        });

这里在每个子项被点击了之后会显示是哪个子项被点击了
特别注意
(1)在使用这个方法的时候需要将自定义的adapter中的isChildSelectable方法的返回值设置为true,否则子项的点击不生效,但子项布局中设置的控件的监听器依然可以生效。
        //  子项是否可选中,如果需要设置子项的点击事件,需要返回true
        @Override
        public boolean isChildSelectable(int i, int i1) {
            return true;
        }

(2)如果在子项中对某个控件设置了监听器,这个控件要注意不能铺满整个子项,所以在设置高度和宽度时要特别注意,否则设置了子项的监听器也是没有用的


2、为子项添加长按的监听器
在ExpandableListView中并没有提供设置子项长按监听器的方法,多方查找之后找到了一个算是比较靠谱的方法,目前用起来暂时还没有什么问题,有问题再来更新这篇文章。
效果图:
Android中ExpandableListView的使用(一)
ExpandableListView中关于长按有一个setOnItemLongClickListener方法,但是这个方法有个问题,没办法区分被长按的item是父项还是子项,所以需要在自定义adapter的getGroupView方法和getChildView方法中加一点东西来区分是父项还是子项:
完整的getGroupView方法和getChildView方法:
        //  获得父项显示的view
        @Override
        public View getGroupView(int parentPos, boolean b, View view, ViewGroup viewGroup) {
            if (view == null) {
                LayoutInflater inflater = (LayoutInflater) ExpandableListViewTestActivity
                        .this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                view = inflater.inflate(R.layout.parent_item, null);
            }
            view.setTag(R.layout.parent_item, parentPos);
            view.setTag(R.layout.child_item, -1);
            TextView text = (TextView) view.findViewById(R.id.parent_title);
            text.setText(parentList[parentPos]);
            return view;
        }

        //  获得子项显示的view
        @Override
        public View getChildView(int parentPos, int childPos, boolean b, View view, ViewGroup viewGroup) {
            if (view == null) {
                LayoutInflater inflater = (LayoutInflater) ExpandableListViewTestActivity
                        .this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                view = inflater.inflate(R.layout.child_item, null);
            }
            view.setTag(R.layout.parent_item, parentPos);
            view.setTag(R.layout.child_item, childPos);
            TextView text = (TextView) view.findViewById(R.id.child_title);
            text.setText(dataset.get(parentList[parentPos]).get(childPos));
            text.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Toast.makeText(ExpandableListViewTestActivity.this, "点到了内置的textview",
                            Toast.LENGTH_SHORT).show();
                }
            });
            return view;
        }
这里用到了view的setTag方法,一共设置了两个Tag,标签虽然在设置的时候提示说只要int类型即可,但一开始使用0和1来做tag的时候,显示没有报错,但编译运行就报错了,要求是资源文件的id才行,因此换成了R.layout.parent_item和R.layout.child_item。
如果是父项,就设置R.layout.parent_item为第几个父项,设置R.layout.child_item为-1。如果是子项,就设置R.layout.parent_item属于第几个父项,设置R.layout.child_item为该父项的第几个子项,这样就可以区分被长按的是父项还是子项了。
然后设置ExpandableListView长按item的监听器:
listview.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
            @Override
            public boolean onItemLongClick(AdapterView<?> adapterView, View view, int i, long l) {
                String content = "";
                if ((int) view.getTag(R.layout.child_item) == -1) {
                    content = "父类第" + view.getTag(R.layout.parent_item) + "项" + "被长按了";
                } else {
                    content = "父类第" + view.getTag(R.layout.parent_item) + "项" + "中的"
                            + "子类第" + view.getTag(R.layout.child_item) + "项" + "被长按了";
                }
                Toast.makeText(ExpandableListViewTestActivity.this, content, Toast.LENGTH_SHORT).show();
                return true;
            }
 });

这样就可以区分父项和子项了。

3、更新数据
列表项更新数据一般使用的都是adapter的notifyDataSetChanged()方法,ExpandableListView也不例外,下面展示一下怎么更新数据。
效果图:
Android中ExpandableListView的使用(一)
首先要在原先的expandable_layout.xml文件中添加一个按钮,用来更新数据:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <ExpandableListView
        android:id="@+id/expandablelistview"
        android:layout_margin="5dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <Button
        android:id="@+id/updateData"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:layout_gravity="center"
        android:text="刷新数据"/>
</LinearLayout>

然后在Activity文件中引入这个Button:

private Button button;
button = (Button) findViewById(R.id.updateData);

为button设置点击的监听器:

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                updateData();
                Toast.makeText(ExpandableListViewTestActivity.this, "数据已更新", Toast.LENGTH_SHORT).show();
            }
        });

其中updateData()方法就是用于更新ExpandableListView的方法,看一下具体的实现:

    /**
     * 更新数据
     */
    private void updateData() {
        childrenList1.clear();
        childrenList1.add(parentList[0] + "-new-" + "first");
        childrenList1.add(parentList[0] + "-new-" + "second");
        childrenList1.add(parentList[0] + "-new-" + "third");
        childrenList2.clear();
        childrenList2.add(parentList[1] + "-new-" + "first");
        childrenList2.add(parentList[1] + "-new-" + "second");
        childrenList2.add(parentList[1] + "-new-" + "third");
        childrenList3.clear();
        childrenList3.add(parentList[2] + "-new-" + "first");
        childrenList3.add(parentList[2] + "-new-" + "second");
        childrenList3.add(parentList[2] + "-new-" + "third");
        adapter.notifyDataSetChanged();
    }

还记得上面初始化数据的时候使用的childrenList1、childrenList2、childrenList3吗,这三个列表是我们用来放子项列表,然后已经添加到dataset里面去了。这个时候无需再用dataset添加一次,只需要将列表清空,添加上更新的内容即可。也可以根据自己的需求选择性地删除一些东西来获得新的子项列表。

需要注意的是,子项列表在内存中的地址是不可以改变的,不能使用形如childrenList1 = new ArrayList<>();这样的方法来获的新列表,这样会使childrenList1在内存中的地址发生改变,导致调用adapter的notifyDataSetChanged()方法时不生效。所以如果想要清空这个列表项时,使用childrenList1.clear()方法,这样才能保证顺利更新列表。

文章中使用的代码源码已放上github:https://github.com/sysukehan/AndroidTests.git,存放在ExpandableListViewTest模块下
关于ExpandableListView的使用方法先说到这里,以后如果有问题或者有东西要补充再更新这篇文章

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/148074.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • 常见雪球期权总结_雪球期权和凤凰期权的区别

    常见雪球期权总结_雪球期权和凤凰期权的区别常见雪球期权总结从风险溢价的角度来看,雪球类产品的本质是买方通过承担下跌的尾部风险,换取远超无风险利率的票息收入。对尾部风险的承担则是通过成为看跌期权卖方的形式实现的。标准雪球期权标准雪球期权(

  • 排序二叉树的实现

    排序二叉树的实现在计算机科学中,二叉树是一种重要的非线性的数据结构。每个结点的度均小于等于2,通常子树称为左子树和右子树。而排序二叉树是二叉树中的一种,其满足:1.如左子树不为空,那么左子树上的结点的值都小于其根上的值;2.如右子树不为空,那么右子树上的结点的值都大于其根上的值;3.其子树也是一个排序二叉树。下面用递归的方式来插入一个结点来满足上述的要求:typedefstructNode{

  • vue 往数组添加数据

    vue 往数组添加数据constnum1=[a,s,d]constnum2=[n,i,hao]for(letnofnum1){num2.push(n)}num2.push(…num1)

  • 重复读取输入流_redis同时读写

    重复读取输入流_redis同时读写inputstream只能读取一次,再次读取则无法获取到内容。这是因为inputStream的内部有个pos指针,当读取的时候指针会不断的移动,当移动到末尾的时候,就无法再次读取了。问题解决:方法一:使用ByteArrayOutputStream将字节缓存,每次读取都从ByteArrayOutputStream里面获取。获取ByteArrayOutputStream…

  • 【C语言的日常实践(十四)】constkeyword详细解释

    【C语言的日常实践(十四)】constkeyword详细解释

  • Django(22)Django执行SQL语句

    Django(22)Django执行SQL语句前言Django在查询数据时,大多数查询都能使用ORM提供的API方法,但对于一些复杂的查询可能难以使用ORM的API方法实现,因此Django引入了SQL语句的执行方法,有以下三种执行方式ext

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号