Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android之實現滑動的七種方法總結

Android之實現滑動的七種方法總結

編輯:關於Android編程

在android開發中,滑動對一個app來說,是非常重要的,流暢的滑動操作,能夠給用戶帶來用好的體驗,那麼本次就來講講android中實現滑動有哪些方式。其實滑動一個View,本質上是移動一個View,改變其當前所屬的位置,要實現View的滑動,就必須監聽用戶觸摸的事件,且獲取事件傳入的坐標值,從而動畫的改變位置而實現滑動。

*layout方法

*offsetLetfAndRight()與offsetTopAndBottom()

*LayoutParams

*scrollTo與scrollBy

*Scroller

*屬性動畫

*ViewDragHelper

android坐標系

首先要知道android的坐標系與我們平常學習的坐標系是不一樣的,在android中是將左上方作為坐標原點,向右為x抽正方向,向下為y抽正方向,像在觸摸事件中,getRawX(),getRawY()獲取到的就是Android坐標中的坐標.

視圖坐標系

android開發中除了上面的這種坐標以外,還有一種坐標,叫視圖坐標系,他的原點不在是屏幕左上方,而是以父布局坐上角為坐標原點,像在觸摸事件中,getX(),getY()獲取到的就是視圖坐標中的坐標.

觸摸事件–MotionEvent

觸摸事件MotionEvent在用戶交互中,有非常重要的作用,因此必須要掌握他,我們先來看看Motievent中封裝的一些常用的觸摸事件常亮:

 //單點觸摸按下動作 public static final int ACTION_DOWN             = 0; //單點觸摸離開動作 public static final int ACTION_UP               = 1; //觸摸點移動動作 public static final int ACTION_MOVE             = 2; //觸摸動作取消 public static final int ACTION_CANCEL           = 3; //觸摸動作超出邊界 public static final int ACTION_OUTSIDE          = 4; //多點觸摸按下動作 public static final int ACTION_POINTER_DOWN     = 5; //多點觸摸離開動作 public static final int ACTION_POINTER_UP       = 6;

