高级控件之分组列表视图(ExpandableListView)

一.ExpandableListView的基础知识

      和ListView不同的是它是一个两级的滚动列表视图,每一个组可以展开,显示一些子项,类似于 QQ列表,这些项目来至于ExpandableListAdapter的子类,也就是说,要实现向里面添加项目,必须写一个子类实现ExpandableListAdapter的接口或者使用系统为我们实现在子类。

(一)常用属性:

  1. android:childDivider 指定各组内子类表项之间的分隔条,
  2. android:childIndicator 显示在子列表旁边的Drawable对象
  3. android:childIndicatorLeft 子列表项指示符的左边约束位置
  4. android:childIndicatorRight 子列表项指示符的右边约束位置
  5. android:groupIndicator 显示在组列表旁边的Drawable对象
  6. android:indicatorLeft 组列表项指示器的左边约束位置
  7. android:indicatorRight 组列表项指示器的右边约束位置

(二)适配器

      一般适用于ExpandableListView的Adapter都要继承BaseExpandableListAdapter这个类, 并且必须重载getGroupView和getChildView这两个最为重要的方法。

当扩展BaseExpandableListAdapter时,关键是实现如下四个抽象方法:
方法1:
//取得显示给定分组给定子位置的数据用的视图。
public abstract View getChildView (int groupPosition, intchildPosition , boolean isLastChild, ViewconvertView, ViewGroup parent){
//groupPosition 包含要取得子视图的分组位置。
//childPosition 分组中子视图(要返回的视图)的位置。
//isLastChild 该视图是否为组中的最后一个视图。
//convertView 如果可能,重用旧的视图对象,使用前你应该保证视图对象为非 空,并且是否是合适的类型,如果该对象不能转换为可以正确显示数据的视图,该方法就创建新 视图.不保证使用先前由getChildView(int, int,boolean, View, ViewGroup)创建的 视图.
//parent 该视图最终从属的父视图.
返回 指定位置相应的子视图.
}

方法2:
//取得指定分组的子元素数。
public abstract int getChildrenCount (int groupPosition){
//groupPosition 要取得子元素个数的分组位置。
返回 指定分组的子元素个数.
}

方法3:
//取得用于显示给定分组的视图.这个方法仅返回分组的视图对象,要想获取子元素的视图对象 ,就需要调用getChildView(int, int, boolean, View, ViewGroup)。
public abstract View getGroupView (int groupPosition, booleanisExpanded, View convertView, ViewGroupparent){
//groupPosition 决定返回哪个视图的组位置.
//isExpanded 该组是展开状态还是收起状态
//convertView 如果可能,重用旧的视图对象.使用前你应该保证视图对象为非空, 并且是否是合适的类型.如果该对象不能转换为可以正确显示数据的视图,该方法就创建新视图. 不保证使用先前由getGroupView(int, boolean,View, ViewGroup)创建的视图.
//parent 该视图最终从属的父视图.
返回 指定位置相应的组视图
}

方法4:
//取得分组数
public abstract int getGroupCount (){
返回 分组数
}

      注意:在XML布局文件中,如果ExpandableListView上一级视图的大小没有严格定义的 话,则不能对ExpandableListView的android:layout_height属性使用wrap_content 值。(例如,如果上一级视图是ScrollView的话,则不应该指定wrap_content的值,因为它可以是任意的长度。不过,如果ExpandableListView的上一级视图有特定的大小的 话,比如100像素,则可以使用wrap_content)。

二.分组列表示例的设计

程序运行后的界面:
f1

点击一级条目后显示二级条目的界面:
f2

程序中图像的显示是圆角的形式的,需要用到自定义类,程序设计代码如下:

(一)主布局页面activity_main.xml的设计

<RelativeLayout 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" >

    <ExpandableListView
        android:dividerHeight="2dp"
        android:divider="@null"
        android:id="@+id/main_elv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:groupIndicator="@null" />

</RelativeLayout>

