HZWZ

Now the young people come up on the sticky source code, to me such a small melon, so appropriate, so inappropriate.

background

The story begins like this

  • One day, I found myself writing a layout without
  • Print as I want
  • With a puzzling beginning


  • One day, two young men did not speak martial virtue

  • Just tell me why this is AppCompatActivity

  • I don’t believe it

  • They made a surprise attack, obviously prepared


  • My careless about the

  • I don’t have a flash

  • Today, I will testify to the truth

  • Mixed yuan door code of the third generation of big disciples, work buff see synonyms at

Familiar with the taste of

Why is that? It’s obviously a normal TextView, why is it a MaterialTextView? Are you kidding me?

ultimately

Working people, working soul, I am mixed yuan door…

Bah, off topic, we are on the right track, today must take your pants.

Enter setContenView() for AppCompatActivity first:

public void setContentView(@LayoutRes int layoutResID) {
    getDelegate().setContentView(layoutResID);
}
Copy the code

Go to appcompatDelegateImpl-setContentView ()

@Override
public void setContentView(int resId) {
  	/ / 1
    ensureSubDecor();
    ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
    contentParent.removeAllViews();
    LayoutInflater.from(mContext).inflate(resId, contentParent);
    mAppCompatWindowCallback.getWrapped().onContentChanged();
}
Copy the code

The inflate behind the root is the contentParent. This is the ensureSubDecor(). Look at what’s going on inside the inflate.


Enter ensureSubDecor ()

private void ensureSubDecor(a) {
    if(! mSubDecorInstalled) { mSubDecor = createSubDecor(); . Omit a chunk of code}}Copy the code

MSubDecorInstalled indicates whether Windows already has a DecorView installed. If so, ignore it.

Why would I know that? Translation,ohhhh.

// true if we have installed a window sub-decor layout.
private boolean mSubDecorInstalled;
Copy the code

Enter createSubDecor ()

    private ViewGroup createSubDecor(a) {
      	/ / 1
        TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
      	if(xxx)xxx
      	/ / 2
        ensureWindow();
      	/ / 3mWindow.getDecorView(); .// Ignore a piece of code
        ViewGroup subDecor = null;
      
      	/ / 4
         subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
				/ / 5
        final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
                R.id.action_bar_activity_content);
        final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
      	
      	/ / 6
        if(windowContentView ! =null) {
            // There might be Views already added to the Window's content view so we need to
            // migrate them to our content view
            while (windowContentView.getChildCount() > 0) {
                final View child = windowContentView.getChildAt(0);
                windowContentView.removeViewAt(0);
                contentView.addView(child);
            }
            windowContentView.setId(View.NO_ID);
            contentView.setId(android.R.id.content);
        }

        / / 7
        mWindow.setContentView(subDecor);
        return subDecor;
    }

Copy the code

This method is relatively complex, and understanding it will end this article.

  1. TypedArray is the same as the activity-setContentView source code, which determines the configuration and the final root layout file.

  2. Initialize mWinodws for AppCompatDategateImpl;

    private void ensureWindow(a) {
        if (mWindow == null && mHost instanceofActivity) { attachToWindow(((Activity) mHost).getWindow()); }}Copy the code
  3. GetDecorView (), its internal namely PhoneWindows getDecorView () method

    @Override
    public final @NonNull View getDecorView(a) {
        if (mDecor == null || mForceDecorInstall) {
            installDecor();
        }
        return mDecor;
    }
    Copy the code

    The installDecor() method, mentioned in the last blog post, is used to ensure that our DecorView and mContentParent are initialized.

  4. Here we get a r.layout. abc_screen_simple layout, which we can understand as a root layout specific to AppCompatActivity.

  5. Here we get the content of the ContentFrameLayout and the DecorView. What does that mean? You know when you get to the next step;

  6. Iterate over windowsContent, add all views from it to contentView, and remove the corresponding view from windowsContent. Finally, set the contentView id to R.D.C. Tent (action_bar_activity_content) and set windowsContent ID to null.

  7. Finally, to set the new contentView to Windows, which is to add the contentView to our root container contenParent, we do the following internally.

    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params). mContentParent.removeAllViews(a); mContentParent.addView(view, params); . }Copy the code

    Empty the root layout and add the layout we just passed in, because we changed the ID in step 6, so this new container is our new root ViewGroup.

