最近準(zhǔn)備材料,發(fā)現(xiàn)了學(xué)習(xí)總結(jié)寫筆記的更多好處,這兩天解bug,涉及到這個(gè)TouchDelegate,玩了一下,決定督促自己文檔,demo都要寫好。
適用范圍
感覺一般的控件要擴(kuò)大點(diǎn)擊區(qū)域,用padding實(shí)現(xiàn)即可,這次碰到一個(gè)特殊的情況,如圖,
因?yàn)椴季謱R的關(guān)系,這個(gè)SeekBar不能有paddingTop,而這時(shí)又需要在上方增加可響應(yīng)區(qū)域,就用TouchDelegate了。
參考
Managing Touch Events in a ViewGroup這篇是Android Developer介紹TouchDelegate的文檔,含demo snippet。下文代碼都截自這篇文檔。
一句話概括
誰有足夠的地,就去跟誰要地種;要地的時(shí)候得說清楚要哪一塊,跟誰要就用誰地盤上的相對坐標(biāo)。
跟誰要地
控件自己的區(qū)域有限,想要響應(yīng)它區(qū)域外的事件,就得要拜托那塊區(qū)域的地主了,拜托他把他的事件通知給自己。因此,
if (View.class.isInstance(myButton.getParent())) {
((View) myButton.getParent()).setTouchDelegate(touchDelegate);
}
只有它的parent的區(qū)域包含你需要擴(kuò)大到的那片區(qū)域,這個(gè)才會有效??碫iew的源碼:
/**
* Sets the TouchDelegate for this View.
*/
public void setTouchDelegate(TouchDelegate delegate) {
mTouchDelegate = delegate;
}
public boolean onTouchEvent(MotionEvent event) {
......
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
......
}
發(fā)現(xiàn)的確是地主有那塊地,才能分給別人去種。
要哪塊地
TouchDelegate的構(gòu)造器
public TouchDelegate(Rect bounds, View delegateView)
第二個(gè)參數(shù)好理解,就是要被擴(kuò)大響應(yīng)范圍的那個(gè)View,那么第一個(gè)bounds區(qū)域到底是什么呢?demo里是這樣寫的
myButton.getHitRect(delegateArea);
delegateArea.right += 100;
delegateArea.bottom += 100;
TouchDelegate touchDelegate = new TouchDelegate(delegateArea, myButton);
if (View.class.isInstance(myButton.getParent())) {
((View) myButton.getParent()).setTouchDelegate(touchDelegate);
}
看看這個(gè)區(qū)域被拿去做什么,上面的代碼看到一個(gè)View如果有被setTouchDelegete,它會先把touch事件給TouchDelegete處理。在這里會用bounds判斷當(dāng)前的事件是否屬于我們想要擴(kuò)充到的范圍,而這一切的位置,無論是MotionEvent的getX(),getY(),還有這個(gè)bounds,都是相對這個(gè)View的內(nèi)的坐標(biāo),而非相對整個(gè)屏幕的坐標(biāo)。由此可見,這個(gè)bounds參數(shù)就是地主要分給別人種的地了。
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:
......
}
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 {
......
}
handled = delegateView.dispatchTouchEvent(event);
}
return handled;
}
要怎么知道要地主的哪塊地呢?demo中直接調(diào)用了View的getHitRect()方法,這個(gè)方法得到的是Hit rectangle in parent's coordinates,也就是說,如果地主是parent,getHitRect()得到的正式那快地的位置,但是parent沒有足夠的地,得要讓parent的parent去做地主呢?
下面這段代碼中,mMiddle是mTarget的parent。通過view和parent的關(guān)系,parent和grandparent的關(guān)系,推算出view和grandparent的關(guān)系,從而獲得當(dāng)parent的parent做地主時(shí),那塊地的坐標(biāo)。
Rect middleDelegateArea = new Rect();
mMiddle.getHitRect(middleDelegateArea);
Rect littleDelegateArea = new Rect();
mTarget.getHitRect(littleDelegateArea);
Rect delegateArea = new Rect();
delegateArea.left = middleDelegateArea.left + littleDelegateArea.left;
delegateArea.top = middleDelegateArea.top + littleDelegateArea.top;
delegateArea.right = delegateArea.left + littleDelegateArea.width();
delegateArea.bottom = delegateArea.top + littleDelegateArea.height();
delegateArea.left -= 150;
delegateArea.top -= 150;
delegateArea.right += 150;
delegateArea.bottom += 150;
if (View.class.isInstance(mTarget.getParent().getParent())) {
((View) mTarget.getParent().getParent()).setTouchDelegate(new TouchDelegate(delegateArea, mTarget));
}
代碼
寫了demo在Github,含apk,分別通過向parent和parent的parent 設(shè)置TouchDelegate來實(shí)現(xiàn)擴(kuò)大點(diǎn)擊范圍,通過這兩種方式的實(shí)現(xiàn)來加深對delegateArea的理解。