UI的绘制流程以及原理

2019-08-05  本文已影响0人  面熟_gson
一.View是如何绘制到窗口屏幕上的
1.源码解析

创建activity的时候我们都会在onCreate里看到setContentView方法,并且传入了我们创建的layout

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

第一步 我们点开setContentView会看到如下源码

 /**
     * Set the activity content from a layout resource.  The resource will be
     * inflated, adding all top-level views to the activity.
     *
     * @param layoutResID Resource ID to be inflated.
     *
     * @see #setContentView(android.view.View)
     * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
     */
    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

我们可以看到 资源文件传入到了getWindow().setContentView(layoutResID);
那么可以继续看看getWindow(),进入getWindow()我们可以看到返回的是一个Window对象:

 /**
     * Retrieve the current {@link android.view.Window} for the activity.
     * This can be used to directly access parts of the Window API that
     * are not available through Activity/Screen.
     *
     * @return Window The current window, or null if the activity is not
     *         visual.
     */
    public Window getWindow() {
        return mWindow;
    }

进入Window中我们发现这个Window是一个抽象类

/**
 * Abstract base class for a top-level window look and behavior policy.  An
 * instance of this class should be used as the top-level view added to the
 * window manager. It provides standard UI policies such as a background, title
 * area, default key processing, etc.
 *
 * <p>The only existing implementation of this abstract class is
 * android.view.PhoneWindow, which you should instantiate when needing a
 * Window.
        大概翻译: 这个抽象类是唯一现有的实现android.view.PhoneWindow,需要时你应该实例化窗口。
 */
public abstract class Window {
    /** Flag for the "options panel" feature.  This is enabled by default. */
    public static final int FEATURE_OPTIONS_PANEL = 0;
    /** Flag for the "no title" feature, turning off the title at the top
     *  of the screen. */
    public static final int FEATURE_NO_TITLE = 1;
    .....此处代码省略
}

( 这个抽象类是唯一现有的实现android.view.PhoneWindow,需要时你应该实例化窗口。)
根据这句话我们可以看出PhoneWindow实现了这个抽象类,所以我们需要进入到PhoneWindow查看setContentView(int layoutResID)方法的具体实现

public class PhoneWindow extends Window implements MenuBuilder.Callback {
    ....此处省略若干代码
        @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();
        }
        mContentParentExplicitlySet = true;
    }
    ....此处省略若干代码
}

我们进入到 setContentView方法中查看后发现,这个方法主要的过程在 installDecor()方法和mLayoutInflater.inflate(layoutResID, mContentParent)。接下来我们主要进入到installDecor() 方法(这个方法我们可以理解为加载DecorView的一个过程)。通过它,我们可以去了解加载Decor的整个过程。

private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);

            .....此处省略若干代码
        }
    }

通过以上源码我们可以发现两个方法

1.generateDecor()源码:

protected DecorView generateDecor(int featureId) {
        // System process doesn't have application context and in that case we need to directly use
        // the context we have. Otherwise we want the application context, so we don't cling to the
        // activity.
        Context context;
        if (mUseDecorContext) {
            Context applicationContext = getContext().getApplicationContext();
            if (applicationContext == null) {
                context = getContext();
            } else {
                context = new DecorContext(applicationContext, getContext());
                if (mTheme != -1) {
                    context.setTheme(mTheme);
                }
            }
        } else {
            context = getContext();
        }
    //这里是重点  我们可以看到返回了一个DecorView对象 然后传入到了generateLayout(mDecor)方法中
        return new DecorView(context, featureId, this, getAttributes());
    }

这里我们可以简单看下DecorView的源码,到底是用来干什么的

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
    ...省略代码
}

我们可以看到DecorView继承自FrameLayout,说明DecorView是一个容器,所以 generateDecor(int featureId)方法就是获取一个DecorView容器

2.generateLayout(mDecor)源码

   protected ViewGroup generateLayout(DecorView decor) {
         // Apply data from current theme.设置系统当前主题

        TypedArray a = getWindowStyle();

        if (false) {
            System.out.println("From style:");
            String s = "Attrs:";
            for (int i = 0; i < R.styleable.Window.length; i++) {
                s = s + " " + Integer.toHexString(R.styleable.Window[i]) + "="
                        + a.getString(i);
            }
            System.out.println(s);
        }

        mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
        int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
                & (~getForcedWindowFlags());
        if (mIsFloating) {
            setLayout(WRAP_CONTENT, WRAP_CONTENT);
            setFlags(0, flagsToUpdate);
        } else {
            setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
        }

        if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
            requestFeature(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);
        }

        if (a.getBoolean(R.styleable.Window_windowActionBarOverlay, false)) {
            requestFeature(FEATURE_ACTION_BAR_OVERLAY);
        }

        if (a.getBoolean(R.styleable.Window_windowActionModeOverlay, false)) {
            requestFeature(FEATURE_ACTION_MODE_OVERLAY);
        }
          ...省略
                // Inflate the window decor.

        int layoutResource;
        int features = getLocalFeatures();
         ...省略部分
              else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
            // If no other features and not embedded, only need a title.
            // If the window is floating, we need a dialog layout
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogTitleDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
                layoutResource = a.getResourceId(
                        R.styleable.Window_windowActionBarFullscreenDecorLayout,
                        R.layout.screen_action_bar);
            } else {
                layoutResource = R.layout.screen_title;
            }
            // System.out.println("Title!");
        } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
            layoutResource = R.layout.screen_simple_overlay_action_mode;
        } else {
            // Embedded, so no decoration is needed.
            layoutResource = R.layout.screen_simple;
            // System.out.println("Simple!");
        }
        mDecor.startChanging();
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }
       
       ...省略部分
            return contentParent;
       
   }

generateLayout方法通过源码我们可以看出他做了如下工作:
1.通过 setFlags(0, flagsToUpdate);和 requestFeature();一系列方法设置了系统主题等配置

2.解析窗口View
根据不同的features获取不同的layoutResource ,然后通过DecorView的onResourcesLoaded方法将layoutResource加载到DecorView中

3.根据layoutResource我们可以通过 ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);获取到对应的contentParent,并且返回

   /**
     * The ID that the main layout in the XML layout file should have.
     */
    public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;

ID_ANDROID_CONTENT就是XML中容器的ID,我们可以进入到 如:R.layout.screen_simple 中查看

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="?attr/actionBarTheme" />
    <FrameLayout
         android:id="@android:id/content"//这里就是我们需要的ID
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:foregroundInsidePadding="false"
         android:foregroundGravity="fill_horizontal|top"
         android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

当我们返回contentParent后,generateLayout方法也就执行完了,installDecor执行的整个过程其实就是在获取DecorView,然后再将layoutResource加载到DecorView中,最终我们又通过com.android.internal.R.id.content这个id获取到contentParent,根据layout代码我们可以看出contentParent实际上就是一个FrameLayout容器

在instalDecor中执行完generateDecor和generateLayout方法后我们获取到contentParent我们回到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();//执行完 获取到contentParent
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
        //通过此方法将我们的layoutResID绑定到mContentParent上
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

总结:

1.在PhoneWindow中我们会创建一个顶层布局容器DecorView(是继承自FrameLayout的)
2.创建完DecorView后,我们会将系统布局加载到DecorView中
3.获取到基础布局中基础容器(R.id.content),这个容器实际上是一个FrameLayout
4.最终将我们通过setContentView传递过来的资源ID绑定到基础容器中

图解:


_20190805180030.png

暂时写到这里后续补充..........

上一篇 下一篇

猜你喜欢

热点阅读