本篇内容来源于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如下:

android View的测量和绘制的更多相关文章

  1. Android view的测量及绘制

    讲真,自我感觉,我的水平真的是渣的一匹,好多东西都只停留在知道和会用的阶段,也想去研究原理和底层的实现,可是一看到代码就懵逼了,然后就看不下去了, 说自己不着急都是骗人的,我自己都不信,前两天买了本& ...

  2. Android View 如何测量

    对于Android View的测量,我们一句话总结为:"给我位置和大小,我就知道您长到那里". 为了让大家更好的理解这个结论,我这里先讲一个日常生活中的小故事:不知道大家玩过&qu ...

  3. 【转载】快速理解android View的测量onMeasure()与MeasureSpec

    笔者之前有一篇文章已经使用onMeasure()解决了listview与scollview的显示冲突问题,博客地址如下: onMeasure简单方法 完美解决ListView与ScollView冲突问 ...

  4. 自定义View_1_关于View,ViewGroup的测量和绘制流程

    自定义View(1) ------ 关于View,ViewGroup的测量和绘制流程 在Android当中,自定义控件属于比较高级的知识体系,今天我们就一起研究研究关于自定义View的那点事,看看它到 ...

  5. Android View框架总结(三)View工作原理

    转载请注明出处:http://blog.csdn.net/hejjunlin/article/details/52180375 测量/布局/绘制顺序 如何引起View的测量/布局/绘制? Perfor ...

  6. 【原创】Android View框架总结(三)View工作原理

    测量/布局/绘制顺序 如何引起View的测量/布局/绘制? PerformTraversales() ViewRoot View工作基本流程  MeasureSpec SpecMode Measure ...

  7. [译]Android view 测量布局和绘制的流程

    原文链接 创造优秀的用户体验是我们开发者的主要目标之一.为此, 我们首先要了解系统是如何工作的, 这样我们才可以更好的与系统配合, 从它的优点中获益, 规避它的缺陷. 之前关于Android渲染过程的 ...

  8. Android View的绘制流程

    写得太好了,本来还想自己写的,奈何肚里墨水有限,直接转吧.正所谓前人种树,后人乘凉.. View的绘制和事件处理是两个重要的主题,上一篇<图解 Android事件分发机制>已经把事件的分发 ...

  9. 简单研究Android View绘制三 布局过程

    2015-07-28 17:29:19 这一篇主要看看布局过程 一.布局过程肯定要不可避免的涉及到layout()和onLayout()方法,这两个方法都是定义在View.java中,源码如下: /* ...

随机推荐

  1. 第一次亲密接触——二狗子初识 CDN

    二狗子是国内知名XXX大学的在校学生,作为一名编程爱好者,他利用业余时间搭建了一个网站,把平时的学习心得和技术分享全都 PO 在自己的网站上.渐渐地,二狗子的网站因为文章质量高,技术分享全面,受到了很 ...

  2. Postman系列二:Postman中get接口实战讲解(接口测试介绍,接口测试流程,头域操作)

    一:接口测试介绍 接口测试:就是针对软件对外提供服务的接口输入输出进行测试,以及接口间相互逻辑的测试,验证接口功能和接口描述文档的一致性. 接口测试好处:接口测试通常能对系统测试的更为彻底,更高的保障 ...

  3. java学习笔记(中级篇)—JDK动态代理

    一.什么是代理模式 相信大家都知道代理商这个概念,在商业中,代理商无处不在.假设你要去买东西,你不可能去找真正的厂家去买,也不可能直接跟厂家提出需求,代理商就是这中间的一桥梁,连接买家和厂商.你要买或 ...

  4. Mac 查找粘贴板记录

    0x00 大落 一件蛮坑爹的事情,复制了找了好久的内容合集,在回别人的信息的时候又进行了复制其他内容的操作,结果吾覆盖了的上一次复制的内容-- 于是开始找找 macOS 有没有粘贴板记录的东西,然后在 ...

  5. c#Winform自定义控件-目录

    前提 入行已经7,8年了,一直想做一套漂亮点的自定义控件,于是就有了本系列文章. 开源地址:https://gitee.com/kwwwvagaa/net_winform_custom_control ...

  6. 苹果电脑基本设置+Linux 命令+Android 实战集锦

    本文微信公众号「AndroidTraveler」首发. 背景 大多数应届毕业生在大学期间使用的比较多的是 windows 电脑,因此初入职场如果拿到一台苹果电脑,可能一时间不能够很快的上手.基于此,这 ...

  7. 使用Cmake编译CEF时遇到Error in configuration process,project file may be invalid的解决办法

    今天在用Cmake编译cef框架时,弹出了错误,如图: 可以排查一下几种原因: 1.在64位计算机编译32位程序 可以更换编译环境,或者下载64位版本来解决这个问题. 2.选择的Visual Stud ...

  8. Hive 系列(五)—— Hive 分区表和分桶表

    一.分区表 1.1 概念 Hive 中的表对应为 HDFS 上的指定目录,在查询数据时候,默认会对全表进行扫描,这样时间和性能的消耗都非常大. 分区为 HDFS 上表目录的子目录,数据按照分区存储在子 ...

  9. nginx单机1w并发优化

    目录 ab工具 整体优化思路 具体的优化思路 编写脚本完成并发优化配置 性能统计工具 tips 总结 ab工具 ab -c 10000 -n 200000 http://localhost/index ...

  10. REST 在 Java 中的使用

    REST是一种混合的架构风格,它的由来以及它的架构元素在笔者的前一篇文章<REST 架构风格的由来 & 元素>中已经描述了.本篇主要描述一下J2EE对REST的支持. Java是在 ...