天天看點

View 滑動的實作View 滑動實作

View 滑動實作

複習一下view滑動的幾種實作方式

1.通過layout實作

通過不斷重新layout view 達到滑動的效果。

@Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // 記錄觸摸點坐标
                lastX = x;
                lastY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                // 計算偏移量
                int offsetX = x - lastX;
                int offsetY = y - lastY;
                // 在目前left、top、right、bottom的基礎上加上偏移量
                layout(getLeft() + offsetX,getTop() + offsetY,getRight() + offsetX,getBottom() + offsetY);

                //與上面的方法是等效,直接設定偏移量
                //offsetLeftAndRight(offsetX);
                //offsetTopAndBottom(offsetY);
                break;
        }
        return true;
    }
           

這裡需要注意,我們使用的getX 和getY 方法,如果使用 getRawX 和getRawY 方法,那麼代碼就要做些許改變。就是在重新layout後,我們需要重新指派 lastX和lastY的值。 否則就會出現意想不到的結果哦。 有興趣的朋友可以試一試,可以加深對于這兩個方法的認識。

2.通過改變LayoutParams

layoutparams 儲存了view的布局參數,我們可以通過不斷修改其參數,重新設定,達到滑動效果。需要修改的參數就是自己與父view的邊距。注意通過getLayotParams 方法擷取layoutparams時類型要根據其父view的類型來确定。

@Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // 記錄觸摸點坐标
                lastX = (int) event.getX();
                lastY = (int) event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                // 計算偏移量
                int offsetX = x - lastX;
                int offsetY = y - lastY;
                // 轉換成這個類型,就不用關心父view的類型了
                ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
                //LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) getLayoutParams();
                layoutParams.leftMargin = getLeft() + offsetX;
                layoutParams.topMargin = getTop() + offsetY;
                setLayoutParams(layoutParams);
                break;
        }
        return true;
    }
           

3.通過view自己的scrollTo和scrollBy 來實作

srollTo(x,y) 表示移動到位置(x,y)處。

srollBy(dx,dy) 表示在目前位置的基礎上移動 dx和dy的距離。dx和dy為偏移量。

public void scrollBy(int x, int y) {  
       scrollTo(mScrollX + x, mScrollY + y);  
   }  
           

同時需要注意,他們移動的都是view自己的内容。對應 viewgroup移動的就是自己的子view。是以父viewgroup中的素有view都會一起移動哦。

@Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                lastX = (int) event.getX();
                lastY = (int) event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                int offsetX = x - lastX;
                int offsetY = y - lastY;
                //這裡要注意,offsetx為我們相對于父viewgroup移動的距離,但是,這裡我們調用的是讓父viewgroup移動的方法,是以參考系,相對于反轉了,是以要在前面加上負号。
                ((View) getParent()).scrollBy(-offsetX, -offsetY);
                break;
        }
        return true;
    }
           

4.scroller

與上面的不同,scroller主要是用于當沒有使用者手動操作的時候,我們用來連續移動 view 的方式。需要配合view 的computeScroll 和scrollTo方法配合使用。該方法會在view 的draw方法中被調用。

@Override
    public void computeScroll() {
        super.computeScroll();
        // 判斷Scroller是否執行完畢
        if (mScroller.computeScrollOffset()) {
            ((View) getParent()).scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            // 通過重繪來不斷調用computeScroll
            invalidate();
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                lastX = (int) event.getX();
                lastY = (int) event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                int offsetX = x - lastX;
                int offsetY = y - lastY;
                ((View) getParent()).scrollBy(-offsetX, -offsetY);
                break;
            case MotionEvent.ACTION_UP:
                // 手指離開時,執行滑動過程
                View viewGroup = ((View) getParent());
                mScroller.startScroll(viewGroup.getScrollX(), viewGroup.getScrollY(), -viewGroup.getScrollX(), -viewGroup.getScrollY());
                invalidate();
                break;
        }
        return true;
    }
           

流程如下:

View 滑動的實作View 滑動實作

說的通俗一點就是:scroller 每次提供一個值 mScroller.getCurrX(),然後view scrollTo這個位置,然後view 重新繪制,然後又調用 computeScroll 方法,然後scroler 又提供一個值mScroller.getCurrX() 又重繪。 相當于一個死循環,不停地移動。 循環的出口就是mScroller.computeScrollOffset() 方法。 這個方法有兩個作用,一個是傳回是否滑動到了指定的位置,二是每次調用時會不斷的改變 mScroller.getCurrX()的值,讓這個越來越接近滑動結束的值,知道達到滑動結束的值時,該方法的傳回值也就變成了false。

看看scroller的部分代碼,首先是其構造函數,其實也就是初始化了一些滑動的位置和時間值:

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 = f / (float) mDuration;  
        // This controls the viscous fluid effect (how much of it)  
        mViscousFluidScale = f;  
        // must be set to 1.0 (used in viscousFluid())  
        mViscousFluidNormalize = f;  
        mViscousFluidNormalize = f / viscousFluid(f);  
    }  
           

然後是其 computeScrollOffset()方法

public boolean computeScrollOffset() {  
    if (mFinished) {  
        return false;  
    }  

    int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);  

    if (timePassed < mDuration) {  
        switch (mMode) {  
        case SCROLL_MODE:  
            float x = (float)timePassed * mDurationReciprocal;  

            if (mInterpolator == null)  
                x = viscousFluid(x);   
            else  
                x = mInterpolator.getInterpolation(x);  

            mCurrX = mStartX + Math.round(x * mDeltaX);  
            mCurrY = mStartY + Math.round(x * mDeltaY);  
            break;  
        case FLING_MODE:  
            float timePassedSeconds = timePassed / f;  
            float distance = (mVelocity * timePassedSeconds)  
                    - (mDeceleration * timePassedSeconds * timePassedSeconds / f);  

            mCurrX = mStartX + Math.round(distance * mCoeffX);  
            // Pin to mMinX <= mCurrX <= mMaxX  
            mCurrX = Math.min(mCurrX, mMaxX);  
            mCurrX = Math.max(mCurrX, mMinX);  

            mCurrY = mStartY + Math.round(distance * mCoeffY);  
            // Pin to mMinY <= mCurrY <= mMaxY  
            mCurrY = Math.min(mCurrY, mMaxY);  
            mCurrY = Math.max(mCurrY, mMinY);  

            break;  
        }  
    }  
    else {  
        mCurrX = mFinalX;  
        mCurrY = mFinalY;  
        mFinished = true;  
    }  
    return true;  
}  
           

5.ViewDragHelper

ViewDragHelper

v4

包中Google 為我們封裝了

Scroller

的一個工具類。為我們實作不同的滑動等效果提供的一種方案。

其一般使用在

ViewGroup

中,通過其工廠方法建立其執行個體,通過其執行個體來處理

View

的事件和移動。 相當于我們把

View

(子

View

)都交給她來處理了。

  • 首先當然是先建立其執行個體對象
mViewDragHelper = ViewDragHelper.create(this, callback);
           
  • ViewGroup

    的事件交給

    mViewDragHelper

    處理
@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return mViewDragHelper.shouldInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mViewDragHelper.processTouchEvent(event);
        return true;
    }
           
  • 因為同樣是 通過

    Scroller

    實作滑動的,是以我們也需要重寫

    computeScroll

    方法
@Override
    public void computeScroll() {
        if (mViewDragHelper.continueSettling(true)) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }
           

這些也都是固定寫法了。

  • 最為關鍵的就是 在建立

    mViewDragHelper

    時我們傳入的

    callback

    對象,這個是我們要自己去實作的。通過這個回調,我們可以實作我們自己需要的滑動效果。
private ViewDragHelper.Callback callback =
            new ViewDragHelper.Callback() {

                //是否處理 該view也就是參數 child。傳回 true 處理,否則這忽略噶子view。
                @Override
                public boolean tryCaptureView(View child, int pointerId) {
                    return mchild == child;
                }

                // 觸摸到View後回調
                @Override
                public void onViewCaptured(View capturedChild,
                                           int activePointerId) {
                    super.onViewCaptured(capturedChild, activePointerId);
                }

                // 當拖拽狀态改變時回調 如:STATE_IDLE、STATE_DRAGGING、STATE_SETTLING
                @Override
                public void onViewDragStateChanged(int state) {
                    super.onViewDragStateChanged(state);
                }

                // 當位置改變的時候調用,常用與滑動時更改scale等
                @Override
                public void onViewPositionChanged(View changedView,
                                                  int left, int top, int dx, int dy) {
                    super.onViewPositionChanged(changedView, left, top, dx, dy);
                }

                // 處理垂直滑動
                @Override
                public int clampViewPositionVertical(View child, int top, int dy) {
                    return ;
                }

                // 處理水準滑動
                @Override
                public int clampViewPositionHorizontal(View child, int left, int dx) {
                    return left;
                }

                // 拖動結束後調用
                @Override
                public void onViewReleased(View releasedChild, float xvel, float yvel) {
                    super.onViewReleased(releasedChild, xvel, yvel);
                    //手指擡起後緩慢移動到指定位置
                    //相當于Scroller的startScroll方法
                     mViewDragHelper.smoothSlideViewTo(mchild, , );
                     ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
                }
            };
           

這樣,我們就完成了想要的滑動了。之後,會分析一下它的源碼,看看到底如何實作的。