The original link: blog.csdn.net/allisonchen…

The AppCompat framework has been around since the Support era to enable new features to run on older versions of Android. But as AndroidX took over, AppCompat’s related classes migrated to the AndroidX library.

Any Android developer should be familiar with building projects on Android Studio that default to AppCompatActivity as a base class for an Activity. Arguably, this class is the most important class in the entire AppCompat framework, and is the starting point for our study of AppCompat today.


AppCompatActivity

AppCompatActivity indirectly inherits from activities, as well as other Activity feature classes in between, allowing activities running on lower versions to have new features such as toolbars and dark themes.

classDiagram
Activity <|-- androidx_core_app_ComponentActivity
androidx_core_app_ComponentActivity <| -- androidx_activity_ComponentActivity
androidx_activity_ComponentActivity <|-- FragmentActivity
FragmentActivity <|-- AppCompatActivity

Parent Class Descriptioin
FragmentActivity Using FragmentController class fragments of AndroidX new component provides support, such as commonly used getSupportFragmentManager provides us () API.
androidx.activity.ComponentActivity The ViewModel interface is implemented and works with the Lifecycle framework to support the ViewModel framework.
androidx.core.app.ComponentActivity Lifecycle interface is implemented and supports the Lifecycle framework through ReportFragment.

Let’s start by getting a feel for appcompatActivities and how activities can appear on the UI.

It doesn’t look very different from the comparison, but it does look a little different from the tree of the UI. For example, AppCompatActivity has a LinearLayout and ViewStub control on top of the content area of the compatactivity, or AppCompatTextView instead of TextView.

So how are these differences achieved and what are the implications?

To talk about compatactivity implementations, you have to mention the AppCompatDelegate class, which is the main driver for compatactivity, which hosts almost any implementation work for AppCompatActivity. For example, appcompatActivities copy the logic of setContentView() and ask a compatdelegate to implement its particular UI structure.

AppCompatDelegate

SetContentView () is the first job of the butler, which is divided into several small tasks as follows.

  1. EnsureSubDecor (): Make sure the unique UI structure for the ActionBar is created

  2. RemoveAllViews () : Make sure all children of the ContentView are removed clean

  3. Inflate () : Parses the content layout of the frame and adds it to the ContentView

The first step ensureSubDecor() has a lot of content, which is divided into several sub-tasks, including calling createSubDecor() to create actionbar-specific layout. SetWindowTitle () reflects the Activity title to the ToolBar and applyFixedSizeWindow() to resize the DecorView.

The core content is the createSubDecor() subtask. It needs to ensure that the ActionBar’s specific layout is created and associated with the Window’s DecorView.

  1. EnsureWindow () : Gets the Window reference to which the Activity belongs and adds window-related callbacks

  2. GetDecorView () : Tell Window to create a DecorView, and mention PhoneWindow’s generateLayout(), which creates a different layout structure depending on the theme. For example AppCompatActivity will parse screen_simple.xml to get the basic structure of the DecorView, which includes the root LinearLayout, The ViewStub that maps the ActionMode layout and the ID that hosts the App content is ContentView

  3. Inflate () : Gets the layout of the ActionBar, mainly in the abc_screen_tools.xml and abc_screen_content_include.xml files

  4. RemoveViewAt () and addView() : Migrate the children of ContentView to the ActionBar layout. To do this, remove all of its children and add them to the ViewGroup action_bar_activity_content in the ActionBar layout and empty the ContentView ID. Also set the id of the target ViewGroup to Content. That means it will be the parent layout of the AppCompatActivity screen hosting the content area

The public API

In addition to the difference between setContentView() in building a layout structure, AppCompatActivity provides several apis that an Activity doesn’t have for developers to use.

  • GetSupportActionBar () : To get appCompat-specific ActionBar components for developers to customize the ActionBar

  • GetDelegate () : Get an instance of an AppCompatDelegate for an internal implementation of AppCompatActivity (which will actually get an instance of an implementation class AppCompatDelegateImpl via static create())

  • GetDrawerToggleDelegate () : obtain the drawer navigation layout DrawerLayout proxy class ActionBarDrawableToggleImpl instance, for the and ActionBar for UI interaction

  • OnNightModeChanged () : Instead of being notified of subject changes only after external configuration changes with uiMode configured, this API can be called back after adaptation modes of dark themes (e.g. follow system Settings, follow battery Settings, etc.) have changed, which can be used for additional processing

