android之view的TouchDelegate

實話實說,之前並不知道有TouchDelegate,直到最近查看view的源碼時候才發現這個新大陸.
在view中有一個私有的TouchDelegate變量:
    private TouchDelegate mTouchDelegate = null;    
在view中的公共方法setTouchDelegate可以給這個view設置一個TouchDelegate對象,源碼如下:
    public void setTouchDelegate(TouchDelegate delegate) {
        mTouchDelegate = delegate;
    }
        
到了這裏,你可能會問:view中的mTouchDelegate到底可以做什麼?爲了回答這個問題,下面分三步來解釋.


第一步:首先來看看TouchDelegate類的一段原文說明,在文件TouchDelegate.java有如下說明:

  Helper class to handle situations where you want a view to have a larger touch area than its
  actual view bounds. The view whose touch area is changed is called the delegate view. This
  class should be used by an ancestor of the delegate.
  其大意是: TouchDelegate是一個工具類,其目的是讓一個view在一個特定的位置擁有比自己實際的觸摸區域更大的可觸摸的區域.觸摸區域被更改的view被稱作"delegate view".這個工具類應該被"delegate view"的父view使用.
  通過上面的意思,我們可以明白其實TouchDelegate的主要目的就是來擴大一個view的觸摸區域的.  


第二步:要深刻理解,我覺得需要查看源碼,看看他是如何實現的.

  查看TouchDelegate可以知道,TouchDelegate是一個很簡單的類,主要有四個變量和兩個方法:
  
 /**
     * View that should receive forwarded touch events
     */
    private View mDelegateView;//需要擴大觸摸區域的view
    
    /**
     * Bounds in local coordinates of the containing view that should be mapped to the delegate
     * view. This rect is used for initial hit testing.
     */
    private Rect mBounds;//定義了這個擴大的觸目區域
               
    /**
     * mBounds inflated to include some slop. This rect is to track whether the motion events
     * should be considered to be be within the delegate view.
     */
    private Rect mSlopBounds;//這是一個相對於mBounds溢出的觸摸區域:實際上就是比mBounds大一點的區域(寬高分別大8),其目的時消除觸摸誤差.
    
    public TouchDelegate(Rect bounds, View delegateView) {//構造函數:bounds 表示觸摸的區域;delegateView 需要擴大觸摸區域的view
        mBounds = bounds;

        mSlop = ViewConfiguration.get(delegateView.getContext()).getScaledTouchSlop();//這裏獲取的是觸摸滑動距離的判斷:就是觸摸滑動距離爲mSlop時候裁判爲是在觸摸滑動move.其默認值爲8
        mSlopBounds = new Rect(bounds);
        mSlopBounds.inset(-mSlop, -mSlop);//這裏擴大觸摸滑動區域(寬高擴大8),其目的時消除觸摸時候的誤差.達到一個觸摸安全處理.
        mDelegateView = delegateView; //需要擴大觸摸區域的view(需要改變觸摸區域的view)
    }
    
    public boolean onTouchEvent(MotionEvent event) {//這是核心代碼:判斷當前觸摸是否在這個區域:mBounds.如果在是則會讓這次的觸摸事件真的在mDelegateView真實的區域內.
        int x = (int)event.getX();
        int y = (int)event.getY();//記錄這次觸摸點位置
        boolean sendToDelegate = false;//標記這次觸摸是否有效(應該傳遞給mDelegateView)
        boolean hit = true; //標記這次觸摸是否在這個區域內(mBounds)
        boolean handled = false;

        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            Rect bounds = mBounds;

            if (bounds.contains(x, y)) {//ACTION_DOWN是否在這個區域內
                mDelegateTargeted = true;//標記這一次觸摸事件在這個區域(在mDelegateView內)
                sendToDelegate = true;
            }
            break;
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_MOVE:
            sendToDelegate = mDelegateTargeted;
            if (sendToDelegate) {
                Rect slopBounds = mSlopBounds;
                if (!slopBounds.contains(x, y)) {
                    hit = false;
                }
            }
            break;
        case MotionEvent.ACTION_CANCEL:
            sendToDelegate = mDelegateTargeted;
            mDelegateTargeted = false;
            break;
        }
        if (sendToDelegate) {//這次觸摸有效:則把觸摸事件傳遞給 mDelegateView
            final View delegateView = mDelegateView;
            //模擬這次觸摸真的在mDelegateView區域內:從新計算event的觸摸點,以保證這次的event事件的觸摸點在mDelegateView真實的區域內
            if (hit) {
                // Offset event coordinates to be inside the target view
                event.setLocation(delegateView.getWidth() / 2, delegateView.getHeight() / 2);
            } else {
                // Offset event coordinates to be outside the target view (in case it does
                // something like tracking pressed state)
                int slop = mSlop;
                event.setLocation(-(slop * 2), -(slop * 2));
            }
            handled = delegateView.dispatchTouchEvent(event);//把計算後的觸摸事件傳遞給mDelegateView的dispatchTouchEvent使其相應觸摸事件.
        }
        return handled;
    }

