實現View滑動的七種方法

回首111 8年前發布 | 10K 次閱讀 安卓開發 Android開發 移動開發

Android坐標系

在介紹如何實現View滑動之前先了解一下Android的坐標系,我們在初中數學就學過坐標系,有原點和X軸Y軸,不過屏幕上的坐標系稍微有點區別,移動設備一般將 屏幕的左上角 定義為原點,向右為X軸正方向,向下為Y軸正方向,如下圖:

View坐標系

與屏幕坐標系相同,View也有自己的坐標系,我們可以稱之為視圖坐標系,描述了本身和父布局的位置關系,原點在View的左上角:

View及MotionEvent坐標獲取

View自身坐標獲取方法

  • getTop():獲取到的,是view自身的頂邊到其父布局頂邊的距離

  • getLeft():獲取到的,是view自身的左邊到其父布局左邊的距離

  • getRight():獲取到的,是view自身的右邊到其父布局左邊的距離

  • getBottom():獲取到的,是view自身的底邊到其父布局頂邊的距離

MotionEvent坐標獲取

  • getX():獲取點擊事件相對控件左邊的x軸坐標,即點擊事件距離控件左邊的距離

  • getY():獲取點擊事件相對控件頂邊的y軸坐標,即點擊事件距離控件頂邊的距離

  • getRawX():獲取點擊事件相對整個屏幕左邊的x軸坐標,即點擊事件距離整個屏幕左邊的距離

  • getRawY():獲取點擊事件相對整個屏幕頂邊的y軸坐標,即點擊事件距離整個屏幕頂邊的距離

說了這么多方法都不如一張圖最直接:

觸控事件onTouch

學好觸控事件是掌握后續內容的重要基礎,觸控事件回調的MotionEvent封裝了一些常用的事件常量,定義了一些常見類型動作。

/**

  • A pressed gesture has started, the motion contains the initial starting location. */ public static final int ACTION_DOWN = 0;

/**

  • A pressed gesture has finished, the motion contains the final release location as well as any intermediate
  • points since the last down or move event. */ public static final int ACTION_UP = 1;

/**

  • A change has happened during a
  • press gesture (between {@link #ACTION_DOWN} and {@link #ACTION_UP}). */ public static final int ACTION_MOVE = 2;

/**

  • The current gesture has been aborted. */ public static final int ACTION_CANCEL = 3;

/**

  • A movement has happened outside of the normal bounds of the UI element. */ public static final int ACTION_OUTSIDE = 4;

/**

  • A non-primary pointer has gone down. */ public static final int ACTION_POINTER_DOWN = 5;