Use caution

The comments on AppCompatActivity have the following description, and the Theme.AppCompat Theme is recommended.

You can add an ActionBar to your activity when running on API level 7 or higher by extending this class for your activity and setting the activity theme to Theme.AppCompat or a similar theme.

After verification, if we use another theme, we will get the following crash.

You need to use a Theme.AppCompat theme (or descendant) with this activity.
Copy the code

The principle is that the main AppCompatDelegate is intentionally ensuring that the Activity is compattheme when creating an ActionBar layout. This exception is thrown especially if AppCompat defines no windowActionBar properties.

// AppCompatThemeImpl.java
	private ViewGroup createSubDecor(a) {
        TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);

        if(! a.hasValue(R.styleable.AppCompatTheme_windowActionBar)) { a.recycle();throw new IllegalStateException(
                    "You need to use a Theme.AppCompat theme (or descendant) with this activity."); }... }Copy the code

As for why an exception should be used to ensure the adoption of AppCompatTheme, since subsequent processing is closely related to AppCompatTheme, many of the latter processing will fail without it.


AppCompatDialog

In addition to using extremely high appcompatactivities, AppCompatDialog has a decent exposure. The implementation principle is the same as the AppCompatActivity project, which relies on a major compatdelegate. Again, to expand on Dialog


AppCompatTheme

AppCompatTheme is divided into two main topics:

  • Theme.AppCompat : AppCompat inherits the base.v7.theme.AppCompat Theme to specify AppCompatViewInflater as a parsing class for widgets and other classes and sets basic properties defined by AppCompatTheme, whose top Theme is still the old theme.holo

  • Theme.AppCompat.DayNight: Can automatically adapt dark themes. It inherits from the Base. V7. Theme. AppCompat. Light, with the Theme. The AppCompat difference lies mainly in its default USES the Theme of Light department, such as the use primary_material_light colorPrimary, Theme.AppCompat uses primary_material_dark

How does the App automatically adapt to The Dark mode if it adopts this theme?


Dark Theme mode

AppCompatActivity can bind BaseContext to anything on an AppCompatDelegate’s applyDayNight() to parse the dark theme mode set by the App and do some configuration work.

For example, the commonly used follow power saving mode refers to that the power saving mode of the device will automatically enter the dark theme to reduce power consumption. Instead, close and return to the day theme.

The implementation is AppCompatDelegate, which registers broadcasts to listen for power-saving mode changes (ACTION_POWER_SAVE_MODE_CHANGED). When power saving mode is on/off, the broadcast receiver automatically calls updateForNightMode() to update the corresponding theme.

    private boolean applyDayNight(final boolean allowRecreation) {...@NightMode final int nightMode = calculateNightMode();
        @ApplyableNightMode final int modeToApply = mapNightMode(nightMode);
        final booleanapplied = updateForNightMode(modeToApply, allowRecreation); .if (nightMode == MODE_NIGHT_AUTO_BATTERY) {
            // Register to listen on radio receivers in power-saving modegetAutoBatteryNightModeManager().setup(); }... }abstract class AutoNightModeManager {...void setup(a) {...if (mReceiver == null) {
                mReceiver = new BroadcastReceiver() {
                    @Override
                    public void onReceive(Context context, Intent intent) {
                        // Callback after power saving mode changeonChange(); }}; } mContext.registerReceiver(mReceiver, filter); }... }private class AutoBatteryNightModeManager extends AutoNightModeManager {...@Override
        public void onChange(a) {
            // After the power saving mode changes, the theme switch method is used to update the theme
            applyDayNight();
        }

        @Override
        IntentFilter createIntentFilterForBroadcastReceiver(a) {
            if (Build.VERSION.SDK_INT >= 21) {
                IntentFilter filter = new IntentFilter();
                filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
                return filter;
            }
            return null; }}Copy the code

The handling of the update topic is the following key code.

    private boolean updateForNightMode(final int mode, final boolean allowRecreation) {...// If the Activity's BaseContext has not been initialized, the new theme value is directly adapted
        if((sAlwaysOverrideConfiguration || newNightMode ! = applicationNightMode) && ! mBaseContextAttached ...) {...try{... ((android.view.ContextThemeWrapper) mHost).applyOverrideConfiguration(conf); handled =true; . }}final int currentNightMode = mContext.getResources().getConfiguration().uiMode
                & Configuration.UI_MODE_NIGHT_MASK;

        // If the Activity's BaseContext is already created,
        // If the App does not state that it wants to handle dark theme changes, it will redraw the Activity
        if(! handled ...) { ActivityCompat.recreate((Activity) mHost); handled =true;
        }

        // If the App declares to handle dark theme changes,
        // Then update the new theme value to the uiMode property of Configuration
        // Call Activity#onConfigurationChanged() and wait for the App to take care of itself
        if(! handled && currentNightMode ! = newNightMode) { ... updateResourcesConfigurationForNightMode(newNightMode, activityHandlingUiMode); handled =true;
        }

        // Finally check if you want to notify the App that the dark theme mode has changed
        // (Note that this refers to the App setting dark theme switch strategy has been changed,
        // For example, change from following system Settings to fixed dark mode etc.)
        if (handled && mHost instanceofAppCompatActivity) { ((AppCompatActivity) mHost).onNightModeChanged(mode); }... }Copy the code

Controls that we normally use in the layout of AppCompatActivity will end up with a class name with the prefix of AppCompat. You declare a TextView control and you end up with an instance of the AppCompatTextView class. How is it done? Why is it done? This can’t be done without AppCompatViewInflater working silently.


AppCompatViewInflater


The core function is to switch the controls in the layout to the AppCompat version. In the stage where LayoutInflater is called to parse the App layout, the major Compatdelegate will call AppCompatViewInflater to replace the widgets in the layout one by one.

    final View createView(View parent, final String name, @NonNull Context context...) {...switch (name) {
            case "TextView":
                view = createTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "ImageView":
                view = createImageView(context, attrs);
                verifyNotNull(view, name);
                break; . }...return view;
    }
	
	protected AppCompatTextView createTextView(Context context, AttributeSet attrs) {
        return new AppCompatTextView(context, attrs);
    }
Copy the code

In addition to the AppCompatTextView mentioned above, the Widget directory of AppCompat has many controls that are extended to accommodate new features. Find out with AppCompatTextView and another popular AppCompatImageView.


AppCompatTextView

It can be seen from the code comments that the control has added Dynamic Tint and Auto Size on the basis of TextView.

Look first what effect these two characteristics are in general.

You can see that the second TextView colors the background a darker green and colors the icon white, making the icon and text inside it look clearer than the first TextView. This is achieved through the backgroundTint and drawableTint properties provided by AppCompatTextView. The ability to dynamically color backgrounds and ICONS is called the Dynamic Tint feature.

In addition, you can see that the text content of the bottom TextView is spread all over the screen without ellipsis at the end, while the font size of the top TextView is larger and indicated by ellipsis at the end. The effect of automatic font sizing is also dependent on the properties provided by AppCompatTextView. This is the Auto Size feature.

Dynamic Tint

Mainly rely on AppCompatBackgroundHelper and AppCompatDrawableManager implementation, including reflect the static configuration and dynamic modification of Tint properties.

The main experience of these steps:

  1. loadFromAttributes()The core process of resolving Tint properties configured in the layout is to be able to resolve the set Tint resources intoColorStateListInstance.
// ColorStateListInflaterCompat.java
    private static ColorStateList inflate(Resources r, XmlPullParser parser) {...while((type = parser.next()) ! = XmlPullParser.END_DOCUMENT && ((depth = parser.getDepth()) >= innerDepth || type ! = XmlPullParser.END_TAG)) { ...final intcolor = modulateColorAlpha(baseColor, alphaMod); colorList = GrowingArrayUtils.append(colorList, listSize, color); stateSpecList = GrowingArrayUtils.append(stateSpecList, listSize, stateSpec); listSize++; }...return new ColorStateList(stateSpecs, colors);
    }
Copy the code
  1. SetInternalBackgroundTint applySupportBackgroundTint and () () is responsible for the management and to distinguish the Tint color from static configuration properties and external dynamic configuration parameters

  2. TintDrawable () is responsible for coloring, essentially calling Drawable#setColorFilter() to refresh the color drawing

// ResourceManagerInternal.java
    static void tintDrawable(Drawable drawable, TintInfo tint, int[] state) {...if (tint.mHasTintList || tint.mHasTintMode) {
            drawable.setColorFilter(createTintFilter(
                    tint.mHasTintList ? tint.mTintList : null,
                    tint.mHasTintMode ? tint.mTintMode : DEFAULT_MODE,
                    state));
        } else{ drawable.clearColorFilter(); }... }Copy the code

Auto Size

To solve the problem is that the Text content according to the maximum width and font size, the best size calculation of adaptive dependence AppCompatTextHelper and AppCompatTextViewAutoSizeHelper implementation.

1.Resolves the configuration of AutoSize attributes and sets whether the font size Flag needs to be automatically adapted.// AppCompatTextViewAutoSizeHelper.java
	void loadFromAttributes(AttributeSet attrs, int defStyleAttr) {...if(a.hasValue(R.styleable.AppCompatTextView_autoSizeTextType)) { mAutoSizeTextType = a.getInt(R.styleable.AppCompatTextView_autoSizeTextType, TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE); }...if (supportsAutoSizeText()) {
            if(mAutoSizeTextType == TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM) { ... setupAutoSizeText(); }... }}private boolean setupAutoSizeText(a) {
        if (supportsAutoSizeText()
                && mAutoSizeTextType == TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM) {
            ...
            if(! mHasPresetAutoSizeValues || mAutoSizeTextSizesInPx.length ==0) {...for (int i = 0; i < autoSizeValuesLength; i++) {
                    autoSizeTextSizesInPx[i] = Math.round(
                            mAutoSizeMinTextSizeInPx + (i * mAutoSizeStepGranularityInPx));
                }
                mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(autoSizeTextSizesInPx);
            }
            mNeedsAutoSizeText = true; }... }Copy the code
  1. The appropriate font size is calculated and reflected on the UI when the text content is initialized or changed.
// AppCompatTextView.java
    protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {...if(mTextHelper ! =null&&! PLATFORM_SUPPORTS_AUTOSIZE && mTextHelper.isAutoSizeEnabled()) { mTextHelper.autoSizeText(); }}// AppCompatTextHelper.java
    void autoSizeText(a) {
        mAutoSizeTextHelper.autoSizeText();
    }

// AppCompatTextViewAutoSizeHelper.java
	void autoSizeText(a) {...if (mNeedsAutoSizeText) {
            ...
            synchronized (TEMP_RECTF) {
                ...
                // Calculate the best size
                final float optimalTextSize = findLargestTextSizeWhichFits(TEMP_RECTF);
                // Update the size if it does not match the preset size
                if(optimalTextSize ! = mTextView.getTextSize()) { setTextSizeInternal(TypedValue.COMPLEX_UNIT_PX, optimalTextSize); }}}... }Copy the code


AppCompatImageView

As with AppCompatTextView, Dynamic Tint for background and SRC is extended.

Unlike AppCompatTextView, AppCompatImageView takes the attribute of icon coloring not Attr# drawableTint but Attr# tint***. Implemented by AppCompatImageHelper and ImageViewCompat classes, the principle is similar and will not be described again.


Auxiliary class

AppCompat framework developers in the implementation of AppCompat extension controls and other features of the use of a lot of auxiliary classes, you can study its details, learn some clever implementation ideas.

  • AppCompatBackgroundHelper
  • AppCompatDrawableManager
  • AppCompatTextHelper
  • AppCompatTextViewAutoSizeHelper
  • AppCompatTextClassifierHelper
  • AppCompatResources
  • AppCompatImageHelper


The class diagram

Finally, let’s look at a simple class diagram of the AppCompat framework to help you get an overview.


The last

The AppCompat framework is relatively simple overall, but it’s so common that it’s easy to overlook. As an entry point in the Jetpack collection, it’s important to know.