第三步:最後來看看view裏面是何時使用mTouchDelegate.
   上面說了,在view裏面定義來一個變量mTouchDelegate來保存當前這個view的TouchDelegate對象,其目的是確保view有這樣的功能:原本在自己區域的觸摸事件實際上相應的卻是別的地方(別的區域)的view.
   那view是如何實現的呢? 查看源碼可以知道,在view的onTouchEvent方法裏面,首先就會去判斷自己是否有可用的TouchDelegate對象,如果有,那麼onTouchEvent方法裏面首先會去執行mTouchDelegate的
   onTouchEvent方法.下面是源碼:
 
   public boolean onTouchEvent(MotionEvent event) {
        final int viewFlags = mViewFlags;

        if (DBG_MOTION) {
            Xlog.d(VIEW_LOG_TAG, "(View)onTouchEvent 1: event = " + event + ",mTouchDelegate = "
                    + mTouchDelegate + ",enable = " + isEnabled() + ",clickable = " + isClickable()
                    + ",isLongClickable = " + isLongClickable() + ",this = " + this);
        }

        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            /// M: we need to reset the pressed state or remove prepressed callback either up or cancel event happens.
            final int action = event.getAction();
            if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
                if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                    setPressed(false);
                } else if ((mPrivateFlags & PFLAG_PREPRESSED) != 0) {
                    Xlog.d(VIEW_LOG_TAG, "View onTouch event, if view is DISABLED & PFLAG_PREPRESSED, remove callback mPrivateFlags = "
                                + mPrivateFlags + ", this = " + this);
                    removeTapCallback();
                }
            }
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            return (((viewFlags & CLICKABLE) == CLICKABLE ||
                    (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
        }

        if (mTouchDelegate != null) {//判斷是否有TouchDelegate對象
            if (mTouchDelegate.onTouchEvent(event)) {執行onTouchEvent
                return true;//如果這個觸摸事件在TouchDelegate設置的區域內,這返回.不會執行其他
            }
        }
        .....
       


最後,來看看一個實例.下面是一個父view誇大自己view觸摸區域的方法:

    public static void enlargeBtnTouchScope(View parent, Button btn, Rect rect, float width, float height){
		rect.top = btn.getTop();
		rect.bottom = btn.getBottom();
		rect.left = btn.getLeft();
		rect.right = btn.getRight();
		
		rect.top -= height;
		rect.bottom += height;
		rect.left -= width;
		rect.right += width;
		
		parent.setTouchDelegate(new TouchDelegate(rect, btn));
    }
上面的代碼可以使得在parent_view的button可以更好地被觸摸到(被點擊到).
 

發佈了51 篇原創文章 · 獲贊 20 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章