代码改变世界

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还可以进一步的定制,从而用起来更简洁方便,不容易出错,这部分内容会稍后带来。