ExpandableListView吐槽系列(一) -> 设置自定义groupIndicator
2013-06-03 01:15 MudooT 阅读(5254) 评论(1) 编辑 收藏 举报Android中提供了可展开的列表控件,很不幸,和很多其他原生控件一样,这个控件有些地方设计的 ridiculous !其中的一个很重要的地方就是本文中要说的这个 groupIndicator 了。话说这玩意是干嘛用的?就是用来展示一个group的展开状态用的↓
好吧,这东西蛋疼的地方有如下几点:
- 位置只能放在固定的位置上(神马?你说可以通过android:indicatorLeft来控制位置?come on 那上下的位置呢?)
- 这个Indicator和你的itemView是完全没关系的2个东西,也就是说这东西可能会覆盖在你原本的view上面哦
- 其实上下的位置也能解决,通过设置自定义Indicator的draw9patch的拉伸区域可以大概的控制 = =#
OK,吐槽先到这,看看大家是怎么替换的吧,一般来说是这样:
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/list_item_expand" android:state_expanded="true"/>
<item android:drawable="@drawable/list_item_collapse"></item>
</selector>
===================================================
<ExpandableListView
…
android:groupIndicator="@drawable/list_expand"
…
/>
不过这样做是会遇到上面几个问题的,为了解决这个事情我们来翻看一下源码,看看系统是怎么去绘制这个Indicator的
先看怎么获得绘制状态
private static final int[] EMPTY_STATE_SET = {};
/** State indicating the group is expanded. */
private static final int[] GROUP_EXPANDED_STATE_SET = { android.R.attr.state_expanded };
/** State indicating the group is empty (has no children). */
private static final int[] GROUP_EMPTY_STATE_SET = { android.R.attr.state_empty };
/** State indicating the group is expanded and empty (has no children). */
private static final int[] GROUP_EXPANDED_EMPTY_STATE_SET = { android.R.attr.state_expanded,
android.R.attr.state_empty };
/** States for the group where the 0th bit is expanded and 1st bit is empty. */
private static final int[][] GROUP_STATE_SETS = { EMPTY_STATE_SET, // 00
GROUP_EXPANDED_STATE_SET, // 01
GROUP_EMPTY_STATE_SET, // 10
GROUP_EXPANDED_EMPTY_STATE_SET // 11
};
======================================================================
if (pos.position.type == ExpandableListPosition.GROUP) {
indicator = mGroupIndicator;
if (indicator != null && indicator.isStateful()) {
// Empty check based on availability of data. If the groupMetadata isn't null,
// we do a check on it. Otherwise, the group is collapsed so we consider it
// empty for performance reasons.
boolean isEmpty = (pos.groupMetadata == null) ||
(pos.groupMetadata.lastChildFlPos == pos.groupMetadata.flPos);
final int stateSetIndex =
(pos.isExpanded() ? 1 : 0) | // Expanded?
(isEmpty ? 2 : 0); // Empty?
indicator.setState(GROUP_STATE_SETS[stateSetIndex]);
}
}
然后看看控制位置和区域
public void setGroupIndicator(Drawable groupIndicator) {
mGroupIndicator = groupIndicator;
if (mIndicatorRight == 0 && mGroupIndicator != null) {
mIndicatorRight = mIndicatorLeft + mGroupIndicator.getIntrinsicWidth();
}
========================================================
if (indicatorRect.left != indicatorRect.right) {
// Use item's full height + the divider height
↑看这句注释就知道开发这个控件的攻城师没带投石车 =。=
if (mStackFromBottom) {
// See ListView#dispatchDraw
indicatorRect.top = t;// - mDividerHeight;
indicatorRect.bottom = b;
} else {
indicatorRect.top = t;
indicatorRect.bottom = b;// + mDividerHeight;
}
// Get the indicator (with its state set to the item's state)
indicator = getIndicator(pos);
if (indicator != null) {
// Draw the indicator
indicator.setBounds(indicatorRect);
indicator.draw(canvas);
}
}
原理大概明白了就可以自己搞搞了~为了便于开发中使用,我们还是应该把这个Indicator放到item的layout里面去。随便大概写一个demo的样子如下:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingBottom="8dp"
android:paddingLeft="10dp"
android:paddingRight="8dp"
android:paddingTop="8dp" >
<ImageView
android:id="@+id/indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:src="@drawable/list_expand" />
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:drawablePadding="3dp"
android:ellipsize="end"
android:paddingLeft="3dp"
android:singleLine="true"
android:textStyle="bold" />
</LinearLayout>
然后在adapter里面把刚刚的那些控制的代码弄进来~
public abstract class ExpandableAdapter<Group, Child> extends BaseExpandableListAdapter {
private static final int[] EMPTY_STATE_SET = {};
/** State indicating the group is expanded. */
private static final int[] GROUP_EXPANDED_STATE_SET = { android.R.attr.state_expanded };
/** State indicating the group is empty (has no children). */
private static final int[] GROUP_EMPTY_STATE_SET = { android.R.attr.state_empty };
/** State indicating the group is expanded and empty (has no children). */
private static final int[] GROUP_EXPANDED_EMPTY_STATE_SET = { android.R.attr.state_expanded,
android.R.attr.state_empty };
/** States for the group where the 0th bit is expanded and 1st bit is empty. */
private static final int[][] GROUP_STATE_SETS = { EMPTY_STATE_SET, // 00
GROUP_EXPANDED_STATE_SET, // 01
GROUP_EMPTY_STATE_SET, // 10
GROUP_EXPANDED_EMPTY_STATE_SET // 11
};
在group的展开和收起的回调中控制一下刷新,这里为了防止子类覆盖父类的回调,这里把这2个方法都final标注一下,然后提供一个扩展的回调。
@Override
public final void onGroupCollapsed(int groupPosition) {
onGroupCollapsedEx(groupPosition);
notifyDataSetChanged();
}
@Override
public final void onGroupExpanded(int groupPosition) {
onGroupExpandedEx(groupPosition);
notifyDataSetChanged();
}
protected void onGroupCollapsedEx(int groupPosition) {
}
protected void onGroupExpandedEx(int groupPosition) {
}
然后提供一个设置Indicator状态的方法
protected void setIndicatorState(Drawable indicator, int groupPosition, boolean isExpanded) {
final int stateSetIndex = (isExpanded ? 1 : 0) | // Expanded?
(getChildrenCount(groupPosition) == 0 ? 2 : 0); // Empty?
indicator.setState(GROUP_STATE_SETS[stateSetIndex]);
}
在子类的getView方法我们可以这样写
ImageView indicatorView = (ImageView)view.findViewById(R.id.indicator);
setIndicatorState(indicatorView.getDrawable(),groupPosition,isExpanded);
很简单,就把这个事情解决了,可见 开发这个控件的攻城师那天真的没带投石车 =。= 其实这个adapter还可以进一步的定制,从而用起来更简洁方便,不容易出错,这部分内容会稍后带来。