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以下:

 

相關文章
相關標籤/搜索