Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android Scroll分析 (二) 教你使用七種方法實現滑動

Android Scroll分析 (二) 教你使用七種方法實現滑動

編輯:關於Android編程

實現滑動的基本思想是:當觸摸View時,系統記下當前觸摸點坐標;當手指移動時,系統記下移動後的觸摸點坐標,從而獲取到相對於前一次坐標點的偏移量,並通過偏移量來修改View的坐標,這樣不斷重復,從而實現滑動過程.

2.1 Layout方法

在View進行繪制時,會調用onLayout()方法來設置顯示的位置
通過修改View的left,top,right,bottom四個屬性來控制View的坐標,在每次回調onTouchEvent的時候,獲取一下觸摸點的坐標:

    // 視圖坐標方式
    @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()來獲取坐標,並使用絕對坐標來計算偏移量,要在每次執行完ACTION_MOVE的邏輯後,一定要重新設置初始坐標,這樣才能准確地獲取偏移量.

   // 絕對坐標方式
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int rawX = (int) (event.getRawX());
        int rawY = (int) (event.getRawY());
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // 記錄觸摸點坐標
                lastX = rawX;
                lastY = rawY;
                break;
            case MotionEvent.ACTION_MOVE:
                // 計算偏移量
                int offsetX = rawX - lastX;
                int offsetY = rawY - lastY;
                // 在當前left、top、right、bottom的基礎上加上偏移量
                layout(getLeft() + offsetX,
                        getTop() + offsetY,
                        getRight() + offsetX,
                        getBottom() + offsetY);
                // 重新設置初始坐標
                lastX = rawX;
                lastY = rawY;
                break;
        }
        return true;
    }

2.2 offsetLeftAndRight()與offsetTopAndBottom()

當計算出偏移量後,只需使用如下代碼就可以完成View的重新布局:

//同時對left和right進行偏移
offsetLeftAndRight(offsetX);
//同時對top和bottom進行偏移
offsetTopAndBottom(offsetY);

2.3 LayoutParams

LayoutParams保存了一個View的布局參數,通過LayoutParams來動態改變View的位置參數,從而改變View位置效果.
使用getLayoutParams()來獲取一個View的LayoutParams.
通過setLayoutParams來改變其LayoutParams.

    @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;
                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;
    }

通過getLayoutParams()獲取LayoutParams時,需要根據View所在父布局的類型來設置不同的類型.
還可以使用ViewGroup.MarginLayoutParams來實現這樣的功能.

2.4 scrollTo與scrollBy

在View中,提供了scrollTo,scrollBy兩種方式來改變一個View的位置,兩者的區別:與英文中To與By的區別類似,scroll(x,y)表示移動到一個具體的坐標點(x,y),而scrollBy(dx,dy)表示移動的增量為dx,dy.
scrollTo,scrollBy方法移動的是View的content,即讓View的內容移動,如果在ViewGroup中使用scrollTo,scrollBy方法,那麼移動的將是所有子View,但如果在View中使用,那麼移動的將是View的內容.
將scrollBy中的參數dx和dy設置為正數,那麼content將向坐標軸負方向移動;如果將scrollBy中的參數dx和dy設置為負數,那麼content將向坐標軸正方向移動.
因此,要實現跟隨手指移動而滑動的效果,就必須將偏移量改為負值:

 @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;
        }
        return true;
    }

在使用絕對坐標時,也可以通過使用scroll方法來實現.

2.5 Scroller

使用Scroller對象,需要三個步驟:
1.初始化Scroller
首先,通過它的構造方法來創建一個Scroller對象:

//初始化Scroller
mScroller=new Scroller(context);

2.重寫computeScroll()方法,實現模擬滑動
重寫computeScroll()方法,它是Scroller類的核心,系統在繪制View的時候會在draw()方法中調用該方法:

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

Scroller類提供了computeScrollOffset()方法來判斷是否完成了整個滑動,通過getCurrX(),getCurrY()方法來獲得當前的滑動坐標,注意invalidate()方法,因為只能在computeScroll()方法中獲取模擬過程中的scrollX和scrollY坐標.但computeScroll()方法是不會自動調用的,只能通過invalidate()->draw()->computeScroll()來間接調用computeScroll()方法,所有需要調用invalidate()方法,實現循環獲取scrollX和scrollY的目的,而當模擬過程結束後,scroller.computeScrollOffset()方法會返回false,從而中斷循環,完成整個平滑移動過程.
3.startScroll開啟模擬過程
在需要使用平滑移動的事件中,使用Scroller類的startScroll()方法來開啟平滑移動過程.
startScroll()方法具有兩個重載方法:

public void startScroll(int startX,int startY,int dx,int dy,int duration)
public void startScroll(int startX,int startY,int dx,int dy)