(二)一级目录布局文件item_group.xml的设计

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/shape"
    android:padding="20dp" >

    <TextView
        android:id="@+id/groupName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="groutName"
        android:textSize="20sp" />

    <TextView
        android:id="@+id/onlinenum"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_centerVertical="true"
        android:text="0 / 8" />

</RelativeLayout>

(三)二级目录的布局item_user.xml文件的设计

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center_vertical"
    android:padding="10dp" >
//圆角自定义View的使用:包名+类名
    <com.example.lesson7_expandablelistview.CircleImageView
        android:id="@+id/item_iv"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_centerVertical="true"
        android:src="@drawable/ic_launcher" />

    <TextView
        android:id="@+id/netName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/item_iv"
        android:paddingBottom="5dp"
        android:text="netName" />

    <TextView
        android:id="@+id/isonline"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/netName"
        android:layout_toRightOf="@id/item_iv"
        android:text="[在线]" />

    <TextView
        android:id="@+id/sign"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/netName"
        android:layout_toRightOf="@id/isonline"
        android:text="sign" />

</RelativeLayout>

(四)圆角显示图片的自定义View的设计

这里不需要它深入理解里面的代码,直接复制使用就可以了

package com.example.lesson7_expandablelistview;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.graphics.Xfermode;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.NinePatchDrawable;
import android.util.AttributeSet;
import android.widget.ImageView;
/**
 *圆角显示图片的自定义View 
 *
 */
public class CircleImageView extends ImageView {
    private static final Xfermode MASK_XFERMODE;
    private Bitmap mask;
    private Paint paint;
    private int mBorderWidth = 10;
    private int mBorderColor = Color.parseColor("#f2f2f2");
    private boolean useDefaultStyle = false;

    static {
        PorterDuff.Mode localMode = PorterDuff.Mode.DST_IN;
        MASK_XFERMODE = new PorterDuffXfermode(localMode);
    }

    public CircleImageView(Context context) {
        super(context);
    }

    public CircleImageView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CircleImageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        TypedArray a = context.obtainStyledAttributes(attrs,
                R.styleable.CircularImage);
        mBorderColor = a.getColor(R.styleable.CircularImage_border_color,
                mBorderColor);
        final int def = (int) (2 * context.getResources().getDisplayMetrics().density + 0.5f);
        mBorderWidth = a.getDimensionPixelOffset(
                R.styleable.CircularImage_border_width, def);
        a.recycle();
    }

    void useDefaultStyle(boolean useDefaultStyle) {
        this.useDefaultStyle = useDefaultStyle;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (useDefaultStyle) {
            super.onDraw(canvas);
            return;
        }
        final Drawable localDraw = getDrawable();
        if (localDraw == null) {
            return;
        }
        if (localDraw instanceof NinePatchDrawable) {
            return;
        }
        if (this.paint == null) {
            final Paint localPaint = new Paint();
            localPaint.setFilterBitmap(false);
            localPaint.setAntiAlias(true);
            localPaint.setXfermode(MASK_XFERMODE);
            this.paint = localPaint;
        }
        final int width = getWidth();
        final int height = getHeight();
        int layer = canvas.saveLayer(0.0F, 0.0F, width, height, null, 31);
        localDraw.setBounds(0, 0, width, height);
        localDraw.draw(canvas);
        if ((this.mask == null) || (this.mask.isRecycled())) {
            this.mask = createOvalBitmap(width, height);
        }
        canvas.drawBitmap(this.mask, 0.0F, 0.0F, this.paint);
        canvas.restoreToCount(layer);
        drawBorder(canvas, width, height);
    }

    private void drawBorder(Canvas canvas, final int width, final int height) {
        if (mBorderWidth == 0) {
            return;
        }
        final Paint mBorderPaint = new Paint();
        mBorderPaint.setStyle(Paint.Style.STROKE);
        mBorderPaint.setAntiAlias(true);
        mBorderPaint.setColor(mBorderColor);
        mBorderPaint.setStrokeWidth(mBorderWidth);
        canvas.drawCircle(width / 2, height / 2, (width - mBorderWidth) / 2,
                mBorderPaint);
        canvas = null;
    }

    public Bitmap createOvalBitmap(final int width, final int height) {
        Bitmap.Config localConfig = Bitmap.Config.ARGB_8888;
        Bitmap localBitmap = Bitmap.createBitmap(width, height, localConfig);
        Canvas localCanvas = new Canvas(localBitmap);
        Paint localPaint = new Paint();
        final int padding = (mBorderWidth - 3) > 0 ? mBorderWidth - 3 : 1;

        RectF localRectF = new RectF(padding, padding, width - padding, height
                - padding);
        localCanvas.drawOval(localRectF, localPaint);

        return localBitmap;
    }

}

