編輯:關於Android編程
上一篇博客我們學習了Android View 觸摸事件傳遞機制,不了解的同學可以查看Android View 觸摸事件傳遞機制。今天繼續學習Android觸摸事件傳遞機制,這篇博客將和大家一起探討ViewGroup的觸摸事件傳遞機制。
示例代碼如下:
public class MainActivity extends ActionBarActivity {
private String TAG = MainActivity;
private MyViewGroup parentView;
private Button childView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
parentView = (MyViewGroup) findViewById(R.id.parent);
childView = (Button) findViewById(R.id.child);
childView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e(TAG, childView=====onClick);
}
});
parentView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.e(TAG, parentView=====onTouch);
return false;
}
});
parentView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e(TAG, parentView=====onClick);
}
});
}
}
自定義MyViewGroup,並且重寫dispatchTouchEvent方法添加打印日志,重寫onInterceptTouchEvent方法添加打印日志:
public class MyViewGroup extends LinearLayout {
private String TAG = MyViewGroup;
public MyViewGroup(Context context) {
super(context);
}
public MyViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.e(TAG, MyViewGroup=====dispatchTouchEvent +ev.getAction());
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.e(TAG, MyViewGroup=====onInterceptTouchEvent);
return super.onInterceptTouchEvent(ev);
}
}
布局如下:
分別點擊Button按鈕和空白區域,打印結果如下:
08-01 17:02:56.792 14706-14706/com.xjp.viewgrouptouchdemo E/MyViewGroup﹕ MyViewGroup=====dispatchTouchEvent
08-01 17:02:56.792 14706-14706/com.xjp.viewgrouptouchdemo E/MainActivity﹕ childView=====onClick
08-01 17:03:31.046 14706-14706/com.xjp.viewgrouptouchdemo E/MyViewGroup﹕ MyViewGroup=====dispatchTouchEvent
08-01 17:03:31.046 14706-14706/com.xjp.viewgrouptouchdemo E/MainActivity﹕ parentView=====onTouch1
08-01 17:03:31.046 14706-14706/com.xjp.viewgrouptouchdemo E/MainActivity﹕ parentView=====onClick
從上面打印可以看出,在ViewGroup嵌套Button布局中,僅僅點擊Button按鈕時只會執行Button的觸摸事件,不會執行ViewGroup的觸摸事件。當你點擊Button以外的空白區域時,才會執行ViewGroup的觸摸事件。那為什麼在ViewGroup嵌套View時只會執行View的觸摸事件而不執行ViewGroup的觸摸事件呢?待著這個疑問,我們來分析下ViewGroup源碼中的dispatchTouchEvent方法。為了方便起見,我這裡都是分析的Android2.0的源碼。
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
final float xf = ev.getX();
final float yf = ev.getY();
final float scrolledXFloat = xf + mScrollX;
final float scrolledYFloat = yf + mScrollY;
final Rect frame = mTempRect;
boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (action == MotionEvent.ACTION_DOWN) {
if (mMotionTarget != null) {
// this is weird, we got a pen down, but we thought it was
// already down!
// XXX: We should probably send an ACTION_UP to the current
// target.
mMotionTarget = null;
}
// If we're disallowing intercept or if we're allowing and we didn't
// intercept
if (disallowIntercept || !onInterceptTouchEvent(ev)) {
// reset this event's action (just to protect ourselves)
ev.setAction(MotionEvent.ACTION_DOWN);
// We know we want to dispatch the event down, find a child
// who can handle it, start with the front-most child.
final int scrolledXInt = (int) scrolledXFloat;
final int scrolledYInt = (int) scrolledYFloat;
final View[] children = mChildren;
final int count = mChildrenCount;
for (int i = count - 1; i >= 0; i--) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null) {
child.getHitRect(frame);
if (frame.contains(scrolledXInt, scrolledYInt)) {
// offset the event to the view's coordinate system
final float xc = scrolledXFloat - child.mLeft;
final float yc = scrolledYFloat - child.mTop;
ev.setLocation(xc, yc);
if (child.dispatchTouchEvent(ev)) {
// Event handled, we have a target now.
mMotionTarget = child;
return true;
}
// The event didn't get handled, try the next view.
// Don't reset the event's location, it's not
// necessary here.
}
}
}
}
}
boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
(action == MotionEvent.ACTION_CANCEL);
if (isUpOrCancel) {
// Note, we've already copied the previous state to our local
// variable, so this takes effect on the next event
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// The event wasn't an ACTION_DOWN, dispatch it to our target if
// we have one.
final View target = mMotionTarget;
if (target == null) {
// We don't have a target, this means we're handling the
// event as a regular view.
ev.setLocation(xf, yf);
return super.dispatchTouchEvent(ev);
}
// if have a target, see if we're allowed to and want to intercept its
// events
if (!disallowIntercept && onInterceptTouchEvent(ev)) {
final float xc = scrolledXFloat - (float) target.mLeft;
final float yc = scrolledYFloat - (float) target.mTop;
ev.setAction(MotionEvent.ACTION_CANCEL);
ev.setLocation(xc, yc);
if (!target.dispatchTouchEvent(ev)) {
// target didn't handle ACTION_CANCEL. not much we can do
// but they should have.
}
// clear the target
mMotionTarget = null;
// Don't dispatch this event to our own view, because we already
// saw it when intercepting; we just want to give the following
// event to the normal onTouchEvent().
return true;
}
if (isUpOrCancel) {
mMotionTarget = null;
}
// finally offset the event to the target's coordinate system and
// dispatch the event.
final float xc = scrolledXFloat - (float) target.mLeft;
final float yc = scrolledYFloat - (float) target.mTop;
ev.setLocation(xc, yc);
return target.dispatchTouchEvent(ev);
}
分析:
1. 代碼3-8行,獲取當前手指在屏幕上觸摸點擊的坐標位置,用於判斷當前手指觸摸點擊的是View區域還是ViewGroup區域。
2. 代碼第10行,獲得disallowIntercept的值,disallowIntercept指的是是否禁用掉事件攔截功能,默認值是false,你可以調用requestDisallowInterceptTouchEvent方法修改它。
3. 代碼第12行,手指觸摸手勢是先ACTION_DOWN操作,所以條件滿足,進入if條件。
4. 代碼第13-19行,清除當前手機屏幕上觸摸點擊對象,也就是將mMotionTarget設置為null。意思是在點擊手機屏幕之前是沒有任何觸摸點擊對象的。
5. 代碼第22行,由於disallowIntercept默認值是false,所以條件是否滿足完全取決於方法onInterceptTouchEvent返回值取反。而我們進入該方法會發現裡面的實現僅僅是返回一個false。也就是if條件滿足。
6. 代碼第31行,通過一個for循環遍歷當前ViewGroup下所以子View。
7. 代碼第35行,獲取遍歷子View在屏幕上的坐標位置,然後代碼第36行,判斷當前屏幕手指觸摸點擊坐標是否包含遍歷的子View在屏幕上的坐標位置范圍?如果包含,者表示當前手指觸摸點擊的地方是該子View,也就是點擊了Button。否則表示當前手指觸摸並沒有點擊到ViewGroup中的子View,也就是點擊到了空白區域。
8. 代碼第41行,調用子View的dispatchTouchEvent方法來處理View的觸摸事件分發,這裡一步就是我們上一篇博客分析的 Android View 觸摸事件傳遞機制入口。在這篇博客中我們知道,當View是可點擊的或者長安點擊或者設置了setOnClickListener點擊監聽事件的,View#dispatchTouchEvent方法一律返回true,否則返回false。所以當條件滿足,也就是子View設置了點擊事件時ViewGroup#dispatchTouchEvent方法返回true,觸摸對象mMotionTarget = child賦值成當前點擊的子Viwe執行結束。因此這也驗證了上面示例代碼,當button設置了點擊事件時只執行了Button的onClick事件,並沒有執行任何關於ViewGroup的觸摸點擊事件。
9. 代碼第66-72行,假如上面的View#dispatchTouchEvent方法返回false,表示子View不可點擊(可以參考上一篇博客),此時mMotionTarget依然為null,那麼target==null條件滿足。執行父類的dispatchTouchEvnet方法,也就是View的dispatchTouchEvent方法。由於ViewGroup的父類是View,所以此處表示執行了ViewGroup的dispatchTouchEvent方法。言外之意就是,當ViewGroup嵌套的子View不可點擊且沒有設置setOnClickListener點擊監聽事件時,點擊View先觸發子View的觸摸事件,然後在觸發ViewGroup的觸摸事件,執行了ViewGroup#dispatchTouchEvent方法,並且返回了,後面代碼不執行。
10. 代碼第76-103行,主要是執行子View的ACTION_UP和ACTION_CANCEL手勢操作的,邏輯這裡就不具體分析了,可以參考上一篇博客。
總結:有上面分析我們知道。
onInterceptTouchEvent方法是用於ViewGroup對子View的觸摸事件攔截功能,默認返回false,不攔截子View的觸摸事件,可以重寫該方法,返回true來攔截子View的觸摸事件傳遞。此時只會執行ViewGroup的觸摸事件傳遞。 當子View是不可點擊的且沒有設置setOnClickListener點擊監聽事件時,會先執行子View的觸摸事件,然後在執行ViewGroup的觸摸事件。現在倆驗證以上兩個結論。
修改MyViewGroup代碼
public class MyViewGroup extends LinearLayout {
private String TAG = MyViewGroup;
public MyViewGroup(Context context) {
super(context);
requestDisallowInterceptTouchEvent(false);
}
public MyViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.e(TAG, MyViewGroup=====dispatchTouchEvent );
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return true;
}
點擊Button按鈕,打印結果如下:
08-01 18:36:56.815 29910-29910/com.xjp.viewgrouptouchdemo E/MyViewGroup﹕ MyViewGroup=====dispatchTouchEvent
08-01 18:36:56.825 29910-29910/com.xjp.viewgrouptouchdemo E/MainActivity﹕ parentView=====onTouch1
08-01 18:36:56.825 29910-29910/com.xjp.viewgrouptouchdemo E/MainActivity﹕ parentView=====onClick
有打印可以看出,當重寫onInterceptTouchEvent方法返回true時,是不會執行Button的觸摸點擊事件的。也正好驗證了前面的結論:當ViewGroup重寫onInterceptTouchEvent方法返回true時,也就是攔截子View的觸摸事件傳遞,此時只會執行ViewGroup的觸摸事件。
代碼修改如下:
public class MainActivity extends ActionBarActivity {
private String TAG = MainActivity;
private MyViewGroup parentView;
private ImageView childView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
parentView = (MyViewGroup) findViewById(R.id.parent);
childView = (ImageView) findViewById(R.id.child);
childView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.e(TAG, childView=====onTouch);
return false;
}
});
parentView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.e(TAG, parentView=====onTouch +event.getAction());
return false;
}
});
parentView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e(TAG, parentView=====onClick);
}
});
}
}
將Button換成ImageView,並且不設置setOnClickListener事件。點擊ImageView打印日志如下:
08-01 18:52:44.720 31219-31219/com.xjp.viewgrouptouchdemo E/MyViewGroup﹕ MyViewGroup=====dispatchTouchEvent
08-01 18:52:44.720 31219-31219/com.xjp.viewgrouptouchdemo E/MainActivity﹕ childView=====onTouch
08-01 18:52:44.730 31219-31219/com.xjp.viewgrouptouchdemo E/MainActivity﹕ parentView=====onTouch0
parentView=====onClick
有打印可以看出,即執行了子View ImageView的觸摸事件,也執行了ViewGroup的觸摸事件。由於ImageView默認情況是不可點擊的,因此:當子View不可點擊或者麼有設置setOnClickListener點擊事件時,點擊子View是先執行View的觸摸事件,然後在執行ViewGroup的觸摸事件的。這也驗證了ViewGroup#dispatchTouchEvent小節的第9點。
最後附帶上一幅ViewGroup觸摸事件傳遞流程圖
任務描述有一套C寫的代號為“Shooter”的核心算法庫可以解決我們面臨的一些問題,只是這個庫一直用在其他平台。我們現在的任務是將其復用到Andr
因為Android Studio是基於IntelliJ IDEA開發過來的,使用的插件其實都是IDEA上面的。IDEA與Android Studio默認已經安裝了Git插
這篇文章是android開發人員的必備知識,是我特別為大家整理和總結的,不求完美,但是有用。1.背景自適應且不失真問題的存在 制作自適應背景圖片是UI開發的一個廣泛問題
RecyclerView是android-support-v7-21版本中新增的一個Widget,官方介紹RecyclerView 是 ListView 的升級版本,更加