Stage thinking

The question is, why is the source code copying all the phoneWindows-WindowsContent views into the new contentView?

Because the default DecorView for an Activity loads R.layout.screen_simple, and our root layout is one of those children FrameLayout, when using AppCompatActivity, for compatibility, It has its own theme layout, so when you set it up, put all the child views from the current root container into the new container, and set the id of the new container to R.I.C.tent. Add this container to the root layout (FrameLayout) in our DecorView to achieve compatibility without affecting the original view display.

Does that make sense to you?

Don’t get it? Okay, let me just say it again in the background

As mentioned above, AppCompatActivity has its own specific container layout, so if you design something that directly replaces the default root container for an Activity, that means AppCompatActivity has to write a copy on its own, which is not suitable. So for that reason, the DecorView variable in AppCompatActivity is called mSubDecor, whereas the one in our basic PhoneWindows is called mDecor, Think about why AppCompatActivity defines a separate so-called DecorView and match that to the appCompatDelegateImpl-Createsubdecor () method.

The details are shown in the figure below:

Check the hierarchy to see that appcompatactivities are nested on the existing Activity layout hierarchy, as described above, and can feel like ohhhh, that’s all.

Let’s think about it.

  1. When we areAppCompatActivityIn the callsetContentView(), its internal call isAppCompatDelegateImplthesetContentView(), and finally calledensureSubDecor()To ensure that the DecorView has been successfully initialized.
  2. inensureSubDecor()So the way to do it is to judgeThe child DecorView(Why is itThe child, because it can’t be directly replace the root DecorView, AppCompatActivity just made a compatible, namely inDecorViewAdd a sub-level on top of that to do thatSwitch to the user shielding, for users, in fact, no awareness) is not installed, if not, callcreateSubDecor()To initialize it;
  3. createSubDecor()Internal configuration will be based on the current topic, and finally set the current root container, and the currentWindows-DecorViewAdd all child views from the root container -FrameLayout to the new container and change the id of the new containerR.id.contentAnd thenwindows.setContentView(), the internaladdThe old containerFrameLayouBecomes a unique child container. This new container is the latest root container.
  4. The following method is very simple, our own layout is directaddTo the root containerViewGroupOn the can.

Why are the prints inconsistent

Wait, what is the reason for the prefix to be added to the View print in the first place? What does this have to do with setContentView()?

Yeah, it doesn’t seem to matter, so you say XXX here, sorry, let’s switch to the next topic.

Let’s go back to the starting appcompatactivity-create ()

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    final AppCompatDelegate delegate = getDelegate();
  	/ / the entry
    delegate.installViewFactory();
    delegate.onCreate(savedInstanceState);
    super.onCreate(savedInstanceState);
}
Copy the code

There’s nothing to say, let’s go straight to installViewFactory()

@Override
public void installViewFactory(a) {
    LayoutInflater layoutInflater = LayoutInflater.from(mContext);
  	/ / 1
    if (layoutInflater.getFactory() == null) {
        LayoutInflaterCompat.setFactory2(layoutInflater, this); }... }Copy the code

If we don’t set the LayoutInflater factory, the default factory is set, and then the onCreateView() method is called when the layout is finally created.


So let’s go to the corresponding onCreateView()

@Override
public View createView(View parent, final String name, @NonNull Context context,
        @NonNull AttributeSet attrs) {...return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,IS_PRE_LOLLIPOP,true,VectorEnabledTintResources.shouldBeUsed()
    );
}
Copy the code

There is nothing to say, enter mAppCompatViewInflater. CreateView ()

final View createView(View parent, final String name, @NonNull Context context,
        @NonNull AttributeSet attrs, boolean inheritContext,
        boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {...switch (name) {
        case "TextView":
            view = createTextView(context, attrs);
            verifyNotNull(view, name);
            break; .Copy the code

That’s why we use AppCompatActivity to print a child View that has a prefix display.