(五)圆角自定义View的资源文件:values中circle_attr.xml文件

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="CircularImage">
        <attr name="border_width" format="dimension" />
        <attr name="border_color" format="color" />
    </declare-styleable>

</resources>

(六)一级目录的数据原型的类

package com.example.lesson7_expandablelistview;

import java.util.ArrayList;
import java.util.List;

/*
 * 设置一级条目中显示的信息,但是里面包含二级条目 的数据
 *   */

public class Group {
    // 条目包含的内容
    // 组名名字
    String groupName;
    // 组里面包含的人的信息,以对象的形式存放在集合中
    List<User> usersList=new ArrayList<User>();

    // 该组的总人数
    public int getNumOfGroup() {
        return usersList.size();
    }

    // 获取对应组中在线的人数
    public int getOnlineUserOfGrout() {

        // 获取在线的人的数量
        int onLineNum = 0;
        // 判断groupList里面的每个人的在线状态,在线就数量加1
        for (User user : usersList) {
            if (user.isOnline) {
                onLineNum++;
            }
        }
        // 返回的是在线的人数
        return onLineNum;
    }

    // 添加组内的用户
    public void addUserToGroup(User user) {
        usersList.add(user);
    }

}

(七)二级条目的数据原型的类的设计

package com.example.lesson7_expandablelistview;

/* 
 * 二级条目数据
 *   每一个用户显示的基本信息
 *  */

public class User {

    // 用户的基本信息
    int imageId;
    String net_Name;
    boolean isOnline;
    String sign;

    // 重写用户的构造方法,方便数据的写入
    public User(int imageId, String net_Name, boolean isOnline, String sign) {
        super();
        this.imageId = imageId;
        this.net_Name = net_Name;
        this.isOnline = isOnline;
        this.sign = sign;
    }

    // get和set方法
    public int getImageId() {
        return imageId;
    }

    public void setImageId(int imageId) {
        this.imageId = imageId;
    }

    public String getNet_Name() {
        return net_Name;
    }

    public void setNet_Name(String net_Name) {
        this.net_Name = net_Name;
    }

    public boolean isOnline() {
        return isOnline;
    }

    public void setOnline(boolean isOnline) {
        this.isOnline = isOnline;
    }

    public String getSign() {
        return sign;
    }

    public void setSign(String sign) {
        this.sign = sign;
    }

}

(八)ExpandableListView的适配器

package com.example.lesson7_expandablelistview;

import java.util.List;

import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.ImageView;
import android.widget.TextView;

public class MyExpandableListViewAdapter extends BaseExpandableListAdapter {

    // 所有组的数据,每一组的数组里面又包含多个User信息
    private List<Group> groupList;

    Context context;

    // 通过构造方法传入上下文
    public MyExpandableListViewAdapter(Context content, List<Group> groupList) {
        this.context = content;
        this.groupList = groupList;
    }

    // 显示的组的数量
    @Override
    public int getGroupCount() {
        // 返回的是页面显示多少个组的信息
        return groupList.size();
    }

    // 显示的是组里面的用户的个数,这里要根据组的变化而变化
    // 参数就是所在组的游标值
    @Override
    public int getChildrenCount(int groupPosition) {
        // 先获得集合中对应的对象,再通过对象的方法获取里面的组的人数
        return groupList.get(groupPosition).getNumOfGroup();
    }

    // 获取对应组的对象
    @Override
    public Group getGroup(int groupPosition) {
        // 通过组的集合的序列值就可以拿到
        return groupList.get(groupPosition);
    }

