android進階(三)-----View事件分發機制及衝突解決

一、View基礎知識

 

1、view的位置參數

view的位置由四個頂點決定的,分別是對應view的四個屬性:

top:左上角縱座標

left:左上角橫座標

right:右下角橫座標

bottom:右下角縱座標

android中的X軸和Y軸的正方向分別是右邊和下邊

從android3.0開始,View增加了幾個額外的參數:x、y、translationX、translationY

x和y是View左上角的座標

translationX、translationY是View左上角相對於父容器的偏移量

 

2、MotionEvent和TouchSlop

(1)MotionEvent在手指接觸屏幕後產生的一系列事件中,有如下幾種:

ACTION_DOWN:手指剛接觸屏幕

ACTION_MOVE:手指在屏幕上移動

ACTION_UP:手指從屏幕上擡起的一瞬間

通過getX和getY我們可以獲取x和y軸的座標

通過getRowX和getRowY我們可以獲取相對於手機屏幕左上角的x和y座標

(2)TouchSlop是系統所能辨別的滑動的最小距離

通過ViewConfiguration.get(context).getScaledTouchSlop()方法獲取這個常量

 

3、GestureDetector和Scroller

(1)GestureDetector:手勢檢測,用於檢測用戶的單擊、滑動、長按、雙擊的行爲

使用方式

創建GestureDetector對象並實現OnGesturelistener接口

OnGesturelistener接口中的方法

onDown:手指觸摸屏幕的一瞬間,由1個ACTION_DOWN觸發

onShowPress:手指觸摸屏幕尚未鬆開或拖動,由1個ACTION_DOWN觸發

onSingleTapUp:手指觸摸屏幕鬆開,由1個ACTION_UP觸發,單擊事件

onScroll:手指按下屏幕並拖動,由1個ACTION_DOWN,多個ACTION_MOVE觸發,拖動事件

onLongPress:用於長按屏幕

onFling:用戶按下屏幕、快速滑動後鬆開,由1個ACTION_DOWN,多個ACTION_MOVE和1個ACTION_UP觸發,快速滑動事件

(2)Scroller:彈性滑動對象,用於實現View的彈性滑動

使用方式:

Scroller scroller = new Scroller(context)

//緩慢滑動到指定位置

private void smoothScrollTo(int destX,int destY){

    int scrollX = getScrollX()

    int delta = destX - scrollX

    scroller.startScroll(scrollX,0,delta,0,1000)

    invalidate()

}

@Override

public void computeScroll(){

    if(mScroller.computeScrollOffset()){

        scrollTo(mScroller.getCurrX(),mScroller.getCurrY())

        postInvalidate()

    }

}

二:View的滑動

通過三種方式可以實現View的滑動:

第一種通過View本身提供的scrollTo/scrollBy方法實現滑動

第二種通過動畫給View添加平移效果實現滑動

第三種通過改變View的LayoutParams使得View重新佈局實現滑動

 

1、使用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); 
}

從源碼可以看出,scrollBy也是調用了scrollTo方法,他實現了基於當前位置的相對滑動,scrollTo實現了基於所傳遞參數的絕對滑動

mScrollX和mScrollY的單位爲像素,從左向右滑動,mScrollX爲負值,反之爲正值,如果從上往下滑動,那麼mScrollY爲負值,反之爲正值

 

2、使用動畫滑動

通過動畫讓一個View進行平移,主要操作View的translationX和translationY屬性,既可以採用傳統View動畫,也可以使用屬性動畫

使用方式:

<set xmlns:android="http://schemas.android.com/apk/res/android"

    android:fillAfter="true"

    android:zAdjustment="normal">


    <translate

        android:duration="100"

        android:fromXDelta="0"

        android:formYDelta="0"

        android:interpolator="@android:anim/linear_interpolator"

        android:toXDelta="100"

        android:toYDelta="100"/>

</set>

使用屬性動畫將View在100ms內向右移動100像素

ObjectAnimator.ofFloat(targetView,"translationX",0,100).setDuration(100).start();

3、改變佈局參數

這種方式很簡單,直接上代碼了

MarginLayoutParams params = (MarginLayoutParams)textview.getLayoutParams();

params.width += 100;

params.leftMargin += 100;

textview.setLayoutParams(params)

4、各種滑動方式的對比

scrollTo/scrollBy

特點:操作簡單,適合對View內容進行滑動。只能滑動View內容,不能滑動View本身

動畫方式

特點:操作簡單,適用於沒有交互的View和實現複雜的動畫效果

佈局方式

特點:操作略微複雜,適用於有交互的View

 

三:View的時間分發機制

1、點擊事件的傳遞規則

點擊事件分發過程:

dispatchTouchEvent:進行事件分發,如果事件能夠傳遞跟當前View,就會被調用,返回結果受當前View的onTouchEvent和下級的dispatchTouchEvent方法的影響,表示是否消費當前事件

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

onTouchEvent:在dispatchTouchEvent方法中調用,用來處理點擊事件,返回結果表示是否消費當前事件,不消費則在同一個事件序列中,當前View無法再次受到事件

2、點擊事件流程圖

 

點擊事件到達頂級View後,會調用ViewGroup的dispatchTouchEvent方法,如果頂級ViewGroup dispatchTouchEvent方法返回true,則事件由ViewGroup處理,這時如果ViewGroup的OnTouchListener被設置,則onTouch會被調用,否則onTouchEvent會被調用,同時設置,onTouch會屏蔽掉onTouchEvent。在onTouchEvent中,如果設置了OnClickListener,則onClick會被調用。如果頂級的ViewGroup不攔截事件,則事件會傳遞給他所在的點擊事件鏈上的子View,這時子View的dispatchTouchEvent會被調用。

 

四:View的滑動衝突

1、常見的滑動衝突

外部滑動方向和內部滑動方向不一致

外部滑動方向和內部滑動方向一致

上面兩種情況的嵌套

2、滑動衝突的處理規則

(1)外部和內部滑動不一致:當用戶左右滑動時,需要讓外部的View攔截事件,當用戶上下滑動時,讓內部View攔截事件(根據滑動是水平滑動還是豎直滑動來判斷是誰來攔截)

(2)外部和內部滑動方向一致:根據業務需要,當處於某種狀態時外部View滑動,而處於另一種狀態時需要內部View滑動

3、滑動衝突的解決方式

(1)外部攔截:事件通過父容器攔截處理,如果負容器需要此事件就攔截,不需要就不攔截。需要重寫onInterceptTouchEvent方法

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 = fasle;

            break;

    }

    mLastXIntercept = x;

    mLastYIntercept = y;

    return intercepted;

}

(2)內部攔截法:指父容器不攔截事件,所有的事件都傳遞給子元素,如果子元素需要此事件就直接消費,否則就交給父容器處理。這種方式和android中的事件分發機制不一致,需要配合requestDisallowInterceptTouchEvent方法,使用起來比外部攔截稍複雜,需要重寫子元素的dispatchTouchEvent方法。僞代碼如下:

public boolean dispatchTouchEvent(MotionEvent event){

    int x = (int) event.getX();

    int y = (int) event.getY();

    switch(event.getAction()){

        case MotionEvent.ACTION_DOWN:

            parent.requestDisallowInterceptTouchEvent(true)

            break;

        case MotionEvent.ACTION_MOVE:

            int deltax = x - mLastX;

            int deltay = y - mLastY;

            if(父容器需要此類點擊事件){

                parent.requestDisallowInterceptTouchEvent(false);

            }

            break;

        case MotionEvent.ACTION_UP:

            break;

    }

    mLastX = x

    mLastY = y

    return super.dispatchTouchEvent(event)

}

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章