Skip to content

Instantly share code, notes, and snippets.

@AvatarQing
Created July 18, 2014 06:38
Show Gist options
  • Save AvatarQing/568d9873529cfd4ce13c to your computer and use it in GitHub Desktop.
Save AvatarQing/568d9873529cfd4ce13c to your computer and use it in GitHub Desktop.
PinnedHeaderExpandableListView中文注释,https://github.com/singwhatiwanna/PinnedHeaderExpandableListView
/**
The MIT License (MIT)
Copyright (c) 2014 singwhatiwanna
https://github.com/singwhatiwanna
http://blog.csdn.net/singwhatiwanna
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package com.ryg.expandable.ui;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.ExpandableListView;
import android.widget.AbsListView.OnScrollListener;
public class PinnedHeaderExpandableListView extends ExpandableListView
implements OnScrollListener {
private static final String TAG = "PinnedHeaderExpandableListView";
private static final boolean DEBUG = true;
public interface OnHeaderUpdateListener {
/**
* 返回一个view对象即可 注意:view必须要有LayoutParams
*/
public View getPinnedHeader();
public void updatePinnedHeader(View headerView, int firstVisibleGroupPos);
}
private View mHeaderView;
private int mHeaderWidth;
private int mHeaderHeight;
private View mTouchTarget;
private OnScrollListener mScrollListener;
private OnHeaderUpdateListener mHeaderUpdateListener;
private boolean mActionDownHappened = false;
public PinnedHeaderExpandableListView(Context context) {
super(context);
initView();
}
public PinnedHeaderExpandableListView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public PinnedHeaderExpandableListView(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
initView();
}
private void initView() {
setFadingEdgeLength(0);
setOnScrollListener(this);
}
@Override
public void setOnScrollListener(OnScrollListener l) {
if (l != this) {
mScrollListener = l;
}
super.setOnScrollListener(this);
}
public void setOnHeaderUpdateListener(OnHeaderUpdateListener listener) {
mHeaderUpdateListener = listener;
if (listener == null) {
mHeaderView = null;
mHeaderWidth = mHeaderHeight = 0;
return;
}
mHeaderView = listener.getPinnedHeader();
int firstVisiblePos = getFirstVisiblePosition();
int firstVisibleGroupPos = getPackedPositionGroup(getExpandableListPosition(firstVisiblePos));
listener.updatePinnedHeader(mHeaderView, firstVisibleGroupPos);
requestLayout();
postInvalidate();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mHeaderView == null) {
return;
}
measureChild(mHeaderView, widthMeasureSpec, heightMeasureSpec);
mHeaderWidth = mHeaderView.getMeasuredWidth();
mHeaderHeight = mHeaderView.getMeasuredHeight();
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (mHeaderView == null) {
return;
}
int delta = mHeaderView.getTop();
mHeaderView.layout(0, delta, mHeaderWidth, mHeaderHeight + delta);
}
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (mHeaderView != null) {
drawChild(canvas, mHeaderView, getDrawingTime());
}
}
// 主要处理HeaderView的点击事件
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// 获取触摸坐标
int x = (int) ev.getX();
int y = (int) ev.getY();
// 将坐标转换为在列表中的触摸位置
int pos = pointToPosition(x, y);
// 在HeaderView上触摸
if (mHeaderView != null && y >= mHeaderView.getTop()
&& y <= mHeaderView.getBottom()) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
mTouchTarget = getTouchTarget(mHeaderView, x, y);
mActionDownHappened = true;
} else if (ev.getAction() == MotionEvent.ACTION_UP) {
View touchTarget = getTouchTarget(mHeaderView, x, y);
if (touchTarget == mTouchTarget && mTouchTarget.isClickable()) {
// 处理HeaderView中的点击事件
mTouchTarget.performClick();
invalidate(new Rect(0, 0, mHeaderWidth, mHeaderHeight));
} else {
// 处理ExpandableListView的组收缩和展开
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);
}
/** 获取触摸到的原子View */
private View getTouchTarget(View view, int x, int y) {
// 如果触摸到的不是容器,就直接返回该View
if (!(view instanceof ViewGroup)) {
return view;
}
// 如果触摸到的是容器,获取到真正触摸到的子View
ViewGroup parent = (ViewGroup) view;
int childrenCount = parent.getChildCount();
final boolean customOrder = isChildrenDrawingOrderEnabled();
View target = null;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder ? getChildDrawingOrder(
childrenCount, i) : i;
final View child = parent.getChildAt(childIndex);
if (isTouchPointInView(child, x, y)) {
target = child;
break;
}
}
if (target == null) {
target = parent;
}
return target;
}
/** 判断触摸点是否在指定的View上 */
private boolean isTouchPointInView(View view, int x, int y) {
if (view.isClickable() && y >= view.getTop() && y <= view.getBottom()
&& x >= view.getLeft() && x <= view.getRight()) {
return true;
}
return false;
}
public void requestRefreshHeader() {
refreshHeader();
invalidate(new Rect(0, 0, mHeaderWidth, mHeaderHeight));
}
/** 滚动ListView时不停的更新HeaderView的布局,保证其固定在顶端 */
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 (DEBUG) {
Log.w(TAG, "refreshHeader firstVisibleGroupPos="
+ firstVisibleGroupPos);
}
// 下一项就是下一组的项目了
if (group == firstVisibleGroupPos + 1) {
// 获得下一组的组头
View view = getChildAt(1);
if (view == null) {
Log.w(TAG, "Warning : refreshHeader getChildAt(1)=null");
return;
}
// 下一组的组头已经开始挤当前固定的组头
if (view.getTop() <= mHeaderHeight) {
// 计算下一组组头挤出当前组头的距离,即从开始挤算的向上滑动距离
int delta = mHeaderHeight - view.getTop();
// 展示当前固定的组头被挤出上去的效果
mHeaderView.layout(0, -delta, mHeaderWidth, mHeaderHeight
- delta);
} else {
// TODO : note it, when cause bug, remove it
// 下一组的组头还没有开始挤当前固定的组头,就继续将当前组头固定在顶部
mHeaderView.layout(0, 0, mHeaderWidth, mHeaderHeight);
}
} else {
// 将当前组头固定在顶部
mHeaderView.layout(0, 0, mHeaderWidth, mHeaderHeight);
}
if (mHeaderUpdateListener != null) {
mHeaderUpdateListener.updatePinnedHeader(mHeaderView,
firstVisibleGroupPos);
}
}
@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);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment