本篇内容来源于android 群英传(徐易生著)
我写到这里,是觉得徐易生讲的确实很好, 另外加入了一些自己的理解,便于自己基础的提高.
另外参考:http://www.gcssloop.com/customview/CustomViewIndex/ 对自定义view讲的不错
如果要绘制一个View , 就需要先取测量它,也就是需要知道它的大小和位置. 这样我们就能在屏幕中滑出来它了.这个过程是在onMeasure()方法中完成的.
一.测量模式
测量view的大小时,需要用到MeasureSpec (测量规范)这个类来指定测量模式 ,一共有3种
EXACTLY (精确模式) , 系统默认值.
如果我们指定控件宽高为 xxdp, xxpx,match_parent(填充父view大小) 这3个中的任意一个那它就是精确模式.
AT_MOST (最大值模式)
这个最大值是啥意思呢? 迷茫很久, 比如父控件的子控件(1个或多个),而子控件的大小又是warp_content ,那么控件的大小就会随着它子控件的内容变化而变化, 此时子控件的尺寸只有不超过父控件允许的最大尺寸即可.
比如父控件指定大小为200 ,那么我们可以这么取值
result = 200;
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);//取出 2者最小值,如果specSize(子控件大小)大于父控件规定的200,就使用200,否则使用specSize
}
UNSPECIFIED
这个属性用的不是太多. --它不指定其大小测量模式,View想多大就多大,通常情况下绘制自定义View才会使用.
二.什么时候使用onMeasure()
首先要说明的一点是, 这个方法不是必须重写的. View类默认的onMeasure()只支持EXACTLY(精确)模式,如果在自定义控件是不重写它,就只能使用EXACTLY模式. 控件可以响应你指定的具体宽高dp或px或match_parent属性.
而如果要想让自定义View支持wrap_content属性,那么就必须重写onMeasure方法来指定warp_content时的大小.
通过MeasureSpec这个类,我们就获取了View的"测量模式"和View想要绘制的大小. 有了这些信息,我们就可以控制View最终显示的大小.
首先来看一下onMeasure()的方法
/**
* 测量View的大小
* 首先这个方法不是必须要重写的.(只有控件使用wrap_content时,才必须重写该方法)
* 在自定义view时, MeasureSpec 这个测量规范类,定义了3中测量规范: exactly, at_most,unspecified
* 如果我们不重写onMeasure() ,系统默认测量规范是并且只能是exactly
* 重写了该方法,就可以使用以上3种模式的任意一个
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//父类onMeasure内部调用了setMeasuredDimension(宽,高) 将最终控件测量的宽高值填进去
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
三.View的绘制
当测量好一个View,就可以绘制他了.绘制需要在 onDraw(Canvas canvas)方法中绘制, 需要用到 Canvas 画布类和Paint 画笔类.
这个方法携带了一个Canvas参数,我们可以创建一个Paint对象就可以在这个画布上面绘制了. 当然如果其他地方需要用到画布, 我们一般会单独创建一个 Canvas canvas=new Canvas(bitmap);
/**
* 创建canvas 画布时,一般都用这个构造方法.而不用无参构造方法.
* 因为这个Bitmap是用来存储Canvas画布上面的像素信息的.
* 这个过程我们称之为装载画布,所以这种方式创建画布后,后面调用的所有canvas.drawXxx();方法都发生在这个bitmap上.
*/
Canvas canvas = new Canvas(bitmap);
四.ViewGroup的测量
ViewGroup ,比如LinearLayout它要去管理它的子View, 其中一个管理项目就是负责 内部子View的显示大小. 当LinearLayout大小为warp_content时,LinearLayout就需要对子View进行遍历, 一遍获取所有子View的大小,从而决定自己的大小. 而在其他模式下则会通过具*定值来设置自身大小.
LinearLayout在测量时通过遍历所有子View,从而调用子View的Measure方法来获取每一个子View测量结果. 当测量完毕后,就要摆放这些子View的位置了, 需要重写onLayout , 遍历调用子view的onlayout 来确定子view位置.
注意: 在自定义ViewGroup时,通常会重写 onLayout来控制其子view显示的位置, 同时如果需要wrap_content属性,还需要重写onMeasure() 来测量子view的大小.
五.ViewGroup的绘制
通常情况下ViewGroup不需要绘制,因为它就是一个容器,本身没什么好绘制的,只有它指定了背景色,才会调用它的onDraw,否则不会调用.但是ViewGroup会使用dispatchDraw() 方法来绘制其子View, 过程和通过遍历所有子View,并调用子View的onDraw()方法来完成绘制是一样的.
六.自定义View
自定义View需要注意的几个回调方法
onFinishInflate() xml 加载后回调
onSizeChanged() 大小改变后回调
onMeasure() 测量控件大小
onLayout() 设置控件位置
onTouchEvent() 控件触摸事件
自定义View并不需要重写以上所有回调,根据需要进行即可.
六. 什么样情况下才使用自定义View?
对现有控件扩展
通过组合来实现新控件
重写View来实现全新控件
6.1 对现有控件扩展
比如一个TextView,我们要给他绘制几层背景, 比如给他绘制2层背景,然后最上面是文字. 原生的TextView使用onDraw方法用于绘制要显示的文字,也就是 调用 super.onDraw();
所以我们在extends TextView之后,需要重写 onDraw, 然后我们可以在这里 绘制2个 矩形背景,最后要调用super.onDraw用于显示文字.
public class MyTextView extends TextView { private Paint mPaint1, mPaint2; public MyTextView(Context context) {
super(context);
initView();
} public MyTextView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
} public MyTextView(Context context, AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
} private void initView() {
mPaint1 = new Paint();
mPaint1.setColor(getResources().getColor(
android.R.color.holo_blue_light));
mPaint1.setStyle(Paint.Style.FILL);
mPaint2 = new Paint();
mPaint2.setColor(Color.YELLOW);
mPaint2.setStyle(Paint.Style.FILL);
} @Override
protected void onDraw(Canvas canvas) {
// 绘制外层矩形
canvas.drawRect(
0,
0,
getMeasuredWidth(),
getMeasuredHeight(),
mPaint1);
// 绘制内层矩形
canvas.drawRect(
25,
25,
getMeasuredWidth() - 25,
getMeasuredHeight() - 25,
mPaint2);
canvas.save(); //在画布移动,旋转,缩放之前,需要先保存它的状态
// 绘制文字前平移10像素
canvas.translate(10, 0);
// 调用父类完成的方法,即绘制文本
super.onDraw(canvas);
canvas.restore();//取出保存后的状态,他和Canvas.save是对应的.
}
}
6.2创建复合控件
比如创建一个自定义的通用TopBar .背景色 ,标题文字大小,文字背景 ,左右按钮,左右按钮图片背景,左右按钮添加文字等功能.
首先是创建attrs.xml文件
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="TopBar">
<!-- 标题文字内容, 文字大小, 文字颜色 -->
<attr name="title" format="string" />
<attr name="titleTextSize" format="dimension" />
<attr name="titleTextColor" format="color" /> <!-- 左侧文字颜色, 左侧背景或图片,左侧文字内容 -->
<attr name="leftTextColor" format="color" />
<attr name="leftBackground" format="reference|color" />
<attr name="leftText" format="string" />
<attr name="rightTextColor" format="color" />
<attr name="rightBackground" format="reference|color" />
<attr name="rightText" format="string" />
</declare-styleable>
</resources>
由于左右按钮既可以指定 背景色, 也可直接一个 图片资源, 所以用 reference|color
代码中获取xml文件属性方式: TypedArray ta = context.obtainStyledArrtibutes(attrs,R.styleable.TopBar); TopBar就是attrs.xml属性中的 name.
public class TopBar extends RelativeLayout {
// 包含topbar上的元素:左按钮、右按钮、标题
private Button mLeftButton, mRightButton;
private TextView mTitleView; // 布局属性,用来控制组件元素在ViewGroup中的位置
private LayoutParams mLeftParams, mTitlepParams, mRightParams; // 左按钮的属性值,即我们在atts.xml文件中定义的属性
private int mLeftTextColor;
private Drawable mLeftBackground;
private String mLeftText;
// 右按钮的属性值,即我们在atts.xml文件中定义的属性
private int mRightTextColor;
private Drawable mRightBackground;
private String mRightText;
// 标题的属性值,即我们在atts.xml文件中定义的属性
private float mTitleTextSize;
private int mTitleTextColor;
private String mTitle; // 映射传入的接口对象
private topbarClickListener mListener; public TopBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
} public TopBar(Context context) {
super(context);
} public TopBar(Context context, AttributeSet attrs) {
super(context, attrs);
// 设置topbar的背景
setBackgroundColor(0xFFF59563);
// 通过这个方法,将你在atts.xml中定义的declare-styleable
// 的所有属性的值存储到TypedArray中
TypedArray ta = context.obtainStyledAttributes(attrs,
R.styleable.TopBar);
// 从TypedArray中取出对应的值来为要设置的属性赋值
mLeftTextColor = ta.getColor(
R.styleable.TopBar_leftTextColor, 0);
mLeftBackground = ta.getDrawable(
R.styleable.TopBar_leftBackground);
mLeftText = ta.getString(R.styleable.TopBar_leftText); mRightTextColor = ta.getColor(
R.styleable.TopBar_rightTextColor, 0);
mRightBackground = ta.getDrawable(
R.styleable.TopBar_rightBackground);
mRightText = ta.getString(R.styleable.TopBar_rightText); mTitleTextSize = ta.getDimension(
R.styleable.TopBar_titleTextSize, 10);
mTitleTextColor = ta.getColor(
R.styleable.TopBar_titleTextColor, 0);
mTitle = ta.getString(R.styleable.TopBar_title); // 获取完TypedArray的值后,一般要调用
// 回收资源,recyle方法来避免重新创建的时候的错误
ta.recycle(); mLeftButton = new Button(context);
mRightButton = new Button(context);
mTitleView = new TextView(context); // 为创建的组件元素赋值
// 值就来源于我们在引用的xml文件中给对应属性的赋值
mLeftButton.setTextColor(mLeftTextColor);
mLeftButton.setBackground(mLeftBackground);
mLeftButton.setText(mLeftText); mRightButton.setTextColor(mRightTextColor);
mRightButton.setBackground(mRightBackground);
mRightButton.setText(mRightText); mTitleView.setText(mTitle);
mTitleView.setTextColor(mTitleTextColor);
mTitleView.setTextSize(mTitleTextSize);
mTitleView.setGravity(Gravity.CENTER); // 为组件元素设置相应的布局元素
mLeftParams = new LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.MATCH_PARENT);
mLeftParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT, TRUE);
// 添加到ViewGroup
addView(mLeftButton, mLeftParams); mRightParams = new LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.MATCH_PARENT);
mRightParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, TRUE);
addView(mRightButton, mRightParams); mTitlepParams = new LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.MATCH_PARENT);
mTitlepParams.addRule(RelativeLayout.CENTER_IN_PARENT, TRUE);
addView(mTitleView, mTitlepParams); // 按钮的点击事件,不需要具体的实现,
// 只需调用接口的方法,回调的时候,会有具体的实现
mRightButton.setOnClickListener(new OnClickListener() { @Override
public void onClick(View v) {
mListener.rightClick();
}
}); mLeftButton.setOnClickListener(new OnClickListener() { @Override
public void onClick(View v) {
mListener.leftClick();
}
});
} // 暴露一个方法给调用者来注册接口回调
// 通过接口来获得回调者对接口方法的实现
public void setOnTopbarClickListener(topbarClickListener mListener) {
this.mListener = mListener;
} /**
* 设置按钮的显示与否 通过id区分按钮,flag区分是否显示
*
* @param id id
* @param flag 是否显示
*/
public void setButtonVisable(int id, boolean flag) {
if (flag) {
if (id == 0) {
mLeftButton.setVisibility(View.VISIBLE);
} else {
mRightButton.setVisibility(View.VISIBLE);
}
} else {
if (id == 0) {
mLeftButton.setVisibility(View.GONE);
} else {
mRightButton.setVisibility(View.GONE);
}
}
} // 接口对象,实现回调机制,在回调方法中
// 通过映射的接口对象调用接口中的方法
// 而不用去考虑如何实现,具体的实现由调用者去创建
public interface topbarClickListener {
// 左按钮点击事件
void leftClick();
// 右按钮点击事件
void rightClick();
}
}
可以单独放到一个 topbar.xml中, 其他地方使用通过 <include >引入
<com.xys.mytopbar.Topbar xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto"
android:id="@+id/topBar"
android:layout_width="match_parent"
android:layout_height="40dp"
custom:leftBackground="@drawable/blue_button"
custom:leftText="Back"
custom:leftTextColor="#FFFFFF"
custom:rightBackground="@drawable/blue_button"
custom:rightText="More"
custom:rightTextColor="#FFFFFF"
custom:title="自定义标题"
custom:titleTextColor="#123412"
custom:titleTextSize="15sp"> </com.xys.mytopbar.Topbar>
6.3重写View创建全新控件
效果如下:
这样的控件,用原生控件是无法满足的,所以自定义一个View来实现,并重写onDraw,onMeasure等方法,如果需要触发触摸事件,还需要重写onTouchEvent等触控事件来实现交互逻辑. 当然也可以给他设置一个attrs.xml来引入自定义属性,丰富自定义的可定制性.
public class CircleProgressView extends View { private int mMeasureHeigth;
private int mMeasureWidth; private Paint mCirclePaint;
private float mCircleXY;
private float mRadius; private Paint mArcPaint;
private RectF mArcRectF;
private float mSweepAngle;
private float mSweepValue = 66; private Paint mTextPaint;
private String mShowText;
private float mShowTextSize; public CircleProgressView(Context context, AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
} public CircleProgressView(Context context, AttributeSet attrs) {
super(context, attrs);
} public CircleProgressView(Context context) {
super(context);
} @Override
protected void onMeasure(int widthMeasureSpec,
int heightMeasureSpec) {
mMeasureWidth = MeasureSpec.getSize(widthMeasureSpec);
mMeasureHeigth = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(mMeasureWidth, mMeasureHeigth);
initView();
} @Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 绘制圆
//参数: 圆心的x和y轴坐标, 半径, 画笔
canvas.drawCircle(mCircleXY, mCircleXY, mRadius, mCirclePaint);
// 绘制弧线
//参数: 椭圆形边界,起始弧度值,掠角度测量时针,如果为true,将会画成一个契形,画笔
//参数3:起始就是 圆弧占 整个圆周360的比例大小. 在Activity调用时可以指定值当为100是,是一个360度的圆弧
canvas.drawArc(mArcRectF, 270, mSweepAngle, false, mArcPaint);
// 绘制文字
//参数: 文字内容,第一个字符索引,文本长度,文字x,y轴坐标,画笔
canvas.drawText(mShowText, 0, mShowText.length(),
mCircleXY, mCircleXY + (mShowTextSize / 4), mTextPaint);
} private void initView() {
float length = 0;
if (mMeasureHeigth >= mMeasureWidth) {
length = mMeasureWidth;
} else {
length = mMeasureHeigth;
} mCircleXY = length / 2;//设置x,y坐标为 控件宽或者高的一半
mRadius = (float) (length * 0.5 / 2);
mCirclePaint = new Paint();
mCirclePaint.setAntiAlias(true);//抗锯齿
mCirclePaint.setColor(getResources().getColor(
android.R.color.holo_blue_bright));//画笔颜色 //椭圆, 要用float类型
mArcRectF = new RectF(
(float) (length * 0.1),
(float) (length * 0.1),
(float) (length * 0.9),
(float) (length * 0.9));
//圆弧比例尺,即占用圆周360度的比例
mSweepAngle = (mSweepValue / 100f) * 360f;
//圆弧画笔
mArcPaint = new Paint();
mArcPaint.setAntiAlias(true);
mArcPaint.setColor(getResources().getColor(
android.R.color.holo_blue_bright));
mArcPaint.setStrokeWidth((float) (length * 0.1));
mArcPaint.setStyle(Style.STROKE); //文字
mShowText = setShowText();
mShowTextSize = setShowTextSize();
mTextPaint = new Paint();
mTextPaint.setTextSize(mShowTextSize);
mTextPaint.setTextAlign(Paint.Align.CENTER);
} private float setShowTextSize() {
this.invalidate();
return 50;
} private String setShowText() {
this.invalidate();
return "Android Skill";
} public void forceInvalidate() {
this.invalidate();
} public void setSweepValue(float sweepValue) {
if (sweepValue != 0) {
mSweepValue = sweepValue;
} else {
mSweepValue = 25;//当不指定比例值,默认是25
}
this.invalidate();
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"> <com.imooc.systemwidget.CircleProgressView
android:id="@+id/circle"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
动态音频条的实现
public class VolumeView extends View { private int mWidth;
private int mRectWidth;
private int mRectHeight;
private Paint mPaint;
private int mRectCount;//矩形数量
private int offset = 5;//偏移量
private double mRandom;
private LinearGradient mLinearGradient;//定义一个 线性梯度, 用于颜色渐变的 public VolumeView(Context context) {
super(context);
initView();
} public VolumeView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
} public VolumeView(Context context, AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
} private void initView() {
mPaint = new Paint();
mPaint.setColor(Color.BLUE);//画笔 蓝色
mPaint.setStyle(Paint.Style.FILL);//设置实心矩形, Paint.Style.STROKE 是空心的
mRectCount = 12; //一共12个矩形条
} @Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = getWidth();//view的 总宽度
mRectHeight = getHeight();//view的高度
mRectWidth = (int) (mWidth * 0.6 / mRectCount);
//创建一个线性梯度着色器
mLinearGradient = new LinearGradient(
0,//梯度线开始的x坐标
0,//梯度线开始的y轴坐标
mRectWidth,//
mRectHeight,//梯度线末端y坐标
Color.YELLOW,
Color.BLUE,
Shader.TileMode.CLAMP);
mPaint.setShader(mLinearGradient);
} @Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//循环创建12个矩形
for (int i = 0; i < mRectCount; i++) {
mRandom = Math.random();
//高度随机
float currentHeight = (float) (mRectHeight * mRandom);
//画矩形
canvas.drawRect(
(float) (mWidth * 0.4 / 2 + mRectWidth * i + offset),//x坐标加一个偏移量
currentHeight,
(float) (mWidth * 0.4 / 2 + mRectWidth * (i + 1)),
mRectHeight,
mPaint);
}
postInvalidateDelayed(300);//延迟300毫秒重绘一次
}
}
七.事件的拦截机制
了解触摸事件的拦截机制,首先要了解什么是触摸事件? 触摸事件就是捕获触摸屏幕后产生的事件. 点击屏幕通常会产生2个或3个事件.
手指按下 -- 这是事件一
不小心手指滑动了 -- 这是事件二
手指抬起 -- 这是事件三
封装这3种事件的类是 MotionEvent ,如果重写onTouchEvent, 该方法就会携带MotionEvent 参数.
要获取触摸点的坐标, 通过 MotionEvent类的 event.getX() 和 event.getY()获取; 获取点击事件类型 可通过MotionEvent.ACTION_UP,MotionEvent.ACTION_DOWN,MotionEvent.ACTION_MOVE, 获取. 由此可知触摸事件可理解为是:一个动作类型+坐标.
由于View的结构是树形结构, 而他们又可以嵌套,一层层叠起来, 而触摸事件就一个,到底作用在了那个view上呢? 同一事件,子view和父ViewGroup都可能想要处理, 因此就产生了"事件拦截" !
群英传举了一个例子很好说明了这个问题, 经理MyViewGourpA(最外层ViewGroup)-->下发一个任务给组长MyViewGroupB(中间层ViewGroup) --> 安排任务为小兵你MyView
当小兵完成任务-->提交给组长,组长审核通过-->提交给经理,经理审核也通过了. 这个事件就完成了.
MyViewGroupA , MyViewGroupB 和 MyView代码如下
public class MyViewGroupA extends LinearLayout {
public MyViewGroupA(Context context) {
super(context);
}
public MyViewGroupA(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyViewGroupA(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d("xys", "ViewGroupA dispatchTouchEvent" + ev.getAction());
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.d("xys", "ViewGroupA onInterceptTouchEvent" + ev.getAction());
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("xys", "ViewGroupA onTouchEvent" + event.getAction());
return super.onTouchEvent(event);
}
}
public class MyViewGroupB extends LinearLayout {
public MyViewGroupB(Context context) {
super(context);
}
public MyViewGroupB(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyViewGroupB(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d("xys", "ViewGroupB dispatchTouchEvent" + ev.getAction());
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.d("xys", "ViewGroupB onInterceptTouchEvent" + ev.getAction());
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("xys", "ViewGroupB onTouchEvent" + event.getAction());
return super.onTouchEvent(event);
}
}
public class MyView extends View {
public MyView(Context context) {
super(context);
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyView(Context context, AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("xys", "View onTouchEvent" + event.getAction());
return super.onTouchEvent(event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.d("xys", "View dispatchTouchEvent" + event.getAction());
return super.dispatchTouchEvent(event);
}
}
可以看出 ,MyView并没有onInterceptTouchEvent() 事件拦截这个方法, 因为他是继承View的,所以没有该方法. 运行截图如下:
蓝色是MyViewGroupA,红色是MyViewGroupB ,黑色是 MyView
当我们点击 黑色部分, 得到Log如下:
由此可分析出, 事件的 传递顺序 和 事件的 处理顺序
事件的传递顺序是: MyViewGroupA --> MyViewGroupB --> MyView . 事件传递先执行dispatchTouchEvent(事件分发),再执行onInterceptTouchEvent(事件拦截).
事件的处理顺序是: MyView --> MyViewGroupB --> MyViewGroupA . 事件的处理都是执行 onTouchEvent.
注意:
事件的传递返回值 默认都是 false, 也就是一路放行(不拦截) ,如果修改返回true,就是拦截住该事件了,不再向下传递了.
事件的处理返回值 默认都是 false, 需要上级审核处理, True表示不需要上级审核处理.
为方便理解这个传递和处理过程, 这里暂时去掉dispatchTouchEvent方法,如下图所示:
如果此时经理MyViewGroupA发现 这个事情太简单,自己就能完成, 所以A 的 onInterceptTouchEvent 直接返回 true, 把该事件拦截了.就不往下传递了 .结果Log是:
同理如果A不拦截,B拦截了,得到Log如下:
上面2种情况对应的事件传递和事件处理关系图分别是:
和
上面都是 事件的 分发和 拦截的机制, 下面看下事件的处理. 对于底层的 MyView, 最开始当处理完任务后,会向上级报告,需要上级审核,所以事件处理返回false, 有一天 底层小兵不想干了,罢工了. 那么这个任务就没人做了,也不需要向上报告了,所以直接在MyView的onTouchEvent中返回True, 再次看Log:
如果 MyView把任务提交给了组长, 但是组长审核不通过, 决定不向经理提交了,组长事件处理返回了True, Log如下: