Android UIの描画プロセス
前言
Android開発者にとって、カスタムViewをマスターするには、measure、layout、drawを含むペイントプロセスを理解する必要があります.AndroidのViewペイントは上から下へのプロセスです.本稿では、UIのペイントプロセスの研究を通じて、自分の能力の向上を強化します.内容は悪くありません.
Part 1、Activity UIの形成過程を初歩的に理解する
まずActivityにsetContentViewと書いて実行するとビューが表示され、初心者にはどうやって来たのか考えられませんが、進級に直面して理解する必要があります.ok、setContentViewソースコードに入ります.
getWindow()は何を指しているのか、attachメソッドを表示することでgetWindowがWindowクラスPhoneWindowオブジェクトを継承していることがわかります
これにより、PhoneWindow#setContentViewへ
installDecor()メソッドはDecorView,mLayoutInflaterを生成する.inflate()メソッドでは、mContentParentがActivityのレイアウトであることがわかります.次にinstallDecorメソッドに入ります.
このメソッドでは、generateDecor()メソッドを呼び出してDecorViewオブジェクトを生成し、generateLayout(mDecor)はコンテンツレイアウトViewを生成してgenerateDecor()メソッドに入ると、直接newがDecorViewであることがわかりますが、DecorViewはカスタムFrameLayoutです
線形レイアウトを使用せずにFrameLayoutを使用する理由など、個人的な見解を聞くことがあります.Dialogのような他のインタフェースはActivityよりも優先度が高いため、FrameLayoutを定義してからDialogをActivityの上に表示し、中心に位置します.
generateLayoutメソッドへのアクセス
レイアウトによって生成されたViewをDecorViewに追加し、コンテンツViewオブジェクトcontentParentを生成して返します.installDecorメソッドを表示すると、返されたViewにグローバル変数mcontentParentが割り当てられていることがわかります.PhoneWindowのsetContentViewメソッドを表示すると、ActivityのレイアウトがmContentParentに追加されていることがわかります.ここではscreen_を示します.simple.xml
レイアウトからActionBarは怠け者のViewStubを使用しており、コンテンツレイアウトはFrameLayoutであることがわかります
解析によると、1、Windowは抽象クラスであり、描画ウィンドウを提供する共通のAPI 2のセットであり、PhoneWindowはWindowの具体的な継承実装クラスであり、このクラスの内部には、すべてのアプリケーションウィンドウのルートView 3、DecorViewはPhoneWindowの内部クラスであり、FrameLayoutのサブクラスであるDecorViewオブジェクトが含まれている.FrameLayoutに対する機能の修飾であり,すべてのアプリケーションウィンドウのルートView Part 2,measure,layout,drawメソッドの実行フローである.
measure:自分の大きさを測り、ViewGroupなら中のサブコントロールの大きさを同時に測ります
Layout:中のサブコントロール(left、top、right、bottom)を置く
draw:描画Activityの起動プロセスを見てみましょう
この方法は、Window、DecorView、WindowManager(ViewManagerのサブクラス)のオブジェクトを取得し、WindowManagerを呼び出す.addViewメソッドはDecorViewを転送しました
さらにaddViewメソッドはWindowManagerGlobalを呼び出した.addViewメソッド
最後に実行されたのはViewRootImplのaddViewメソッドがDecorViewを転送したことが明らかになった.
このメソッドはViewRootImplクラスのrequestLayoutメソッドを実行し、最後にViewRootImplを実行したperformTraversals()を一歩一歩確認します.
このメソッドは、performMeasure、performLayout、performDrawの順に実行されていることがわかります.
performMeasureを表示する前にgetRootMeasureSpecメソッドを表示します
tips:
1、MeasureSpec:測定規格、単位int 32位、前2位をmode、後30位を値とする.mode:(1)、EXACTLY:精確またはMatch_parent (2)、AT_MOST:親容器の現在の大きさに基づいて、指定したサイズの基準値と組み合わせて、あなたがどれだけのサイズであるべきかを考え、(3)、UNSPECIFIED:最も多い意味を計算する必要があります.現在の状況に基づいて、あなたが制定した寸法基準値と結びつけて、親容器が制限した寸法を超えない前提で、あなたの適切な内容寸法を測定します.使用が少なく、一般的にはScrollView、ListView(大きさが不確定で、大きさが変化している.何度も測定することで本当に幅が決まる.)value:幅の高い値.2、setMeasuredDimension(w,h)を呼び出して自分の最終的な幅を確定する
3、getRootMeasureSpecメソッドを呼び出してMeasureSpecを取得しperformMeasureに渡すが、実際にはDecorViewに伝える
再performMeasureで実行するのはmViewです.Measure()は、上記のActivityの描画フローを分析すると、mViewがDecorViewを表していることがわかり、DecorViewが描画を開始します.
ただしDecorViewにはmeasureメソッドはなく、継承されたFrameLayoutもないので、ここではViewクラスのmeasureメソッドを呼び出します
ここではonMeasureメソッドを呼び出したが,DecorViewクラスにはonMeasureメソッドがあるため,DecorViewのonMeasureメソッドを呼び出したが,ViewGroup実装onMeasureメソッドごとに異なるため,ここではFrameLayoutのonMeasureメソッドを解析する.
次にgetChildMeasureSpecメソッドを見てみましょう
まとめ:
1、大量の測量を経て、最終的に自分の幅と高さを確定し、setMeasureDimension()メソッドを呼び出す必要がある
2、カスタムコントロールを書き直すときは、自分の幅の高さを計算し、measureを経て幅の高さを得る必要があります.幅の高さを得る方法はgetWidthではなくgetMeasureWidthです
3、仕様からmodeとvalueを取得する
modeとsizeを1つの仕様に結合
4、ビューをカスタマイズするときは、自分の幅を測るだけでいいです
5、ViewGroupをカスタマイズし、onMeasureが自分を測定するだけでなく、サブコントロールのサイズも測定する必要がある.
Android開発者にとって、カスタムViewをマスターするには、measure、layout、drawを含むペイントプロセスを理解する必要があります.AndroidのViewペイントは上から下へのプロセスです.本稿では、UIのペイントプロセスの研究を通じて、自分の能力の向上を強化します.内容は悪くありません.
Part 1、Activity UIの形成過程を初歩的に理解する
まずActivityにsetContentViewと書いて実行するとビューが表示され、初心者にはどうやって来たのか考えられませんが、進級に直面して理解する必要があります.ok、setContentViewソースコードに入ります.
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
getWindow()は何を指しているのか、attachメソッドを表示することでgetWindowがWindowクラスPhoneWindowオブジェクトを継承していることがわかります
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mWindow = new PhoneWindow(this, window);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
......
}
これにより、PhoneWindow#setContentViewへ
@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();
}
……
mLayoutInflater.inflate(layoutResID, mContentParent);
}
installDecor()メソッドはDecorView,mLayoutInflaterを生成する.inflate()メソッドでは、mContentParentがActivityのレイアウトであることがわかります.次にinstallDecorメソッドに入ります.
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
}
このメソッドでは、generateDecor()メソッドを呼び出してDecorViewオブジェクトを生成し、generateLayout(mDecor)はコンテンツレイアウトViewを生成してgenerateDecor()メソッドに入ると、直接newがDecorViewであることがわかりますが、DecorViewはカスタムFrameLayoutです
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
線形レイアウトを使用せずにFrameLayoutを使用する理由など、個人的な見解を聞くことがあります.Dialogのような他のインタフェースはActivityよりも優先度が高いため、FrameLayoutを定義してからDialogをActivityの上に表示し、中心に位置します.
generateLayoutメソッドへのアクセス
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
TypedArray a = getWindowStyle();
......//requestFeature setFlag
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
......
mDecor.setWindowBackground(background);
final Drawable frame;
if (mFrameResource != 0) {
frame = getContext().getDrawable(mFrameResource);
} else {
frame = null;
}
mDecor.setWindowFrame(frame);
mDecor.finishChanging();
return contentParent;
}
レイアウトによって生成されたViewをDecorViewに追加し、コンテンツViewオブジェクトcontentParentを生成して返します.installDecorメソッドを表示すると、返されたViewにグローバル変数mcontentParentが割り当てられていることがわかります.PhoneWindowのsetContentViewメソッドを表示すると、ActivityのレイアウトがmContentParentに追加されていることがわかります.ここではscreen_を示します.simple.xml
レイアウトからActionBarは怠け者のViewStubを使用しており、コンテンツレイアウトはFrameLayoutであることがわかります
解析によると、1、Windowは抽象クラスであり、描画ウィンドウを提供する共通のAPI 2のセットであり、PhoneWindowはWindowの具体的な継承実装クラスであり、このクラスの内部には、すべてのアプリケーションウィンドウのルートView 3、DecorViewはPhoneWindowの内部クラスであり、FrameLayoutのサブクラスであるDecorViewオブジェクトが含まれている.FrameLayoutに対する機能の修飾であり,すべてのアプリケーションウィンドウのルートView Part 2,measure,layout,drawメソッドの実行フローである.
measure:自分の大きさを測り、ViewGroupなら中のサブコントロールの大きさを同時に測ります
Layout:中のサブコントロール(left、top、right、bottom)を置く
draw:描画Activityの起動プロセスを見てみましょう
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume) {
......
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (a.mVisibleFromClient) {
a.mWindowAdded = true;
wm.addView(decor, l);
}
......
}
この方法は、Window、DecorView、WindowManager(ViewManagerのサブクラス)のオブジェクトを取得し、WindowManagerを呼び出す.addViewメソッドはDecorViewを転送しました
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
さらにaddViewメソッドはWindowManagerGlobalを呼び出した.addViewメソッド
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
......
ViewRootImpl root;
View panelParentView = null;
......
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
}
}
最後に実行されたのはViewRootImplのaddViewメソッドがDecorViewを転送したことが明らかになった.
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
......
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
requestLayout();
if ((mWindowAttributes.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
mInputChannel = new InputChannel();
}
......
}
}
}
このメソッドはViewRootImplクラスのrequestLayoutメソッドを実行し、最後にViewRootImplを実行したperformTraversals()を一歩一歩確認します.
private void performTraversals() {
......
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
boolean triggerGlobalLayoutListener = didLayout
|| mAttachInfo.mRecomputeGlobalAttributes;
if (didLayout) {
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
}
if (!cancelDraw && !newSurface) {
if (!skipDraw || mReportNextDraw) {
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}
performDraw();
}
} else {
if (viewVisibility == View.VISIBLE) {
// View performTraversals
scheduleTraversals();
} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).endChangingAnimations();
}
mPendingTransitions.clear();
}
}
}
このメソッドは、performMeasure、performLayout、performDrawの順に実行されていることがわかります.
performMeasureを表示する前にgetRootMeasureSpecメソッドを表示します
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;
}
tips:
1、MeasureSpec:測定規格、単位int 32位、前2位をmode、後30位を値とする.mode:(1)、EXACTLY:精確またはMatch_parent (2)、AT_MOST:親容器の現在の大きさに基づいて、指定したサイズの基準値と組み合わせて、あなたがどれだけのサイズであるべきかを考え、(3)、UNSPECIFIED:最も多い意味を計算する必要があります.現在の状況に基づいて、あなたが制定した寸法基準値と結びつけて、親容器が制限した寸法を超えない前提で、あなたの適切な内容寸法を測定します.使用が少なく、一般的にはScrollView、ListView(大きさが不確定で、大きさが変化している.何度も測定することで本当に幅が決まる.)value:幅の高い値.2、setMeasuredDimension(w,h)を呼び出して自分の最終的な幅を確定する
3、getRootMeasureSpecメソッドを呼び出してMeasureSpecを取得しperformMeasureに渡すが、実際にはDecorViewに伝える
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);
}
}
再performMeasureで実行するのはmViewです.Measure()は、上記のActivityの描画フローを分析すると、mViewがDecorViewを表していることがわかり、DecorViewが描画を開始します.
ただしDecorViewにはmeasureメソッドはなく、継承されたFrameLayoutもないので、ここではViewクラスのmeasureメソッドを呼び出します
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
......
int cacheIndex = forceLayout ? -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;
}
}
ここではonMeasureメソッドを呼び出したが,DecorViewクラスにはonMeasureメソッドがあるため,DecorViewのonMeasureメソッドを呼び出したが,ViewGroup実装onMeasureメソッドごとに異なるため,ここではFrameLayoutのonMeasureメソッドを解析する.
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();// FrameLayout
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
mMatchParentChildren.clear();
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
// Child
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
// Child Margin , Child measure , child
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
// View , FrameLayout wrap_Content View
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());
// Child match_parent View measureMatchParentChild
// Match_Parent View FrameLayout
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
// padding
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
//
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
//
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
count = mMatchParentChildren.size();
if (count > 1) {
// Child march_parent
for (int i = 0; i < count; i++) {
final View child = mMatchParentChildren.get(i);
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec;
// march_parent , FrameLayout Child View Mode EXACTLY
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 {// FrameLayout mode Child
childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
lp.leftMargin + lp.rightMargin,
lp.width);
}
//
......
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
次にgetChildMeasureSpecメソッドを見てみましょう
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
// Mode size
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
switch (specMode) {
/*
View,FrameLayout
View , Mode EXACTLY
Match_parent View FrameLayout , Mode EXACTLY
Wrap_content View FrameLayout , Mode AT_Most
( View FrameLayout )*/
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// FrameLayout Mode AT_Most UNSPECIFIED
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
まとめ:
1、大量の測量を経て、最終的に自分の幅と高さを確定し、setMeasureDimension()メソッドを呼び出す必要がある
2、カスタムコントロールを書き直すときは、自分の幅の高さを計算し、measureを経て幅の高さを得る必要があります.幅の高さを得る方法はgetWidthではなくgetMeasureWidthです
3、仕様からmodeとvalueを取得する
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
modeとsizeを1つの仕様に結合
MeasureSpec.makeMeasureSpec(resultSize, resultMode);
4、ビューをカスタマイズするときは、自分の幅を測るだけでいいです
int mode = MeasureSpec.getMode(widthMeasureSpec);
int Size = MeasureSpec.getSize(widthMeasureSpec);
int viewSize = 0;
switch(mode){
case MeasureSpec.EXACTLY:
viewSize = size;// view
break;
case MeasureSpec.AT_MOST:
viewSize = Math.min(size, getContentSize());// view 。
break;
case MeasureSpec.UNSPECIFIED:
viewSize = getContentSize();// , 。
break;
default:
break;
}
//setMeasuredDimension(width, height);
setMeasuredDimension(size);
5、ViewGroupをカスタマイズし、onMeasureが自分を測定するだけでなく、サブコントロールのサイズも測定する必要がある.
//1.
ViewGroup.onMeasure();
// child (MeasureSpec)
ViewGroup.getChildMeasureSpec();
// , View, view
child.measure();
// View ,ViewGroup View
child.getChildMeasuredSize();//child.getMeasuredWidth() child.getMeasuredHeight()
//ViewGroup (Padding ),
ViewGroup.calculateSelfSize();
//2.
ViewGroup.setMeasuredDimension(size);