Android 扩大 View 的点击区域,2021 阿里手淘 Android 面试题目
@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 是没有的,它是不能对事件进行拦截的。
那这时候,如果我们点击里面的 ImageView,会有怎样的输出呢?结果如下图。
那如果点击外层呢?
0,1,2 分别是代表?ACTION_DOWN,ACTION_UP,ACTION_MOVE;从中也可以看出一个点击动作包含一个 Down,一个 Up,还有多个 Move 操作。
再来看一段源码:
public boolean dispatchTouchEvent(MotionEvent ev){
boolean consume = false;
if(onInterceptTouchEvent(ev)){
consume = onTouchEvent(ev);
} else{
consume = child.dispatchTouchEvent(ev);
}
return consume;
}
上述的代码把三者的关系说得很清楚了,对于一个对于一个 ViewGroup 来说,点击事件产生后,首先会传递给它,这时候会调用 dispatchTouchEvent,如果这个?ViewGroup 的?onInterceptTouchEvent 返回 true ,则表示它要拦截该事件,也就会交给它的?onTouchEvent 来进行处理。如果这个?ViewGroup 的?onInterceptTouchEvent 返回 false 则会传给子元素,子元素的?dispatchTouchEvent 就会被调用,如此反复循环。这与上面一张图打出的结果是一致的。
这里还有说明的是,如果代码设置了?OnTouchListener,那么就会先调用?onTouch 方法,然后在调用?onTouchEvent。OnClickListener 是优先级最低的,所以最后才会调用?onClick。
因此,从第二张结果图也可以看出,当存在 onTouch 之后,onTouchEvent 和?onClick 两个方法都不会在调用了。
相信到这里,大家对于 View 的事件分发机制有一定的了解了。
这里回到开头提的那个问题,那么有什么办法可以扩大 View 的点击区域呢?
答案:在父 View 设置 OnTouchListener 对点击事件进行拦截,通过判断点击的位置,来决定是相应子 View 的事件,还是父 View 的事件。
具体实现代码如下:
public class TouchFactory {
/** 扩展垂直方向点击区域尺寸 */
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 对点击事件进行了封装,并通过对点击区域的判断,来决定要不要拦截点击事件。
下面是?MyFrameLayout 的具体实现。由于是一个自定义 view, 因此,变量?myImageView 是一定为空的,所以要对其进行赋值。
public class MyFrameLayout extends FrameLayout {
private static final String TAG = "Event";
private MyImageView myImageVi
ew;
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;
}
}
评论