Android實(shí)現(xiàn)擴(kuò)大View點(diǎn)擊區(qū)域的三種方式
在 Android 應(yīng)用開(kāi)發(fā)中,有時(shí)候需要擴(kuò)大 View 的點(diǎn)擊區(qū)域以提高用戶交互的便利性,尤其是當(dāng)視圖元素較小或用戶界面密集時(shí)。擴(kuò)大點(diǎn)擊區(qū)域可以讓用戶更容易點(diǎn)擊目標(biāo),改善用戶體驗(yàn)。以下提供幾種擴(kuò)大點(diǎn)擊區(qū)域的思路。
方式一:增加padding
通過(guò)設(shè)置padding來(lái)增大點(diǎn)擊區(qū)域,如:
<TextView android:id="@+id/tv_view_delegate2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@color/gray_holo_light" android:padding="20dp" android:text="TouchDelegate2" />
上面的代碼通過(guò)在XML中設(shè)置padding來(lái)擴(kuò)大點(diǎn)擊區(qū)域,當(dāng)然也可以通過(guò)代碼設(shè)置setPadding來(lái)實(shí)現(xiàn)。雖然設(shè)置padding可以起到效果,但是如果使用不當(dāng)可能會(huì)影響視圖的布局和外觀,比如對(duì)ImageView設(shè)置padding的話可能會(huì)擠壓其形狀,所以使用Padding擴(kuò)大點(diǎn)擊區(qū)域時(shí)需要確保不影響視圖的布局和外觀。
方式二:TouchDelegate
TouchDelegate 類是 Android 中的一個(gè)輔助類,用于擴(kuò)展 View 的觸摸區(qū)域,使其大于實(shí)際的 View 邊界。這對(duì)于增加某些 UI 元素的觸控便捷性非常有用,比如小按鈕。
TouchDelegate 使用示例:
/** * 擴(kuò)展方法,擴(kuò)大點(diǎn)擊區(qū)域 * NOTE: 需要保證目標(biāo)targetView有父View,否則無(wú)法擴(kuò)大點(diǎn)擊區(qū)域 * * @param expandSize 擴(kuò)大的大小,單位px */ fun View.expandTouchView(expandSize: Int = 10.dp2px()) { val parentView = (parent as? View) parentView?.post { val rect = Rect() getHitRect(rect) //getHitRect(rect)將視圖在父容器中所占據(jù)的區(qū)域存儲(chǔ)到rect中。 log("rect = $rect") rect.left -= expandSize rect.top -= expandSize rect.right += expandSize rect.bottom += expandSize log("expandRect = $rect") parentView.touchDelegate = TouchDelegate(rect, this) } }
在Activity中使用:
private val tvExpandTouch: TextView by id(R.id.tv_view_delegate) tvExpandTouch.run { expandTouchView(50.dp2px()) //擴(kuò)大點(diǎn)擊區(qū)域 setOnClickListener { showToast("通過(guò)TouchDelegate擴(kuò)大點(diǎn)擊區(qū)域") } }
上面就實(shí)現(xiàn)了View擴(kuò)大點(diǎn)擊區(qū)域,繼續(xù)來(lái)看下TouchDelegate 的源碼:
public class TouchDelegate { private View mDelegateView; //需要接收觸摸事件的 View,即代理 View。 private Rect mBounds;//本地坐標(biāo)中的代理 View 的邊界,用于初始命中測(cè)試。 private Rect mSlopBounds;//增加一定范圍的 mBounds,用于追蹤觸摸事件是否應(yīng)被視為在代理 View 內(nèi)。 @UnsupportedAppUsage private boolean mDelegateTargeted; private TouchDelegateInfo mTouchDelegateInfo; public TouchDelegate(Rect bounds, View delegateView) { mBounds = bounds; mSlop = ViewConfiguration.get(delegateView.getContext()).getScaledTouchSlop(); mSlopBounds = new Rect(bounds); mSlopBounds.inset(-mSlop, -mSlop); mDelegateView = delegateView; } //接收并處理觸摸事件。若事件在 mBounds 內(nèi),則會(huì)將其轉(zhuǎn)發(fā)到 mDelegateView。 public boolean onTouchEvent(@NonNull MotionEvent event) { int x = (int)event.getX(); int y = (int)event.getY(); boolean sendToDelegate = false; boolean hit = true; boolean handled = false; switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: mDelegateTargeted = mBounds.contains(x, y); sendToDelegate = mDelegateTargeted; break; case MotionEvent.ACTION_POINTER_DOWN: case MotionEvent.ACTION_POINTER_UP: 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) { if (hit) { // Offset event coordinates to be inside the target view event.setLocation(mDelegateView.getWidth() / 2, mDelegateView.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)); } //NOTE:重點(diǎn)看這里,最終是調(diào)用的代理View去處理事件了。 handled = mDelegateView.dispatchTouchEvent(event); } return handled; } }
View.java 源碼中使用 TouchDelegate:
private TouchDelegate mTouchDelegate = null; public void setTouchDelegate(TouchDelegate delegate) { mTouchDelegate = delegate; } public TouchDelegate getTouchDelegate() { return mTouchDelegate; } public boolean onTouchEvent(MotionEvent event) { //...... if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } } switch (action) { case MotionEvent.ACTION_DOWN: //...省略... case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: } }
可以看到在onTouchEvent中,優(yōu)先去判斷是否有TouchDelegate,如果有的話會(huì)先去找對(duì)應(yīng)的代理View去處理事件。使用TouchDelegate的注意事項(xiàng):
- 目標(biāo)View必須有父View;
- 給多個(gè)目標(biāo)View擴(kuò)大點(diǎn)擊區(qū)域時(shí),不能是同一個(gè)父View,從View類的源碼中可以看到,設(shè)置setTouchDelegate時(shí),會(huì)把之前的覆蓋掉。
方式三:RectF & getLocationOnScreen
RectF 是一個(gè)用于表示浮點(diǎn)坐標(biāo)的矩形區(qū)域的類,而 getLocationOnScreen 則用于獲取視圖在整個(gè)屏幕中的絕對(duì)坐標(biāo)。結(jié)合兩者,可以檢查觸摸事件是否在子視圖的“擴(kuò)展區(qū)域”內(nèi),然后執(zhí)行相應(yīng)的操作。代碼示例:
class ParentInnerTouchView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : ConstraintLayout(context, attrs, defStyleAttr) { private val tvChildView: TextView init { inflate(context, R.layout.expand_touch_view, this) tvChildView = findViewById(R.id.tv_expand_view) tvChildView.setOnClickListener { showToast("擴(kuò)大了點(diǎn)擊事件") } } @SuppressLint("ClickableViewAccessibility") override fun onTouchEvent(event: MotionEvent?): Boolean { event?.let { ev -> if (ev.action == MotionEvent.ACTION_DOWN) { val isChildHit = isHitExpandChildView(tvChildView, Pair(ev.rawX, ev.rawY)) if (isChildHit) { //將事件傳遞給子控件 tvChildView.performClick() } } } return super.onTouchEvent(event) } /** * 判斷是否點(diǎn)擊到了子 View 的擴(kuò)大區(qū)域 * @param childView 子 View * @param touchPair 點(diǎn)擊的位置 (x, y) * @param expandSize 擴(kuò)大區(qū)域的大小 * @return 是否命中 */ private fun isHitExpandChildView( childView: View, touchPair: Pair<Float, Float>, expandSize: Int = 50.dp2px() ): Boolean { // 獲取子 View 在屏幕上的位置 val location = IntArray(2) childView.getLocationOnScreen(location) val childX = location[0].toFloat() val childY = location[1].toFloat() val touchX = touchPair.first val touchY = touchPair.second // 擴(kuò)大點(diǎn)擊區(qū)域 val rect = RectF() rect.set( childX - expandSize, childY - expandSize, childX + childView.width + expandSize, childY + childView.height + expandSize ) // 判斷點(diǎn)擊是否在擴(kuò)大的子 View 區(qū)域內(nèi) return rect.contains(touchX, touchY) } }
- getLocationOnScreen: 用于獲取子視圖在屏幕上的絕對(duì)坐標(biāo),返回一個(gè)包含 x 和 y 坐標(biāo)的數(shù)組。利用這些坐標(biāo)計(jì)算出子視圖在屏幕上的位置。
- RectF: 創(chuàng)建一個(gè)矩形區(qū)域,通過(guò)調(diào)用 set 方法擴(kuò)展矩形的上下左右邊界,從而擴(kuò)大點(diǎn)擊區(qū)域。
- onTouchEvent: 監(jiān)聽(tīng)觸摸事件,如果點(diǎn)擊位置在擴(kuò)大的區(qū)域內(nèi),則調(diào)用 performClick 觸發(fā)子視圖的點(diǎn)擊事件。
以上就是Android實(shí)現(xiàn)擴(kuò)大View點(diǎn)擊區(qū)域的三種方式的詳細(xì)內(nèi)容,更多關(guān)于Android View點(diǎn)擊區(qū)域的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android開(kāi)發(fā)之圖片切割工具類定義與用法示例
這篇文章主要介紹了Android開(kāi)發(fā)之圖片切割工具類定義與用法,結(jié)合實(shí)例形式分析了Android圖片切割工具類的定義與簡(jiǎn)單使用方法,需要的朋友可以參考下2017-11-11Android Studio 3.0中mipmap-anydpi-v26是什么東東
在Android Studio 3.0中一旦我們創(chuàng)建了一個(gè)項(xiàng)目,一個(gè)名為mipmap-anydpi-v26自動(dòng)創(chuàng)建的文件夾在res文件夾下。它究竟能干什么?為什么我們需要這個(gè)?我們?cè)陂_(kāi)發(fā)時(shí)該如何利用它,下面通過(guò)本文給大家介紹下2017-12-12Android基礎(chǔ)之獲取LinearLayout的寬高
LinearLayout是線性布局控件,它包含的子控件將以橫向或豎向的方式排列,按照相對(duì)位置來(lái)排列所有的widgets或者其他的containers,超過(guò)邊界時(shí),某些控件將缺失或消失。有的時(shí)候,我們需要想獲取LinearLayout寬高,下面通過(guò)這篇文章來(lái)跟著小編一起學(xué)習(xí)學(xué)習(xí)吧。2016-11-11Android中ImageView實(shí)現(xiàn)選擇本地圖片并顯示功能
本文主要介紹了android中ImageView實(shí)現(xiàn)選擇本地圖片并顯示功能的示例代碼。具有很好的參考價(jià)值。下面跟著小編一起來(lái)看下吧2017-04-04Android中FileProvider的各種場(chǎng)景應(yīng)用詳解
這篇文章主要為大家介紹了Android中FileProvider的各種場(chǎng)景應(yīng)用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09Android中g(shù)oogle Zxing實(shí)現(xiàn)二維碼與條形碼掃描
這篇文章主要介紹了Android中g(shù)oogle Zxing實(shí)現(xiàn)二維碼與條形碼掃描的相關(guān)資料,需要的朋友可以參考下2017-05-05Android開(kāi)發(fā)中使用Intent打開(kāi)第三方應(yīng)用及驗(yàn)證可用性的方法詳解
這篇文章主要介紹了Android開(kāi)發(fā)中使用Intent打開(kāi)第三方應(yīng)用及驗(yàn)證可用性的方法,結(jié)合實(shí)例形式分析了Android使用Intent打開(kāi)第三方應(yīng)用的三種常用方式及使用注意事項(xiàng),需要的朋友可以參考下2017-11-11Android GridView實(shí)現(xiàn)滾動(dòng)到指定位置的方法
這篇文章主要介紹了Android GridView實(shí)現(xiàn)滾動(dòng)到指定位置的方法,本文介紹了4個(gè)相關(guān)的方法,分別對(duì)它們做了講解,需要的朋友可以參考下2015-06-06解決EditText編輯時(shí)hint 在6.0 手機(jī)上顯示不出來(lái)的問(wèn)題
下面小編就為大家?guī)?lái)一篇解決EditText編輯時(shí)hint 在6.0 手機(jī)上顯示不出來(lái)的問(wèn)題。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-05-05