快速實現 ListView下拉,圖片放大重新整理操作,listview下拉
今天要寫的這個效果屬於重新整理類,比較實用,像很多流行的 app 都是用了這種效果,大家熟知的QQ空間、微博個人首頁等,這個效果在 github 上也有別人實現好的源碼,點擊查看。這裡也參考了上面的源碼;還是那句話,看 blog主要是學習其中的原理和思路。
動態
圖片放大的原理是什麼呢?
通過改變圖片顯示控制項 ImageView 的父控制項的高度,比如這裡的頭部 View 是一個 FrameLayout,FrameLayout 中再 通過 add 方法把圖片 View 添加進去,addView(ImageView),ImageView有幾個屬性是要特別注意的,ImageView 的放縮類型為從中間截取
setScaleType(ImageView.ScaleType.CENTER_CROP);
並且寬高設為匹配父控制項;所以想要圖片有放大效果,只需設定 FrameLayout 的 LayoutParams 中的 height值,通過改變 height 的值從而改變 ImageView 的顯示高度。講的有點混亂,可以結合下面的代碼來理解。
如果你是對手勢事件處理很瞭解的朋友,對這個效果的實現應該沒有什麼難度,唯一的一點就是判斷何時該放大圖片,何時該滾動ListView。
這裡就直接貼代碼:
/**
* Created by gyzhong on 15/3/22.
*/
public class PullZoomListView extends ListView {
/*頭部View 的容器*/private FrameLayout mHeaderContainer;/*頭部View 的圖片*/private ImageView mHeaderImg;/*螢幕的高度*/private int mScreenHeight;/*螢幕的寬度*/private int mScreenWidth;private int mHeaderHeight;/*無效的點*/private static final int INVALID_POINTER = -1;/*滑動動畫執行的時間*/private static final int MIN_SETTLE_DURATION = 200; // ms/*定義了一個時間插值器,根據ViewPage控制項來定義的*/private static final Interpolator sInterpolator = new Interpolator() { public float getInterpolation(float t) { t -= 1.0f; return t * t * t * t * t + 1.0f; }};/*記錄上一次手指觸摸的點*/private float mLastMotionX;private float mLastMotionY;/*當前活動的點Id,有效點的Id*/protected int mActivePointerId = INVALID_POINTER;/*開始滑動的標誌距離*/private int mTouchSlop;/*放大的倍數*/private float mScale;/*上一次放大的倍數*/private float mLastScale;/*最大放大的倍數*/private final float mMaxScale = 2.0f;/*是否需要禁止ListView 的事件響應*/private boolean isNeedCancelParent;/*這個不解釋*/private OnScrollListener mScrollListener ;/*下拉重新整理的閾值*/private final float REFRESH_SCALE = 1.20F;/*下拉重新整理監聽*/private OnRefreshListener mRefreshListener ;public PullZoomListView(Context context) { super(context); init(context);}public PullZoomListView(Context context, AttributeSet attrs) { super(context, attrs); init(context);}public PullZoomListView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context);}private void init(Context context) { /*這裡擷取的是一個無用值,可忽略*/ final ViewConfiguration configuration = ViewConfiguration.get(context); mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration); /*建立頭部View 的容器*/ mHeaderContainer = new FrameLayout(context); /*擷取螢幕的像素值*/ DisplayMetrics metrics = new DisplayMetrics(); ((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(metrics); mScreenHeight = metrics.heightPixels; mScreenWidth = metrics.widthPixels; /*設定頭部View 的初始大小*/ mHeaderHeight = (int) ((9 * 1.0f / 16) * mScreenWidth); LayoutParams absLayoutParams = new LayoutParams(mScreenWidth, mHeaderHeight); mHeaderContainer.setLayoutParams(absLayoutParams); /*建立圖片顯示的View*/ mHeaderImg = new ImageView(context); FrameLayout.LayoutParams imgLayoutParams = new FrameLayout.LayoutParams (ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); mHeaderImg.setScaleType(ImageView.ScaleType.CENTER_CROP); mHeaderImg.setLayoutParams(imgLayoutParams); mHeaderContainer.addView(mHeaderImg); /*增加頭部View*/ addHeaderView(mHeaderContainer); /*設定監聽事件*/ super.setOnScrollListener(new InternalScrollerListener() );}/*處理事件用*/@Overridepublic boolean onTouchEvent(MotionEvent ev) { final int action = ev.getAction() & MotionEventCompat.ACTION_MASK; switch (action) { case MotionEvent.ACTION_DOWN: /*計算 x,y 的距離*/ int index = MotionEventCompat.getActionIndex(ev); mActivePointerId = MotionEventCompat.getPointerId(ev, index); if (mActivePointerId == INVALID_POINTER) break; mLastMotionX = MotionEventCompat.getX(ev, index); mLastMotionY = MotionEventCompat.getY(ev, index); // 結束動畫,目前沒做處理,可忽略 abortAnimation(); /*計算算一次放縮的比例*/ mLastScale = (this.mHeaderContainer.getBottom() / this.mHeaderHeight); /*當按下的時候把這個標誌為設為有效*/ isNeedCancelParent = true ; break; case MotionEvent.ACTION_MOVE: int indexMove = MotionEventCompat.getActionIndex(ev); mActivePointerId = MotionEventCompat.getPointerId(ev, indexMove); if (mActivePointerId == INVALID_POINTER) { /*這裡相當於鬆手*/ finishPull(); isNeedCancelParent = true ; } else { /*這是一個關索引值,通過這個值來判斷是否需要放大圖片*/ if (mHeaderContainer.getBottom() >= mHeaderHeight) { ViewGroup.LayoutParams params = this.mHeaderContainer .getLayoutParams(); final float y = MotionEventCompat.getY(ev, indexMove); float dy = y - mLastMotionY; float f = ((y - this.mLastMotionY + this.mHeaderContainer .getBottom()) / this.mHeaderHeight - this.mLastScale) / 2.0F + this.mLastScale; if ((this.mLastScale <= 1.0D) && (f <= this.mLastScale)) { params.height = this.mHeaderHeight; this.mHeaderContainer .setLayoutParams(params); return super.onTouchEvent(ev); } /*這裡設定緊湊度*/ dy = dy * 0.5f * (mHeaderHeight * 1.0f / params.height); mLastScale = (dy + params.height) * 1.0f / mHeaderHeight; mScale = clamp(mLastScale, 1.0f, mMaxScale);
// Log.v(“zgy”, “=======mScale=====” + mLastScale+”,f = “+f);
params.height = (int) (mHeaderHeight * mScale); mHeaderContainer.setLayoutParams(params); mLastMotionY = y; /*這裡,如果圖片有放大,則屏蔽ListView 的其他事件響應*/ if(isNeedCancelParent ){ isNeedCancelParent = false; MotionEvent motionEvent = MotionEvent.obtain(ev); motionEvent.setAction(MotionEvent.ACTION_CANCEL); super.onTouchEvent(motionEvent); } return true; } mLastMotionY = MotionEventCompat.getY(ev, indexMove); } break; case MotionEvent.ACTION_UP: /*結束事件響應,做相應的操作*/ finishPull(); break; case MotionEvent.ACTION_POINTER_UP: /*這裡需要注意,多點處理,這裡的處理方式是:如果有兩個手指按下,抬起的是後按下的手指,則不做處理 * 如果抬起的是最先按下的手指,則複原圖片效果。 * */ int pointUpIndex = MotionEventCompat.getActionIndex(ev); int pointId = MotionEventCompat.getPointerId(ev, pointUpIndex); if (pointId == mActivePointerId) { /*鬆手執行結束拖拽操作*/ /*結束事件響應,做相應的操作*/ finishPull(); } break; } return super.onTouchEvent(ev);}@Overridepublic void setOnScrollListener(OnScrollListener l) { mScrollListener = l ;}private void abortAnimation() { /*啥都沒做,暫時沒做而已*/}private void finishPull() { mActivePointerId = INVALID_POINTER; /*這是一個閾值,如果成立,則表示圖片已經放大了,在手指抬起的時候需要複原圖片*/ if (mHeaderContainer.getBottom() > mHeaderHeight){
// Log.v(“zgy”, “===super====onTouchEvent========”);
/這裡是下拉重新整理的閾值,當達到了,則表示需要重新整理,可以添加重新整理動畫/
if (mScale > REFRESH_SCALE){
if (mRefreshListener != null){
mRefreshListener.onRefresh();
}
}
/圖片複原動畫/
pullBackAnimation();
}
}
/** * 這是屬性動畫的知識,不懂的可以去看看屬性動畫的知識 */private void pullBackAnimation(){ ValueAnimator pullBack = ValueAnimator.ofFloat(mScale , 1.0f); pullBack.setInterpolator(sInterpolator); pullBack.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { float value = (float) animation.getAnimatedValue(); LayoutParams params = (LayoutParams) mHeaderContainer.getLayoutParams(); params.height = (int) (mHeaderHeight * value); mHeaderContainer.setLayoutParams(params); } }); pullBack.setDuration((long) (MIN_SETTLE_DURATION*mScale)); pullBack.start();}/** * 通過事件和點的 id 來擷取點的索引 * * @param ev * @param id * @return */private int getPointerIndex(MotionEvent ev, int id) { int activePointerIndex = MotionEventCompat.findPointerIndex(ev, id); if (activePointerIndex == -1) mActivePointerId = INVALID_POINTER; return activePointerIndex;}public void setOnRefreshListener(OnRefreshListener l){ mRefreshListener = l ;}public ImageView getHeaderImageView() { return this.mHeaderImg;}private float clamp(float value, float min, float max) { return Math.min(Math.max(value, min), max);}private class InternalScrollerListener implements OnScrollListener{ @Override public void onScrollStateChanged(AbsListView view, int scrollState) { if (mScrollListener != null){ mScrollListener.onScrollStateChanged(view,scrollState); } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { float diff = mHeaderHeight - mHeaderContainer.getBottom(); if ((diff > 0.0F) && (diff < mHeaderHeight)) { int i = (int) (0.3D * diff); mHeaderImg.scrollTo(0, -i); } else if (mHeaderImg.getScrollY() != 0) { mHeaderImg.scrollTo(0, 0); } Log.v("zgy","=========height==="+getScrolledY()); if (mScrollListener != null){ mScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount); } }}public int getScrolledY() { View c = getChildAt(0); if (c == null) { return 0; } int firstVisiblePosition = getFirstVisiblePosition(); int top = c.getTop(); int headerHeight = 0; if (firstVisiblePosition >= 1) { headerHeight = mHeaderHeight; } return -top + firstVisiblePosition * c.getHeight() + headerHeight;}public interface OnRefreshListener { void onRefresh() ;}public void computeRefresh(){ if (mActivePointerId != INVALID_POINTER){ }}
}
比較難理解的地方都做了注釋,所以。。。應該還是很好理解的。
總結:
今天 blog的一個痛點就是,手勢的處理,下拉放大的條件判斷;針對這種問題,我也沒有很好的解決方案,我的經驗就是,直接通過 列印 Log 的方式來尋找規律,因為有的時候想的挺煩的,而且邏輯很容易混亂。
接著是圖片放大的原理,知道了就很好實現此功能。
源碼