android View的测量和绘制

本篇内容来源于android 群英传(徐易生著)css

我写到这里,是以为徐易生讲的确实很好, 另外加入了一些本身的理解,便于本身基础的提升.android

另外参考:http://www.gcssloop.com/customview/CustomViewIndex/  对自定义view讲的不错canvas

 

若是要绘制一个View , 就须要先取测量它,也就是须要知道它的大小和位置. 这样咱们就能在屏幕中滑出来它了.这个过程是在onMeasure()方法中完成的.dom

一.测量模式ide

测量view的大小时,须要用到MeasureSpec (测量规范)这个类来指定测量模式 ,一共有3种oop

EXACTLY (精确模式) , 系统默认值.
若是咱们指定控件宽高为 xxdp, xxpx,match_parent(填充父view大小) 这3个中的任意一个那它就是精确模式. 布局

AT_MOST (最大值模式)
这个最大值是啥意思呢? 迷茫好久, 好比父控件的子控件(1个或多个),而子控件的大小又是warp_content ,那么控件的大小就会随着它子控件的内容变化而变化, 此时子控件的尺寸只有不超过父控件容许的最大尺寸便可.
好比父控件指定大小为200 ,那么咱们能够这么取值post

result = 200;
if (specMode == MeasureSpec.AT_MOST) {
      result = Math.min(result, specSize);//取出 2者最小值,若是specSize(子控件大小)大于父控件规定的200,就使用200,不然使用specSize
}

UNSPECIFIEDthis

这个属性用的不是太多. --它不指定其大小测量模式,View想多大就多大,一般状况下绘制自定义View才会使用.spa

 

二.何时使用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以下:

 

相关文章
相关标签/搜索