它們的區別在於:是否具有指定的持續時長.其它參數分別為起始坐標與偏移量.
在獲取坐標時,使用getScrollX()和getScrollY()方法來獲取父視圖中content所滑動到的點的坐標.注意正負值,與scrollBy,scrollTo的情況相同.
例子:
演示一下如何使用Scroller類實現平滑移動.在這個實例中,讓子View跟隨手指的滑動而滑動,但是在手指離開屏幕是,讓子View平滑的移動到初始位置,即屏幕左上角:

public class DragView extends View {

    private int lastX;
    private int lastY;
    private Scroller mScroller;

    public DragView(Context context) {
        super(context);
        ininView(context);
    }

    public DragView(Context context, AttributeSet attrs) {
        super(context, attrs);
        ininView(context);
    }

    public DragView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        ininView(context);
    }

    private void ininView(Context context) {
        setBackgroundColor(Color.BLUE);
        // 初始化Scroller
        mScroller = new Scroller(context);
    }

    @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;
    }
}

在startScroll()方法中,獲取子View移動的距離–getScrollX(),getScrollY(),並將偏移量設置為其相反數,從而將子View滑動到原位置.注意invalidate()方法,需要使用這個方法來通知View進行重繪,從而來調用computeScroll()的模擬過程.

2.6 ViewDragHelper

ViewDragHelper可以實現各種不同的滑動 拖放需求.
使用方法:
初始化ViewDragHelper
首先,自然是需要初始化ViewDragHelper.ViewDragHelper通常定義在一個ViewGroup的內部,並通過其靜態工廠方法進行初始化.

mViewDragHelper=ViewDragHelper.create(this,callback);

第一個參數是要監聽的View,通常需要是一個ViewGroup,即parentView;第二個參數是一個Callback回調,這個回調是整個ViewDragHelper核心.
攔截事件
接下來,重寫事件攔截方法,將事件傳遞給ViewDragHelper進行處理:

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

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //將觸摸事件傳遞給ViewDragHelper,此操作必不可少
        mViewDragHelper.processTouchEvent(event);
        return true;
    }

處理computeScroll()
因為ViewDragHelp內部是通過Scroller來實現平滑移動的,所以需要實現處理computeScroll().

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

處理回調Callback
下面就是最關鍵的Callback實現:

private ViewDragHelper.Callback callback =
            new ViewDragHelper.Callback() {

                // 何時開始檢測觸摸事件
                @Override
                public boolean tryCaptureView(View child, int pointerId) {
                    //如果當前觸摸的child是mMainView時開始檢測
                    return mMainView == child;
                }

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

                // 當拖拽狀態改變,比如idle,dragging
                @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 0;
                }

                // 處理水平滑動
                @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);
                    //手指抬起後緩慢移動到指定位置
                    if (mMainView.getLeft() < 500) {
                        //關閉菜單
                        //相當於Scroller的startScroll方法
                        mViewDragHelper.smoothSlideViewTo(mMainView, 0, 0);
                        ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
                    } else {
                        //打開菜單
                        mViewDragHelper.smoothSlideViewTo(mMainView, 300, 0);
                        ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
                    }
                }
            };

實現側滑菜單的完整例子:

public class DragViewGroup extends FrameLayout {

    private ViewDragHelper mViewDragHelper;
    private View mMenuView, mMainView;
    private int mWidth;

    public DragViewGroup(Context context) {
        super(context);
        initView();
    }

    public DragViewGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    public DragViewGroup(Context context,
                         AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mMenuView = getChildAt(0);
        mMainView = getChildAt(1);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = mMenuView.getMeasuredWidth();
    }

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

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //將觸摸事件傳遞給ViewDragHelper,此操作必不可少
        mViewDragHelper.processTouchEvent(event);
        return true;
    }

    private void initView() {
        mViewDragHelper = ViewDragHelper.create(this, callback);
    }

    private ViewDragHelper.Callback callback =
            new ViewDragHelper.Callback() {

                // 何時開始檢測觸摸事件
                @Override
                public boolean tryCaptureView(View child, int pointerId) {
                    //如果當前觸摸的child是mMainView時開始檢測
                    return mMainView == child;
                }

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

                // 當拖拽狀態改變,比如idle,dragging
                @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 0;
                }

                // 處理水平滑動
                @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);
                    //手指抬起後緩慢移動到指定位置
                    if (mMainView.getLeft() < 500) {
                        //關閉菜單
                        //相當於Scroller的startScroll方法
                        mViewDragHelper.smoothSlideViewTo(mMainView, 0, 0);
                        ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
                    } else {
                        //打開菜單
                        mViewDragHelper.smoothSlideViewTo(mMainView, 300, 0);
                        ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
                    }
                }
            };

    @Override
    public void computeScroll() {
        if (mViewDragHelper.continueSettling(true)) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }
}
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved