Android开发:三种扩大View点击区域的方法
创作时间:
作者:
@小白创作中心
Android开发:三种扩大View点击区域的方法
引用
CSDN
1.
https://blog.csdn.net/u013700502/article/details/140936913
在Android应用开发中,有时候需要扩大View的点击区域以提高用户交互的便利性,尤其是当视图元素较小或用户界面密集时。扩大点击区域可以让用户更容易点击目标,改善用户体验。本文将介绍三种实现方式:通过设置padding、使用TouchDelegate类以及结合RectF和getLocationOnScreen。
方式一:增加padding
通过设置padding来增大点击区域,如:
<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" />
上面的代码通过在XML中设置padding来扩大点击区域,当然也可以通过代码设置setPadding来实现。虽然设置padding可以起到效果,但是如果使用不当可能会影响视图的布局和外观,比如对ImageView设置padding的话可能会挤压其形状,所以使用Padding扩大点击区域时需要确保不影响视图的布局和外观。
方式二:TouchDelegate
TouchDelegate 类是 Android 中的一个辅助类,用于扩展 View 的触摸区域,使其大于实际的 View 边界。这对于增加某些 UI 元素的触控便捷性非常有用,比如小按钮。
TouchDelegate 使用示例:
/**
* 扩展方法,扩大点击区域
* NOTE: 需要保证目标targetView有父View,否则无法扩大点击区域
*
* @param expandSize 扩大的大小,单位px
*/
fun View.expandTouchView(expandSize: Int = 10.dp2px()) {
val parentView = (parent as? View)
parentView?.post {
val rect = Rect()
getHitRect(rect) //getHitRect(rect)将视图在父容器中所占据的区域存储到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()) //扩大点击区域
setOnClickListener { showToast("通过TouchDelegate扩大点击区域") }
}
上面就实现了View扩大点击区域,继续来看下TouchDelegate 的源码:
public class TouchDelegate {
private View mDelegateView; //需要接收触摸事件的 View,即代理 View。
private Rect mBounds;//本地坐标中的代理 View 的边界,用于初始命中测试。
private Rect mSlopBounds;//增加一定范围的 mBounds,用于追踪触摸事件是否应被视为在代理 View 内。
@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 内,则会将其转发到 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:重点看这里,最终是调用的代理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中,优先去判断是否有TouchDelegate,如果有的话会先去找对应的代理View去处理事件。使用TouchDelegate的注意事项:
- 目标View必须有父View ;
- 给多个目标View扩大点击区域时,不能是同一个父View,从View类的源码中可以看到,设置setTouchDelegate时,会把之前的覆盖掉 。
方式三:RectF & getLocationOnScreen
RectF 是一个用于表示浮点坐标的矩形区域的类,而 getLocationOnScreen 则用于获取视图在整个屏幕中的绝对坐标。结合两者,可以检查触摸事件是否在子视图的“扩展区域”内,然后执行相应的操作。代码示例:
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("扩大了点击事件") }
}
@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)
}
/**
* 判断是否点击到了子 View 的扩大区域
* @param childView 子 View
* @param touchPair 点击的位置 (x, y)
* @param expandSize 扩大区域的大小
* @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
// 扩大点击区域
val rect = RectF()
rect.set(
childX - expandSize,
childY - expandSize,
childX + childView.width + expandSize,
childY + childView.height + expandSize
)
// 判断点击是否在扩大的子 View 区域内
return rect.contains(touchX, touchY)
}
}
- getLocationOnScreen : 用于获取子视图在屏幕上的绝对坐标,返回一个包含 x 和 y 坐标的数组。利用这些坐标计算出子视图在屏幕上的位置。
- RectF : 创建一个矩形区域,通过调用 set 方法扩展矩形的上下左右边界,从而扩大点击区域。
- onTouchEvent : 监听触摸事件,如果点击位置在扩大的区域内,则调用 performClick 触发子视图的点击事件。
热门推荐
到底什么是编制?行政编和事业编有啥区别呢?
行为规范化是什么?从概念到实践的全面解析
如何持续提升手术室运转效率? 这七招很管用!
2025年白蜡金蛇运势预测:本命年如何应对挑战与机遇?
中国企业级SaaS发展面临的挑战详解
化肥采购合同:确保农业生产可持续性的重要协议
深圳公司注册流程及费用详解
如何识别并辨别虚假新闻报道案件
韩国 AI 作战分队扩军!2028 年前建 90 支,16 个训练场备战
机油多久换一次?按时间还是里程?今天全部给你说清楚!
BigMint:2024年全球煤炭出口同比增长超过4%
我们的带薪假期算长吗?假期长短会如何影响经济?
深入解析古诺模型及其在经济学中的应用
节能生活从家开始!高效家居电器选购与使用宝典,打造绿色节能家庭
汽车节气门清洁全攻略:原理、必要性、步骤及周期详解
购物心得体会
每对夫妻都可能生“唐氏儿”,筛查很很很很很重要!
贵州“四大怪菜”,全是当地人的心头爱,外地人看了摇头:真敢吃
这种遗传病广东高发!怀孕前这类人群一定注意筛查
集装箱房需要审批吗
详解 Ubuntu 安装时的硬盘分区方法及备份数据的重要性
体温计可以带上飞机吗?体温计上飞机,这些细节你不得不知!
如何获取Windows注册码?使用正版注册器可行吗?
价格蹭蹭涨,商家也发愁!广东人破防:没它开不了饭
深海鱼的营养价值:专家解读其对人体健康的多重益处
表彰人力资源短句文案怎么写
派出所报案申请书范本的撰写要点及法律依据
浅说舰艇排水量
“气候变化进进进”倡导活动开展 呼吁各方支持气候变化教育
小母猫术后护理常规