Android中View的繪制過程 onMeasure方法簡述 附有自定義View例子
Android中View的繪制過程
當Activity獲得焦點時,它將被要求繪制自己的布局,Android framework將會處理繪制過程,Activity只需提供它的布局的根節點。
繪制過程從布局的根節點開始,從根節點開始測量和繪制整個layout tree。
每一個ViewGroup 負責要求它的每一個孩子被繪制,每一個View負責繪制自己。
因為整個樹是按順序遍歷的,所以父節點會先被繪制,而兄弟節點會按照它們在樹中出現的順序被繪制。
繪制是一個兩遍(two pass)的過程:一個measure pass和一個layout pass。
測量過程(measuring pass)是在measure(int, int)中實現的,是從樹的頂端由上到下進行的。
在這個遞歸過程中,每一個View會把自己的dimension specifications傳遞下去。
在measure pass的最后,每一個View都存儲好了自己的measurements,即測量結果。
第二個是布局過程(layout pass),它發生在 layout(int, int, int, int)中,仍然是從上到下進行(top-down)。
在這一遍中,每一個parent都會負責用測量過程中得到的尺寸,把自己的所有孩子放在正確的地方。
尺寸的父子關系處理
當一個View對象的 measure() 方法返回時,它的 getMeasuredWidth()
和 getMeasuredHeight()
值應該被設置好了,並且它的所有子孫的值也應該一起被設置好了。
一個View對象的measured width 和measured height的值必須考慮到它的父容器給它的限制。
這樣就保證了在measure pass的最后,所有的parent都接受了它的所有孩子的measurements結果。
注意:一個parent可能會不止一次地對它的孩子調用measure()方法。
比如,第一遍的時候,一個parent可能測量它的每一個孩子,並沒有指定尺寸,parent只是為了發現它們想要多大;
如果第一遍之后得知,所有孩子的無限制的尺寸總和太大或者太小,parent會再次對它的孩子調用measure()方法,這時候parent會設定規則,介入這個過程,使用實際的值。
(即,讓孩子自由發展不成,於是家長介入)。
布局屬性說明
LayoutParams是View用來告訴它的父容器它想要怎樣被放置的參數。
最基本的LayoutParams基類僅僅描述了View想要多大,即指明了尺寸屬性。
即View在XML布局時通常需要指明的寬度和高度屬性。
每一個維度都可以指定成下列三種值之一:
1.FILL_PARENT (API Level 8之后重命名為MATCH_PARENT),表示View想要盡量和它的parent一樣大(減去邊距)。
2.WRAP_CONTENT,表示View想要剛好大到可以包含它的內容(包括邊距)。
3.具體的數值。
ViewGroup的不同子類(不同的布局類)有相應的LayoutParams子類,其中會包含更多的布局相關屬性。
onMeasure方法
onMeasure方法是測量view和它的內容,決定measured width和measured height的,這個方法由 measure(int, int)
方法喚起,子類可以覆寫onMeasure來提供更加准確和有效的測量。
有一個約定:在覆寫onMeasure方法的時候,必須調用 setMeasuredDimension(int,int)
來存儲這個View經過測量得到的measured width and height。
如果沒有這么做,將會由measure(int, int)方法拋出一個IllegalStateException。
onMeasure方法的聲明如下:
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec)
其中兩個輸入參數:
widthMeasureSpec
heightMeasureSpec
分別是parent提出的水平和垂直的空間要求。
這兩個要求是按照View.MeasureSpec類來進行編碼的。
參見View.MeasureSpec這個類的說明:這個類包裝了從parent傳遞下來的布局要求,傳遞給這個child。
每一個MeasureSpec代表了對寬度或者高度的一個要求。
每一個MeasureSpec有一個尺寸(size)和一個模式(mode)構成。
MeasureSpecs這個類提供了把一個<size, mode>的元組包裝進一個int型的方法,從而減少對象分配。當然也提供了逆向的解析方法,從int值中解出size和mode。
有三種模式:
UNSPECIFIED
這說明parent沒有對child強加任何限制,child可以是它想要的任何尺寸。
EXACTLY
Parent為child決定了一個絕對尺寸,child將會被賦予這些邊界限制,不管child自己想要多大。
AT_MOST
Child可以是自己任意的大小,但是有個絕對尺寸的上限。
覆寫onMeasure方法的時候,子類有責任確保measured height and width至少為這個View的最小height和width。
(getSuggestedMinimumHeight()
and getSuggestedMinimumWidth()
)。
onLayout
這個方法是在layout pass中被調用的,用於確定View的擺放位置和大小。方法聲明:
protected void onLayout (boolean changed, int left, int top, int right, int bottom)
其中的上下左右參數都是相對於parent的。
如果View含有child,那么onLayout中需要對每一個child進行布局。
自定義View Demo
API Demos中的LabelView類是一個繼承自View的自定義類的例子:
/* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.example.android.apis.view; // Need the following import to get access to the app resources, since this // class is in a sub-package. import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Paint; import android.util.AttributeSet; import android.view.View; import com.example.android.apis.R; /** * Example of how to write a custom subclass of View. LabelView * is used to draw simple text views. Note that it does not handle * styled text or right-to-left writing systems. * */ public class LabelView extends View { private Paint mTextPaint; private String mText; private int mAscent; /** * Constructor. This version is only needed if you will be instantiating * the object manually (not from a layout XML file). * @param context */ public LabelView(Context context) { super(context); initLabelView(); } /** * Construct object, initializing with any attributes we understand from a * layout file. These attributes are defined in * SDK/assets/res/any/classes.xml. * * @see android.view.View#View(android.content.Context, android.util.AttributeSet) */ public LabelView(Context context, AttributeSet attrs) { super(context, attrs); initLabelView(); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LabelView); CharSequence s = a.getString(R.styleable.LabelView_text); if (s != null) { setText(s.toString()); } // Retrieve the color(s) to be used for this view and apply them. // Note, if you only care about supporting a single color, that you // can instead call a.getColor() and pass that to setTextColor(). setTextColor(a.getColor(R.styleable.LabelView_textColor, 0xFF000000)); int textSize = a.getDimensionPixelOffset(R.styleable.LabelView_textSize, 0); if (textSize > 0) { setTextSize(textSize); } a.recycle(); } private final void initLabelView() { mTextPaint = new Paint(); mTextPaint.setAntiAlias(true); // Must manually scale the desired text size to match screen density mTextPaint.setTextSize(16 * getResources().getDisplayMetrics().density); mTextPaint.setColor(0xFF000000); setPadding(3, 3, 3, 3); } /** * Sets the text to display in this label * @param text The text to display. This will be drawn as one line. */ public void setText(String text) { mText = text; requestLayout(); invalidate(); } /** * Sets the text size for this label * @param size Font size */ public void setTextSize(int size) { // This text size has been pre-scaled by the getDimensionPixelOffset method mTextPaint.setTextSize(size); requestLayout(); invalidate(); } /** * Sets the text color for this label. * @param color ARGB value for the text */ public void setTextColor(int color) { mTextPaint.setColor(color); invalidate(); } /** * @see android.view.View#measure(int, int) */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec)); } /** * Determines the width of this view * @param measureSpec A measureSpec packed into an int * @return The width of the view, honoring constraints from measureSpec */ private int measureWidth(int measureSpec) { int result = 0; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); if (specMode == MeasureSpec.EXACTLY) { // We were told how big to be result = specSize; } else { // Measure the text result = (int) mTextPaint.measureText(mText) + getPaddingLeft() + getPaddingRight(); if (specMode == MeasureSpec.AT_MOST) { // Respect AT_MOST value if that was what is called for by measureSpec result = Math.min(result, specSize); } } return result; } /** * Determines the height of this view * @param measureSpec A measureSpec packed into an int * @return The height of the view, honoring constraints from measureSpec */ private int measureHeight(int measureSpec) { int result = 0; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); mAscent = (int) mTextPaint.ascent(); if (specMode == MeasureSpec.EXACTLY) { // We were told how big to be result = specSize; } else { // Measure the text (beware: ascent is a negative number) result = (int) (-mAscent + mTextPaint.descent()) + getPaddingTop() + getPaddingBottom(); if (specMode == MeasureSpec.AT_MOST) { // Respect AT_MOST value if that was what is called for by measureSpec result = Math.min(result, specSize); } } return result; } /** * Render the text * * @see android.view.View#onDraw(android.graphics.Canvas) */ @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawText(mText, getPaddingLeft(), getPaddingTop() - mAscent, mTextPaint); } }
參考資料
API Guides:How Android Draws Views
http://developer.android.com/guide/topics/ui/how-android-draws.html
API Guides:Custom Components
http://developer.android.com/guide/topics/ui/custom-components.html
View onMeasure:
http://developer.android.com/reference/android/view/View.html
ViewGroup.LayoutParams:
http://developer.android.com/reference/android/view/ViewGroup.LayoutParams.html