概述
本節課介紹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