以上是比較常用的一些觸摸事件,通常情況下,我們會在OnTouchEvent(MotionEvent event)方法中通過event.getAction()方法來獲取觸摸事件的類型,其代碼模式如下:

 @Overridepublic boolean onTouchEvent(MotionEvent event){    //獲取當前輸入點的坐標,(視圖坐標)    float x = event.getX();    float y = event.getY();    switch (event.getAction()) {        case MotionEvent.ACTION_DOWN:            //處理輸入按下事件            break;        case MotionEvent.ACTION_MOVE:            //處理輸入的移動事件            break;        case MotionEvent.ACTION_UP:            //處理輸入的離開事件            break;    }    return true; //注意,這裡必須返回true,否則只能響應按下事件}    

以上只是一個空殼的架構,遇到的具體的場景,也有可能會新增多其他事件,或是用不到這麼多事件等等,要根據實際情況來處理。在介紹如何實現滑動之前先來看看android中給我們提供了那些常用的獲取坐標值,相對距離等的方法,主要是有以下兩個類別:

View 提供的獲取坐標方法

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

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

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

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

MotionEvent提供的方法

getX(): 獲取點擊事件距離控件左邊的距離,即視圖坐標

getY(): 獲取點擊事件距離控件頂邊的距離,即視圖坐標

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

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

介紹上面一些基本的知識點後,下面我們就來進入正題了,android中實現滑動的其中方法:

實現滑動的7種方法

其實不管是哪種滑動,他們的基本思路是不變的,都是:當觸摸View時,系統記下當前的觸摸坐標;當手指移動時,系統記下移動後的觸摸點坐標,從而獲得相對前一個點的偏移量,通過偏移量來修改View的坐標,並不斷的更新,重復此動作,即可實現滑動的過程。
首先我們先來定義一個View,並置於LinearLayout中,我們的目的是要實現View隨著我們手指的滑動而滑動,布局代碼如下:

     

layout方法

我們知道,在進行View繪制時,會調用layout()方法來設置View的顯示位置,而layout方法是通過left,top,right,bottom這四個參數來確定View的位置的,所以我們可以通過修改這四個參數的值,從而修改View的位置。首先我們在onTouchEvent方法中獲取觸摸點的坐標:

float x = event.getX();float y = event.getY();

接著在ACTION_DOWN的時候記下觸摸點的坐標值:

case MotionEvent.ACTION_DOWN:            //記錄按下觸摸點的位置            mLastX = x;            mLastY = y;            break;

最後在ACTION_MOVE的時候計算出偏移量,且將偏移量作用到layout方法中:

case MotionEvent.ACTION_MOVE:            //計算偏移量(此次坐標值-上次觸摸點坐標值)            int offSetX = (int) (x - mLastX);            int offSetY = (int) (y - mLastY);            //在當前left,right,top.bottom的基礎上加上偏移量            layout(getLeft() + offSetX,                    getTop() + offSetY,                    getRight() + offSetX,                    getBottom() + offSetY            );            break;     

這樣每次在手指移動的時候,都會調用layout方法重新更新布局,從而達到移動的效果,完整代碼如下:

package com.liaojh.scrolldemo;import android.content.Context;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.View;/** * @author LiaoJH * @DATE 15/11/7 * @VERSION 1.0 * @DESC TODO */public class DragView extends View{    private float mLastX;    private float mLastY;     public DragView(Context context)    {        this(context, null);    }public DragView(Context context, AttributeSet attrs){    this(context, attrs, 0);}public DragView(Context context, AttributeSet attrs, int defStyleAttr){    super(context, attrs, defStyleAttr);}@Overridepublic boolean onTouchEvent(MotionEvent event){    //獲取當前輸入點的坐標,(視圖坐標)    float x = event.getX();    float y = event.getY();    switch (event.getAction())    {        case MotionEvent.ACTION_DOWN:            //記錄按下觸摸點的位置            mLastX = x;            mLastY = y;            break;        case MotionEvent.ACTION_MOVE:            //計算偏移量(此次坐標值-上次觸摸點坐標值)            int offSetX = (int) (x - mLastX);            int offSetY = (int) (y - mLastY);            //在當前left,right,top.bottom的基礎上加上偏移量            layout(getLeft() + offSetX,                    getTop() + offSetY,                    getRight() + offSetX,                    getBottom() + offSetY            );            break;    }    return true;}}     

當然也可以使用getRawX(),getRawY()來獲取絕對坐標,然後使用絕對坐標來更新View的位置,但要注意,在每次執行完ACTION_MOVE的邏輯之後,一定要重新設置初始坐標,這樣才能准確獲取偏移量,否則每次的偏移量都會加上View的父控件到屏幕頂邊的距離,從而不是真正的偏移量了。

   @Overridepublic boolean onTouchEvent(MotionEvent event){    //獲取當前輸入點的坐標,(絕對坐標)    float rawX = event.getRawX();    float rawY = event.getRawY();    switch (event.getAction())    {        case MotionEvent.ACTION_DOWN:            //記錄按下觸摸點的位置            mLastX = rawX;            mLastY = rawY;            break;        case MotionEvent.ACTION_MOVE:            //計算偏移量(此次坐標值-上次觸摸點坐標值)            int offSetX = (int) (rawX - mLastX);            int offSetY = (int) (rawY - mLastY);            //在當前left,right,top.bottom的基礎上加上偏移量            layout(getLeft() + offSetX,                    getTop() + offSetY,                    getRight() + offSetX,                    getBottom() + offSetY            );            //重新設置初始位置的值            mLastX = rawX;            mLastY = rawY;            break;    }    return true;}

offsetLeftAndRight()與offsetTopAndBottom()

這個方法相當於系統提供了一個對左右,上下移動的API的封裝,在計算出偏移量之後,只需使用如下代碼設置即可:

 offsetLeftAndRight(offSetX); offsetTopAndBottom(offSetY);

偏移量的計算與上面一致,只是換了layout方法而已。

LayoutParams

LayoutParams保存了一個View的布局參數,因此可以在程序中通過動態的改變布局的位置參數,也可以達到滑動的效果,代碼如下:

 LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) getLayoutParams(); lp.leftMargin = getLeft() + offSetX; lp.topMargin = getTop() + offSetY; setLayoutParams(lp);

使用此方式時需要特別注意:通過getLayoutParams()獲取LayoutParams時,需要根據View所在的父布局的類型來設置不同的類型,比如這裡,View所在的父布局是LinearLayout,所以可以強轉成LinearLayout.LayoutParams。

在通過改變LayoutParams來改變View的位置時,通常改變的是這個View的Margin屬性,其實除了LayoutParams之外,我們有時候還可以使用ViewGroup.MarginLayoutParams來改變View的位置,代碼如下:

ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) getLayoutParams();lp.leftMargin = getLeft() + offSetX;lp.topMargin = getTop() + offSetY;setLayoutParams(lp);//使用這種方式的好處就是不用考慮父布局類型

scrollTo與scrollBy

在一個View中,系統提供了scrollTo與scrollBy兩種方式來改變一個View的位置,其中scrollTo(x,y)表示移動到一個具體的坐標點(x,y),而scrollBy(x,y)表示移動的增量。與前面幾種計算偏移量相同,使用scrollBy來移動View,代碼如下:

 scrollBy(offSetX,offSetY);

然後我們拖動View,發現View並沒有移動,這是為雜呢?其實,方法沒有錯,view也的確移動了,只是他移動的不是我們想要的東西。scrollTo,scrollBy方法移動的是view的content,即讓view的內容移動,如果是在ViewGroup中使用scrollTo,scrollBy方法,那麼移動的將是所有的子View,而如果在View中使用的話,就是view的內容,所以我們需要改一下我們之前的代碼:

((View)getParent()).scrollBy(offSetX, offSetY);

這次是可以滑動了,但是我們發現,滑動的效果跟我們想象的不一樣,完全相反了,這又是為什麼呢?其實這是因為android中對於移動參考系選擇的不同從而實現這樣的效果,而我們想要實現我們滑動的效果,只需將偏移量設置為負值即可,代碼如下:

((View) getParent()).scrollBy(-offSetX, -offSetY);

同樣的在使用絕對坐標時,使用scrollTo也可以達到這樣的效果。

scroller

如果讓一個View向右移動200的距離,使用上面的方式,大家應該發現了一個問題,就是移動都是瞬間完成的,沒有那種慢慢平滑的感覺,所以呢,android就給我們提供了一個類,叫scroller類,使用該類就可以實現像動畫一樣平滑的效果。

其實它實現的原理跟前面的scrooTo,scrollBy方法實現view的滑動原理類似,它是將ACTION_MOVE移動的一段位移劃分成N段小的偏移量,然後再每一個偏移量裡面使用scrollBy方法來實現view的瞬間移動,這樣在整體的效果上就實現了平滑的效果,說白了就是利用人眼的視覺暫留特性。

下面我們就來實現這麼一個例子,移動view到某個位置,松開手指,view都吸附到左邊位置,一般來說,使用Scroller實現滑動,需經過以下幾個步驟:

初始化Scroller

//初始化Scroller,使用默認的滑動時長與插值器mScroller = new Scroller(context);  

重寫computeScroll()方法

該方法是Scroller類的核心,系統會在繪制View的時候調用draw()方法中調用該方法,這個方法本質上是使用scrollTo方法,通過Scroller類可以獲取到當前的滾動值,這樣我們就可以實現平滑一定的效果了,一般模板代碼如下:

 @Overridepublic void computeScroll(){    super.computeScroll();    //判斷Scroller是否執行完成    if (mScroller.computeScrollOffset()) {        ((View)getParent()).scrollTo(            mScroller.getCurrX(),            mScroller.getCurrY()        );        //調用invalidate()computeScroll()方法        invalidate();    }}

Scroller類提供中的方法:

computeScrollOffset(): 判斷是否完成了真個滑動getCurrX(): 獲取在x抽方向上當前滑動的距離getCurrY(): 獲取在y抽方向上當前滑動的距離

startScroll開啟滑動

最後在需要使用平滑移動的事件中,使用Scroller類的startScroll()方法來開啟滑動過程,startScroller()方法有兩個重載的方法:

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

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

可以看到他們的區別只是多了duration這個參數,而這個是滑動的時長,如果沒有使用默認時長,默認是250毫秒,而其他四個坐標則表示起始坐標與偏移量,可以通過getScrollX(),getScrollY()來獲取父視圖中content所滑動到的點的距離,不過要注意這個值的正負,它與scrollBy,scrollTo中說的是一樣的。經過上面這三步,我們就可以實現Scroller的平滑一定了。

繼續上面的例子,我們可以在onTouchEvent方法中監聽ACTION_UP事件動作,調用startScroll方法,其代碼如下:

 case MotionEvent.ACTION_UP:            //第三步            //當手指離開時,執行滑動過程            ViewGroup viewGroup = (ViewGroup) getParent();            mScroller.startScroll(                    viewGroup.getScrollX(),                    viewGroup.getScrollY(),                    -viewGroup.getScrollX(),                    0,                    800            );            //刷新布局,從而調用computeScroll方法            invalidate();            break;

屬相動畫

使用屬性動畫同樣可以控制一個View的滑動,下面使用屬相動畫來實現上邊的效果(關於屬相動畫,請關注其他的博文),代碼如下:

 case MotionEvent.ACTION_UP:            ViewGroup viewGroup = (ViewGroup) getParent();            //屬性動畫執行滑動            ObjectAnimator.ofFloat(this, "translationX", viewGroup.getScrollX()).setDuration(500)                          .start();            break;

ViewDragHelper

一看這個類的名字,我們就知道他是與拖拽有關的,猜的沒錯,通過這個類我們基本可以實現各種不同的滑動,拖放效果,他是非常強大的一個類,但是它也是最為復雜的,但是不要慌,只要你不斷的練習,就可以數量的掌握它的使用技巧。下面我們使用這個類來時實現類似於QQ滑動側邊欄的效果,相信廣大朋友們多與這個現象是很熟悉的吧。

先來看看使用的步驟是如何的:

初始化ViewDragHelper

ViewDragHelper這個類通常是定義在一個ViewGroup的內部,並通過靜態方法進行初始化,代碼如下:

//初始化ViewDragHelper
viewDragHelper = ViewDragHelper.create(this,callback);

它的第一個參數是要監聽的View,通常是一個ViewGroup,第二個參數是一個Callback回調,它是整個ViewDragHelper的邏輯核心,後面進行具體介紹。

攔截事件

重寫攔截事件onInterceptTouchEvent與onTouchEvent方法,將事件傳遞交給ViewDragHelper進行處理,代碼如下:

@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev){    //2. 將事件交給ViewDragHelper    return  viewDragHelper.shouldInterceptTouchEvent(ev);}@Overridepublic boolean onTouchEvent(MotionEvent event){    //2. 將觸摸事件傳遞給ViewDragHelper,不可少    viewDragHelper.processTouchEvent(event);    return true;}

處理computeScroll()方法

前面我們在使用Scroller類的時候,重寫過該方法,在這裡我們也需要重寫該方法,因為ViewDragHelper內部也是使用Scroller類來實現的,代碼如下:

//3. 重寫computeScroll@Overridepublic void computeScroll(){    //持續平滑動畫 (高頻率調用)    if (viewDragHelper.continueSettling(true))        //  如果返回true, 動畫還需要繼續執行        ViewCompat.postInvalidateOnAnimation(this);}

處理回調Callback

通過如下代碼創建一個Callback:

     private ViewDragHelper.Callback callback = new ViewDragHelper.Callback(){    @Override    //此方法中可以指定在創建ViewDragHelper時,參數ViewParent中的那些子View可以被移動    //根據返回結果決定當前child是否可以拖拽    //  child 當前被拖拽的View    //  pointerId 區分多點觸摸的id    public boolean tryCaptureView(View child, int pointerId)    {        //如果當前觸摸的view是mMainView時開始檢測        return mMainView == child;    }    @Override    //水平方向的滑動    // 根據建議值 修正將要移動到的(橫向)位置   (重要)    // 此時沒有發生真正的移動    public int clampViewPositionHorizontal(View child, int left, int dx)    {        //返回要滑動的距離,默認返回0,既不滑動        //參數參考clampViewPositionVertical        f (child == mMainView)        {            if (left > 300)            {                left = 300;            }            if (left < 0)            {                left = 0;            }         }        return left;    }    @Override    //垂直方向的滑動    // 根據建議值 修正將要移動到的(縱向)位置   (重要)    // 此時沒有發生真正的移動    public int clampViewPositionVertical(View child, int top, int dy)    {        //top : 垂直向上child滑動的距離,        //dy: 表示比較前一次的增量,通常只需返回top即可,如果需要精確計算padding等屬性的話,就需要對left進行處理        return super.clampViewPositionVertical(child, top, dy); //0    }};

到這裡就可以拖拽mMainView移動了。

下面我們繼續來優化這個代碼,還記得之前我們使用Scroller時,當手指離開屏幕後,子view會吸附到左邊位置,當時我們監聽ACTION_UP,然後調用startScroll來實現的,這裡我們使用ViewDragHelper來實現。

在ViewDragHelper.Callback中,系統提供了這麼一個方法—onViewReleased(),我們可以通過重寫這個方法,來實現之前的操作,當然這個方法內部也是通過Scroller來實現的,這也是為什麼我們要重寫computeScroll方法的原因,實現代碼如下:

    @Override    //拖動結束時調用    public void onViewReleased(View releasedChild, float xvel, float yvel)    {        if (mMainView.getLeft() < 150)        {            // 觸發一個平滑動畫,關閉菜單,相當於Scroll的startScroll方法            if (viewDragHelper.smoothSlideViewTo(mMainView, 0, 0))            {                // 返回true代表還沒有移動到指定位置, 需要刷新界面.                // 參數傳this(child所在的ViewGroup)                ViewCompat.postInvalidateOnAnimation(DragLayout.this);            }        }        else        {            //打開菜單            if (viewDragHelper.smoothSlideViewTo(mMainView, 300, 0)) ;            {                ViewCompat.postInvalidateOnAnimation(DragLayout.this);            }        }        super.onViewReleased(releasedChild, xvel, yvel);    }

當滑動的距離小於150時,mMainView回到原來的位置,當大於150時,滑動到300的位置,相當於打開了mMenuView,而且滑動的時候是很平滑的。此外還有一些方法:

    @Override    public void onViewCaptured(View capturedChild, int activePointerId)    {        // 當capturedChild被捕獲時,調用.        super.onViewCaptured(capturedChild, activePointerId);    }    @Override    public int getViewHorizontalDragRange(View child)    {        // 返回拖拽的范圍, 不對拖拽進行真正的限制. 僅僅決定了動畫執行速度        return 300;    }    @Override    //當View位置改變的時候, 處理要做的事情 (更新狀態, 伴隨動畫, 重繪界面)    // 此時,View已經發生了位置的改變    public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy)    {        // changedView 改變位置的View        // left 新的左邊值        // dx 水平方向變化量        super.onViewPositionChanged(changedView, left, top, dx, dy);    }

說明:裡面還有很多關於處理各種事件方法的定義,如:

onViewCaptured():用戶觸摸到view後回調

onViewDragStateChanged(state):這個事件在拖拽狀態改變時回調,比如:idle,dragging等狀態

onViewPositionChanged():這個是在位置改變的時候回調,常用於滑動時伴隨動畫的實現效果等

對於裡面的方法,如果不知道什麼意思,則可以打印log,看看參數的意思。

總結

這裡介紹的就是android實現滑動的七種方法,至於使用哪一種好,就要結合具體的項目需求場景了,畢竟硬生生的實現這個效果,而不管用戶的使用體驗式不切實際的,這裡面個人覺得比較重要的是Scroller類的使用。屬性動畫以及ViewDragHelper類,特別是最後一個,也是最難最復雜的,但也是甩的最多的。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved