Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發 >> 關於android開發 >> Android開發藝術探索學習筆記(三),android藝術探索

Android開發藝術探索學習筆記(三),android藝術探索

編輯:關於android開發

Android開發藝術探索學習筆記(三),android藝術探索


第三章  View的事件體系

  3.1 View基礎知識

  3.1.1 什麼是view

    View 是Android中所有控件的基類,是一種界面層的控件的一種抽象,它代表了一個控件。

  3.1.2 View的位置參數

    View的位置主要由它的四個頂點來決定,分別對應於View的四個屬性:top,left,right,bottom;需要注意的是這些坐標都是相對於View的父容器來說的;(在Android中X軸和Y軸的正方向分別為右和下)。

    3.0開始新增的屬性

    x-  view左上角橫坐標;

    y-  view左上角縱坐標;

    translationX-  view左上角相對於父容器的水平偏移量;

    translationY-  view左上角相對於父容器的垂直偏移量;

    View在平移過程中,top和left表示的是原始左上角的位置信息,其值並不會發生變化,此時發生變化的是x,y,translationX,translationY這四個參數。

  3.1.3 MotionEvent和TouchSlop

    1 MotionEvent

    getX和getY方法返回的是當前View左上角的x和y坐標,而getRawC和getRawY方法返回的是相對於手機屏幕左上角的x和y坐標。

    2 TouchSlop

    TouchSlop是系統所能識別出的被認為是滑動的最小距離,是一個常量,通過如下方式可以獲得這個常量:ViewConfiguration.get(getContext()).getScaledTouchSlop();

  3.1.4 VelocityTracker,GestureDetector和Scroller

    1 VelocityTracker

    VelocityTracker—速度追蹤,追蹤手指在滑動過程中的速度(水平垂直兩個方向),注意速度可以為負值,當手指從右向左滑動時,水平方向速度即為負值。

    2 GestureDetector

    GestureDetector—手勢檢測,用於輔助檢測用戶的單擊,滑動,長,雙擊等行為。

    建議:如果只是監聽滑動相關的,在onTouchEvent中實現,如果要監聽雙擊行為就用GestureDetector。

    3 Scroller

    Scroller—彈性滑動,用於實現View的彈性滑動(有過渡效果的滑動),需要配合View的computeScroll方法使用。

  3.2 View的滑動

    實現View滑動的三種方式:

    1 通過View本身的scrollTo/scrollBy方法實現滑動;

    2 通過動畫給View施加平移效果實現滑動;

    3 通過改變View的LayoutParams使得View重新布局實現滑動;

  3.2.1 使用scrollTo/scrollBy

    scrollTo/scrollBy的源碼如下:

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();
    }
  }
}

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

    其中mScrollX的值總是等於View左邊緣和View內容左邊緣在水平方向的距離,mScrollY的值總是等於View的上邊緣和View內容上邊緣在垂直方向上的距離;

     scrollTo/scrollBy只能改變View內容的位置而不能改變自身在布局中的位置。

     也就是說使用scrollTo/scrollBy來實現View的滑動只能將View的內容進行移動,並不能將View本身進行移動。

  3.2.2 使用動畫

    使用動畫來移動View 主要是操作View的translationX和translationY屬性。(傳統的View動畫和屬性動畫)

     View動畫是對View的影像操作,它並不能真正改變View的位置參數,包括寬/高。

  3.2.3 改變布局參數

    主要是通過改變View的LayoutParams來實現;下面的代碼展示如何給一個mButton1重新設置LayoutParams:

MarginLayoutParams params=(MarginLayoutParams)mButton1.getLayoutParams();
params.width+=100;
params.leftMargin+=100;
mButton1.requestLayout();
//或者mButton1.setLayoutParams(params)

  3.2.4各種滑動方式的對比

    scrollTo/scrollBy:操作簡單,適合對View內容的滑動;

     動畫:操作簡單,主要適用於沒有交互的View和實現復雜的動畫效果;

     改變布局參數:操作稍微復雜,適用於有交互的View。

  3.3彈性滑動

  3.3.1使用Scroller

     注意Scroller產生的滑動也是對View內容的滑動而非View本身位置的改變。

    Scroller的典型使用方式如下:

