1. 程式人生 > >android自定義控制元件系列教程----繼承ViewGroup實現帶阻力效果的可回彈的SrollView

android自定義控制元件系列教程----繼承ViewGroup實現帶阻力效果的可回彈的SrollView

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裡面這樣使用就可以了。