Android 擴(kuò)大 View 的點(diǎn)擊區(qū)域的方法
有時(shí)候,按照視覺圖做出來效果后,發(fā)現(xiàn)點(diǎn)擊區(qū)域過小,不好點(diǎn)擊,用戶體驗(yàn)肯定不好。擴(kuò)大視圖,就會(huì)導(dǎo)致整個(gè)視覺圖變得不好看。那么有沒有什么辦法在不改變視圖大小的前提下擴(kuò)大點(diǎn)擊區(qū)域呢?
答案是有!
能夠解決這個(gè)問題的前提你要對(duì) View 的事件分發(fā)機(jī)制有一定的了解。
下面我將簡單介紹一下View 的事件分發(fā)機(jī)制,方便大家理解后面的解決辦法。
為了更清楚的說明整個(gè)機(jī)制,采用如下的視圖來說明點(diǎn)擊的事件分發(fā)機(jī)制。下圖是一個(gè) FrameLayout (ViewGroup) 里面包含著一個(gè) ImageView (View)。
先自定義一個(gè) MyFrameLayout,繼承FrameLayout,并實(shí)現(xiàn)兩個(gè)點(diǎn)擊相關(guān)的接口;具體代碼如下:
public class MyFrameLayout extends FrameLayout implements OnClickListener, OnTouchListener { private static final String TAG = "Event"; public MyFrameLayout(Context context, AttributeSet attrs) { super(context, attrs); Log.d(TAG, "MyFrameLayout init"); setOnClickListener(this); setOnTouchListener(this); } @Override public boolean dispatchTouchEvent(MotionEvent event) { Log.d(TAG, "MyFrameLayout dispatchTouchEvent " + event.getAction()); return super.dispatchTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { Log.d(TAG, "MyFrameLayout onTouchEvent " + event.getAction() ); return super.onTouchEvent(event); } @Override public void onClick(View view) { Log.d(TAG, "MyFrameLayout onClick"); } @Override public boolean onTouch(View view, MotionEvent event) { Log.d(TAG, "MyFrameLayout onTouch " + event.getAction()); return true; } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { Log.d(TAG, "MyFrameLayout onInterceptTouchEvent " + ev.getAction()); return super.onInterceptTouchEvent(ev); } }
接著,對(duì)于 ImageView 也做類似的操作,具體代碼如下:
public class MyImageView extends ImageView implements OnClickListener, OnTouchListener { private static final String TAG = "Event"; public MyImageView(Context context, AttributeSet attrs) { super(context, attrs); Log.d(TAG, "MyImageView init"); setOnClickListener(this); setOnTouchListener(this); } @Override public boolean dispatchTouchEvent(MotionEvent event) { Log.d(TAG, "MyImageView dispatchTouchEvent "+ event.getAction()); return super.dispatchTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { Log.d(TAG, "MyImageView onTouchEvent "+ event.getAction()); return super.onTouchEvent(event); } @Override public boolean onTouch(View arg0, MotionEvent arg1) { Log.d(TAG, "MyImageView onTouch " + arg1.getAction()); return false; } @Override public void onClick(View arg0) { Log.d(TAG, "MyImageView onClick"); } }
這里要說明的是,只有ViewGroup才有 onInterceptTouchEvent 方法的,普通的 View 是沒有的,它是不能對(duì)事件進(jìn)行攔截的。
那這時(shí)候,如果我們點(diǎn)擊里面的 ImageView,會(huì)有怎樣的輸出呢?結(jié)果如下圖。
那如果點(diǎn)擊外層呢?
0,1,2分別是代表 ACTION_DOWN,ACTION_UP,ACTION_MOVE;從中也可以看出一個(gè)點(diǎn)擊動(dòng)作包含一個(gè)Down,一個(gè)Up,還有多個(gè)Move操作。
再來看一段源碼:
public boolean dispatchTouchEvent(MotionEvent ev){ boolean consume = false; if(onInterceptTouchEvent(ev)){ consume = onTouchEvent(ev); } else{ consume = child.dispatchTouchEvent(ev); } return consume; }
上述的代碼把三者的關(guān)系說得很清楚了,對(duì)于一個(gè)對(duì)于一個(gè) ViewGroup 來說,點(diǎn)擊事件產(chǎn)生后,首先會(huì)傳遞給它,這時(shí)候會(huì)調(diào)用 dispatchTouchEvent,如果這個(gè) ViewGroup 的 onInterceptTouchEvent 返回 true ,則表示它要攔截該事件,也就會(huì)交給它的 onTouchEvent 來進(jìn)行處理。如果這個(gè) ViewGroup 的 onInterceptTouchEvent 返回 false 則會(huì)傳給子元素,子元素的 dispatchTouchEvent 就會(huì)被調(diào)用,如此反復(fù)循環(huán)。這與上面一張圖打出的結(jié)果是一致的。
這里還有說明的是,如果代碼設(shè)置了 OnTouchListener,那么就會(huì)先調(diào)用 onTouch 方法,然后在調(diào)用 onTouchEvent。OnClickListener 是優(yōu)先級(jí)最低的,所以最后才會(huì)調(diào)用 onClick。
因此,從第二張結(jié)果圖也可以看出,當(dāng)存在 onTouch 之后,onTouchEvent 和 onClick 兩個(gè)方法都不會(huì)在調(diào)用了。
相信到這里,大家對(duì)于View的事件分發(fā)機(jī)制有一定的了解了。
這里回到開頭提的那個(gè)問題,那么有什么辦法可以擴(kuò)大 View 的點(diǎn)擊區(qū)域呢?
答案:在父 View 設(shè)置 OnTouchListener 對(duì)點(diǎn)擊事件進(jìn)行攔截,通過判斷點(diǎn)擊的位置,來決定是相應(yīng)子 View 的事件,還是父 View 的事件。
具體實(shí)現(xiàn)代碼如下:
public class TouchFactory { /** 擴(kuò)展垂直方向點(diǎn)擊區(qū)域尺寸 */ private static final int EXT_V_SIZE = 200; public static View.OnTouchListener creatTouchListener(){ return new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if (expendTouchSize(v, event)) { return true; } return false; } }; } public static boolean expendTouchSize(View root, MotionEvent event) { if (root instanceof MyFrameLayout) { ImageView view = ((MyFrameLayout) root).getMyImageView(); if (view != null && view.getVisibility() == View.VISIBLE) { Rect touchRect = new Rect(); view.getGlobalVisibleRect(touchRect); int action = event.getAction(); float x = event.getRawX(); float y = event.getRawY(); if ((y >= touchRect.top - EXT_V_SIZE) && (y <= touchRect.bottom + EXT_V_SIZE)) { if (x >= touchRect.left) { if (action == MotionEvent.ACTION_UP) { Toast.makeText(view.getContext(), "touch", Toast.LENGTH_SHORT).show(); } return true; } } } } return false; } }
TouchFactory 對(duì)點(diǎn)擊事件進(jìn)行了封裝,并通過對(duì)點(diǎn)擊區(qū)域的判斷,來決定要不要攔截點(diǎn)擊事件。
下面是 MyFrameLayout 的具體實(shí)現(xiàn)。由于是一個(gè)自定義 view, 因此,變量 myImageView 是一定為空的,所以要對(duì)其進(jìn)行賦值。
public class MyFrameLayout extends FrameLayout { private static final String TAG = "Event"; private MyImageView myImageView; public MyFrameLayout(Context context) { this(context, null); } public MyFrameLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public MyFrameLayout(Context context, AttributeSet attrs, int def) { super(context, attrs, def); init(); } public void init() { this.setOnTouchListener(TouchFactory.creatTouchListener()); } public MyImageView getMyImageView() { if (myImageView == null) { myImageView = findViewById(R.id.mImage); } return myImageView; } }
注意事項(xiàng): 當(dāng)對(duì)子 View 設(shè)置 OnClickListener,點(diǎn)擊區(qū)域剛好是子 View 內(nèi)部的時(shí)候,就會(huì)消耗此事見,父 View 的攔截處理就無效了,因此,一旦選擇攔截來擴(kuò)大點(diǎn)擊區(qū)域,就不要再去子 View 設(shè)置點(diǎn)擊回調(diào)來消耗點(diǎn)擊事件了。
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- 一文搞懂Android RecyclerView點(diǎn)擊展開、折疊效果的實(shí)現(xiàn)代碼
- Android擴(kuò)大View點(diǎn)擊范圍的方法
- Android實(shí)現(xiàn)WebView點(diǎn)擊攔截跳轉(zhuǎn)原生
- Android開發(fā)實(shí)現(xiàn)ListView點(diǎn)擊展開收起效果示例
- Android XRecyclerView最簡單的item點(diǎn)擊事件處理
- Android RecyclerView實(shí)現(xiàn)點(diǎn)擊條目刪除
- Android擴(kuò)大View點(diǎn)擊區(qū)域方案示例
相關(guān)文章
基于Alarmmanager實(shí)現(xiàn)簡單鬧鐘功能
這篇文章主要為大家詳細(xì)介紹了基于Alarmmanager實(shí)現(xiàn)簡單鬧鐘功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-06-06android中px和dp,px和sp之間的轉(zhuǎn)換方法
在Android開發(fā)中dp和px,sp和px之間的轉(zhuǎn)換時(shí)必不可少的。下面腳本之家小編給大家?guī)砹薬ndroid中px和dp,px和sp之間的轉(zhuǎn)換方法,感興趣的朋友一起看看吧2018-06-06另外兩種Android沉浸式狀態(tài)欄實(shí)現(xiàn)思路
這篇文章主要為大家介紹了另外兩種Android沉浸式狀態(tài)欄實(shí)現(xiàn)思路,android5.0及以后版本都支持給狀態(tài)欄著色,而目前android主流版本還是4.4,想要深入了解的朋友可以參考一下2016-01-01Android Room數(shù)據(jù)庫自動(dòng)升級(jí)與遷移的策略
在 Android 應(yīng)用開發(fā)中,Room 是 Google 提供的一個(gè)輕量級(jí)數(shù)據(jù)庫框架,用于簡化與 SQLite 的交互,本文將介紹 Room 數(shù)據(jù)庫升級(jí)的幾種場景和常見的處理方法,包括手動(dòng)遷移和自動(dòng)遷移的策略,需要的朋友可以參考下2024-09-09Android那兩個(gè)你碰不到但是很重要的類之ActivityThread
上篇文章我們聊了些Android里那些我們平時(shí)碰不到但很重要的類ViewRootImpl,這一篇我們就來看看另外那個(gè)類ActivityThread,文中有相關(guān)的代碼示例,感興趣的同學(xué)可以跟著小編一起來學(xué)習(xí)2023-05-05