Scroller scroller=new Scroller(context);
//緩慢滾動到指定位置
private void smoothScrollTo(int destX,int destY){
    int scrollx=getScrollX();
    int deltaX=destX-scrollX;
    //1000ms內滑向destX,效果就是慢慢滑動
    scroller.startScroll(scrollX,0,deltaX,0,1000);
    invalidate();
}
@Override
public void computeScroll(){
    if(scroller.computeScrollOffset()){
    scrollTo(scroller.getCurrX(),scroller.getCurrY());
    postInvalidate();
    }
}

    Scroller的工作機制:Scroller本身並不能實現View的滑動,它需要配合View的computeScroll方法才能完成彈性滑動的效果,它不斷的讓View重繪,而每一次重繪距滑動起始時間會有一個時間間隔,通過這個時間間隔Scroller就可以得出View當前的滑動位置,知道了滑動位置就可以通過scrollTo方法來View的滑動。就這樣,View的每一次重繪都會導致View進行小幅度的滑動,而多次的小幅度滑動就組成了彈性滑動。

  3.4 View的時間分發機制

  3.4.1 點擊事件的傳遞規則

    點擊事件的分發過程主要由三個方法來完成。

    dispatchTouchEvent() :用來進行事件的分發,如果事件能夠傳遞給當前View,此方法一定會被調用,返回結果受當前View的onTouchEvent和下級View的dispatchTouchEvent方法影響,表示是否消耗當前事件。

    onInterceptTouchEvent() :在dispatchTouchEvent方法內部調用,判斷是否攔截某個事件,如果當前View攔截了某個事件,那麼在同一個事件序列中,此方法不會在被調用,返回結果表示是否攔截當前事件。

    onTouchEvent() :在dispatchTouchEvent方法中調用,用來處理點擊事件,返回結果表示是否消耗當前事件,如果不消耗,則在同一個事件序列中,當前View無法再次接收到其他事件。

    事件序列是指從手指接觸屏幕的那一刻起,到手指離開屏幕的那一刻結束,在這個過程中所產生的一系列事件,這個事件序列以down事件開始,中間含有數量不固定的move事件,最終以up事件結束。

    理解了這三個方法的工作過程也就理解了事件的分發機制。偽代碼表示三個方法的關系如下:

