概述
本节课介绍Android中可实现2级可展开收缩列表的ExpandableListView容器,笔者感觉它非常难用并且难理解,很多时候我们可能需要对控件进行扩大和定制,但是它不太方便扩大,它使用难点主要在数据结构上和对控件的事件监听,其他的实现方式类似ListView,下面会提供笔者在实际开发中使用到的案例。
案例
上面实现的效果可展开的2级列表,每一个组项都可能有若干个子项,默许的ExpandableListView不太美观,我们需要通过自定义布局类美化它,在使用进程中有1些需要我们去了解的点,会在实现进程提1下。
实现进程
源码已上传到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
<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
<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
<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" > <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)
- 传入子项列表(如:List)
适配器代码:
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 ListgroupList; private ListchildList; private LayoutInflater inflater; private OnGroupExpandListener OnGroupExpandListener; private OnGroupClickListener onGroupClickListener; private OnChildItemClickListener onChildClickListener; private HashMap maps = new HashMap(); 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, ListgroupList,
ListchildList) { 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();
} @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);
}
}); 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);
}
}
解析1下上面的代码,我们可以看到ExpandableListView除1个组项,每一个组项下面有若干个子项,我们在使用的时候首先要肯定要展现的数据结构,组项有groupPosition来标识位置,但是子项需要根据groupPosition和ChildPosition来标识位置,我们设置数据的时候分别在getGroupView和getChildView方法来设置组视图和子项视图数据,最后返回填充数据的视图对象,1些逻辑控制的代码也是在这两个方法中进行,比如控制组项的展开、组项的点击、子项的点击、子项被选中效果等等,这里笔者是自定义了回调接口来满足业务的需求,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 ListgroupList; private ListchildList; @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();
childList = new ArrayList();
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")); groupItem.setText(groupObj.optString("title"));
groupList.add(groupItem);
Listcategories = new ArrayList(); 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.setOnChildClickListener(mOnChildClickListener); 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) { 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上的需求,原生的效果是完全不能满足我们的,这里提1点就是,熟练掌握API和解决问题能力很重要,不管UI怎样变我们都有办法去实现,可能只要我们找到对应的API设置1下或看看有无大神造好了轮子,终究我们还是可以找到解决方案,在Android开发当中我们常常打交道也最头痛的是UI,多实践和学习才能更好的完成工作,谢谢大家。
转载请注明:IT_xiao小巫 http://blog.csdn.net/wwj_748
欢迎关注我的公众号:wwjblog