本篇內容來源於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以下: