轉載請註明出處 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 的原理,爲什麼它能擴大一個控件的點擊範圍。
根據 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