標籤:
本篇內容來源於android 群英傳(徐易生著)
我寫到這裡,是覺得徐易生講的確實很好, 另外加入了一些自己的理解,便於自己基礎的提高.
如果要繪製一個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
...後續明天再寫
android View的測量和繪製