A022-列表容器之ExpandableListView
来源:互联网 发布:科蓝软件java面试题 编辑:程序博客网 时间:2024/05/17 13:39
概述
本节课介绍Android中可实现二级可展开收缩列表的ExpandableListView容器,笔者感觉它非常难用并且难理解,很多时候我们可能需要对控件进行扩展和定制,然而它不太方便扩展,它使用难点主要在数据结构上和对控件的事件监听,其他的实现方式类似ListView,下面会提供笔者在实际开发中使用到的案例。
案例
上面实现的效果可展开的二级列表,每个组项都可能有若干个子项,默认的ExpandableListView不太美观,我们需要通过自定义布局类美化它,在使用过程中有一些需要我们去了解的点,会在实现过程提一下。
实现过程
源码已经上传到Git@OSC,各位可以clone参考
http://git.oschina.net/devilwwj/AndroidDevelopCourse/tree/master/code?dir=1&filepath=code&oid=7961fb146029fb10776b101b918c59ff77fbd672&sha=e65897b0a246924292356f2b488d430c081081ff
布局分为:
- Activity布局
- 组项布局(layout_expand_group.xml)
- 子项布局(layout_expand_item.xml)
layout/activity_expandablelistview.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_width="match_parent" android:layout_height="match_parent" android:cacheColorHint="@color/transparent" android:childDivider="@color/transparent" android:fastScrollEnabled="true" android:groupIndicator="@color/transparent" android:divider="@null" android:listSelector="@color/transparent" android:choiceMode="singleChoice" android:scrollbars="none" > </ExpandableListView></LinearLayout>
layout/layout_expand_group.xml
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/group_layout" android:layout_width="match_parent" android:layout_height="57dp" android:background="#ffffff" android:clickable="true" android:minHeight="?android:attr/listPreferredItemHeight" > <ImageView android:id="@+id/iv_group_icon" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_centerVertical="true" android:layout_marginLeft="18dp" android:background="@drawable/ic_leftnav_10" /> <TextView android:id="@+id/tv_group_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginLeft="59dp" android:text="时局" android:textColor="#333333" android:textSize="16sp" /> <View android:id="@+id/item_group_devider" android:layout_width="match_parent" android:layout_height="0.5dp" android:layout_alignLeft="@+id/tv_group_text" android:layout_alignParentBottom="true" android:background="@color/listview_divider" android:layout_marginRight="10dp" android:visibility="visible"/> <ImageView android:id="@+id/iv_expand" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:layout_marginRight="25dp" android:clickable="true" android:padding="5dp" android:src="@drawable/ic_leftnav_down" /></RelativeLayout>
layout/layout_expand_item.xml
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="40dp" android:background="@drawable/slidingmenu_item_selector" android:minHeight="?android:attr/listPreferredItemHeight" > <!-- android:color="@drawable/item_selected_text_color"/> --> <TextView android:id="@+id/tv_item_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginLeft="75dp" android:text="时局" android:textColor="@drawable/slidingmenu_item_text_selector" android:textSize="15sp" android:clickable="true" /> <View android:id="@+id/item_devider" android:layout_width="match_parent" android:layout_height="0.5dp" android:layout_alignParentBottom="true" android:layout_marginLeft="59dp" android:layout_marginRight="10dp" android:background="@color/listview_divider" android:visibility="visible" /></RelativeLayout>
自定义Adapter
- 继承BaseExpandableListAdapter并实现以下方法
- getGroupCount(获取组项的个数)
- getChildrenCount(获取子项个数)
- getGroup(获取组对象)
- getChild(获取子对象)
- getGroupId(获取组项id)
- getChildId(获取子项id)
- hasStableIds(组和子元素是否持有稳定的ID)
- getGroupView(获取显示指定组的视图对象)
- getChildView(获取显示指定项的视图对象)
- isChildSelectable(子项是否可选中)
- 传入组项列表(如:List<GroupItem>
)
- 传入子项列表(如:List<List<Category>>
)
适配器代码:
com.devilwwj.androiddevelopcourse.adapters.ExpandableListViewAdapter
package com.devilwwj.androiddevelopcourse.adapters;import android.content.Context;import android.view.LayoutInflater;import android.view.View;import android.view.View.OnClickListener;import android.view.ViewGroup;import android.widget.BaseExpandableListAdapter;import android.widget.ExpandableListView;import android.widget.ImageView;import android.widget.RelativeLayout;import android.widget.TextView;import com.devilwwj.androiddevelopcourse.R;import com.devilwwj.androiddevelopcourse.domain.Category;import com.devilwwj.androiddevelopcourse.domain.GroupItem;import java.util.HashMap;import java.util.List;/** * 自定义可展开列表的适配器 */public class ExpandableListViewAdapter extends BaseExpandableListAdapter { private Context mContext;// 上下文 private ExpandableListView expandableListView; // 可展开列表对象 private List<GroupItem> groupList; // 组列表 private List<List<Category>> childList; // 子项列表 private LayoutInflater inflater; // 自定义接口回调监听器 private OnGroupExpandListener OnGroupExpandListener; private OnGroupClickListener onGroupClickListener; private OnChildItemClickListener onChildClickListener; private HashMap<Integer, Boolean> maps = new HashMap<Integer, Boolean>(); private boolean expandStateAtPosition = false; public OnGroupClickListener getOnGroupClickListener() { return onGroupClickListener; } public void setOnGroupClickListener( OnGroupClickListener onGroupClickListener) { this.onGroupClickListener = onGroupClickListener; } public OnGroupExpandListener getOnGroupExpandListener() { return OnGroupExpandListener; } public void setOnGroupExpandListener( OnGroupExpandListener onGroupExpandListener) { OnGroupExpandListener = onGroupExpandListener; } public OnChildItemClickListener getOnChildClickListener() { return onChildClickListener; } public void setOnChildClickListener( OnChildItemClickListener onChildClickListener) { this.onChildClickListener = onChildClickListener; } private int mExpandedGroupPosition; public int getExpandedGroupPosition() { return mExpandedGroupPosition; } public void setExpandedGroupPosition(int mExpandedGroupPosition) { this.mExpandedGroupPosition = mExpandedGroupPosition; } public ExpandableListViewAdapter(Context context, ExpandableListView expandableListView, List<GroupItem> groupList, List<List<Category>> childList) { super(); this.mContext = context; this.expandableListView = expandableListView; this.groupList = groupList; this.childList = childList; inflater = LayoutInflater.from(context); // 初始化列表展开状态 for (int i = 0; i < groupList.size(); i++) { maps.put(i, false); } } private int mGroupPosition = 0; private int mChildPosition = 0; /** * 设置子项被选中方法 * * @param groupPosition * @param childPosition */ public void setItemChecked(int groupPosition, int childPosition) { if (expandableListView == null) { return; } this.mGroupPosition = groupPosition; this.mChildPosition = childPosition; int numberOfGroupThatIsOpened = 0; for (int i = 0; i < groupPosition; i++) { if (expandableListView.isGroupExpanded(i)) { numberOfGroupThatIsOpened += this.getChildrenCount(i); } } int position = numberOfGroupThatIsOpened + groupPosition + childPosition + 1; if (!expandableListView.isItemChecked(position)) { expandableListView.setItemChecked(position, true); } } @Override public int getGroupCount() { return groupList.size(); } @Override public int getChildrenCount(int groupPosition) { return childList.get(groupPosition).size(); } /* * 获得组项 (non-Javadoc) * * @see android.widget.ExpandableListAdapter#getGroup(int) */ @Override public GroupItem getGroup(int groupPosition) { return groupList.get(groupPosition); } @Override public Category getChild(int groupPosition, int childPosition) { return childList.get(groupPosition).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; } @Override public View getGroupView(final int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) { final GroupViewHolder groupViewHolder; if (convertView == null) { convertView = inflater.inflate(R.layout.layout_expand_group, parent, false); groupViewHolder = new GroupViewHolder(convertView); convertView.setTag(groupViewHolder); } else { groupViewHolder = (GroupViewHolder) convertView.getTag(); } GroupItem groupItem = groupList.get(groupPosition); groupViewHolder.itemGroupIcon.setBackgroundResource(groupItem .getDrawableId()); groupViewHolder.itemGroupText.setText(groupItem.getText()); // 如果该组没有子项,则不显示箭头 if (childList.get(groupPosition).size() == 0) { groupViewHolder.itemArrow.setVisibility(View.GONE); groupViewHolder.itemGroupLayout .setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { onGroupClickListener.onGroupClick(groupPosition); } }); } else { groupViewHolder.itemArrow.setVisibility(View.VISIBLE); groupViewHolder.itemGroupLayout .setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { OnGroupExpandListener.onGroupExpand(groupPosition); } }); } groupViewHolder.itemGroupText.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { onGroupClickListener.onGroupClick(groupPosition); } }); groupViewHolder.itemArrow.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { OnGroupExpandListener.onGroupExpand(groupPosition); } }); // 判断isExpanded就可以控制按下还是关闭,同时更换图片,这里使用属性动画来控制旋转 if (isExpanded) { groupViewHolder.itemArrow .setImageResource(R.drawable.ic_leftnav_up); // 没有孩子项就不隐藏分割线 if (childList.get(groupPosition).size() > 0) { groupViewHolder.itemDivider.setVisibility(View.INVISIBLE); } else { groupViewHolder.itemDivider.setVisibility(View.VISIBLE); } } else { groupViewHolder.itemArrow .setImageResource(R.drawable.ic_leftnav_down); groupViewHolder.itemDivider.setVisibility(View.VISIBLE); } return convertView; } @Override public View getChildView(final int groupPosition, final int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { final ChildViewHolder childViewHolder; if (convertView == null) { convertView = inflater.inflate(R.layout.layout_expand_item, parent, false); childViewHolder = new ChildViewHolder(convertView); convertView.setTag(childViewHolder); } else { childViewHolder = (ChildViewHolder) convertView.getTag(); } String content = childList.get(groupPosition).get(childPosition) .getTitle(); // 设置内容 childViewHolder.itemChildText.setText(content); // 设置文本点击事件 childViewHolder.itemChildText.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { onChildClickListener.onChildItemClick(groupPosition, childPosition); } }); if (childPosition == childList.get(groupPosition).size() - 1) { childViewHolder.itemDivider.setVisibility(View.VISIBLE); } else { childViewHolder.itemDivider.setVisibility(View.GONE); } // 设置子项被选中的状态 return convertView; } @Override public boolean isChildSelectable(int groupPosition, int childPosition) { // 设置孩子项可选中 return true; } private static class GroupViewHolder { RelativeLayout itemGroupLayout; ImageView itemGroupIcon; TextView itemGroupText; ImageView itemArrow; View itemDivider; public GroupViewHolder(View convertView) { itemGroupLayout = (RelativeLayout) convertView .findViewById(R.id.group_layout); itemGroupIcon = (ImageView) convertView .findViewById(R.id.iv_group_icon); itemGroupText = (TextView) convertView .findViewById(R.id.tv_group_text); itemArrow = (ImageView) convertView.findViewById(R.id.iv_expand); itemDivider = (View) convertView .findViewById(R.id.item_group_devider); } } private static class ChildViewHolder { TextView itemChildText; View itemDivider; public ChildViewHolder(View convertView) { itemChildText = (TextView) convertView .findViewById(R.id.tv_item_text); itemDivider = (View) convertView.findViewById(R.id.item_devider); } } public interface OnGroupExpandListener { void onGroupExpand(int position); } public interface OnGroupClickListener { void onGroupClick(int position); } public interface OnChildItemClickListener { void onChildItemClick(int groupPosition, int childPosition); } public boolean getExpandStateAtPosition(int groupPosition) { // 获得当前位置的展开状态 expandStateAtPosition = maps.get(groupPosition).booleanValue(); return expandStateAtPosition; } public void setExpandStateAtPosition(int groupPosition, boolean expandStateAtPosition) { this.expandStateAtPosition = expandStateAtPosition; maps.put(groupPosition, expandStateAtPosition); }}
解析一下上面的代码,我们可以看到ExpandableListView除了一个组项,每个组项下面有若干个子项,我们在使用的时候首先要确定要展示的数据结构,组项有groupPosition来标识位置,然而子项需要根据groupPosition和ChildPosition来标识位置,我们设置数据的时候分别在getGroupView和getChildView方法来设置组视图和子项视图数据,最后返回填充数据的视图对象,一些逻辑控制的代码也是在这两个方法中进行,比如控制组项的展开、组项的点击、子项的点击、子项被选中效果等等,这里笔者是自定义了回调接口来满足业务的需求,Android API也提供的类似的方法,大家可以查看官方文档。
Activity代码
com.devilwwj.androiddevelopcourse.activities.ExpandableListViewTestActivity
package com.devilwwj.androiddevelopcourse.activities;import android.content.Context;import android.os.Bundle;import android.support.v7.app.ActionBarActivity;import android.view.View;import android.widget.ExpandableListView;import com.devilwwj.androiddevelopcourse.R;import com.devilwwj.androiddevelopcourse.adapters.ExpandableListViewAdapter;import com.devilwwj.androiddevelopcourse.domain.Category;import com.devilwwj.androiddevelopcourse.domain.GroupItem;import com.devilwwj.androiddevelopcourse.utils.ResourceUtil;import org.json.JSONArray;import org.json.JSONException;import org.json.JSONObject;import java.util.ArrayList;import java.util.List;/** * A022-列表容器之ExpandableListView * * @author devilwwj */public class ExpandableListViewTestActivity extends ActionBarActivity implements ExpandableListViewAdapter.OnGroupClickListener, ExpandableListViewAdapter.OnGroupExpandListener , ExpandableListViewAdapter.OnChildItemClickListener { private ExpandableListView expandableListView; private Context mContext; private ExpandableListViewAdapter expandAdapter; private List<GroupItem> groupList; private List<List<Category>> childList; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_expandablelistview); mContext = ExpandableListViewTestActivity.this; expandableListView = (ExpandableListView) this.findViewById(R.id.expandablelistview); expandableListView.setGroupIndicator(null); // 去掉默认指示器 // 设置展开列表数据 setExpandableListView(); } private void setExpandableListView() { try { // 这里分别模拟组项和子项数据 groupList = new ArrayList<GroupItem>(); childList = new ArrayList<List<Category>>(); ResourceUtil resourceUtil = new ResourceUtil(this); // 从本地获取目录 String str = getString(R.string.categories); JSONObject jsonObject = new JSONObject(str); JSONObject categoryObj = jsonObject.optJSONObject("categories"); // 左边侧边栏 JSONArray jsonArray = categoryObj.optJSONArray("left"); for (int i = 0; i < jsonArray.length(); i++) { JSONArray groupArray = jsonArray.getJSONArray(i); GroupItem groupItem = new GroupItem(); groupItem.setDrawableId(resourceUtil.getResId("ic_leftnav_" + (i + 1), "drawable")); JSONObject groupObj = groupArray.optJSONObject(0); groupItem.setGroupId(groupObj.optString("cat_id")); // 设置组别的分类id groupItem.setText(groupObj.optString("title")); groupList.add(groupItem); List<Category> categories = new ArrayList<Category>(); if (groupArray.length() > 0) { // 如果多于一项分类 for (int index = 1; index < groupArray.length(); index++) { JSONObject itemObj = groupArray.optJSONObject(index); Category categorie = new Category(); categorie.setTitle(itemObj.optString("title")); categorie.setCat_id(itemObj.optString("cat_id")); categories.add(categorie); } } childList.add(categories); } // 实例化适配器 expandAdapter = new ExpandableListViewAdapter(this, expandableListView, groupList, childList); expandableListView.setAdapter(expandAdapter); // 设置ExpandableListView的相关事件监听 // 子项选中、子项被点击、组项展开、组项被点击 // expandableListView.setOnItemSelectedListener(itemSelectedListener); expandableListView.setOnChildClickListener(mOnChildClickListener);// expandableListView.setOnGroupExpandListener(mOnGroupExpandListener); // expandableListView.setOnGroupClickListener(mOnGroupClickListener); expandAdapter.setOnGroupClickListener(this); expandAdapter.setOnGroupExpandListener(this); expandAdapter.setOnChildClickListener(this); } catch (JSONException e) { e.printStackTrace(); } } final private ExpandableListView.OnChildClickListener mOnChildClickListener = new ExpandableListView.OnChildClickListener() { @Override public boolean onChildClick(ExpandableListView parent, View v, final int groupPosition, final int childPosition, long id) { // 设置子项被选中的背景 // expandAdapter.setItemChecked(groupPosition, childPosition); // 返回true,列表不可展开 return false; } }; final private ExpandableListView.OnGroupExpandListener mOnGroupExpandListener = new ExpandableListView.OnGroupExpandListener() { @Override public void onGroupExpand(int groupPosition) { for (int i = 0, count = expandableListView .getExpandableListAdapter().getGroupCount(); i < count; i++) { expandAdapter.setExpandedGroupPosition(groupPosition); if (groupPosition != i) { // 关闭其他组 expandableListView.collapseGroup(i); } } } }; final private ExpandableListView.OnGroupClickListener mOnGroupClickListener = new ExpandableListView.OnGroupClickListener() { @Override public boolean onGroupClick(ExpandableListView parent, View v, final int groupPosition, long id) { return true; } }; @Override public void onGroupExpand(int position) { // 组项被展开时会回调这个方法 if (expandAdapter.getExpandStateAtPosition(position)) { // 收起 expandableListView.collapseGroup(position); expandAdapter.setExpandStateAtPosition(position, false); } else { expandableListView.expandGroup(position, true); expandAdapter.setExpandStateAtPosition(position, true); } } @Override public void onGroupClick(int position) { // 执行选中后的操作 } @Override public void onChildItemClick(int groupPosition, int childPosition) { // 子项被点击会回调这个方法 }}
最后
实际开发中,我们可能会遇到其他UI上的需求,原生的效果是完全不能满足我们的,这里提一点就是,熟练掌握API和解决问题能力很重要,不管UI怎么变我们都有办法去实现,可能只要我们找到对应的API设置一下或者看看有没有大神造好了轮子,终究我们还是可以找到解决方案,在Android开发当中我们经常打交道也最头痛的是UI,多实践和学习才能更好的完成工作,谢谢大家。
转载请注明:IT_xiao小巫 http://blog.csdn.net/wwj_748
欢迎关注我的公众号:wwjblog
- A022-列表容器之ExpandableListView
- 高级控件之分组列表视图(ExpandableListView)
- Android控件之ExpandableListView多级列表
- A020-列表容器之ListView
- A021-列表容器之GridView
- Python内置容器之列表
- 自定义ExpandableListView容器
- android开发步步为营之5:ExpandableListView模拟QQ好友列表
- ExpandableListView------下拉列表
- Android ExpandableListView (二级列表)
- Android ExpandableListView(多级列表)
- 多级列表ExpandableListView
- 二级列表ExpandableListView
- android ExpandableListView二级列表
- 二级列表ExpandableListView
- ExpandableListView 二级展开列表
- ExpandableListView二级列表
- 二级列表ExpandableListView
- [OHCI]架构
- gradle of Andorid(一·)
- 内存管理图解---------页目录表 页表
- Nginx配置相关结构划分的技巧和禁止IP访问
- CentOS6.7配置静态IP
- A022-列表容器之ExpandableListView
- 安装Spark 客户端 提示"the install4j wizard could not find a java(TM) Runntime......“
- Lucene之——搜索实例
- scanf()函数
- Oracle常用的数据操作函数
- Unity3D之实现火炬之光遮挡效果
- 使用TFTP进行文件传输
- 文章标题
- 使用protobuf()