使用 TouchDelegate 擴大控件的點擊範圍

轉載請註明出處 http://blog.csdn.net/yxhuang2008/article/details/51126009

當我們的控件太小,導致我們無法準確的點擊,這個時候我們可以在這個控件外面再加一層佈局,但是這樣對性能不太好。其實,我們可以使用 TouchDelegate 擴大我們的點擊範圍。


下面是例子,我們在 LinearLayout 中放置一個 ImageButton, 然後給它們設置點擊監聽。

<?xml version="1.0" encoding="utf-8"?>
	<LinearLayout
	    xmlns:android="http://schemas.android.com/apk/res/android"
	    android:id="@+id/rl_touch"
	    android:layout_width="match_parent"
	    android:layout_height="match_parent"
	    android:orientation="vertical">

	    <ImageButton
		android:id="@+id/ib_touch"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:layout_centerInParent="true"
		android:layout_marginTop="100dp"
		android:layout_gravity="center_horizontal"
		android:background="@android:color/darker_gray"
		android:src="@mipmap/clean_normal"/>

	</LinearLayout>
我們看到 ImageButton 太小了,不能準確的點擊

我們看看 java 代碼

public class TouchDelegateActivity extends Activity {
    private static  final String TAG = "yxh";

    private LinearLayout mLinearLayout;
    private ImageButton mImageButton;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_touch_delegate);

        mLinearLayout = (LinearLayout) findViewById(R.id.rl_touch);
        mImageButton = (ImageButton) findViewById(R.id.ib_touch);

        mLinearLayout.post(new Runnable() {
            @Override
            public void run() {


                // 將 ImageButton 的 enable 設爲true 確保它能接收到點擊事件
                mImageButton.setEnabled(true);
                mImageButton.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        Toast.makeText(TouchDelegateActivity.this, " Button touch", Toast.LENGTH_SHORT).show();
                        Log.i(TAG, "Button  onClick ");
                    }
                });


                // 1、設置 ImageButton 可點擊的範圍
                Rect delegateArea = new Rect();
                mImageButton.getHitRect(delegateArea);

                // Extend the touch area of the button beyond its bound on the right and bottom
                // 2、擴大 ImageButton 的點擊範圍
                delegateArea.right += 100;
                delegateArea.bottom +=500;

                // Instantiate a TouchDelegate
                // 3、實例化 TouchDelegate
                TouchDelegate touchDelegate = new TouchDelegate(delegateArea, mImageButton);

                // Sets the TouchDelegate on the parent view, such that touches within the touch delegate
                // are routed to the child.
                ///4、將 touchDelegate 設置到 ImageButton 的父視圖上。
                if (View.class.isInstance(mImageButton.getParent())){
                    ((View)mImageButton.getParent()).setTouchDelegate(touchDelegate);
                }
            }
        });
      }
    }
  下面是使用 TouchDelegate 擴大控件的點擊範圍

1、設置 ImageButton 的點擊範圍

        Rect delegateArea = new Rect();
        mImageButton.getHitRect(delegateArea);
2、擴大 ImageButton 的點擊範圍,我們這裏是右邊增大 100 px, 底部增加了 500 px              

<span style="white-space:pre">	</span>delegateArea.right += 100;
 <span style="white-space:pre">	</span>delegateArea.bottom +=500;
3、將要擴大點擊範圍的控件作爲參數,實例化 TouchDelegate, 我們這裏傳的是 ImageButton

<span style="white-space:pre">	</span>TouchDelegate touchDelegate = new TouchDelegate(delegateArea, mImageButton);
4、將 touchDelegate 設置到 ImageButton 的父視圖上
 <span style="white-space:pre">	</span>if (View.class.isInstance(mImageButton.getParent())){
                  ((View)mImageButton.getParent()).setTouchDelegate(touchDelegate);
          }


這就是使用 TouchDelegate 的整個過程。


下面讓我們來看看 TouchDelegate 的原理,爲什麼它能擴大一個控件的點擊範圍。
根據 View 點擊事件的傳遞,關於點擊事件的傳遞,可參考我的上一篇文章 【讀書筆記】【Android 開發藝術探索】第3章 View 的事件體系 

當一個最後會傳遞到它的 onTouchEvent(...) 方法,這裏因爲 LinearLayout 中沒有可接受的點擊子控件,所以
LinearLayout 會當初 View 準備自己處理點擊事件。在源碼 onTouchEvent(...) 方法中,有這樣的幾行代碼。

  <span style="white-space:pre">	</span>if (mTouchDelegate != null) {
		    if (mTouchDelegate.onTouchEvent(event)) {
			return true;
		    }
	   }

我們在第四步調用了 setTouchDelegate ,所以 mTouchDelegate 不爲空,會執行裏面的內容。 裏面會調用 TouchDelegate 的 onTouchEvent(...)
public boolean onTouchEvent(MotionEvent event) {
        int x = (int)event.getX();
        int y = (int)event.getY();
        boolean sendToDelegate = false;
        boolean hit = true;
        boolean handled = false;

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

            if (bounds.contains(x, y)) {
                mDelegateTargeted = true;
                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) {
            final View delegateView = 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);
        }
        return handled;
    }
 

mBounds、mDelegateTargeted、mDelegateView、mSlop  這些變量是在構建 TouchDelegate 對象時創建的

   public TouchDelegate(Rect bounds, View delegateView) {
        mBounds = bounds;

        mSlop = ViewConfiguration.get(delegateView.getContext()).getScaledTouchSlop();
        mSlopBounds = new Rect(bounds);
        mSlopBounds.inset(-mSlop, -mSlop);
        mDelegateView = delegateView;
    }
 bounds 是第 3 部傳進來的點擊範圍,delegateView 是傳進來的 ImageButton. mSlop 是系統默認爲可以是活動的距離。mSlopBounds 

是我們傳進來的範圍減去系統默認活動的距離。


  在 ACTION_DOWN 中,如果我們點擊的座標在 mSlopBounds 裏面,則將 mDelegateTargeted 和 sendToDelegate 

設置爲 true。
  在 ACTION_MOVE 中,判斷因爲滑動的而導致點擊的座標是否還在可點擊的範圍之內。如果不在了,這說明不是點擊的,

將 hit 設置爲 false.
  在最後,如果sendToDelegate 爲 true ,無論 hit 是否爲 true ,都重新設置點擊座標 event.setLocation(x, y). 調用 

delegateView.dispatchTouchEvent(...)方法。
 
如果 delegate 是 View , 則會調用 View 的 dispatchTouchEvent(...) 方法。這裏是 ImageButton, 所以就好執行 ImageButton 

的點擊事件的監聽。
如果 delegate 是 ViewGroup , 則會調用 ViewGroup 的 dispatchTouchEvent(...) ,在此方法中 ViewGroup 要將點擊事件分

發給它的子 View, 而在分發的過程中會調用 isTransformedTouchPointInView(...)方法判斷點擊的座標是否在子 View 中。所以,

在上面纔會要設置點擊的座標。

這樣就是爲什麼 TouchDelegate 會讓控件擴大了點擊的範圍。

更詳細的內容看參考官網 http://developer.android.com/training/gestures/viewgroup.html










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