上一篇《Android自訂群組件系列【6】——進階實踐(3)》中補充了關於Android中事件分發的過程知識,這一篇我們接著來分析任老師的《可下拉的PinnedHeaderExpandableListView的實現》。
一、StickyLayout中的OnGiveUpTouchEventListener介面的作用是什嗎?
public interface OnGiveUpTouchEventListener { public boolean giveUpTouchEvent(MotionEvent event); }在StickyLayout中還提供了設定監聽的方法如下:
public void setOnGiveUpTouchEventListener(OnGiveUpTouchEventListener l) { mGiveUpTouchEventListener = l; }這種方式其實是一種鉤子方法,在OnGiveUpTouchEventListener中定義了一個抽象方法(未具體實現)giveUpTouchEvent.,然後通過MainActivity繼承OnGiveUpTouchEventListener介面來實現具體邏輯。
@Override public boolean giveUpTouchEvent(MotionEvent event) { if (expandableListView.getFirstVisiblePosition() == 0) { View view = expandableListView.getChildAt(0); if (view != null && view.getTop() >= 0) { return true; } } return false; }這個方法中的邏輯:取到ExpandableListView中的第一個可見項,如果是它的子View中的第一個則說明現在首先應該滑動上面的Header部分(讓其展開)。
這裡返回的true和false有什麼不同呢?向下看
@Override public boolean onInterceptTouchEvent(MotionEvent event) { int intercepted = 0; int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { mLastXIntercept = x; mLastYIntercept = y; mLastX = x; mLastY = y; intercepted = 0; break; } case MotionEvent.ACTION_MOVE: { int deltaX = x - mLastXIntercept; int deltaY = y - mLastYIntercept; if (mStatus == STATUS_EXPANDED && deltaY <= -mTouchSlop) { intercepted = 1; } else if (mGiveUpTouchEventListener != null) { if (mGiveUpTouchEventListener.giveUpTouchEvent(event) && deltaY >= mTouchSlop) { intercepted = 1; } } break; } case MotionEvent.ACTION_UP: { intercepted = 0; mLastXIntercept = mLastYIntercept = 0; break; } default: break; } Log.d(TAG, intercepted= + intercepted); return intercepted != 0; }在StickyLayout類中的事件攔截方法的ACTION_MOVE中有這麼幾句代碼:
if (mStatus == STATUS_EXPANDED && deltaY <= -mTouchSlop) { intercepted = 1; } else if (mGiveUpTouchEventListener != null) { if (mGiveUpTouchEventListener.giveUpTouchEvent(event) && deltaY >= mTouchSlop) { intercepted = 1; } }現在應該明白了吧,呵呵。STATUS_EXPANDED是一個狀態值,意思是現在Header部分是展開的,如果Header部分是收合的則會判斷giveUpTouchEvent的傳回值,如果giveUpTouchEvent返回true說明列表全部被拉下來了,此時應該將Header部分展開。如果返回false則應該下滑列表而不是展開Header部分。
二、PinnedHeaderExpandableListView對OnScrollLister的實現
@Override public void onScrollStateChanged(AbsListView view, int scrollState) { if (mHeaderView != null && scrollState == SCROLL_STATE_IDLE) { int firstVisiblePos = getFirstVisiblePosition(); if (firstVisiblePos == 0) { mHeaderView.layout(0, 0, mHeaderWidth, mHeaderHeight); } } if (mScrollListener != null) { mScrollListener.onScrollStateChanged(view, scrollState); } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if (totalItemCount > 0) { refreshHeader(); } if (mScrollListener != null) { mScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount); } }OnScrollListener是ListView的滾動事件。
在onScrollStateChanged(AbsListView view, int scrollState)中,scrollState有三種狀態,分別是:
1、SCROLL_STATE_FLING:開始滾動
2、SCROLL_STATE_TOUCH_SCROLL:正在滾動
3、SCROLL_STATE_IDLE:已經停止
onScroll()方法在列表滾動時一直回調,知道滾動停止才停止回調,另外單擊時也回調一次。而OnScrollStateChanged的意思是上面三種狀態改變時回調,回調順序如下:
- 第1次:scrollState = SCROLL_STATE_TOUCH_SCROLL(1) 正在滾動
- 第2次:scrollState = SCROLL_STATE_FLING(2) 手指做了拋的動作(手指離開螢幕前,用力滑了一下)
- 第3次:scrollState = SCROLL_STATE_IDLE(0) 停止滾動
- 上面的OnScrollStateChanged方法中在滾動停止後再次布局(繪製)了列表的頭。在onScroll方法中主要是為了調用前面提到的refreshHeader()方法去回調並重新整理列表頭。
- 四、如何處理下面列表頭將上面列表頭頂上去?
protected void refreshHeader() { if (mHeaderView == null) { return; } int firstVisiblePos = getFirstVisiblePosition(); int pos = firstVisiblePos + 1; int firstVisibleGroupPos = getPackedPositionGroup(getExpandableListPosition(firstVisiblePos)); int group = getPackedPositionGroup(getExpandableListPosition(pos)); if (group == firstVisibleGroupPos + 1) { View view = getChildAt(1); if (view.getTop() <= mHeaderHeight) { int delta = mHeaderHeight - view.getTop(); mHeaderView.layout(0, -delta, mHeaderWidth, mHeaderHeight - delta); } } else { mHeaderView.layout(0, 0, mHeaderWidth, mHeaderHeight); } if (mHeaderUpdateListener != null) { mHeaderUpdateListener.updatePinnedHeader(firstVisibleGroupPos); } }refreshHeader()方法中可以看到,先判斷上面的是不是多個列表頭如果是則重新設定下面列表頭的位置到“標準位置”,這樣就感覺有一種頂上去的感覺了。
- 三、如何展開收縮列表
@Override public boolean dispatchTouchEvent(MotionEvent ev) { int x = (int) ev.getX(); int y = (int) ev.getY(); Log.d(TAG, dispatchTouchEvent); int pos = pointToPosition(x, y); if (y >= mHeaderView.getTop() && y <= mHeaderView.getBottom()) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { mActionDownHappened = true; } else if (ev.getAction() == MotionEvent.ACTION_UP) { int groupPosition = getPackedPositionGroup(getExpandableListPosition(pos)); if (groupPosition != INVALID_POSITION && mActionDownHappened) { if (isGroupExpanded(groupPosition)) { collapseGroup(groupPosition); } else { expandGroup(groupPosition); } mActionDownHappened = false; } } return true; } return super.dispatchTouchEvent(ev); }可以看到PinnedHeaderExpandableListView中的事件分發函數中有如下代碼:
if (y >= mHeaderView.getTop() && y <= mHeaderView.getBottom())
限制高度(在列表頭位置)
if (isGroupExpanded(groupPosition)) { collapseGroup(groupPosition); } else { expandGroup(groupPosition); }