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.
-
TypedArray is the same as the activity-setContentView source code, which determines the configuration and the final root layout file.
-
Initialize mWinodws for AppCompatDategateImpl;
private void ensureWindow(a) { if (mWindow == null && mHost instanceofActivity) { attachToWindow(((Activity) mHost).getWindow()); }}Copy the code
-
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.
-
Here we get a r.layout. abc_screen_simple layout, which we can understand as a root layout specific to AppCompatActivity.
-
Here we get the content of the ContentFrameLayout and the DecorView. What does that mean? You know when you get to the next step;
-
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.
-
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.
- When we are
AppCompatActivity
In the callsetContentView(), its internal call isAppCompatDelegateImpl
thesetContentView(), and finally calledensureSubDecor()To ensure that the DecorView has been successfully initialized. - inensureSubDecor()So the way to do it is to judge
The child DecorView
(Why is itThe child
, because it can’t be directly replace the root DecorView, AppCompatActivity just made a compatible, namely inDecorView
Add 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; - createSubDecor()Internal configuration will be based on the current topic, and finally set the current root container, and the current
Windows-DecorView
Add 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 containerFrameLayou
Becomes a unique child container. This new container is the latest root container. - The following method is very simple, our own layout is directaddTo the root container
ViewGroup
On 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.