1. Android视图构成
2. View 的绘制流程
当 Activity 接收到焦点的时候,它会被请求绘制布局,该请求由 Android 的 framework 层处理。绘制是从根节点开始,从上到下开始遍历,对布局树递归地进行 measure、layout 和 draw
。整个 View 树的绘图流程在 ViewRoot.java 类的 performTraversals() 函数展开,该函数所做 的工作可简单概况为是否需要重新计算视图大小(measure
)、是否需要重新安置视图的位置(layout
)、以及是否需要重绘(draw
),流程图如下:
3. 事件分发机制
(1) 为什么要使用事件分发
android中View是树形结构的,View可能会重叠在一起,当我们点击一个地方有多个View都可以响应,这个点击事件应该分配给谁呢?为了解决这个问题,就有了事件分发机制。
(2) 三个重要的事件分发事件
Android事件分发机制主要由“事件分发”—>“事件拦截”—>“事件响应”这三步来进行逻辑控制的。
- 事件分发:dispatchTouchEvent(MotionEvent event)
- 事件拦截:onInterceptTouchEvent()
- 事件响应:onTouchEvent()
(3) 事件分发流程
问题
:如下图所示,点击View1的位置时,由于View重叠,View1、GroupView 和 RootView都可响应事件,这个点击事件应该分配给谁呢?
分析
:点击View1位置时,如其他父控件都不拦截,仅View1对事件进行拦截,则事件分发流程如下
相关结论
:
a. Activity 和 View 是没有拦截事件的,即无onInterceptTouchEvent()方法。原因是
- Activity 作为事件的原始分发者,若拦截了事件,则整个屏幕都会无法响应事件。
- View 作为事件传递的最末端,要么消费处理掉事件,要么不处理并回传事件给activity,因为向下没有子控件了,所有没必要再对事件拦截并向下分发。
b. 屏幕被点击后,事件传递过程为:Activity—>PhoneWindow—>DecorView—>GroupView—>...—>View,即点击事件发生后,事件先传到Activity、再传到ViewGroup、最终再传到View。
c. 点击事件的分发过程如下:dispatchTouchEvent—>onTouchListener的OnTouch方法—>onTouchEvent—>onClickListener的onClick方法。从而也可以看出onTouch优先于onClick执行,即事件传递的顺序是先经过onTouch,再传递到onClick。如:
为一个按钮同事注册点击事件和触摸事件。
package comi.example.liy.mytestdemo;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
/**
* Created by liy on 2019-12-18 8:55
*/
public class EventDispatchActivity extends AppCompatActivity {
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_event_dispatch);
button = findViewById(R.id.btn_event);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d("liy", "onClick execute");
}
});
button.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.d("liy", "onTouch execute, action " + event.getAction());
/*return false;*/
switch (event.getAction()) {// 获取当前触摸事件的Action
case MotionEvent.ACTION_DOWN://手指按下
Log.d("liy", "手指按下: " + event.getAction());
break;
case MotionEvent.ACTION_MOVE://手指滑动
//获取手指滑动到新的位置
Log.d("liy", "手指滑动: " + event.getAction());
break;
case MotionEvent.ACTION_UP://手指抬起
Log.d("liy", "手指抬起: " + event.getAction());
break;
}
//onTouch事件默认返回false;如果设置为true,那么这个触摸事件会被onTouch消费掉,不会再继续向下传递。
return false;
}
});
}
}
点击按钮打印结果为:
onTouch基础:
- onTouch方法里能做的事情比onClick要多一些,比如判断手指按下、抬起、移动等事件。
- onTouch事件默认返回false;如果设置为true,那么这个触摸事件会被onTouch消费掉,不会再继续向下传递,即不会触发onClick事件。
(4) 事件分发—源码分析
- onTouch和onTouchEvent的区别:从源码中可以看出,这两个方法都是在View的dispatchTouchEvent中调用的,onTouch优先于onTouchEvent执行。如果在onTouch方法中通过返回true将事件消费掉,onTouchEvent将不会再执行。
- 注意:如果控件是非enable的,那么给它注册onTouch事件将永远得不到执行。对于这一类控件(如 ImageView),如果我们想要监听它的touch事件:第一,在ImageView的onTouch方法里返回true,这样可以保证ACTION_DOWN之后的其它action都能得到执行;第二,在布局文件里面给ImageView增加一个android:clickable="true"的属性,这样ImageView变成可点击的之后,即使在onTouch里返回了false,ACTION_DOWN之后的其它action也是可以得到执行的;第三:重写该控件的onTouchEvent方法。
- touch事件的层级传递:如果给一个控件注册了touch事件,每次点击它的时候都会触发一系列的ACTION_DOWN,ACTION_MOVE,ACTION_UP等事件。这里需要注意,如果在执行ACTION_DOWN的时候返回了false,后面一系列其它的action就不会再得到执行了。简单的说,就是当dispatchTouchEvent在进行事件分发的时候,只有前一个action返回true,才会触发后一个action。
3. 事件分发示例
(1) android外接USB扫码枪
(2) ImageView的触摸事件
ivPicture.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
// 获取当前触摸事件的Action
switch (event.getAction()) {
//手指按下
case MotionEvent.ACTION_DOWN:
//获取绘画开始的位置
startX = event.getX();
startY = event.getY();
break;
//手指滑动
case MotionEvent.ACTION_MOVE:
//获取手指滑动到新的位置
float newStartX = event.getX();
float newStartY = event.getY();
//开始绘画
cacheCanvas.drawLine(startX, startY, newStartX, newStartY, paint);
//手指滑动过程要不断初始化开始位置,不然开始位置不变
startX = newStartX;
startY = newStartY;
//重新设置iv显示副本
ivPicture.setImageBitmap(cacheBitmap);
break;
//手指抬起
case MotionEvent.ACTION_UP:
cacheCanvas.drawPath(path,paint);
path.reset();
break;
}
//如果设置为true,那么这个触摸事件由该组件控制
return true;
}
});