android项目开发的目的是为了让用户使用,那么用户使用的最直接的方式就是屏幕的点击操作,所以关于点击方面的事件我还是得好好了解的,所以,我开始去了解关于屏幕点击方面事件的传递机制
在屏幕接收触摸事件的情况中,总体来说是两种情况
1、View接收触摸事件
public boolean dispatchTouchEvent(MotionEvent event)
public boolean onTouchEvent(MotionEvent event)
至于我们常用的onClick事件,则是在onTouchEvent之后的,这个可以后面分析
2、ViewGroup接收触摸事件
public boolean dispatchTouchEvent(MotionEvent event)
public boolean onTouchEvent(MotionEvent event)
public boolean onInterceptTouchEvent(MotionEvent event)
那么,了解到有哪些方法之后就可以对方法进行了解
第一步、我们需要知道这些方法的作用以及参数作用
/**
* 方法用于事件的分发,Android中所有的事件都必须经过这个方法的分发,然后决定是自身消费当前事件还是继续往下分发给子控件处理。
* @param MotionEvent MotionEvent继承于InputEvent,用于标记各种动作事件。
* 按下(ACTION_DOWN)
* 移动(ACTION_MOVE)
* 抬起(ACTION_UP)
* @return boolean 返回true表示不继续分发,事件没有被消费。返回false则继续往下分发,
* 如果是ViewGroup则分发给onInterceptTouchEvent进行判断是否拦截该事件。
*/
public boolean dispatchTouchEvent(MotionEvent event)
/**
* 方法用于事件的处理
* @param MotionEvent MotionEvent继承于InputEvent,用于标记各种动作事件。
* 按下(ACTION_DOWN)
* 移动(ACTION_MOVE)
* 抬起(ACTION_UP)
* @return boolean 返回true表示消费处理当前事件,返回false则不处理,交给子控件进行继续分发。
*/
public boolean onTouchEvent(MotionEvent event)
/**
* 是ViewGroup中才有的方法,View中没有,它的作用是负责事件的拦截
* @param MotionEvent MotionEvent继承于InputEvent,用于标记各种动作事件。
* 按下(ACTION_DOWN)
* 移动(ACTION_MOVE)
* 抬起(ACTION_UP)
* @return boolean 返回true的时候表示拦截当前事件,不继续往下分发,交给自身的onTouchEvent进行处理。
* 返回false则不拦截,继续往下传。这是ViewGroup特有的方法,因为ViewGroup中可能 还有子View,而在Android中View中是不能再包含子View的
*/
public boolean onInterceptTouchEvent(MotionEvent ev)
那么在了解了这些方法的作用之后,我们还需要了解这些方法的执行顺序
第二步、方法的执行顺序
其实看了上面方法的注解后就应该能猜到一点调用的顺序了,但是实际我们还是需要在程序中验证的,首先来测试一下View
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i("main", "MainActivity---dispatchTouchEvent---DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.i("main", "MainActivity---dispatchTouchEvent---MOVE");
break;
case MotionEvent.ACTION_UP:
Log.i("main", "MainActivity---dispatchTouchEvent---UP");
break;
default:
break;
}
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i("main", "MainActivity---onTouchEvent---DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.i("main", "MainActivity---onTouchEvent---MOVE");
break;
case MotionEvent.ACTION_UP:
Log.i("main", "MainActivity---onTouchEvent---UP");
break;
default:
break;
}
return super.onTouchEvent(event);
}
}
public class MyView extends View{
/**
* 文本
*/
private String mTitleText;
/**
* 文本的颜色
*/
private int mTitleTextColor;
/**
* 文本的大小
*/
private float mTitleTextSize;
/**
* 绘制时控制文本绘制的范围
*/
private Rect mBound;
private Rect mBound2;
private Paint mPaint;
private Paint mPaint2;
public MyView(Context context) {
this(context,null);
}
public MyView(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initMyViewData(context, attrs);
}
private void initMyViewData(Context context,AttributeSet attrs){
//这里获取到我们在attrs.xml设置的declare-styleable
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.MyView);
//然后通过declare-styleable获取到之前定义在里面的几个属性
mTitleText = array.getString(R.styleable.MyView_titleText);
mTitleTextColor = array.getColor(R.styleable.MyView_titleTextColor, Color.BLACK);
mTitleTextSize = array.getDimension(R.styleable.MyView_titleTextSize, 36);
array.recycle(); //一定要调用,否则这次的设定会对下次的使用造成影响
/**
* 获得绘制文本的宽和高
*/
mPaint = new Paint();
mPaint2 = new Paint();
mPaint.setTextSize(mTitleTextSize);
mPaint2.setTextSize(mTitleTextSize);
// mPaint.setColor(mTitleTextColor);
mBound = new Rect();
mBound2 = new Rect();
//这句话我自己的理解是在mBound的矩形内,适配mTitleText第一个字符到最后一个字符的范围,这里是我第二和第三个参数填的0和字符串长度所以是第一个字符到最后一个字符的宽度范围
mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mBound);
Log.i("main", "结果:" + mBound.left + ",右边:" + mBound.right + ",上面:" + mBound.top + ",下面:" + mBound.bottom);
mPaint2.getTextBounds("我是第二行吗", 0, 6, mBound2);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
super.onLayout(changed, left, top, right, bottom);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
Log.i("main", "开始画画");
mPaint.setColor(Color.YELLOW);
//在画布的x、y、right、bottom范围内画,画笔为mPaint
canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);
//设置画笔颜色
mPaint.setColor(mTitleTextColor);
// canvas.drawText(text, x, y, paint),第一个参数是我们需要绘制的文本,第三个参数是我们的画笔,
// 这两个不用多说,主要是第二和第三个参数的含义,这两个参数在不同的情况下的值还是不一样的,x默认是这个字符串的左边在屏幕的位置,
// 如果设置了paint.setTextAlign(Paint.Align.CENTER);那就是字符的中心,y是指定这个字符baseline在屏幕上的位置
canvas.drawText(mTitleText, 0, mBound2.height(), mPaint);
canvas.drawText("我是第二行吗", getWidth() / 2 - mBound2.width() / 2, getHeight() / 2 + mBound2.height() / 2, mPaint2);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i("main", "MyView---dispatchTouchEvent---DOWN");
System.out.println("MyView---dispatchTouchEvent---DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.i("main", "MyView---dispatchTouchEvent---MOVE");
break;
case MotionEvent.ACTION_UP:
Log.i("main", "MyView---dispatchTouchEvent---UP");
break;
default:
break;
}
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i("main", "MyView---onTouchEvent---DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.i("main", "MyView---onTouchEvent---MOVE");
break;
case MotionEvent.ACTION_UP:
Log.i("main", "MyView---onTouchEvent---UP");
break;
default:
break;
}
return super.onTouchEvent(event);
}
}
我基本就加了一点点击的输出而已,得到的输出为:
可以发现,在接受到点击事件之后,首先是Activity的dispatchTouchEvent接收事件后分发给MyView,MyView接收到之后又返回给MainActivity处理,但是对于UP事件,MyView却始终没有接收到
然后,我再MyView的onTouchEvent事件中固定写了返回true之后
可见,当MyView的onTouchEvent中返回了true,表示了自己消化点击事件后,则MyView就可以接收到接下来的MOVE以及UP事件了,但是,MainActivity就接受不到了
说明:
1、在onTouchEvent事件中,上下层中,都可以接受到DOWN事件,但是如果某一层消化了点击事件,另一层就接收不到接下来的点击事件了,可以推断中间有一个对于下层是否接受事件有一个中间存储的变量
2、在dispatchTouchEvent事件中,上层总是可以接收到所有的点击事件,而下层能否接受到所有的点击事件取决于下层是否消化了onTouchEvent事件(返回true)
这样的话,在我们代码中涉及到上下层都需要处理的点击事件时就比较明了了
那么到这里是对onTouchEvent的接收影响比较了解,还有dispatchTouchEvent事件,从它的注释来理解的话,是掌控事件的对子控件的分发,还是比较好理解的,这个也可以实验
1、MainActivity dispatchTouchEvent retrun true
MyView dispatchTouchEvent return false;
可以看到当MainActivity 的dispatchTouchEvent 返回true之后其后的所有点击事件都被截断了
2、MainActivity dispatchTouchEvent retrun false
MyView dispatchTouchEvent return true;
可以看到,MyView的dispatchTouchEvent 返回true之后对于MainActivity的dispatchTouchEvent 是没有影响的
这一点是比较好理解的,但是还可以发现,这样返回之后,MainActivity的onTouchEvent也接受不到点击事件了,那么在这里我们可以发现一点
dispatchTouchEvent不仅会影响自身以及下层View的onTouchEvent事件,还会影响上层的onTouchEvent事件
在这里也可以推论出一点,onTouchEvent的调用是从下层往上层的调用,dispatchTouchEvent可以打断这个过程
那么到这里就是View在Activity中的处理,并且对于dispatchTouchEvent 以及onTouchEvent的处理方式可以说是比较了解了,那么还有的就是关于ViewGroup的点击事件的处理了
在ViewGroup的点击实验中,我布局文件如下修改了下
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:myview="http://schemas.android.com/apk/res/com.example.testview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" />
<com.example.testview.MyViewGroup
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<com.example.testview.MyView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
myview:titleText="我是文字内容"
myview:titleTextColor="#ff0000"
myview:titleTextSize="40sp"
/>
</com.example.testview.MyViewGroup>
</LinearLayout>
就是在原本的MyView外面包了一层,然后在没有指定那一层确定返回true还是false的情况下点击后产生如下效果
那么,ViewGroup的调用顺序可以看到,onInterceptTouchEvent的调用是在dispatchTouchEvent之后的,并且onTouchEvent的执行顺序也是如我之前得出的结论,从最底层开始往上面调用
而我们这次的目的主要是测试onInterceptTouchEvent的用途,按照注释来看的话,主要是截断其内部子View的点击事件的接收,那么就来测试一下
我修改了onInterceptTouchEvent的返回值固定为true之后
可以看到,MyView所有点击事件都没有接收到,即使是dispatchTouchEvent也没有调用到,然而MainActivity的事件是都能接收到的
那么这一步可以看出,完全和注释一模一样,那么结合之前的测试,可以看出onInterceptTouchEvent的确有着其独特的作用
一、onInterceptTouchEvent与dispatchTouchEvent 比较
1、onInterceptTouchEvent与dispatchTouchEvent 的区别是它不会影响自身以及上层的onTouchEvent事件的执行
2、dispatchTouchEvent 比onInterceptTouchEvent多屏蔽了一层onTouchEvent的执行
二、onInterceptTouchEvent与onTouchEvent比较
1、onInterceptTouchEvent不会影响上层以及自身onTouchEvent的调用执行
2、onInterceptTouchEvent只会向下影响,而影响不到上层
这样,onInterceptTouchEvent的特性就比较明显了,到了这里,对于android中触摸事件的传递就有了一个非常清晰的流程了解,这样在处理点击等事件的时候,相信就能很好地处理了