ExpandableListView吐槽系列(一) -> 設置自定義groupIndicator

轉自:http://www.tuicool.com/articles/FJZfma


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還可以進一步的定製,從而用起來更簡潔方便,不容易出錯,這部分內容會稍後帶來。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章