/**

  • A non-primary pointer has gone up. */ public static final int ACTION_POINTER_UP = 6;</code></pre>

    我們讓View滑動的大概思路是重寫View的onTouchEvent(MotionEvent event)方法,來控制View的移動,這個代碼模板基本固定的,show me the code :

    @Override
    public boolean onTouch(View v, MotionEvent event) {
     // 記錄當前point所在的位置
     x = (int) event.getX();
     y = (int) event.getY();
     switch (event.getAction()) {
    
     case MotionEvent.ACTION_DOWN:
     //處理按下事件
     break;
    
    case MotionEvent.ACTION_MOVE:
     //處理移動事件
     break;
    
    case MotionEvent.ACTION_UP:
     //處理松開事件
     break;
    
    } // 事件處理完畢 return true; }</code></pre>

    該方法return true 代表觸控事件到這里就處理完畢了,不必要再繼續傳遞,不懂的可以去再回顧一下Android的 觸摸事件分發機制 。下面我們就可以進入主題,來看一下有哪些方法可以移動View。

    實現滑動

    我們了解了Android坐標系和觸控事件,接著我們可以模擬實現View的滑動了,思路是:當發生onTouch事件時,記錄下位置,當手指移動時,記錄移動的坐標,獲得一個相對偏移量,然后修改View的位置,不斷重復下去就實現了View的模擬滑動。那么,怎么改動View的位置呢,下面有介紹幾種方法可以設置View的位置。

    ##layout方法

    • 在ACTION_DOWN里面,記錄下按下的坐標
    case MotionEvent.ACTION_DOWN:
     lastX = x;
     lastY = y;
     break;
    • 每次onTouch回調記錄下該點的坐標
    int x = (int) event.getX();
    int y = (int) event.getY();
    • 在ACTION_MOVE里面計算偏移量,然后調用layout方法
    case MotionEvent.ACTION_MOVE:
     int offsetX = x - lastX;
     int offsetY = y - lastY;
     layout(getLeft() + offsetX, getTop() + offsetY, getRight() + offsetX, getBottom() + offsetY);
     break;

    效果如圖:

    offsetLeftAndRight()和offsetTopAndBottom()

    看命名就知道這個方法的作用,這是系統提供的對View上下、左右同時進行移動的API,效果與上相同。就不贅述了。

    case MotionEvent.ACTION_MOVE:
     int offsetX = x - lastX;
     int offsetY = y - lastY;
     offsetLeftAndRight(offsetX);
     offsetTopAndBottom(offsetY);
     break;

    LayoutParams

    通過改變View的LayoutParams布局參數,就可以移動View的位置,這里通常修改View的Margin屬性,代碼如下:

    case MotionEvent.ACTION_MOVE:
     int offsetX = x - lastX;
     int offsetY = y - lastY;
     ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) getLayoutParams();
     params.leftMargin = getLeft() + offsetX;
     params.topMargin = getTop() + offsetY;
     break;

    其實根據父布局的類型,可以設置LinearLayout.LayoutParams或者RelativeLayout.LayoutParams,不過這樣就必須先知道父布局的類型,不如ViewGroup.MarginLayoutParams來的方便。

    scrollTo和scrollBy

    前者表示移動到具體的坐標點位置,后者表示在原有的位置基礎上再移動一個偏移量。但是與之前三個方法不同的是,前三個方法都是移動View自己本身,而這兩個方法移動的都是 View里面的內容 ,如果放在ViewGroup中使用,則移動的是ViewGroup里面 所有的子View

    那我們的思路就要換一下了,我們為了移動View,那我們就來移動View所在的ViewGroup,但是要注意的是,移動的偏移量要 取反 ,為什么呢?這是因為本來是該View移動dx、dy,現在View保持不動,讓ViewGroup移動,則根據 相對運動原理 ,就相當于ViewGroup移動了-dx、-dy。

    下面我們來舉個簡單的例子解釋scrollBy。如下圖:ViewGroup里面是可視區域,第二個小人的坐標是(200,100),現在我們把ViewGroup移到第二個小人的位置, scrollBy(200,100) ,效果如第二張圖:在可視區域內,相當于第二個小人的偏移量為 (-200,-100)

    這么解釋一定明白多了,我們看一下實現代碼:

    case MotionEvent.ACTION_MOVE:
     int offsetX = x - lastX;
     int offsetY = y - lastY;
     ((View)getParent()).scrollBy(-offsetX,-offsetY);
     break;

    效果如圖:

    Scroller

    通過Scroller類來實現一些平滑的動畫效果,可以設置動畫時間等等,簡直就是滑動利器!現在我們來實現一個效果: View跟著手指滑動,當松開手指時就讓View回到原始位置

    • 在View構造函數里初始化Scroller
    scroller = new Scroller(context);
    • 重寫computeScroll方法
    @Override
    public void computeScroll() {
     super.computeScroll();
     if (scroller.computeScrollOffset()) {
    
     offsetLeftAndRight(scroller.getCurrX()-getLeft());
     offsetTopAndBottom(scroller.getCurrY()-getTop());
     invalidate();
    
    } }</code></pre>
    • 在ACTION_UP里啟動動畫
    scroller.startScroll(getLeft(),getTop(),-getLeft()+initX,-getTop()+initY,2000);

    startScroll前兩個參數是起始位置,后兩個參數為終點位置,第五個參數是動畫持續時間,可以省略。

    效果如圖:

    屬性動畫

    這個后續再單獨寫一篇介紹屬性動畫的,跳過。

    ViewDragHelper

    在開發自定義ViewGroup的時候,經常要根據業務需求實現onInterceptTouchEvent和onTouch(很繁瑣啊!有木有!),不過Google在support庫中為我們提供了一個超級強大的類ViewDragHelper,可以實現諸多滑動布局,側滑菜單就是之一。這里奉上 官方介紹 ,可能需國內或許不能訪問?

    下面我們舉個實現側滑菜單的例子:自定義ViewGroup布局,然后里面有MenuView和MainView,滑動MainView超過一定距離就顯示MenuView。

    • 初始化
    private View mMenuView,mMainView;
    private int mWidth;