    // 获取对应组里面的对应用户(里面包含用户的信息)
    // 第一个参数组的游标值,
    // 第二个参数数用户在该组的游标值
    @Override
    public User getChild(int groupPosition, int childPosition) {
        // 先找到该组,然后通过该组的用户列表集合的对应序列,来获取用户的信息
        return groupList.get(groupPosition).usersList.get(childPosition);
    }

    // 组的序列号
    @Override
    public long getGroupId(int groupPosition) {
        return groupPosition;
    }

    // 用户的序列号
    @Override
    public long getChildId(int groupPosition, int childPosition) {
        return childPosition;
    }

    // 是否是稳定的ID,这里一般选择的是true
    @Override
    public boolean hasStableIds() {
        return true;
    }

    // 用户条目是否可点击,这里一般也是选择返回true
    @Override
    public boolean isChildSelectable(int groupPosition, int childPosition) {
        return true;
    }

    // 下面两个方法是最麻烦和最重要的方法

    // 返回的是组的视图界面
    @Override
    public View getGroupView(int groupPosition, boolean isExpanded,
            View convertView, ViewGroup parent) {

        GroupHolder holder;

        // 第一个加载条目时
        if (convertView == null) {
            // 加载组的布局页面
            convertView = View.inflate(context, R.layout.item_group, null);
            // 把加载的布局添加到holder里面
            holder = new GroupHolder(convertView);
            // 在缓冲的convertView对象中添加一个holder对象的标签
            convertView.setTag(holder);
            // 之后每一个对应的convertView都会有一个holder对象的值

        } else {
            // 如果不是第一次来加载条目,就用以前缓冲的数据
            holder = (GroupHolder) convertView.getTag();// holder里面已经包含了布局的信息
            Group group = getGroup(groupPosition);// 获取组的条目的对象
            // 对组中的布局显示Group对象的文本数据
            holder.groupName.setText(group.groupName);
            holder.onlineNum.setText(group.getOnlineUserOfGrout() + " / "
                    + group.getNumOfGroup());

        }

        // 返回的数具有View数据的缓冲数据
        return convertView;
    }

    // 返回的是每一个用户的视图界面
    @Override
    public View getChildView(int groupPosition, int childPosition,
            boolean isLastChild, View convertView, ViewGroup parent) {
        ChildHolder holder;

        // 第一个加载条目时
        if (convertView == null) {
            // 加载组的布局页面
            convertView = View.inflate(context, R.layout.item_user, null);
            // 把加载的布局添加到holder里面
            holder = new ChildHolder(convertView);
            // 在缓冲的convertView对象中添加一个holder对象的标签
            convertView.setTag(holder);
            // 之后每一个对应的convertView都会有一个holder对象的值

        } else {
            // 如果不是第一次来加载条目,就用以前缓冲的数据
            holder = (ChildHolder) convertView.getTag();// holder里面已经包含了布局的信息
            User user = getChild(groupPosition, childPosition);// 获取用户的条目的对象
            // 对用户的布局显示User对象的文本数据
            holder.iv.setImageResource(user.imageId);
            holder.netName.setText(user.net_Name);
            holder.isonline.setText(user.isOnline ? "[在线]" : "[离线]");
            holder.sign.setText(user.sign);

        }

        // 返回的数具有View数据的缓冲数据
        return convertView;
    }

    // 这里还是创建两个ViewHolder来方便进行缓冲数据

    class GroupHolder {
        TextView groupName;
        TextView onlineNum;

        public GroupHolder(View convertView) {
            groupName = (TextView) convertView.findViewById(R.id.groupName);
            onlineNum = (TextView) convertView.findViewById(R.id.onlinenum);
        }
    }

    class ChildHolder {
        ImageView iv;
        TextView netName;
        TextView isonline;
        TextView sign;

        public ChildHolder(View convertView) {
            iv = (ImageView) convertView.findViewById(R.id.item_iv);
            netName = (TextView) convertView.findViewById(R.id.netName);
            isonline = (TextView) convertView.findViewById(R.id.isonline);
            sign = (TextView) convertView.findViewById(R.id.sign);
        }
    }

}

(九)主方法类的设计

package com.example.lesson7_expandablelistview;

import java.util.ArrayList;
import java.util.List;

import android.app.Activity;
import android.os.Bundle;
import android.widget.ExpandableListView;

public class MainActivity extends Activity {

    // 组的对象集合
    List<Group> groupList;

    // 布局控件
    ExpandableListView elv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();// 初始化数据
    }

    // 初始化数据
    private void initView() {
        elv = (ExpandableListView) findViewById(R.id.main_elv);

        // 添加数据源

        // 数据源
        // 图片1
        int[] images = { R.drawable.a1, R.drawable.a2, R.drawable.a3,
                R.drawable.a4, R.drawable.a5, R.drawable.a6, R.drawable.a7,
                R.drawable.a8, R.drawable.a9 };
        // 姓名
        String[] names = { "唐嫣", "杨幂", "赵丽颖", "王子文", "张佳宁", "江铠同", "杨紫", "唐艺昕",
                "关晓彤" };

        // 文本内容
        String[] sings = {
                "1983年12月6日出生于上海。2006年毕业于中央戏剧学院表演系本科班。",
                "1986年9月12日出生于北京市,中国内地影视女演员、流行乐歌手、影视制片人。",
                "1987年10月16日出生于河北省廊坊市,中国内地影视女演员。2006年,因获得雅虎搜星比赛冯小刚组冠军而进入演艺圈;",
                "1987年2月28日出生于四川成都,中国内地影视女演员、模特,毕业于中央戏剧学院。",
                "1989年出生于吉林省辽源市,中国大陆女演员,2009年毕业于中央戏剧学院表演系本科。",
                "1989年10月24日出生于山东省青岛市,毕业于中央戏剧学院,中国内地女演员。",
                "1992年11月6日出生于北京市,2014年毕业于北京电影学院2010级表演系本科班。2002年,出演古装历史情感剧《孝庄秘史》开始崭露头角;",
                "出生于四川省,现居住地重庆 ,中国内地女演员。2010年毕业于重庆大学美视电影学院表演系。2011年,唐艺昕参与郑晓龙导演执导的《后宫·甄嬛传》,开启了演艺生涯。 ",
                "1997年9月17日出生于北京,学生、演员。2016年4月14日,北影艺考成绩出炉,关晓彤专业排名第一。" };
        boolean[] isonlines = { true, false, true, false, true, true, false,
                true, true };

        User[] user = new User[names.length];

        // 实例化User对象
        for (int i = 0; i < names.length; i++) {
            user[i] = new User(images[i], names[i], isonlines[i], sings[i]);
        }

        // 这里九个用户,设置第一组3人,第二组2人,第三组4人
        // 实例化组对象存放的集合
        groupList = new ArrayList<Group>();
        // 一组
        Group group1 = new Group();
        group1.addUserToGroup(user[0]);
        group1.addUserToGroup(user[1]);
        group1.addUserToGroup(user[2]);
        group1.groupName = "小学同学";

        // 二组
        Group group2 = new Group();
        group2.addUserToGroup(user[3]);
        group2.addUserToGroup(user[4]);
        group2.groupName = "初中同学";

        // 三组
        Group group3 = new Group();
        group3.addUserToGroup(user[5]);
        group3.addUserToGroup(user[6]);
        group3.addUserToGroup(user[7]);
        group3.addUserToGroup(user[8]);
        group3.groupName = "大学同学";

        // 把一组添加到组的集合中
        groupList.add(group1);
        // 把二组添加到组的集合中
        groupList.add(group2);
        // 把三组添加到组的集合中
        groupList.add(group3);

        // 创建自定义好的适配器
        MyExpandableListViewAdapter adapter = new MyExpandableListViewAdapter(this, groupList);
        // 添加适配器到固定的布局中
        elv.setAdapter(adapter);

    }

}

上面就是ExpandableListView显示的使用示例。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

峥嵘life

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值