In the previous "Android custom components series [6] -- advanced practice (3)", we added the process knowledge about event distribution in Android, in this article, we will analyze the implementation of the drop-down PinnedHeaderExpandableListView.
I. What is the role of the OnGiveUpTouchEventListener interface in StickyLayout?
public interface OnGiveUpTouchEventListener { public boolean giveUpTouchEvent(MotionEvent event); }
In StickyLayout, you can set the listener as follows:
public void setOnGiveUpTouchEventListener(OnGiveUpTouchEventListener l) { mGiveUpTouchEventListener = l; }
This method is actually a hook method. In OnGiveUpTouchEventListener, an abstract method (not implemented) giveUpTouchEvent is defined. Then, the MainActivity inherits the OnGiveUpTouchEventListener interface to implement the specific logic.
@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; }
Logic in this method: Get the first visible item in ExpandableListView. If it is the first visible item in its subview, it indicates that you should first slide the Header section above (let it expand ).
What is the difference between true and false returned here? Look down
@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; }
There are several codes in ACTION_MOVE of the event Interception Method in the StickyLayout class:
if (mStatus == STATUS_EXPANDED && deltaY <= -mTouchSlop) { intercepted = 1; } else if (mGiveUpTouchEventListener != null) { if (mGiveUpTouchEventListener.giveUpTouchEvent(event) && deltaY >= mTouchSlop) { intercepted = 1; } }
Now you should understand it, huh, huh. STATUS_EXPANDED is a status value, which indicates that the Header is expanded. If the Header is collapsed, the returned value of giveUpTouchEvent is determined. If giveUpTouchEvent returns true, the list is all pulled down, in this case, expand the Header section. If false is returned, the list should be dropped rather than the Header part.
II. Implementation of OnScrollLister in PinnedHeaderExpandableListView
@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 is a rolling event of ListView.
In onScrollStateChanged (AbsListView view, int scrollState), scrollState has three states:
1. SCROLL_STATE_FLING: Start rolling
2. SCROLL_STATE_TOUCH_SCROLL: Rolling
3. SCROLL_STATE_IDLE: stopped
The onScroll () method always calls back when the list is rolled. The callback is stopped only when the rolling is stopped. In addition, the callback is also performed once when the list is clicked. OnScrollStateChanged indicates the callback when the preceding three States change. The callback sequence is as follows:
- 1st times: scrollState = SCROLL_STATE_TOUCH_SCROLL (1) Rolling
- 2nd times: scrollState = SCROLL_STATE_FLING (2) the finger throws (the finger slides hard before leaving the screen)
- 3rd Times: scrollState = SCROLL_STATE_IDLE (0) Stop rolling
- In the OnScrollStateChanged method above, the list header is laid out again (drawn) after the rolling is stopped. The onScroll method is mainly used to call the refreshHeader () method mentioned above to call back and refresh the list header.
- 4. What should I do if the following list header is placed on the top of the list?
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); } }
You can see in the refreshHeader () method that, first, judge whether there are multiple list headers. If so, reset the position of the following list header to the "standard position ", in this way, I feel like there is a kind of top-up feeling.
- Iii. How to expand and collapse the list
@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); }
We can see that the event Distribution Function in PinnedHeaderExpandableListView contains the following code:
if (y >= mHeaderView.getTop() && y <= mHeaderView.getBottom())
Height limit (in the header of the List)
if (isGroupExpanded(groupPosition)) { collapseGroup(groupPosition); } else { expandGroup(groupPosition); }