//布局完成后調用 @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();//側滑菜單的寬度 }

//構造函數 public ViewDragLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mViewDragHelper = ViewDragHelper.create(this,callback); }</code></pre>

其中,構造函數里的callback是我們要自己實現的業務邏輯。也是該類的重要 核心內容

  • 攔截事件交給ViewDragHelper處理
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
    return mViewDragHelper.shouldInterceptTouchEvent(event);
}

@Override public boolean onTouchEvent(MotionEvent event) { mViewDragHelper.processTouchEvent(event); return true; }</code></pre>

  • 重寫computeScroll方法
@Override
public void computeScroll() {
    if (mViewDragHelper.continueSettling(true)) {
        ViewCompat.postInvalidateOnAnimation(this);
    }
}
  • 實現callback
private final ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {
    @Override
    public boolean tryCaptureView(View child, int pointerId) {
        //觸摸的布局是否為MainView
        return mMainView==child;
    }

@Override
public int clampViewPositionVertical(View child,int top,int dy) {
    //不需要檢測垂直滑動,直接返回0
    return 0;
}

@Override
public int clampViewPositionHorizontal(View child,int left,int dx) {
    return left;
}

@Override
public void onViewReleased(View child,float xVel,float yVel) {
    super.onViewReleased(child,xVel,yVel);
    //核心邏輯:滑動MainView超過一定距離就顯示MenuView
    if (mMainView.getLeft() < mWidth) {
        mViewDragHelper.smoothSlideViewTo(mMainView,0,0);
        ViewCompat.postInvalidateOnAnimation(ViewDragLayout.this);
    } else {
        mViewDragHelper.smoothSlideViewTo(mMainView,mWidth,0);
        ViewCompat.postInvalidateOnAnimation(ViewDragLayout.this);
    }
}

};</code></pre>

來看一下效果:

ViewDragHelper.Callback中定義有大量的回調方法,就不一一介紹了。

最后

到這里,我們介紹的View滑動方法就學習完了,最后我們來實現一個滑動ViewGroup,來模擬微信下拉的粘性動畫,直接上代碼:

@Override
public boolean onTouchEvent(MotionEvent event) {
    int y = (int) event.getRawY();
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            if (scroller.computeScrollOffset())
                scroller.forceFinished(true);
            lastY = y;
            break;
        case MotionEvent.ACTION_MOVE:
            int scrollY = y - lastY;
            offsetTopAndBottom(scrollY/3-getTop());
            break;
        case MotionEvent.ACTION_UP:
            scroller.startScroll(getLeft(),getTop(),0,-getTop()+initY,duration);
            invalidate();
            break;
    }
    return true;
}

在ACTION_DOWN里判斷動畫有沒有結束,可以強制結束,這樣就可以連續向下拖動。在ACTION_MOVE里設置偏移量,除以3可以調節偏移量滑動的比例。最后在松手時回到原位置,一起來看一下效果吧。

 

來自:http://www.biglong.cc/android/2016/09/23/實現View滑動的七種方法

 

 本文由用戶 回首111 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!