View移動、Scroller、GestureDetector詳解

View滾動

view的移動動有兩個api方法:
1、scrollTo(int targetX,int targetY)——移動到座標點(targetX,targetY)處;
2、scrollBy(int deltaX,int deltaY)——在x軸上移動deltaX距離,在y軸上移動deltaY距離。實際上是通過當前位置和deltaX、deltaY,計算出最終位置,調用scrollTo()方法;

scrollTo()源碼如下:

public void scrollTo(int x, int y) {
        if (mScrollX != x || mScrollY != y) {
            int oldX = mScrollX;
            int oldY = mScrollY;
            mScrollX = x;
            mScrollY = y;
            invalidateParentCaches();
            onScrollChanged(mScrollX, mScrollY, oldX, oldY);
            if (!awakenScrollBars()) {
                postInvalidateOnAnimation();
            }
        }
    }

邏輯就是,記錄最終位置,調用postInvalidateOnAnimation()請求重繪,然後就會回調draw()方法,draw()方法部分代碼如下:

。。。。。。
。。。。。。
        // Step 2, save the canvas' layers
        int paddingLeft = mPaddingLeft;

        final boolean offsetRequired = isPaddingOffsetRequired();
        if (offsetRequired) {
            paddingLeft += getLeftPaddingOffset();
        }

        int left = mScrollX + paddingLeft;
        int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
        int top = mScrollY + getFadeTop(offsetRequired);
        int bottom = top + getFadeHeight(offsetRequired);
。。。。。。
。。。。。。

draw()方法中使用剛纔存儲的“最終位置”,計算出上下左右座標值,在“最終位置”繪製view,達到移動的效果。

但是這樣的移動是瞬時完成的,沒有“滾動”效果,所以ScrollView、ListView等肯定有另一種機制,這種機制就是Scroller類。

Scroller

Scroller的源碼非常簡單,這個類只做兩件事:計算座標值、存儲座標值。在View類中,通過Scroller實例,拿到座標值,在draw()方法中進行“移動”。

Scroller常用的方法有兩個,startScroll()和computeScrollOffset(),源碼如下:

// 計算、存儲座標值
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
        mMode = SCROLL_MODE;
        mFinished = false;
        mDuration = duration;
        mStartTime = AnimationUtils.currentAnimationTimeMillis();
        mStartX = startX;
        mStartY = startY;
        mFinalX = startX + dx;
        mFinalY = startY + dy;
        mDeltaX = dx;
        mDeltaY = dy;
        mDurationReciprocal = 1.0f / (float) mDuration;
    }
// 根據設置的滑動時間、當前已經滑動的時間,計算接下來應該滑動多少距離
public boolean computeScrollOffset() {
        if (mFinished) {
            return false;
        }

        // 已經滑動的時間
        int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);

        if (timePassed < mDuration) {
            switch (mMode) {
            case SCROLL_MODE:
                // 通過時間,計算應該滑動的距離,計算出接下來的目標位置
                final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
                mCurrX = mStartX + Math.round(x * mDeltaX);
                mCurrY = mStartY + Math.round(x * mDeltaY);
                break;
            case FLING_MODE:
                final float t = (float) timePassed / mDuration;
                。。。。。。
                。。。。。。
                。。。。。。
                break;
            }
        }
        else {
            mCurrX = mFinalX;
            mCurrY = mFinalY;
            mFinished = true;
        }
        return true;
    }

ScrollView中使用了Scroller的子類,滑動邏輯也比較複雜,就不以ScrollView源碼來分析了,下面一個自定義View來分析、解讀Scroller的用法。

public class MyView extends LinearLayout {
    private Scroller mScroller;

    public MyView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mScroller = new Scroller(context);
    }

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mScroller = new Scroller(context);
    }

    public MyView(Context context) {
        super(context);
        mScroller = new Scroller(context);
    }

    @Override
    // 爲了更簡單地說明Scroller類的用法,設定爲觸摸View時就向上滑動100距離,滑動時間爲10000毫秒
    public boolean onTouchEvent(MotionEvent ev) {
        // getScrollY()是View類的方法,返回View當前的y座標
        mScroller.startScroll(0, getScrollY(), 0, 100, 10000);
        // 進行重繪,回調draw()方法,draw()方法中會調用computeScroll()方法
        invalidate();
        return super.onTouchEvent(ev);
    }

    @Override
    public void computeScroll() {
        // 調用computeScrollOffset()計算已經滑動的時間、接下來該滑動的距離等等
        if (mScroller.computeScrollOffset()) {
            // 執行移動操作。getCurrX()就是在computeScrollOffset()中計算出來的
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            // 繼續請求重繪,再次回調computeScroll()方法,循環此過程,直到滑動時間10000毫秒用完,mScroller.computeScrollOffset()返回false,跳出循環
            postInvalidate();
        }
    }

GestureDetector

先貼上使用方法:

public class MyView extends LinearLayout {
    private GestureDetector mGesture;

    // 還有OnDoubleTapListener、SimpleGestureListener等監聽器
    private GestureDetector.OnGestureListener mGesListener = new GestureDetector.OnGestureListener() {
        @Override
        public boolean onDown(MotionEvent e) {
            // 手指按下時觸發
            return false;
        }

        @Override
        public void onShowPress(MotionEvent e) {
            // 手指按下、沒有鬆開時觸發
        }

        @Override
        public boolean onSingleTapUp(MotionEvent e) {
            // 手指按下、快速鬆開時觸發
            return false;
        }

        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            // 手指按下、滑動時觸發
            return false;
        }

        @Override
        public void onLongPress(MotionEvent e) {
            // 長按時觸發
        }

        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            // 快速滑動時觸發
            return false;
        }
    };

    public MyView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mGesture = new GestureDetector(context,mGesListener);
    }

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mGesture = new GestureDetector(context,mGesListener);
    }

    public MyView(Context context) {
        super(context);
        mGesture = new GestureDetector(context,mGesListener);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        mGesture.onTouchEvent(ev);
        // 一定要返回true,GesListener裏的方法纔會全部回調
        return true;
    }
}

onTouchEvent()方法中,讓GestureDetector接管MotionEvent事件,GestureDetector通過分析ACTION_DOWN、ACTION_MOVE、ACTION_UP事件,判斷出用戶的滑動動作,從而回調相應的方法。

總結

1、自己在onTouchEvent()方法裏對動作、時間進行計算,也可以判斷出用戶的動作,一般簡單一點的動作就直接在onTouchEvent()方法裏自己判斷,複雜一點的動作就通過GestureDetector來判斷;

2、Scroller一般和GestureDetector結合使用,在onScroll()、onFling()方法中進行控件移動操作;

3、Scroller類中mStartX、mCurrX、mFinalX分別是移動開始時的座標、移動過程中的實時座標(移動是耗時過程)、移動目標位置的座標;

4、View類中的getScrollX()方法返回值是mScrollX,而mScrollXscrollTo(int x ,int y)方法中直接賦值mScrollX=y,因爲scrollTo()是瞬間完成的,所以getScrollX()得到的總是View的當前座標;

發佈了35 篇原創文章 · 獲贊 52 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章