android自定義控制元件系列教程----繼承ViewGroup實現帶阻力效果的可回彈的SrollView
阿新 • • 發佈:2019-02-09
package com.example.scolview; import android.content.Context; import android.util.Log; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.widget.ListView; import android.widget.ScrollView; import android.widget.Scroller; import java.io.InputStream; /* * @FileName:QSrollView.java * @Version:V1.0 * @Date: 2015-2-1 Create * @author: edsheng * */ class QSrollView extends ViewGroup { public final static String TAG = QSrollView.class.getSimpleName(); public final static int TOUCH_STATE_SROLLING = 1; // 當前在滑動狀態 public final static int TOUCH_STATE_FLING = 2; // 當前fling狀態 public final static int TOUCH_STATE_DEFALUT = 0; // 預設 private int mTouchState = TOUCH_STATE_DEFALUT; private int mTouchSlop = 0; // 當前滑動閥值 private int mLastMontionY; // 記錄上次y的位置 Scroller mScroller; // 滑動輔助類 private int mTotalLength = 0; // 整個控制元件的長度 private int mMaxmumVelocity = 0; // Velocity的閥值 private VelocityTracker mVelocityTracker; // Velocity int mPointID = 0; // pointID public QSrollView(Context context) { super(context); init(); } private void init() { mScroller = new Scroller(getContext()); mTouchSlop = ViewConfiguration.getTouchSlop(); mMaxmumVelocity = ViewConfiguration.getMaximumFlingVelocity(); } @Override public void scrollBy(int x, int y) { // 判斷當前檢視是否超過了頂部或者頂部就讓它滑動的距離為1/3這樣就有越拉越拉不動的效果 if (getScrollY() < 0 || getScrollY() + getHeight() > mTotalLength) { super.scrollBy(x, y / 3); } else { super.scrollBy(x, y); } } /** * 事件攔截 */ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { final int action = ev.getAction(); // 表示已經開始滑動了,不需要走該Action_MOVE方法了(第一次時可能呼叫)。 if ((action == MotionEvent.ACTION_MOVE) && (mTouchState != TOUCH_STATE_DEFALUT)) { return true; } int y = (int) ev.getY(); switch (action) { case MotionEvent.ACTION_MOVE: final int xDiff = (int) Math.abs(mLastMontionY - y); // 超過了最小滑動距離 if (xDiff > mTouchSlop) { mTouchState = TOUCH_STATE_SROLLING; } break; case MotionEvent.ACTION_POINTER_DOWN: mPointID = ev.getPointerId(ev.getActionIndex()); // 記錄當前pointID break; case MotionEvent.ACTION_DOWN: mLastMontionY = y; Log.e(TAG, mScroller.isFinished() + ""); if (!mScroller.isFinished()) // 當動畫還沒有結束的時候強制結束 { mScroller.abortAnimation(); mScroller.forceFinished(true); } mTouchState = TOUCH_STATE_DEFALUT; break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: mTouchState = TOUCH_STATE_DEFALUT; break; } Log.e(TAG, mTouchState + "====" + TOUCH_STATE_DEFALUT); return mTouchState != TOUCH_STATE_DEFALUT; } @Override public boolean onTouchEvent(MotionEvent event) { int touchIndex = event.getActionIndex(); if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } mVelocityTracker.addMovement(event); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mPointID = event.getPointerId(0); mLastMontionY = (int) event.getY();// 記錄按下的點 break; case MotionEvent.ACTION_POINTER_DOWN: // 新增多點觸控的處理 mPointID = event.getPointerId(touchIndex); mLastMontionY = (int) (event.getY(touchIndex) + 0.5f); // 記錄按下的點 break; case MotionEvent.ACTION_MOVE: touchIndex = event.findPointerIndex(mPointID); if (touchIndex < 0) // 當前index小於0就返false繼續接受下一次事件 return false; int detaY = (int) (mLastMontionY - event.getY(touchIndex)); // 計算滑動的距離 scrollBy(0, detaY); // 呼叫滑動函式 mLastMontionY = (int) event.getY(touchIndex); // 記錄上一次按下的點 break; case MotionEvent.ACTION_UP: Log.d("edsheng", "Action UP"); mVelocityTracker.computeCurrentVelocity(1000); if (Math.abs(mVelocityTracker.getYVelocity()) > mMaxmumVelocity&&!checkIsBroad()) { mScroller.fling(getScrollX(), getScrollY(), 0,-(int) mVelocityTracker.getYVelocity(), 0, 0, 0, mTotalLength - getHeight()); } else { actionUP(); // 回彈效果 } mTouchState = TOUCH_STATE_DEFALUT; break; case MotionEvent.ACTION_POINTER_UP: // 新增多點觸控的支援 if (event.getPointerId(touchIndex) == mPointID) { final int newIndex = touchIndex == 0 ? 1 : 0; mPointID = event.getPointerId(newIndex); mLastMontionY = (int) (event.getY(newIndex) + 0.5f); } break; case MotionEvent.ACTION_CANCEL: mTouchState = TOUCH_STATE_DEFALUT; break; default: break; } // super.onTouchEvent(event); return true; } /** * 回彈函式 */ private void actionUP() { if (getScrollY() < 0 || getHeight() > mTotalLength) // 頂部回彈 { Log.d("edsheng", "頂部回彈!!!!"); mScroller.startScroll(0, getScrollY(), 0, -getScrollY()); // 開啟回彈效果 invalidate(); } else if (getScrollY() + getHeight() > mTotalLength) // 底部回彈 { // 開啟底部回彈 mScroller.startScroll(0, getScrollY(), 0, -(getScrollY() + getHeight() - mTotalLength)); invalidate(); } } /*** * 檢測當前是否可回彈 * * @return */ boolean checkIsBroad() { if (getScrollY() < 0 || getScrollY() + getHeight() > mTotalLength) // 頂部回彈) // //頂部回彈 return true; else return false; } /** * 重寫onMeasure方法計算 */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { final int size = getChildCount(); final int parentWidthSize = MeasureSpec.getSize(widthMeasureSpec); final int paretnHeightSize = MeasureSpec.getSize(heightMeasureSpec); for (int i = 0; i < size; ++i) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { LayoutParams childLp = child.getLayoutParams(); final boolean childWidthWC = childLp.width == LayoutParams.WRAP_CONTENT; final boolean childHeightWC = childLp.height == LayoutParams.WRAP_CONTENT; int childWidthMeasureSpec; int childHeightMeasureSpec; if (child.getLayoutParams() instanceof MarginLayoutParams) { MarginLayoutParams childMarginLp = (MarginLayoutParams) childLp; childWidthMeasureSpec = childWidthWC ? MeasureSpec .makeMeasureSpec(parentWidthSize, MeasureSpec.UNSPECIFIED) : getChildMeasureSpec(widthMeasureSpec, getPaddingLeft() + getPaddingRight() + childMarginLp.leftMargin + childMarginLp.rightMargin, childLp.width); childHeightMeasureSpec = childHeightWC ? MeasureSpec .makeMeasureSpec(paretnHeightSize, MeasureSpec.UNSPECIFIED) : getChildMeasureSpec(heightMeasureSpec, getPaddingTop() + getPaddingBottom() + childMarginLp.topMargin + childMarginLp.bottomMargin, childMarginLp.height); } else { childWidthMeasureSpec = childWidthWC ? MeasureSpec .makeMeasureSpec(parentWidthSize, MeasureSpec.UNSPECIFIED) : getChildMeasureSpec(widthMeasureSpec, getPaddingLeft() + getPaddingRight(), childLp.width); childHeightMeasureSpec = childHeightWC ? MeasureSpec .makeMeasureSpec(paretnHeightSize, MeasureSpec.UNSPECIFIED) : getChildMeasureSpec(heightMeasureSpec, getPaddingTop() + getPaddingBottom(), childLp.height); } child.measure(childWidthMeasureSpec, childHeightMeasureSpec); } } super.onMeasure(widthMeasureSpec, heightMeasureSpec); } /*** * 重寫layout方法 */ @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int childStartPostion = 0; mTotalLength = 0; final int count = getChildCount(); if (count == 0) { return; } childStartPostion = getPaddingTop(); for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child != null && child.getVisibility() != View.GONE) { LayoutParams lp = child.getLayoutParams(); final int childHeight = child.getMeasuredHeight(); int leftMargin = 0; int rightMargin = 0; int topMargin = 0; int bottomMargin = 0; if (lp instanceof MarginLayoutParams) { MarginLayoutParams mlp = (MarginLayoutParams) lp; leftMargin = mlp.leftMargin; rightMargin = mlp.rightMargin; topMargin = mlp.topMargin; bottomMargin = mlp.bottomMargin; } childStartPostion += topMargin; int startX = (getWidth() - leftMargin - rightMargin - child .getMeasuredWidth()) / 2 + leftMargin; child.layout(startX, childStartPostion, startX + child.getMeasuredWidth(), childStartPostion + childHeight); childStartPostion += (childHeight + bottomMargin); } } childStartPostion += getPaddingBottom(); mTotalLength = childStartPostion; } @Override public void computeScroll() { if (mScroller.computeScrollOffset()) // 計算當前位置 { // 滾動 scrollTo(0, mScroller.getCurrY()); postInvalidate(); } } }
然後我們在我們的Activity裡面這樣使用就可以了。