Skip to content

Instantly share code, notes, and snippets.

@AvatarQing
Last active November 8, 2015 15:53
Show Gist options
  • Save AvatarQing/1426c59c0047b6a41bfb to your computer and use it in GitHub Desktop.
Save AvatarQing/1426c59c0047b6a41bfb to your computer and use it in GitHub Desktop.
PullToZoomListView改进+中文注释
package com.matrixxun.pulltozoomlistsimple;
import android.content.Context;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.animation.Interpolator;
import android.widget.AbsListView;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.ListView;
import android.widget.RelativeLayout;
public class PullToZoomListView extends ListView implements
AbsListView.OnScrollListener {
private static final String TAG = PullToZoomListView.class.getSimpleName();
/** 无效值 */
private static final int INVALID_VALUE = -1;
// 自定义的插值器
private static final Interpolator sInterpolator = new Interpolator() {
public float getInterpolation(float fraction) {
float f = fraction - 1.0F;
return 1.0F + f * (f * (f * (f * f)));
}
};
private int mActivePointerId = INVALID_VALUE;
/** 头部View容器 */
private RelativeLayout mHeaderContainer;
/** 头部View的高度 */
private int mHeaderHeight;
/** 头部View里的图片视图 */
private ImageView mHeaderImage;
private float mLastMotionY = INVALID_VALUE;
private float mLastScale = INVALID_VALUE;
private float mMaxScale = INVALID_VALUE;
private float mHeaderScrollSpeed = 1f;
private AbsListView.OnScrollListener mOnScrollListener;
private ScalingRunnalable mScalingRunnalable;
/** 屏幕高度 */
private int mScreenHeight;
private ImageView mShadow;
public PullToZoomListView(Context context) {
super(context);
init(context);
}
public PullToZoomListView(Context context, AttributeSet paramAttributeSet) {
super(context, paramAttributeSet);
init(context);
}
public PullToZoomListView(Context context, AttributeSet paramAttributeSet,
int paramInt) {
super(context, paramAttributeSet, paramInt);
init(context);
}
private void endScraling() {
if (this.mHeaderContainer.getBottom() >= this.mHeaderHeight) {
Log.d(TAG, "endScraling");
}
this.mScalingRunnalable.startAnimation(200L);
}
private void init(Context context) {
// 获取屏幕高度
DisplayMetrics localDisplayMetrics = new DisplayMetrics();
((WindowManager) context.getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay().getMetrics(localDisplayMetrics);
this.mScreenHeight = localDisplayMetrics.heightPixels;
// 生成头部View
this.mHeaderContainer = new RelativeLayout(context);
this.mHeaderImage = new ImageView(context);
// 获得屏幕宽度
int i = localDisplayMetrics.widthPixels;
// 设置头部View的宽高
setHeaderViewSize(i, (int) (9.0F * (i / 16.0F)));
// 生成阴影视图
// this.mShadow = new ImageView(context);
// FrameLayout.LayoutParams shadowLp = new FrameLayout.LayoutParams(
// FrameLayout.LayoutParams.MATCH_PARENT,
// FrameLayout.LayoutParams.WRAP_CONTENT);
// shadowLp.gravity = 80;
// this.mShadow.setLayoutParams(shadowLp);
// 将图片和阴影视图添加到头部View容器
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.MATCH_PARENT,
RelativeLayout.LayoutParams.MATCH_PARENT);
params.topMargin = 0;
this.mHeaderContainer.addView(this.mHeaderImage, params);
// this.mHeaderContainer.addView(this.mShadow);
// 将头部View添加到ListView里
addHeaderView(this.mHeaderContainer);
this.mScalingRunnalable = new ScalingRunnalable();
// 设置滑动监听
super.setOnScrollListener(this);
}
private void onSecondaryPointerUp(MotionEvent ev) {
int i = ev.getActionIndex();
if (ev.getPointerId(i) == this.mActivePointerId) {
if (i != 0) {
this.mLastMotionY = ev.getY(0);
this.mActivePointerId = ev.getPointerId(0);
return;
}
}
}
/**
* 重置头部View缩放相关的参数
*/
private void reset() {
this.mActivePointerId = INVALID_VALUE;
this.mLastMotionY = INVALID_VALUE;
this.mMaxScale = INVALID_VALUE;
this.mLastScale = INVALID_VALUE;
}
public ImageView getHeaderView() {
return this.mHeaderImage;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (this.mHeaderHeight == 0) {
// 记录头部View正常大小时的高度
this.mHeaderHeight = this.mHeaderContainer.getHeight();
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
Log.d("test", "onScroll()");
float space = this.mHeaderHeight - this.mHeaderContainer.getBottom();
Log.d(TAG, "onScroll()->Header scrolled space:" + space);
if ((space > 0.0F) && (space < this.mHeaderHeight)) {
int i = (int) (mHeaderScrollSpeed * space);
this.mHeaderImage.scrollTo(0, -i);
} else if (this.mHeaderImage.getScrollY() != 0) {
this.mHeaderImage.scrollTo(0, 0);
}
if (this.mOnScrollListener != null) {
this.mOnScrollListener.onScroll(view, firstVisibleItem,
visibleItemCount, totalItemCount);
}
}
public void onScrollStateChanged(AbsListView paramAbsListView, int paramInt) {
if (this.mOnScrollListener != null) {
this.mOnScrollListener.onScrollStateChanged(paramAbsListView,
paramInt);
}
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
// 过滤掉多点触控的触控点信息(Action的高八位),获取触摸事件类型(Action的低八位)
int actionMasked = ev.getActionMasked();
switch (actionMasked) {
case MotionEvent.ACTION_OUTSIDE:
// 用户触碰超出了正常的UI边界
case MotionEvent.ACTION_DOWN:
// 用户开始触摸
Log.d(TAG, "onTouchEvent()->ACTION_DOWN or ACTION_OUTSIDE");
if (!this.mScalingRunnalable.mIsFinished) {
this.mScalingRunnalable.abortAnimation();
}
// 记录开始触摸时的y坐标
this.mLastMotionY = ev.getY();
// 记录第一个有效触摸的手指的id
this.mActivePointerId = ev.getPointerId(0);
// 记录头部View最大的放大比例
this.mMaxScale = (this.mScreenHeight / this.mHeaderHeight);
// 记录开始触摸时头部View已经缩放的比例
this.mLastScale = (this.mHeaderContainer.getBottom() / this.mHeaderHeight);
break;
case MotionEvent.ACTION_MOVE:
// 用户在移动(手指或者其他)
Log.d(TAG, "onTouchEvent()->ACTION_MOVE");
Log.d(TAG, "mActivePointerId" + mActivePointerId);
// 记录第一个有效触摸手指的索引
int pointerIndex = ev.findPointerIndex(this.mActivePointerId);
// 检查手指索引的有效性
if (pointerIndex == INVALID_VALUE) {
// 无效的手指指针索引
Log.e(TAG, "Invalid pointerId=" + this.mActivePointerId
+ " in onTouchEvent");
} else {// 有效的手指指针索引
// 确保滑动时滑动前的y坐标记录正确
if (this.mLastMotionY == INVALID_VALUE) {
this.mLastMotionY = ev.getY(pointerIndex);
}
// 如果头部达到放大标准或者已经放大(头容器的底部坐标达到或者超出了正常大小的高度),就处理放大头部View逻辑
// if (this.mHeaderContainer.getBottom() >= this.mHeaderHeight)
// {
// 获取头容器当前布局参数
ViewGroup.LayoutParams hlp = this.mHeaderContainer
.getLayoutParams();
// 计算新的缩放比例
float newScale = ((ev.getY(pointerIndex) - this.mLastMotionY + this.mHeaderContainer
.getBottom()) / this.mHeaderHeight - this.mLastScale)
/ 2.0F + this.mLastScale;
// 如果上一次的缩放比正常比例要小,并且新的缩放比也小于上一次的缩放比
// XXX 为什么新的缩放比也小于上一次的缩放比?
if ((this.mLastScale <= 1.0f)) {
mHeaderImage.setScaleType(ScaleType.FIT_CENTER);
// // 那就恢复正常大小
// hlp.height = this.mHeaderHeight;
// this.mHeaderContainer.setLayoutParams(hlp);
// // 调用父类方法处理onScroll事件
// return super.onTouchEvent(ev);
newScale = (mHeaderContainer.getBottom() + (ev
.getY(pointerIndex) - this.mLastMotionY))
/ this.mHeaderHeight;
Log.d("test",
"newScale:"
+ newScale
+ ",mHeaderContainer.getBottom():"
+ mHeaderContainer.getBottom()
+ ",deltaY:"
+ (ev.getY(pointerIndex) - this.mLastMotionY)
+ ",mHeaderHeight:" + mHeaderHeight);
} else {
mHeaderImage.setScaleType(ScaleType.CENTER_CROP);
}
// 记录最后一次的缩放比
this.mLastScale = Math.min(Math.max(0, newScale),
this.mMaxScale);
// 计算头容器新的高度
int newHeight = (int) (this.mHeaderHeight * this.mLastScale);
Log.d("test", "hlp.height:" + hlp.height);
if (newHeight <= 10) {
Log.d("test", "newHeight" + newHeight);
hlp.height = 1;
this.mHeaderContainer.setLayoutParams(hlp);
this.mLastScale = 0;
return true;
}
// 新的高度不能超过整个屏幕的高度
if (newHeight < this.mScreenHeight) {
// 将新的高度参数应用到头容器上
// 容器里的布局会自动伸缩
hlp.height = newHeight;
this.mHeaderContainer.setLayoutParams(hlp);
}
// 记录最后一次手指触摸的y坐标
this.mLastMotionY = ev.getY(pointerIndex);
// 消耗掉触摸事件
Log.d("test", "this.mLastScale" + this.mLastScale);
if (this.mLastScale <= 0.1) {
return super.onTouchEvent(ev);
}
return true;
// }
// this.mLastMotionY = ev.getY(pointerIndex);
}
break;
case MotionEvent.ACTION_UP:
// 用户抬起了手指
Log.d(TAG, "onTouchEvent()->ACTION_UP");
reset();
endScraling();
break;
case MotionEvent.ACTION_CANCEL:
// 表示手势被取消了
Log.d(TAG, "onTouchEvent()->ACTION_CANCEL");
int i = ev.getActionIndex();
this.mLastMotionY = ev.getY(i);
this.mActivePointerId = ev.getPointerId(i);
break;
case MotionEvent.ACTION_POINTER_DOWN:
// 有一个非主要的手指按下了.
Log.d(TAG, "onTouchEvent()->ACTION_POINTER_DOWN");
onSecondaryPointerUp(ev);
this.mLastMotionY = ev.getY(ev
.findPointerIndex(this.mActivePointerId));
break;
case MotionEvent.ACTION_POINTER_UP:
// 一个非主要的手指抬起来了
Log.d(TAG, "onTouchEvent()->ACTION_POINTER_UP");
break;
}
return super.onTouchEvent(ev);
}
/**
* 设置头部View的宽、高
*
* @param width
* @param height
*/
public void setHeaderViewSize(int width, int height) {
Object lp = this.mHeaderContainer.getLayoutParams();
if (lp == null) {
lp = new AbsListView.LayoutParams(width, height);
}
((ViewGroup.LayoutParams) lp).width = width;
((ViewGroup.LayoutParams) lp).height = height;
this.mHeaderContainer.setLayoutParams((ViewGroup.LayoutParams) lp);
this.mHeaderHeight = height;
}
public void setOnScrollListener(
AbsListView.OnScrollListener paramOnScrollListener) {
this.mOnScrollListener = paramOnScrollListener;
}
public void setShadow(int paramInt) {
this.mShadow.setBackgroundResource(paramInt);
}
private class ScalingRunnalable implements Runnable {
private long mDuration;
private boolean mIsFinished = true;
private float mScale;
private long mStartTime;
ScalingRunnalable() {
}
public void abortAnimation() {
this.mIsFinished = true;
}
public boolean isFinished() {
return this.mIsFinished;
}
public void run() {
float scale = 0f;
ViewGroup.LayoutParams lp = null;
if ((!this.mIsFinished) && (this.mScale > 1.0D)) {
// 头部View缩小动画播放开始,已经逝去的时间占总时间的百分比
float animatedTimePercent = ((float) SystemClock
.currentThreadTimeMillis() - (float) this.mStartTime)
/ (float) this.mDuration;
// 计算缩放量
scale = this.mScale
- (this.mScale - 1.0F)
* PullToZoomListView.sInterpolator
.getInterpolation(animatedTimePercent);
lp = PullToZoomListView.this.mHeaderContainer.getLayoutParams();
if (scale > 1.0F) {
Log.d(TAG, "ScalingRunnalable->run()-> scale > 1.0");
// 计算头容器的高度
lp.height = ((int) (scale * PullToZoomListView.this.mHeaderHeight));
// 参数应用到头部View上
PullToZoomListView.this.mHeaderContainer
.setLayoutParams(lp);
// 播放动画,不停的更新参数和布局
PullToZoomListView.this.post(this);
return;
}
// 头部View缩小到原大小时停止缩小动画
this.mIsFinished = true;
}
}
/**
* 以一个动画的过程将头部View缩放到正常的大小
*
* @param duration
* 动画时长
*/
public void startAnimation(long duration) {
this.mStartTime = SystemClock.currentThreadTimeMillis();
this.mDuration = duration;
this.mScale = ((float) (PullToZoomListView.this.mHeaderContainer
.getBottom()) / PullToZoomListView.this.mHeaderHeight);
this.mIsFinished = false;
PullToZoomListView.this.post(this);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment