Android UIの描画プロセス


前言
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);