public boolean dispatchTouchEvent(MotionEvent ev){
    boolean consume=false;
    if(onInterceptTouchEvent(ev)){
         consume=onTouchEvent(ev);      
    }else{
         consume=child.dispatchTouchEvent(ev);    
    } 
    return consume;     
}

    具體傳遞規則:對於一個根ViewGroup來說,點擊事件產生以後,首先會傳遞給它,這時它的dispatchTouchEvent就會被調用,如果這個ViewGroup的onInterceptTouchEvent方法返回true就表示它要攔截當前事件,接著事件就會交給這個ViewGroup處理,即它的onTouchEvent方法就會被調用,如果這個ViewGroup的onInterceptTouchEvent返回false就表示它不攔截當前事件,這時當前事件就會繼續傳遞給它的子元素,接著子元素的dispatchTouchEvent方法就會被調用,如此反復直到事件被最終處理。

     當一個點擊事件產生後,它的傳遞過程遵循如下順序:Activity->Window->View,即事件總是先傳遞給Activity,Activity在傳遞給Window,最後Window在傳遞給頂級View。頂級View接收到事件後,就會按照事件分發機制去分發事件。考慮一種情況,如果一個View的onTouchEvent返回false,那麼它的父容器的onTouchEvent將會被調用,依此類推。如果所用的元素都不處理這個事件,那麼這個事件將會最終傳遞給Activity處理,即Activity的onTouchEvent方法被調用。
    幾個重要結論:

    正常情況下,一個事件序列只能被一個VIew攔截且消耗,因為一旦一個元素攔截了某個事件,那麼同一個事件序列內的所有事件都會直接交給它處理,因此同一個事件序列中的事件不能分別有兩個View同時處理,但是通過特殊手段可以做到,比如一個View將本該自己處理的事件通過onTouchEvent強行傳遞給其他View處理。

    某個View一旦決定攔截,那麼這一個事件序列都只能由它處理,並且它的onInterceptTouchEvent不會再被調用。

    某個View一旦開始處理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回了false),那麼同一事件序列中的其他事件都不會再交給它處理,並且事件將重新交由它的父元素去處理,即父元素的onTouchEvent會被調用。意思就是事件一旦交給一個View處理,那麼它就必須消耗掉,否則同一事件序列中剩下的事件就不再交給它來處理了。

    如果View不消耗出ACTION_DOWN以外的其他事件,那麼這個點擊事件會消失,最終這些消失的點擊事件會傳遞給Activity處理。

    ViewGroup默認不攔截任何事件。

    View沒有onInterceptTouchEvent方法,一旦有事件傳遞給它,那麼它的onTouchEvent方法就會被調用。

    View的OnTouchEvent方法默認都會消耗事件(返回true)。

    View的enable屬性不影響onTouchEvent的默認返回值。

    onClick會發生的前提是當前View是可點擊的,並且它收到了down和up的事件。

    事件的傳遞過程是由外向內的,即事件總是項傳遞給父元素,然後再由父元素分發給子View,通過requestDisallowInterceptTouchEvent方法可以在子元素中干預父元素的事件分發過程,但是ACTION_DOWN事件除外。(requestDisallowInterceptTouchEvent方法主要設置父元素中的FLAG_DISALLOW_INTERCEPT標記位,一旦設置後,ViewGroup將無法攔截除了ACTION_DOWN以外的其他點擊事件)

  3.5 View的滑動沖突

     常見的滑動沖突場景

    

     解決滑動沖突的方式:外部攔截法和內部攔截法。

    1 外部攔截法

    點擊事件都先經過父容器的攔截處理,如果父容器需要此事件就攔截,如果不需要此事件就不攔截。

    實現方法主要是重寫父容器的onInterceptTouchEvent方法,代碼如下:

  @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        boolean intercepted=false;
        int x=(int)event.getX();
        int y=(int)event.getY();
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                intercepted=false;
                break;
            case MotionEvent.ACTION_MOVE:
                if(父容器需要當前點擊事件){
                   intercepted=true;
                }else {
                    intercepted=false;
                }
                break;
            case MotionEvent.ACTION_UP:
                intercepted=false;
                break;
            default:
                break;
        }
        mLastXIntercept=x;
        mLastYIntercept=y;
        return intercepted;
    }

    2 內部攔截法

    父容器不攔截任何事件,所用的事件都傳遞給子元素,如果子元素需要此事件就直接消耗掉,否則就交由父容器進行處理。需要配合requestDisallowInterceptTouchEvent方法才能正常工作,同時重寫子元素的dispatchTouchEvent方法,代碼如下:

    子元素:

   @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        int x=(int)event.getX();
        int y=(int)event.getY();
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                int deltaX=x-mLastX;
                int deltaY=y-mLastY;
                if(父容器需要當前點擊事件){
                    getParent().requestDisallowInterceptTouchEvent(false);
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
            default:
                break;
        }
        mLastX=x;
        mLastY=y;
        return super.dispatchTouchEvent(event);
    }

    父元素:

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        int action=event.getAction();
        if (action==MotionEvent.ACTION_DOWN){
            return false;
        }else{
            return true;
        }
    }

 

    以上就是滑動處理滑動沖突的典型代碼,當面對不同的滑動策略時(場景1,2,3)只需要修改裡面的條件即可。

            

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