自定義控制元件View之onMeasure呼叫時機原始碼分析
先上測試程式碼:
MainActivity.java
MyView.javaimport android.app.Activity; import android.os.Bundle; import android.util.Log; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.e("hty", "before setContextView"); setContentView(R.layout.activity_main); Log.e("hty", "after setContextView"); } @Override protected void onResume() { super.onResume(); Log.e("hty", "onResume"); } @Override protected void onDestroy() { super.onDestroy(); Log.e("hty", "onDestroy"); } }
activity_main.xmlimport android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.util.AttributeSet; import android.util.Log; import android.view.View; public class MyView extends View { Paint paint; public MyView(Context context) { this(context, null); } public MyView(Context context, AttributeSet attrs) { super(context, attrs); Log.e("hty","view constructor"); paint = new Paint(); paint.setColor(Color.RED); paint.setTextSize(20); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); Log.e("hty","view onMeasure"); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); Log.e("hty","view onLayout"); } String str = "這裡是測試"; @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); Log.e("hty","view onDraw"); canvas.drawText(str, getWidth()/2-paint.measureText(str)/2,getHeight()/2, paint); } }
正常執行後,檢視對應的Log:<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="com.zqc.mytest.MainActivity"> <com.zqc.mytest.MyView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" /> </RelativeLayout>
從Log輸出可以看出在一個View的繪製過程中,onMeasure是被多次呼叫了的。下面通過原始碼來一步步分析 onMeasure(int widthMeasureSpec, int heightMeasureSpec)函式,尤其是傳過來的兩個引數到底是從哪裡來的。
首先看下MainActivity裡面的setContentView,進入該函式後,其對應的程式碼如下:
Activity.java
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
即呼叫了getWindow()的setContentView方法,檢視getWindow方法,其返還的是類Window的一個例項mWindow,該類是一個抽象類,其具體實現類是PhoneWindow,即呼叫的是PhoneWindow的setContentView方法,檢視相應的程式碼如下:PhoneWindow.java
@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
該方法首先判斷mContentParent是否為空,不為空則呼叫installDecor()方法來初始化mContentParent,檢視具體的程式碼:PhoneWindow.java
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();//這裡生成了mDecor,它是所有應用視窗的根View 。
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);//這裡就生成了mContentParent,這個generateLayout會根據設定的style來佈局顯示的介面
// Set up decor part of UI to ignore fitsSystemWindows if appropriate.
mDecor.makeOptionalFitsSystemWindows();
....
}
}
其中generateDecor方法就直接返回一個DecorView,程式碼如下:PhoneWindow.java
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
而generateLayout(mDecor)方法會根據程式Activity設定的style來佈局顯示的介面,其程式碼如下:PhoneWindow.java
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
TypedArray a = getWindowStyle();//獲取視窗的style
。。。。
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
requestFeature(FEATURE_NO_TITLE);//看到沒,你在xml裡面設定的FEATURE_NO_TITLE,在這裡生效了
} else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
// Don't allow an action bar if there is no title.
requestFeature(FEATURE_ACTION_BAR);
}
。。。。
final Context context = getContext();
。。。。
WindowManager.LayoutParams params = getAttributes();
if (!hasSoftInputMode()) {
params.softInputMode = a.getInt(
R.styleable.Window_windowSoftInputMode,
params.softInputMode);
}
if (a.getBoolean(R.styleable.Window_backgroundDimEnabled,
mIsFloating)) {
/* All dialogs should have the window dimmed */
if ((getForcedWindowFlags()&WindowManager.LayoutParams.FLAG_DIM_BEHIND) == 0) {
params.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND;
}
if (!haveDimAmount()) {
params.dimAmount = a.getFloat(
android.R.styleable.Window_backgroundDimAmount, 0.5f);
}
}
。。。。
int layoutResource;
int features = getLocalFeatures();
。。。。
View in = mLayoutInflater.inflate(layoutResource, null);//這裡把給定的佈局加載出來,然後加到decor中
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);//看到沒這個ID_ANDROID_CONTENT,也就是一個視窗的根佈局
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
。。。。
mDecor.finishChanging();
return contentParent;
}
通過一張圖來分析下一個視窗的佈局具體是怎樣的。
圖上標的很詳細,在最外層是一個FramLayout,其實也就是DecorView,是所有視窗的根佈局,在該根佈局下有一個(0)LinearLayout和一個(1)View,這個(1)View就是狀態列,(0)LinearLayout裡面有個FrameLayout,在裡面的多個View有固定的id,在圖中已經標明,所有在一個Activity通過findViewById獲取的ID_ANDROID_CONTENT就是
(0)FrameLayout->(0)LinearLayout->(0)FrameLayout->(1)FrameLayout對應的View。
要知道onMeasure兩個引數到底是從哪裡來的,還得再找下View是如何繪製的,上一篇文章有分析。View的繪製從ViewRootImpl的performTraversals()函式開始,下面進入該方法中具體分析下。
ViewRootImpl.java
private void performTraversals() {
// cache mView since it is used so much below...
final View host = mView;
。。。。
mIsInTraversal = true;
mWillDrawSoon = true;
boolean windowSizeMayChange = false;
boolean newSurface = false;
boolean surfaceChanged = false;
WindowManager.LayoutParams lp = mWindowAttributes;
int desiredWindowWidth;
int desiredWindowHeight;
final int viewVisibility = getHostVisibility();
boolean viewVisibilityChanged = mViewVisibility != viewVisibility
|| mNewSurfaceNeeded;
WindowManager.LayoutParams params = null;
if (mWindowAttributesChanged) {
mWindowAttributesChanged = false;
surfaceChanged = true;
params = lp;
}
。。。。
Rect frame = mWinFrame;
if (mFirst) {
mFullRedrawNeeded = true;
mLayoutRequested = true;
if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL
|| lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {
// NOTE -- system code, won't try to do compat mode.
Point size = new Point();
mDisplay.getRealSize(size);
desiredWindowWidth = size.x;
desiredWindowHeight = size.y;
} else {
DisplayMetrics packageMetrics =
mView.getContext().getResources().getDisplayMetrics();
desiredWindowWidth = packageMetrics.widthPixels;
desiredWindowHeight = packageMetrics.heightPixels;
}
。。。。
} else {
desiredWindowWidth = frame.width();
desiredWindowHeight = frame.height();
if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
if (DEBUG_ORIENTATION) Log.v(TAG,
"View " + host + " resized to: " + frame);
mFullRedrawNeeded = true;
mLayoutRequested = true;
windowSizeMayChange = true;
}
}
if (viewVisibilityChanged) {
mAttachInfo.mWindowVisibility = viewVisibility;
host.dispatchWindowVisibilityChanged(viewVisibility);
if (viewVisibility != View.VISIBLE || mNewSurfaceNeeded) {
destroyHardwareResources();
}
if (viewVisibility == View.GONE) {
// After making a window gone, we will count it as being
// shown for the first time the next time it gets focus.
mHasHadWindowFocus = false;
}
}
。。。。
boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
if (layoutRequested) {
final Resources res = mView.getContext().getResources();
if (mFirst) {
// make sure touch mode code executes by setting cached value
// to opposite of the added touch mode.
mAttachInfo.mInTouchMode = !mAddedTouchMode;
ensureTouchModeLocally(mAddedTouchMode);
} else {
。。。。
if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
|| lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
windowSizeMayChange = true;
if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL
|| lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {
// NOTE -- system code, won't try to do compat mode.
Point size = new Point();
mDisplay.getRealSize(size);
desiredWindowWidth = size.x;
desiredWindowHeight = size.y;
} else {
DisplayMetrics packageMetrics = res.getDisplayMetrics();
desiredWindowWidth = packageMetrics.widthPixels;
desiredWindowHeight = packageMetrics.heightPixels;
}
}
}
// Ask host how big it wants to be
windowSizeMayChange |= measureHierarchy(host, lp, res,
desiredWindowWidth, desiredWindowHeight);
}
。。。。
if (!mStopped || mReportNextDraw) {
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);//獲取
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
if (DEBUG_LAYOUT) Log.v(TAG, "Ooops, something changed! mWidth="
+ mWidth + " measuredWidth=" + host.getMeasuredWidth()
+ " mHeight=" + mHeight
+ " measuredHeight=" + host.getMeasuredHeight()
+ " coveredInsetsChanged=" + contentInsetsChanged);
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);//看這裡,看這裡
// Implementation of weights from WindowManager.LayoutParams
// We just grow the dimensions as needed and re-measure if
// needs be
int width = host.getMeasuredWidth();
int height = host.getMeasuredHeight();
boolean measureAgain = false;
if (lp.horizontalWeight > 0.0f) {
width += (int) ((mWidth - width) * lp.horizontalWeight);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (lp.verticalWeight > 0.0f) {
height += (int) ((mHeight - height) * lp.verticalWeight);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (measureAgain) {
if (DEBUG_LAYOUT) Log.v(TAG,
"And hey let's measure once more: width=" + width
+ " height=" + height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);//看這裡,看這裡
}
layoutRequested = true;
}
}
}
。。。。
mIsInTraversal = false;
}
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
看performTraversals方法中呼叫的performMeasure的地方,performMeasure即呼叫了View的measure方法,而measure方法會去呼叫onMeasure方法。看下如下兩行程式碼
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);//獲取
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
在這兩行程式碼中獲取了child的寬高,使用的方法是getRootMeasureSpec,其中引數lp.width是傳入的MATCH_PARENT或者WRAP_CONTENT,mWidth是視窗期望的大小,getRootMeasureSpec程式碼如下:ViewRootImpl.java
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
這裡應該很好理解了,其中呼叫了MeasureSpec類中的方法,關於MeasureSpec類網上資料很多,該類中用一個int值的兩部分分別表示Mode和具體的尺寸。其中最高兩位表示
Mode,而最低的30位表示具體的尺寸值,這裡計算完之後就進入了View的measure函式中,程式碼如下:
View.java
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int oWidth = insets.left + insets.right;
int oHeight = insets.top + insets.bottom;
widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth);
heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
}
// Suppress sign extension for the low bytes
long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec) {
// first clears the measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
// flag not set, setMeasuredDimension() was not invoked, we raise
// an exception to warn the developer
if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
throw new IllegalStateException("View with id " + getId() + ": "
+ getClass().getName() + "#onMeasure() did not set the"
+ " measured dimension by calling"
+ " setMeasuredDimension()");
}
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}
該方法是final的,因而不能被繼承,但是裡面提供了onMeasure回撥,這樣子類就可以直接繼承onMeasure函式來實現相應的操作。這個View型別的,但是還有一種是ViewGroup型別,也就是容器型別的控制元件,在具體容器型別的控制元件裡面可以通過重寫onMeasure來實現,比如FrameLayout中的onMeasure函式如下:FrameLayout.java
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
mMatchParentChildren.clear();
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
childState = combineMeasuredStates(childState, child.getMeasuredState());
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
// Account for padding too
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
// Check against our minimum height and width
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
// Check against our foreground's minimum height and width
final Drawable drawable = getForeground();
if (drawable != null) {
maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
}
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
count = mMatchParentChildren.size();
if (count > 1) {
for (int i = 0; i < count; i++) {
final View child = mMatchParentChildren.get(i);
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec;
if (lp.width == LayoutParams.MATCH_PARENT) {
final int width = Math.max(0, getMeasuredWidth()
- getPaddingLeftWithForeground() - getPaddingRightWithForeground()
- lp.leftMargin - lp.rightMargin);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
width, MeasureSpec.EXACTLY);
} else {
childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
lp.leftMargin + lp.rightMargin,
lp.width);
}
final int childHeightMeasureSpec;
if (lp.height == LayoutParams.MATCH_PARENT) {
final int height = Math.max(0, getMeasuredHeight()
- getPaddingTopWithForeground() - getPaddingBottomWithForeground()
- lp.topMargin - lp.bottomMargin);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
height, MeasureSpec.EXACTLY);
} else {
childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
lp.topMargin + lp.bottomMargin,
lp.height);
}
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
大概也就是回撥本容器裡面的子View的measure函式實現尺寸計算。這裡通過方法ViewGroup類中的getChildMeasureSpec()來獲取子類期望自己獲取的寬高大小。其程式碼是ViewGroup.java
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
在重寫onMeasure方法時一定要呼叫setMeasuredDimension,該方法會將mPrivateFlags經過或使得View知道已經經過了measure這個步驟了。程式碼如下:View.java
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;
measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
至此分析結束,所以說一個View的大小是由自己和父類兩者共同決定的。
相關推薦
自定義控制元件View之onMeasure呼叫時機原始碼分析
先上測試程式碼:MainActivity.javaimport android.app.Activity; import android.os.Bundle; import android.util.Log; public class MainActivity extend
自定義控制元件學習之繪製刻度盤
以前面試的時候面試官問過我會不會寫標尺工具,我沒做過呀,然後胡亂的說什麼畫布,ondraw繪製。。然後就沒有然後了--!,現在想想真的有點囧。所以今天我試了下自己畫刻度盤,不是很難,只有方法對了,輕輕鬆鬆。。大神勿噴,這是菜鳥的日常(高手退散退散。。巴拉巴拉能量**>_<**)
自定義控制元件View
先在佈局中寫 <com.luyao.dell.yuan.YurnTableView android:layout_width="match_parent" android:layout_height="match_parent" /> 然後在定義一個cl
解決自定義控制元件View在MainActivity中findviewbyid為空的問題
同事在自定義轉盤的程式碼里加了一個介面回撥,一直崩潰,一直以為是介面的問題 ,後來才發現是view中建構函式的問題 public Lucky(Context context) { this(context,null); } public Lucky(
Android自定義控制元件view(草稿版)
Ⅰ、繼承現有控制元件,對其控制元件的功能進行拓展。(拓展功能) Ⅱ、將現有控制元件進行組合,實現功能更加強大控制元件。(佈局重用) Ⅲ、重寫View實現全新的控制元件(不規則效果控制元件) 本文來討論最難的一種自定義控制元件形式,重寫View來實現全新的控制元件。 1.構
自定義控制元件三部曲之動畫篇(十一)——layoutAnimation與gridLayoutAnimation
前言:人或許天生是懶惰的,明知道的不足,卻不努力彌補。 前幾篇給大家講述瞭如何針對某一個控制元件應用動畫,這篇將給大家講解如何給容器中的控制元件應用統一動畫。即在容器中控制元件出現時,不必為每個控制元件新增進入動畫,可以在容器中為其新增統一的進入和
androidの自定義控制元件View在Activity中使用findByViewId得到結果為null,解決方法。。
androidの自定義控制元件View在Activity中使用findByViewId得到結果為null 1. 大家常常自定義view,,然後在xml 中新增該view 元件。。如果在Activity 中使用findByViewId 方法獲取該view 時候,返回物件總為
自定義控制元件三部曲之繪圖篇(六)——Path之貝賽爾曲線和手勢軌跡、水波紋效果
前言:好想義無反顧地追逐夢想從這篇開始,我將延續androidGraphics系列文章把圖片相關的知識給大家講完,這一篇先稍微進階一下,給大家把《android Graphics(二):路徑及文字》略去的quadTo(二階貝塞爾)函式,給大家補充一下。 本篇最終將以兩個例子給
自定義控制元件三部曲之繪圖篇(二十)——RadialGradient與水波紋按鈕效果
前言:每當感嘆自己的失敗時,那我就問你,如果讓你重新來一次,你會不會成功?如果會,那說明並沒有拼盡全力。 系列文章: 最近博主實在是太忙了,部落格更新實在是太慢了,真是有愧大家。 這篇將是Shader的最後一篇,下部分,我們將講述Canvas變
自定義控制元件三部曲之動畫篇(一)——alpha、scale、translate、rotate、set的xml屬性及用法
前言:這幾天做客戶回訪,感觸很大,使用者只要是留反饋資訊,總是一種恨鐵不成鋼的心態,想用你的app,卻是因為你的技術問題,讓他們不得不放棄,而你一個回訪電話卻讓他們盡釋前嫌,當最後把手機號留給他們以便隨時溝通的時候,總會發來一條條的鼓勵簡訊,讓我不自主的開始內疚。哎,多麼可愛
【Android自定義控制元件】之仿網易星球浮動小球
仿網易星球浮動小球 讀唄開發過程中遇到新需求,類似於網易星球收集黑鑽的介面,考慮到可能也有人會使用,索性封裝成庫,後面好移植使用 先看看需要實現的效果: 需求分析: 資料集合可能是int、double、float等型別 小球位置隨機 沒有資料時
自定義控制元件三部曲之繪圖篇(十三)——Canvas與圖層(一)
前言:猛然知道姥姥79了,我好怕,好想哭系列文章:一、如何獲得一個Canvas物件方法一:自定義view時, 重寫onDraw、dispatchDraw方法(1)、定義 我們先來看一下onDraw、dispatchDraw方法的定義protected void onDraw(
Android自定義控制元件-Path之貝賽爾曲線和手勢軌跡、水波紋效果
從這篇開始,我將延續androidGraphics系列文章把圖片相關的知識給大家講完,這一篇先稍微進階一下,給大家把《android Graphics(二):路徑及文字》略去的quadTo(二階貝塞爾)函式,給大家補充一下。 本篇最終將以兩個例子給大家演示貝塞爾曲線
自定義控制元件三部曲之動畫篇(二)——Interpolator插值器
前言:雖然我不太能欣賞的了帕爾哈提的音樂,但我確實很欣賞他的人生態度,專心做自己,不想名利得失,有一天,你想要的東西都會來。其實我覺得,人生最可怕的就是停止不前,只要一直前行,總有一天會到達人生巔峰。相關文章:一、概述Interpolator屬性是Animation類的一個X
Android自定義控制元件封裝之自定義屬性的實現
在開發中有時候我們需要去自定義一些組合控制元件,而在使用過程中,又想要自己的組合控制元件能有原生控制元件那樣可以在xml中使用屬性控制,那麼我們就需要去自定義一些屬性了 1:首先在values/attrs.xml中進行屬性的定義 <?xml version="1.
winCE 自定義控制元件開發之groupBox
2016-1-11 22:08:18 後面補充了一些對控制元件的修改 百度文庫有一篇非常適合學習的文件:c#自定義控制元件開發 wince裡用不了groupbox控制元件。所以只能自己寫。 思路就是簡單的組合控制元件:panel和label 後來發現直接用一個label就
【UI學習】Android github開源專案,酷炫自定義控制元件(View)彙總
近期整理的比較酷炫並且我們會經常用到的custom view,也有一些不是custom view,但是也是android UI相關的,實現了酷炫UI效果的開源庫,總結的專案最後維護時間一般不會超過6個月,會持續更新,如果覺的不錯,歡迎star。如果描述有誤的話,歡迎大家
Android自定義控制元件熱身之scrollTo和scrollBy詳解
View通過ScrollTo和ScrollBy 方法可以實現滑動。那麼兩者有什麼區別呢?我們先來看一下原始碼 ScrollTo原始碼: public void scrollTo(int x,
自定義控制元件三部曲之動畫篇(十)——聯合動畫的XML實現與使用示例
前言:不畏人生,或許才能方得始終;大膽拼,大膽闖是要有一定資本的,在能力不到的時候,就只有選擇忍氣吞聲! 上篇給大家講了有關AnimatorSet的程式碼實現方法,這篇我們就分別來看看如何利用xml來實現ValueAnimator、ObjectAn
Android 自定義控制元件之繼承view
一.自定義控制元件的型別: 1.繼承view(自繪檢視:view中的內容是我們自己繪製出來的,需要重寫onDraw方法) 2.繼承已有原生控制元件 3.自定義組合控制元件(將系統原生的控制元件組合到一起) 本