android13#settings#reset options

1.简介

学习下settings》system》reset options相关

2.reset options

2.1.system_dashboard_fragment.xml

ini 复制代码
    <Preference
        android:key="reset_dashboard"
        android:title="@string/reset_dashboard_title"
        android:icon="@drawable/ic_restore"
        android:order="-30"
        android:fragment="com.android.settings.system.ResetDashboardFragment"
        settings:controller="com.android.settings.system.ResetPreferenceController"/>

>1.ResetPreferenceController

reset options是否可用是配置里的字段控制的

csharp 复制代码
    public int getAvailabilityStatus() {
        return mContext.getResources().getBoolean(R.bool.config_show_reset_dashboard)
                ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
    }

2.2.ResetDashboardFragment

这个就是点击reset options以后跳转的页面

>1.reset_dashboard_fragment.xml

xml 复制代码
<PreferenceScreen
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:settings="http://schemas.android.com/apk/res-auto"
    android:title="@string/reset_dashboard_title">

    <!-- Network reset ,跳转到fragment见小节3-->
    <com.android.settingslib.RestrictedPreference
        android:key="network_reset_pref"
        android:title="@string/reset_network_title"
        settings:userRestriction="no_network_reset"
        settings:useAdminDisabledSummary="true"
        android:fragment="com.android.settings.ResetNetwork" />

    <!-- Reset app preferences -->
    <Preference
        android:key="reset_app_prefs"
        android:title="@string/reset_app_preferences" />

    <!-- Erase Euicc data -->
    <Preference
        android:key="erase_euicc_data"
        android:title="@string/reset_esim_title"
        settings:controller="com.android.settings.network.EraseEuiccDataController" />

    <!-- Factory reset -->
    <com.android.settingslib.RestrictedPreference
        android:key="factory_reset"
        android:title="@string/main_clear_title"
        settings:keywords="@string/keywords_factory_data_reset"
        settings:userRestriction="no_factory_reset"
        settings:useAdminDisabledSummary="true"
        android:fragment="com.android.settings.MainClear" />
</PreferenceScreen>

>2.buildPreferenceControllers

相关的controller,具体见2.3

csharp 复制代码
    private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
            Lifecycle lifecycle) {
        final List<AbstractPreferenceController> controllers = new ArrayList<>();
        controllers.add(new NetworkResetPreferenceController(context));
        controllers.add(new FactoryResetPreferenceController(context));
        controllers.add(new ResetAppPrefPreferenceController(context, lifecycle));
        return controllers;
    }

2.3.Controller

>1.NetworkResetPreferenceController

网络重置,控制是否可用

java 复制代码
    public NetworkResetPreferenceController(Context context) {
        super(context);
        mRestrictionChecker = new NetworkResetRestrictionChecker(context);
    }

    @Override
    public boolean isAvailable() {
        return !mRestrictionChecker.hasUserRestriction();
    }

>2.ResetAppPrefPreferenceController

重置app的控制器

typescript 复制代码
    public boolean handlePreferenceTreeClick(Preference preference) {
        if (!TextUtils.equals(preference.getKey(), getPreferenceKey())) {
            return false;
        }
        mResetAppsHelper.buildResetDialog();//显示一个dialog
        return true;
    }

    @Override
    public boolean isAvailable() {
        return true;//默认可用
    }

//点击是弹出一个对话框

csharp 复制代码
    void buildResetDialog() {
        if (mResetDialog == null) {
            mResetDialog = new AlertDialog.Builder(mContext)
                    .setTitle(R.string.reset_app_preferences_title)
                    .setMessage(R.string.reset_app_preferences_desc)
                    .setPositiveButton(R.string.reset_app_preferences_button, this)
                    .setNegativeButton(R.string.cancel, null)
                    .setOnDismissListener(this)
                    .show();
        }
    }

>3.FactoryResetPreferenceController

恢复出厂设置的控制器

java 复制代码
    public FactoryResetPreferenceController(Context context) {
        super(context);
        mUm = (UserManager) context.getSystemService(Context.USER_SERVICE);
    }

    /**系统用户或者测试用户可用*/
    @Override
    public boolean isAvailable() {
        return mUm.isAdminUser() || Utils.isDemoUser(mContext);
    }

    @Override
    public boolean handlePreferenceTreeClick(Preference preference) {
        if (KEY_FACTORY_RESET.equals(preference.getKey())) {
        //点击跳转到其他activity,见小节4
            final Intent intent = new Intent(mContext, Settings.FactoryResetActivity.class);
            mContext.startActivity(intent);
            return true;
        }
        return false;
    }

>4.EraseEuiccDataController

这个是sim卡相关的,有对应的功能才可见

typescript 复制代码
    public boolean handlePreferenceTreeClick(Preference preference) {
        if (!TextUtils.equals(preference.getKey(), getPreferenceKey())) {
            return false;
        }
        //选项的点击事件
        EraseEuiccDataDialogFragment.show(mHostFragment);
        return true;
    }

    @Override
    public int getAvailabilityStatus() {
    //是否可用,
        return mContext.getPackageManager().hasSystemFeature(
                PackageManager.FEATURE_TELEPHONY_EUICC) ? AVAILABLE_UNSEARCHABLE
                : UNSUPPORTED_ON_DEVICE;
    }

3.ResetNetwork

scala 复制代码
public class ResetNetwork extends InstrumentedFragment {

3.1.reset_network.xml

ini 复制代码
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
    android:orientation="vertical" >
    <androidx.core.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="0dip"
        android:layout_marginTop="12dp"
        android:layout_weight="1">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">
            <TextView //图上看到的文字就是这个
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:textAppearance="@style/TextAppearance.PreferenceTitle.SettingsLib"
                android:textDirection="locale"
                android:text="@string/reset_network_desc" />
            <include layout="@layout/reset_esim_checkbox"/>
        </LinearLayout>
    </androidx.core.widget.NestedScrollView>
    <Spinner android:id="@+id/reset_network_subscription"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="20dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <Button
        android:id="@+id/initiate_reset_network"
        android:layout_gravity="end"
        android:layout_marginEnd="@dimen/reset_button_margin_end"
        android:layout_marginTop="20dp"
        android:layout_marginBottom="12dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/reset_network_button_text"
        android:gravity="center"
        style="@style/ActionPrimaryButton"/>
</LinearLayout>

>1.reset_esim_checkbox.xml

这个是擦除sim信息的布局,没有sim卡信息的话这里就不显示了

ini 复制代码
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/erase_esim_container"
    style="@style/SudDescription"
    android:layout_marginTop="40dp"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:focusable="true"
    android:clickable="true"
    android:visibility="gone">

    <CheckBox
        android:id="@+id/erase_esim"
        style="@style/SudCheckBox"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:focusable="false"
        android:clickable="false"
        android:checked="false"
        android:duplicateParentState="true"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:orientation="vertical">

        <TextView
            style="@style/TextAppearance.PreferenceTitle.SettingsLib"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/reset_esim_title"/>

        <TextView
            style="?android:attr/textAppearanceSmall"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/reset_esim_desc"/>
    </LinearLayout>
</LinearLayout>

>2.reset_network_desc

xml 复制代码
<string name="reset_network_desc">This will reset all network settings, including:\n\n
<li>Wi\u2011Fi</li>\n
<li>Mobile data</li>\n
<li>Bluetooth</li>"</string>

3.2.onCreateView

sql 复制代码
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View view = (new ResetNetworkRestrictionViewBuilder(getActivity())).build();
        if (view != null) {
            return view;
        }
        //加载布局
        mContentView = inflater.inflate(R.layout.reset_network, null);

        establishInitialState(getActiveSubscriptionInfoList());
        return mContentView;
    }

>1.getActiveSubscriptionInfoList

获取活动的订阅信息列表,没有的话返回空

scss 复制代码
    private List<SubscriptionInfo> getActiveSubscriptionInfoList() {
        SubscriptionManager mgr = getActivity().getSystemService(SubscriptionManager.class);
        if (mgr == null) {
            Log.w(TAG, "No SubscriptionManager");
            return Collections.emptyList();
        }
        return Optional.ofNullable(mgr.getActiveSubscriptionInfoList())
                .orElse(Collections.emptyList());
    }

3.3.onCreate

less 复制代码
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getActivity().setTitle(R.string.reset_network_title);
        //注册一个intent的launcher后边用
        mActivityResultLauncher = registerForActivityResult(
                new ActivityResultContracts.StartActivityForResult(),
                result -> onActivityLauncherResult(result));
    }

3.4.establishInitialState

ini 复制代码
    private void establishInitialState(List<SubscriptionInfo> subscriptionsList) {
        mSubscriptionSpinner = (Spinner) mContentView.findViewById(R.id.reset_network_subscription);
        //sim卡相关的容器以及checkbox
        mEsimContainer = mContentView.findViewById(R.id.erase_esim_container);
        mEsimCheckbox = mContentView.findViewById(R.id.erase_esim);

        mSubscriptions = subscriptionsList;
        //有数据的话Spinner可见,没有数据的话不可见
        if (mSubscriptions != null && mSubscriptions.size() > 0) {
            // Get the default subscription in the order of data, voice, sms, first up.
            int defaultSubscription = SubscriptionManager.getDefaultDataSubscriptionId();
            if (!SubscriptionManager.isUsableSubscriptionId(defaultSubscription)) {
                defaultSubscription = SubscriptionManager.getDefaultVoiceSubscriptionId();
            }
            if (!SubscriptionManager.isUsableSubscriptionId(defaultSubscription)) {
                defaultSubscription = SubscriptionManager.getDefaultSmsSubscriptionId();
            }
            if (!SubscriptionManager.isUsableSubscriptionId(defaultSubscription)) {
                defaultSubscription = SubscriptionManager.getDefaultSubscriptionId();
            }

            int selectedIndex = 0;
            int size = mSubscriptions.size();
            List<String> subscriptionNames = new ArrayList<>();
            for (SubscriptionInfo record : mSubscriptions) {
                if (record.getSubscriptionId() == defaultSubscription) {
                    // Set the first selected value to the default
                    selectedIndex = subscriptionNames.size();
                }
                String name = SubscriptionUtil.getUniqueSubscriptionDisplayName(
                        record, getContext()).toString();
                if (TextUtils.isEmpty(name)) {
                    name = record.getNumber();
                }
                if (TextUtils.isEmpty(name)) {
                    CharSequence carrierName = record.getCarrierName();
                    name = TextUtils.isEmpty(carrierName) ? "" : carrierName.toString();
                }
                if (TextUtils.isEmpty(name)) {
                    name = String.format("MCC:%s MNC:%s Slot:%s Id:%s", record.getMcc(),
                            record.getMnc(), record.getSimSlotIndex(), record.getSubscriptionId());
                }
                subscriptionNames.add(name);
            }
            ArrayAdapter<String> adapter = new ArrayAdapter<String>(getActivity(),
                    android.R.layout.simple_spinner_item, subscriptionNames);
            adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
            mSubscriptionSpinner.setAdapter(adapter);
            mSubscriptionSpinner.setSelection(selectedIndex);
            if (mSubscriptions.size() > 1) {
                mSubscriptionSpinner.setVisibility(View.VISIBLE);
            } else {
                mSubscriptionSpinner.setVisibility(View.INVISIBLE);
            }
        } else {
            mSubscriptionSpinner.setVisibility(View.INVISIBLE);
        }
        //右侧button的点击事件
        mInitiateButton = (Button) mContentView.findViewById(R.id.initiate_reset_network);
        mInitiateButton.setOnClickListener(mInitiateListener);
        
        if (showEuiccSettings(getContext())) {
            mEsimContainer.setVisibility(View.VISIBLE);
            mEsimContainer.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    mEsimCheckbox.toggle();
                }
            });
        } else {
            mEsimCheckbox.setChecked(false /* checked */);
        }
    }

>1.showEuiccSettings

eUICC是否可用,可以理解为嵌入式的sim卡,是否显示相关设置

java 复制代码
    private boolean showEuiccSettings(Context context) {
        EuiccManager euiccManager =
                (EuiccManager) context.getSystemService(Context.EUICC_SERVICE);
        //不可用
        if (!euiccManager.isEnabled()) {
            return false;
        }
        ContentResolver resolver = context.getContentResolver();
        //sim卡是否下载过配置,或者是否是开发者模式
        return Settings.Global.getInt(resolver, Global.EUICC_PROVISIONED, 0) != 0
                || DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(context);
    }

>2.mInitiateListener

java 复制代码
    private final Button.OnClickListener mInitiateListener = new Button.OnClickListener() {

        @Override
        public void onClick(View v) {
            //先验证keyguard
            if (!runKeyguardConfirmation(KEYGUARD_REQUEST)) {
            //见3.5
                showFinalConfirmation();
            }
        }
    };

>3.runKeyguardConfirmation

java 复制代码
    private boolean runKeyguardConfirmation(int request) {
        Resources res = getActivity().getResources();
        final ChooseLockSettingsHelper.Builder builder =
                new ChooseLockSettingsHelper.Builder(getActivity(), this);
        return builder.setRequestCode(request)
                .setTitle(res.getText(R.string.reset_network_title))
                .setActivityResultLauncher(mActivityResultLauncher)
                .show();
    }

3.5.showFinalConfirmation

跳到最终的确认页ResetNetworkConfirm.java,如下

scss 复制代码
    void showFinalConfirmation() {
        Bundle args = new Bundle();

        ResetNetworkRequest request = new ResetNetworkRequest(
                ResetNetworkRequest.RESET_CONNECTIVITY_MANAGER |
                ResetNetworkRequest.RESET_VPN_MANAGER |
                ResetNetworkRequest.RESET_WIFI_MANAGER |
                ResetNetworkRequest.RESET_WIFI_P2P_MANAGER |
                ResetNetworkRequest.RESET_BLUETOOTH_MANAGER
        );
//...

        new SubSettingLauncher(getContext())
        //跳转到这里
                .setDestination(ResetNetworkConfirm.class.getName())
                .setArguments(args)
                .setTitleRes(R.string.reset_network_confirm_title)
                .setSourceMetricsCategory(getMetricsCategory())
                .launch();
    }

4.FactoryResetActivity

Settings.java的内部类

scala 复制代码
    public static class FactoryResetActivity extends SettingsActivity {
        @Override
        protected void onCreate(Bundle savedState) {
            setTheme(SetupWizardUtils.getTheme(this, getIntent()));
            ThemeHelper.trySetDynamicColor(this);
            super.onCreate(savedState);
        }

        @Override
        protected boolean isToolbarEnabled() {
            return false;
        }
    }

4.1.清单文件

ini 复制代码
        <activity android:name="Settings$FactoryResetActivity"
                  android:permission="android.permission.BACKUP"
                  android:label="@string/main_clear_title"
                  android:exported="true"
                  android:theme="@style/SudThemeGlif.Light">
            <intent-filter>
                <action android:name="com.android.settings.action.FACTORY_RESET"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
            <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
                       android:value="com.android.settings.MainClear"/>
            <meta-data android:name="com.android.settings.HIGHLIGHT_MENU_KEY"
                       android:value="@string/menu_key_system"/>
        </activity>

5.MainClear

SettingsActivity前边有介绍过,上边的清单文件里有声明meta-data,所以会加载对应的fragment

scala 复制代码
public class MainClear extends InstrumentedFragment implements OnGlobalLayoutListener {

5.1.onCreateView

scss 复制代码
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final Context context = getContext();
        //是否允许
        final EnforcedAdmin admin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced(context,
                UserManager.DISALLOW_FACTORY_RESET, UserHandle.myUserId());
        final UserManager um = UserManager.get(context);
        final boolean disallow = !um.isAdminUser() || RestrictedLockUtilsInternal
                .hasBaseUserRestriction(context, UserManager.DISALLOW_FACTORY_RESET,
                        UserHandle.myUserId());
        if (disallow && !Utils.isDemoUser(context)) {
            return inflater.inflate(R.layout.main_clear_disallowed_screen, null);
        } else if (admin != null) {
            new ActionDisabledByAdminDialogHelper(getActivity())
                    .prepareDialogBuilder(UserManager.DISALLOW_FACTORY_RESET, admin)
                    .setOnDismissListener(__ -> getActivity().finish())
                    .show();
            return new View(getContext());
        }
        //走到这里说明是允许
        mContentView = inflater.inflate(R.layout.main_clear, null);

        establishInitialState();
        return mContentView;
    }

>1.main_clear.xml

ini 复制代码
<com.google.android.setupdesign.GlifLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/setup_wizard_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:icon="@drawable/ic_delete_accent"
    app:sucHeaderText="@string/main_clear_title">

    <ScrollView
        android:id="@+id/main_clear_scrollview"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:id="@+id/main_clear_container"
            style="@style/SudContentFrame"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="16dp"
            android:orientation="vertical">

            <TextView //见补充3
                style="@style/TextAppearance.PreferenceTitle.SettingsLib"
                android:id="@+id/sud_layout_description"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/main_clear_desc"/>
            <TextView //见补充4
                android:id="@+id/also_erases_external"
                style="@style/TextAppearance.PreferenceTitle.SettingsLib"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:visibility="gone"
                android:text="@string/main_clear_desc_also_erases_external"/>
            <TextView //esim卡
                android:id="@+id/also_erases_esim"
                style="@style/TextAppearance.PreferenceTitle.SettingsLib"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:visibility="gone"
                android:text="@string/main_clear_desc_also_erases_esim"/>
            <TextView //你目前已登录如下账户
                android:id="@+id/accounts_label"
                style="@style/TextAppearance.PreferenceTitle.SettingsLib"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:visibility="gone"
                android:text="@string/main_clear_accounts"/>
            <LinearLayout //这里就是登录的账户信息,动态添加的
                android:id="@+id/accounts"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                android:visibility="gone">
                <!-- Do not add any children here as they will be removed in the MainClear.java
                    code. A list of accounts will be inserted programmatically. -->
            </LinearLayout>
            <TextView  //见补充6
                android:id="@+id/other_users_present"
                style="@style/TextAppearance.PreferenceTitle.SettingsLib"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:visibility="gone"
                android:text="@string/main_clear_other_users_present"/>
            <TextView //见补充7
                android:id="@+id/no_cancel_mobile_plan"
                style="@style/TextAppearance.PreferenceTitle.SettingsLib"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:visibility="gone"
                android:text="@string/main_clear_desc_no_cancel_mobile_plan"/>
            <TextView //补充8
                android:id="@+id/erase_external_option_text"
                style="@style/TextAppearance.PreferenceTitle.SettingsLib"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/main_clear_desc_erase_external_storage"/>
            <LinearLayout
                android:id="@+id/erase_external_container"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal"
                android:focusable="true"
                android:clickable="true">
                <CheckBox
                    android:id="@+id/erase_external"
                    style="@style/SudCheckBox"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center_vertical"
                    android:focusable="false"
                    android:clickable="false"
                    android:duplicateParentState="true"/>
                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center_vertical"
                    android:orientation="vertical">
                    <TextView
                        style="@style/TextAppearance.PreferenceTitle.SettingsLib"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="@string/erase_external_storage"/>
                    <TextView
                        style="?android:attr/textAppearanceListItemSecondary"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="@string/erase_external_storage_description"/>
                </LinearLayout>
            </LinearLayout>

            <include layout="@layout/reset_esim_checkbox"/>

        </LinearLayout>
    </ScrollView>
</com.google.android.setupdesign.GlifLayout>

>2.图片

下图是2个文本控件,字符串见补充3和4

>3.main_clear_desc

bash 复制代码
<string name="main_clear_desc" product="tablet" msgid="1651178880680056849">
"This will erase all data from your tablet's "<b>"internal storage"</b>", including:\n\n"
<li>"Your Google Account"</li>\n
<li>"System and app data and settings"</li>\n
<li>"Downloaded apps"</li></string> 

>4.main_clear_desc_also_erases_external

typescript 复制代码
<string name="main_clear_desc_also_erases_external" msgid="3687911419628956693">
<li>"Music"</li>\n
<li>"Photos"</li>\n
<li>"Other user data"</li></string>

>5.main_clear_accounts

c 复制代码
 <string name="main_clear_accounts" product="default" msgid="7675859115108318537">
 \n\n"You are currently signed in to the following accounts:\n"</string>

>6.main_clear_other_users_present

c 复制代码
 <string name="main_clear_other_users_present" product="default" msgid="2672976674798019077">
 \n\n"There are other users present on this device.\n"</string>

>7.main_clear_desc_no_cancel_mobile_plan

c 复制代码
<string name="main_clear_desc_no_cancel_mobile_plan" msgid="369883568059127035">
\n\n"This will not cancel your mobile service plan."</string>

>8.main_clear_desc_erase_external_storage

typescript 复制代码
<string name="main_clear_desc_erase_external_storage" product="nosdcard" msgid="4441604184663452046">
\n\n"To clear music, pictures and other user data, the "<b>"USB storage"</b>" needs to be erased."</string>

>9.log

这个是竖屏下打印的ScrollView的父容器结构,可以参考后边小节6里讲解的替换布局来理解

css 复制代码
15:58:05.631  android.widget.ScrollView{6161e8b VFED.V... ......ID 0,0-1120,939 #7f0a0369 app:id/main_clear_scrollview}
15:58:05.631  android.widget.FrameLayout{b443b81 V.E...... ......ID 0,261-1120,1200 #7f0a05b7 app:id/sud_layout_content}
15:58:05.631  android.widget.LinearLayout{cf4d426 V.E...... ......ID 0,0-1120,1200}
15:58:05.631  com.google.android.setupdesign.view.BottomScrollView{8461867 VFED.V... ......ID 0,0-1120,1200 #7f0a05cd app:id/sud_scroll_view}
15:58:05.631  android.widget.LinearLayout{7f1f614 V.E...... ......ID 0,0-1120,1344 #7f0a05c5 app:id/sud_layout_template_content}
## 上边的布局见小节6.2.3竖屏
15:58:05.631  com.google.android.setupdesign.view.IntrinsicSizeFrameLayout{2780ebd V.E...... ......ID 40,204-1160,1548}
15:58:05.631  android.widget.LinearLayout{4ca0cb2 V.E...... ......ID 0,0-1200,1752 #7f0a05a1 app:id/suc_layout_status}
## 上边的布局见6.2.2
15:58:05.631  com.google.android.setupdesign.GlifLayout{d3be569 V.E...... ......ID 0,0-1200,1752 #7f0a0530 app:id/setup_wizard_layout}
15:58:05.631  android.widget.FrameLayout{32c88e4 V.E...... ......ID 0,0-1200,1752 #7f0a036a app:id/main_content}
15:58:05.631  android.widget.LinearLayout{d91d403 V.E...... ......ID 0,0-1200,1752}
15:58:05.631  android.widget.FrameLayout{3025f80 V.E...... ......ID 0,48-1200,1800 #7f0a0198 app:id/content_frame}
15:58:05.631  android.widget.LinearLayout{e1bd9b9 V.E...... ......ID 0,0-1200,1800 #7f0a0199 app:id/content_parent}
15:58:05.631  android.widget.FrameLayout{c4841fe V.E...... ......ID 0,0-1200,1800 #1020002 android:id/content}
15:58:05.631  android.widget.LinearLayout{753ad5f V.E...... ......ID 0,0-1200,1800}
15:58:05.631  DecorView@3edf3ac[Settings$FactoryResetActivity]
15:58:05.631  android.view.ViewRootImpl@f975875

5.2.establishInitialState

scss 复制代码
    void establishInitialState() {
        setUpActionBarAndTitle();
        setUpInitiateButton();

        mExternalStorageContainer = mContentView.findViewById(R.id.erase_external_container);
        mExternalStorage = mContentView.findViewById(R.id.erase_external);
        mEsimStorageContainer = mContentView.findViewById(R.id.erase_esim_container);
        mEsimStorage = mContentView.findViewById(R.id.erase_esim);
        if (mScrollView != null) {
            mScrollView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
        }
        mScrollView = mContentView.findViewById(R.id.main_clear_scrollview);
        
       
        boolean isExtStorageEmulated = Environment.isExternalStorageEmulated();
        if (isExtStorageEmulated
                || (!Environment.isExternalStorageRemovable() && isExtStorageEncrypted())) {
         //外部存储器可用,隐藏相关的控件,正常都会走这里
            mExternalStorageContainer.setVisibility(View.GONE);

            final View externalOption = mContentView.findViewById(R.id.erase_external_option_text);
            externalOption.setVisibility(View.GONE);
        //显示 music,photos,other user data 文本框
            final View externalAlsoErased = mContentView.findViewById(R.id.also_erases_external);
            externalAlsoErased.setVisibility(View.VISIBLE);

            //没有意义,容器mExternalStorageContainer都不可见了
            mExternalStorage.setChecked(!isExtStorageEmulated);
        } else {
            mExternalStorageContainer.setOnClickListener(new View.OnClickListener() {

                @Override
                public void onClick(View v) {
                //没有外部存储器,点击容器切换checkbox状态
                    mExternalStorage.toggle();
                }
            });
        }
        //是否显示wipe sim卡内容
        if (showWipeEuicc()) {
            //系统属性,默认false
            if (showWipeEuiccCheckbox()) {
                mEsimStorageContainer.setVisibility(View.VISIBLE);
                mEsimStorageContainer.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        mEsimStorage.toggle();
                    }
                });
            } else {
                final View esimAlsoErased = mContentView.findViewById(R.id.also_erases_esim);
                esimAlsoErased.setVisibility(View.VISIBLE);

                final View noCancelMobilePlan = mContentView.findViewById(
                        R.id.no_cancel_mobile_plan);
                noCancelMobilePlan.setVisibility(View.VISIBLE);
                mEsimStorage.setChecked(true /* checked */);
            }
        } else {
            mEsimStorage.setChecked(false /* checked */);
        }

        final UserManager um = (UserManager) getActivity().getSystemService(Context.USER_SERVICE);
        //加载账户信息,布局里有个线性布局,这里动态添加内容
        loadAccountList(um);

        //
        mScrollView.setOnScrollChangeListener(new OnScrollChangeListener() {
            @Override
            public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX,
                    int oldScrollY) {
                if (v instanceof ScrollView && hasReachedBottom((ScrollView) v)) {
                    mInitiateButton.setEnabled(true);
                    mScrollView.setOnScrollChangeListener(null);
                }
            }
        });

        //监听布局改变,设置那个button的状态
        mScrollView.getViewTreeObserver().addOnGlobalLayoutListener(this);
    }
    //监听如下
    public void onGlobalLayout() {
        mInitiateButton.setEnabled(hasReachedBottom(mScrollView));
    }

>1.setUpInitiateButton

java 复制代码
    protected final Button.OnClickListener mInitiateListener = new Button.OnClickListener() {

        public void onClick(View view) {
        //demouser cod ignore..
            //有密码的走这里,密码验证成功会走onActivityResult,如下
            if (runKeyguardConfirmation(KEYGUARD_REQUEST)) {
                return;
            }
            //没有密码的走这里
            //这个需要配置的,默认是没有配置的,返回是null
            Intent intent = getAccountConfirmationIntent();
            if (intent != null) {
                showAccountCredentialConfirmation(intent);
            } else {
            //见5.3,最终reset确认页面
                showFinalConfirmation();
            }
        }
    };

//

scss 复制代码
    void onActivityResultInternal(int requestCode, int resultCode, Intent data) {
        if (!isValidRequestCode(requestCode)) {
            return;
        }

        if (resultCode != Activity.RESULT_OK) {
            establishInitialState();
            return;
        }

        Intent intent = null;
        //密码验证完,先判断是否有自定义账户的验证,有的话跳转到对应的页面
        if (CREDENTIAL_CONFIRM_REQUEST != requestCode
                && (intent = getAccountConfirmationIntent()) != null) {
            showAccountCredentialConfirmation(intent);
        } else {
        //没有自定义账户,直接跳到最终reset页面
        //见5.3
            showFinalConfirmation();
        }
    }

5.3.showFinalConfirmation

这个就是最终跳转的confirm页面,里边有个按钮点了就是真正的reset操作了

scss 复制代码
    void showFinalConfirmation() {
        final Bundle args = new Bundle();
        args.putBoolean(ERASE_EXTERNAL_EXTRA, mExternalStorage.isChecked());
        args.putBoolean(ERASE_ESIMS_EXTRA, mEsimStorage.isChecked());
        final Intent intent = new Intent();
        //要跳转的activity
        intent.setClass(getContext(),
                com.android.settings.Settings.FactoryResetConfirmActivity.class);
        //activity里加载的fragment
        intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT, MainClearConfirm.class.getName());
        intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
        intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RESID,
                R.string.main_clear_confirm_title);
        intent.putExtra(MetricsFeatureProvider.EXTRA_SOURCE_METRICS_CATEGORY, getMetricsCategory());
        getContext().startActivity(intent);
    }

6.GlifLayout

external/setupdesign/main/src/com/google/android/setupdesign/GlifLayout.java

  • 这个是小节5用的根布局,自定义的帧布局,看下为啥有时候是全屏的,有时候是带边框非全屏的
  • 打印的布局结构为啥和5.1.1不一样,看下这个自定义控件是如何替换布局的。
scala 复制代码
public class GlifLayout extends PartnerCustomizationLayout {

6.1.init

构造方法里调用,这里就是处理第三方颜色,padding的

scss 复制代码
  private void init(AttributeSet attrs, int defStyleAttr) {
    if (isInEditMode()) {
      return;
    }

    TypedArray a =
        getContext().obtainStyledAttributes(attrs, R.styleable.SudGlifLayout, defStyleAttr, 0);
    boolean usePartnerHeavyTheme =
        a.getBoolean(R.styleable.SudGlifLayout_sudUsePartnerHeavyTheme, false);
    applyPartnerHeavyThemeResource = shouldApplyPartnerResource() && usePartnerHeavyTheme;
    //注册需要的对象
    registerMixin(HeaderMixin.class, new HeaderMixin(this, attrs, defStyleAttr));
    registerMixin(DescriptionMixin.class, new DescriptionMixin(this, attrs, defStyleAttr));
    registerMixin(IconMixin.class, new IconMixin(this, attrs, defStyleAttr));
    registerMixin(ProgressBarMixin.class, new ProgressBarMixin(this, attrs, defStyleAttr));
    registerMixin(IllustrationProgressMixin.class, new IllustrationProgressMixin(this));
    final RequireScrollMixin requireScrollMixin = new RequireScrollMixin(this);
    registerMixin(RequireScrollMixin.class, requireScrollMixin);
    //..

    ColorStateList primaryColor = a.getColorStateList(R.styleable.SudGlifLayout_sudColorPrimary);
    if (primaryColor != null) {
    //见6.5
      setPrimaryColor(primaryColor);
    }
    if (shouldApplyPartnerHeavyThemeResource()) {
    //rootView颜色设置
      updateContentBackgroundColorWithPartnerConfig();
    }

    View view = findManagedViewById(R.id.sud_layout_content);
    if (view != null) {
    //见7.2,是否使用第三方的资源
      if (shouldApplyPartnerResource()) {
      //设置第三方的padding,主要是左右padding
        LayoutStyler.applyPartnerCustomizationExtraPaddingStyle(view);
      }

      if (!(this instanceof GlifPreferenceLayout)) {
      //top padding设置
        tryApplyPartnerCustomizationContentPaddingTopStyle(view);
      }
    }
    //根据margion调整padding
    updateLandscapeMiddleHorizontalSpacing();
    
    //背景颜色设置,见6.5以及6.4,就是自己的第一个child的背景
    ColorStateList backgroundColor =
        a.getColorStateList(R.styleable.SudGlifLayout_sudBackgroundBaseColor);
    setBackgroundBaseColor(backgroundColor);
    //见6.5
    boolean backgroundPatterned =
        a.getBoolean(R.styleable.SudGlifLayout_sudBackgroundPatterned, true);
    setBackgroundPatterned(backgroundPatterned);

    final int stickyHeader = a.getResourceId(R.styleable.SudGlifLayout_sudStickyHeader, 0);
    if (stickyHeader != 0) {
      inflateStickyHeader(stickyHeader);
    }
    a.recycle();
  }

6.2.onInflateTemplate

这里定义的模板layout带主题

less 复制代码
  protected View onInflateTemplate(LayoutInflater inflater, @LayoutRes int template) {
    if (template == 0) {
    //,默认使用的模板,不同尺寸指向不同的布局,具体在layout.xml里声明的,见补充1
      template = R.layout.sud_glif_template;
    }
    return inflateTemplate(inflater, R.style.SudThemeGlif_Light, template);
  }

>1.sud_glif_template

我们是平板,所以直接看大尺寸的即可

bash 复制代码
#values
<item name="sud_glif_template" type="layout">@layout/sud_glif_template_compact</item>
#values-sw600dp
<item name="sud_glif_template" type="layout">@layout/sud_glif_template_card</item>

>2.sud_glif_template_card

上下都是比重为1的view,所以中间的控件居中显示

ini 复制代码
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/suc_layout_status"
    style="@style/SudGlifCardBackground"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:gravity="center_horizontal"
    android:orientation="vertical">

    <View
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:visibility="invisible" />

    <com.google.android.setupdesign.view.IntrinsicSizeFrameLayout
        style="@style/SudGlifCardContainer"
        android:layout_width="@dimen/sud_glif_card_width"
        android:layout_height="wrap_content"
        android:height="@dimen/sud_glif_card_height">
        //见补充3,分横竖屏
        <include layout="@layout/sud_glif_template_content" />

    </com.google.android.setupdesign.view.IntrinsicSizeFrameLayout>

    <View
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:visibility="invisible" />

</LinearLayout>

>3.横屏sud_glif_template_content

ini 复制代码
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/sud_layout_template_content"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <!--header在左侧,content在右侧-->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:orientation="horizontal">

        <LinearLayout
            android:id="@+id/sud_landscape_header_area"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="@dimen/sud_glif_land_header_area_weight"
            android:orientation="vertical">

            <ViewStub
                android:id="@+id/sud_layout_sticky_header"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />

            <com.google.android.setupdesign.view.BottomScrollView
                android:id="@+id/sud_header_scroll_view"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:fillViewport="true"
                android:scrollIndicators="?attr/sudScrollIndicators">

                <include layout="@layout/sud_glif_header" />

            </com.google.android.setupdesign.view.BottomScrollView>

        </LinearLayout>

        <LinearLayout
            android:id="@+id/sud_landscape_content_area"
            style="@style/SudLandContentContianerStyle"
            android:orientation="vertical">

            <com.google.android.setupdesign.view.BottomScrollView
                android:id="@+id/sud_scroll_view"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:fillViewport="true"
                android:scrollIndicators="?attr/sudScrollIndicators"
                android:importantForAccessibility="no">

                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="vertical">

                    <FrameLayout
                        android:id="@+id/sud_layout_content"
                        android:paddingTop="?attr/sudGlifContentPaddingTop"
                        android:layout_width="match_parent"
                        android:layout_height="0dp"
                        android:layout_weight="1" />

                    <ViewStub
                        android:id="@+id/sud_layout_illustration_progress_stub"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:inflatedId="@+id/sud_layout_progress_illustration"
                        android:layout="@layout/sud_progress_illustration_layout" />
                </LinearLayout>

            </com.google.android.setupdesign.view.BottomScrollView>

        </LinearLayout>

    </LinearLayout>

    <ViewStub
        android:id="@+id/suc_layout_footer"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>

>3.竖屏sud_glif_template_content

ini 复制代码
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/sud_layout_template_content"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <ViewStub
        android:id="@+id/sud_layout_sticky_header"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <!---->
    <com.google.android.setupdesign.view.BottomScrollView
        android:id="@+id/sud_scroll_view"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:fillViewport="true"
        android:scrollIndicators="?attr/sudScrollIndicators"
        tools:ignore="UnusedAttribute">
    <!--header和content是垂直布局-->
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <include layout="@layout/sud_glif_header" />

            <ViewStub
                android:id="@+id/sud_layout_illustration_progress_stub"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:inflatedId="@+id/sud_layout_progress_illustration"
                android:layout="@layout/sud_progress_illustration_layout" />

            <FrameLayout
                android:id="@+id/sud_layout_content"
                android:layout_width="match_parent"
                android:layout_height="0dp"
                android:layout_weight="1" />

        </LinearLayout>

    </com.google.android.setupdesign.view.BottomScrollView>

    <ViewStub
        android:id="@+id/suc_layout_footer"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>

6.3.findContainer

这个容器的作用:把GlifLayout里原本的内容放到这个容器里

ini 复制代码
    protected ViewGroup findContainer(int containerId) {
      if (containerId == 0) {
        containerId = R.id.sud_layout_content;
      }
      return super.findContainer(containerId);
    }

6.4.updateBackground

用到的地方见6.5

scss 复制代码
  private void updateBackground() {
  //见6.2.2布局,GlifLayout里替换的根布局就是这个
    final View patternBg = findManagedViewById(R.id.suc_layout_status);
    if (patternBg != null) {
      int backgroundColor = 0;
      if (backgroundBaseColor != null) {
        backgroundColor = backgroundBaseColor.getDefaultColor();
      } else if (primaryColor != null) {
        backgroundColor = primaryColor.getDefaultColor();
      }
      Drawable background =
          backgroundPatterned
              ? new GlifPatternDrawable(backgroundColor)
              : new ColorDrawable(backgroundColor);
      //设置statusbar背景,就是patternBg,具体可以去StatusBarMixin查看
      getMixin(StatusBarMixin.class).setStatusBarBackground(background);
    }
  }

6.5.color设置

less 复制代码
  public void setPrimaryColor(@NonNull ColorStateList color) {
    primaryColor = color;
    updateBackground();
    getMixin(ProgressBarMixin.class).setColor(color);
  }

  public void setBackgroundBaseColor(@Nullable ColorStateList color) {
    backgroundBaseColor = color;
    updateBackground();
  }

  /**
   * Sets whether the background should be {@link GlifPatternDrawable}. If {@code false}, the
   * background will be a solid color.
   */
  public void setBackgroundPatterned(boolean patterned) {
    backgroundPatterned = patterned;
    updateBackground();
  }

7.PartnerCustomizationLayout

external/setupcompat/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java

scala 复制代码
public class PartnerCustomizationLayout extends TemplateLayout {

7.1.init

构造方法里会调用

scss 复制代码
  private void init(AttributeSet attrs, int defStyleAttr) {
    if (isInEditMode()) {
      return;
    }

    TypedArray a =
        getContext()
            .obtainStyledAttributes(
                attrs, R.styleable.SucPartnerCustomizationLayout, defStyleAttr, 0);

    boolean layoutFullscreen =
        a.getBoolean(R.styleable.SucPartnerCustomizationLayout_sucLayoutFullscreen, true);

    a.recycle();

    if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP && layoutFullscreen) {
      setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
    }
    //这里注册了3个对象
    registerMixin(
        StatusBarMixin.class, new StatusBarMixin(this, activity.getWindow(), attrs, defStyleAttr));
    registerMixin(SystemNavBarMixin.class, new SystemNavBarMixin(this, activity.getWindow()));
    registerMixin(FooterBarMixin.class, new FooterBarMixin(this, attrs, defStyleAttr));

    getMixin(SystemNavBarMixin.class).applyPartnerCustomizations(attrs, defStyleAttr);

    if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
      activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
      activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
      activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
    }
  }

7.2.shouldApplyPartnerResource

是否使用合作者的资源配置

kotlin 复制代码
  /** Returns if the current layout/activity applies partner customized configurations or not. */
  public boolean shouldApplyPartnerResource() {
    if (!enablePartnerResourceLoading()) {//默认为true,见补充1
      return false;
    }
    if (!usePartnerResourceAttr) {//见7.3
      return false;
    }
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
      return false;
    }
    if (!PartnerConfigHelper.get(getContext()).isAvailable()) {
      return false;
    }
    return true;
  }

>1.enablePartnerResourceLoading

typescript 复制代码
  protected boolean enablePartnerResourceLoading() {
    return true;
  }

7.3.onBeforeTemplateInflated

ini 复制代码
  protected void onBeforeTemplateInflated(AttributeSet attrs, int defStyleAttr) {

    usePartnerResourceAttr = true;

    activity = lookupActivityFromContext(getContext());
    //是否是setup流程
    boolean isSetupFlow = WizardManagerHelper.isAnySetupWizard(activity.getIntent());

    TypedArray a =
        getContext()
            .obtainStyledAttributes(
                attrs, R.styleable.SucPartnerCustomizationLayout, defStyleAttr, 0);
//setup流程中,或者布局配置里sucUsePartnerResource为true
    usePartnerResourceAttr =
        isSetupFlow
            || a.getBoolean(R.styleable.SucPartnerCustomizationLayout_sucUsePartnerResource, true);

    useDynamicColor = a.hasValue(R.styleable.SucPartnerCustomizationLayout_sucFullDynamicColor);
    useFullDynamicColorAttr =
        a.getBoolean(R.styleable.SucPartnerCustomizationLayout_sucFullDynamicColor, false);

    a.recycle();
  }

7.4.PartnerConfigHelper.java

>1.isAvailable

bundle不为空就返回true

typescript 复制代码
  public boolean isAvailable() {
    return resultBundle != null && !resultBundle.isEmpty();
  }

>2.getPartnerConfigBundle

bundle数据来源是谷歌的setupwizard的contentProvider

ini 复制代码
  private void getPartnerConfigBundle(Context context) {
    if (resultBundle == null || resultBundle.isEmpty()) {
      try {
        resultBundle =
            context
                .getContentResolver()
                .call(
                    getContentUri(),
                    SUW_GET_PARTNER_CONFIG_METHOD,
                    /* arg= */ null,
                    /* extras= */ null);
        partnerResourceCache.clear();
      } 
    }
  }
arduino 复制代码
public static final String SUW_GET_PARTNER_CONFIG_METHOD = "getOverlayConfig";

public static final String SUW_AUTHORITY = "com.google.android.setupwizard.partner";

  static Uri getContentUri() {
    return new Uri.Builder()
        .scheme(ContentResolver.SCHEME_CONTENT)
        .authority(SUW_AUTHORITY)
        .build();
  }

8.TemplateLayout

external/setupcompat/main/java/com/google/android/setupcompat/internal/TemplateLayout.java

scala 复制代码
public class TemplateLayout extends FrameLayout {

8.1.init

构造方法里调用

ini 复制代码
  private void init(int template, int containerId, AttributeSet attrs, int defStyleAttr) {
    final TypedArray a =
        getContext().obtainStyledAttributes(attrs, R.styleable.SucTemplateLayout, defStyleAttr, 0);
    if (template == 0) {
      template = a.getResourceId(R.styleable.SucTemplateLayout_android_layout, 0);
    }
    if (containerId == 0) {
      containerId = a.getResourceId(R.styleable.SucTemplateLayout_sucContainer, 0);
    }
    onBeforeTemplateInflated(attrs, defStyleAttr);
    inflateTemplate(template, containerId);

    a.recycle();
  }

>1.inflateTemplate

两个参数在子类都覆写了,如果为0,会给与默认值,见小节6.2,6.3

scss 复制代码
  private void inflateTemplate(int templateResource, int containerId) {
    final LayoutInflater inflater = LayoutInflater.from(getContext());
    final View templateRoot = onInflateTemplate(inflater, templateResource);
    //添加到当前容器里
    addViewInternal(templateRoot);
    //查找容器
    container = findContainer(containerId);
    if (container == null) {
      throw new IllegalArgumentException("Container cannot be null in TemplateLayout");
    }
    onTemplateInflated();
  }

根据template加载对应的布局view,子类重写了,给与默认的template,以及theme,见6.2

less 复制代码
  protected View onInflateTemplate(LayoutInflater inflater, @LayoutRes int template) {
    return inflateTemplate(inflater, 0, template);
  }
less 复制代码
  protected final View inflateTemplate(
      LayoutInflater inflater, @StyleRes int fallbackTheme, @LayoutRes int template) {
    if (template == 0) {
      throw new IllegalArgumentException("android:layout not specified for TemplateLayout");
    }
    if (fallbackTheme != 0) {
      inflater =
          LayoutInflater.from(new FallbackThemeWrapper(inflater.getContext(), fallbackTheme));
    }
    return inflater.inflate(template, this, false);
  }

8.2.addView

init方法里会获取container,可以看到最终的child实际加到这个容器里了,这里完成了替换,

csharp 复制代码
  public void addView(View child, int index, ViewGroup.LayoutParams params) {
    container.addView(child, index, params);
  }

8.3.mixins

swift 复制代码
  private final Map<Class<? extends Mixin>, Mixin> mixins = new HashMap<>();

>1.registerMixin

数据注册见子类小节7.1

typescript 复制代码
  protected <M extends Mixin> void registerMixin(Class<M> cls, M mixin) {
    mixins.put(cls, mixin);
  }

2.getMixin

typescript 复制代码
  public <M extends Mixin> M getMixin(Class<M> cls) {
    return (M) mixins.get(cls);
  }

8.4.总结

  • 小节6到8都是GlifLayout布局相关,主要学习下它是如何替换内部child的
  • 主要就是内部自己加载了一套布局作为container,完事add到GlifLayout里,之后把FlifLayout里原本的child放到container里

9.FooterBarMixin

9.1.setPrimaryButton

scss 复制代码
  public void setPrimaryButton(FooterButton footerButton) {
    ensureOnMainThread("setPrimaryButton");
    ensureFooterInflated();

    // Setup button partner config
    FooterButtonPartnerConfig footerButtonPartnerConfig =
        new FooterButtonPartnerConfig.Builder(footerButton)
            .setPartnerTheme(
                getPartnerTheme(
                    footerButton,
                    /* defaultPartnerTheme= */ R.style.SucPartnerCustomizationButton_Primary,
                    /* buttonBackgroundColorConfig= */ PartnerConfig
                        .CONFIG_FOOTER_PRIMARY_BUTTON_BG_COLOR))
            .setButtonBackgroundConfig(PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_BG_COLOR)
            .setButtonDisableAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_ALPHA)
            .setButtonDisableBackgroundConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_BG_COLOR)
            .setButtonDisableTextColorConfig(
                PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_DISABLED_TEXT_COLOR)
            .setButtonIconConfig(getDrawablePartnerConfig(footerButton.getButtonType()))
            .setButtonRadiusConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RADIUS)
            .setButtonRippleColorAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RIPPLE_COLOR_ALPHA)
            .setTextColorConfig(PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_TEXT_COLOR)
            .setMarginStartConfig(PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_MARGIN_START)
            .setTextSizeConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_TEXT_SIZE)
            .setButtonMinHeight(PartnerConfig.CONFIG_FOOTER_BUTTON_MIN_HEIGHT)
            .setTextTypeFaceConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_FONT_FAMILY)
            .setTextStyleConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_TEXT_STYLE)
            .build();

    FooterActionButton button = inflateButton(footerButton, footerButtonPartnerConfig);
    // update information for primary button. Need to update as long as the button inflated.
    primaryButtonId = button.getId();
    button.setPrimaryButtonStyle(/* isPrimaryButtonStyle= */ true);
    primaryButton = footerButton;
    primaryButtonPartnerConfigForTesting = footerButtonPartnerConfig;
    onFooterButtonInflated(button, footerBarPrimaryBackgroundColor);
    onFooterButtonApplyPartnerResource(button, footerButtonPartnerConfig);

    // Make sure the position of buttons are correctly and prevent primary button create twice or
    // more.
    repopulateButtons();
  }

9.2.inflateButton

根据footerButton的配置,生成一个FooterActionButton

less 复制代码
  private FooterActionButton inflateButton(
      FooterButton footerButton, FooterButtonPartnerConfig footerButtonPartnerConfig) {
    FooterActionButton button =
        createThemedButton(context, footerButtonPartnerConfig.getPartnerTheme());
    if (Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) {
      button.setId(View.generateViewId());
    } else {
      button.setId(generateViewId());
    }

    // apply initial configuration into button view.
    button.setText(footerButton.getText());
    button.setOnClickListener(footerButton);
    button.setVisibility(footerButton.getVisibility());
    button.setEnabled(footerButton.isEnabled());
    button.setFooterButton(footerButton);

    footerButton.setOnButtonEventListener(createButtonEventListener(button.getId()));
    return button;
  }

9.3.onFooterButtonInflated

把按钮添加到容器里

less 复制代码
  protected void onFooterButtonInflated(Button button, @ColorInt int defaultButtonBackgroundColor) {
    
    if (defaultButtonBackgroundColor != 0) {
      FooterButtonStyleUtils.updateButtonBackground(button, defaultButtonBackgroundColor);
    }
    //见补充,看下容器哪里来的
    buttonContainer.addView(button);
    autoSetButtonBarVisibility();
  }

9.4.ensureFooterInflated

scss 复制代码
  private LinearLayout ensureFooterInflated() {
    if (buttonContainer == null) {
      if (footerStub == null) {
        throw new IllegalStateException("Footer stub is not found in this template");
      }
      //见补充2
      buttonContainer = (LinearLayout) inflateFooter(R.layout.suc_footer_button_bar);
      onFooterBarInflated(buttonContainer);
      onFooterBarApplyPartnerResource(buttonContainer);
    }
    return buttonContainer;
  }

>1.footerStub

less 复制代码
  public FooterBarMixin(
      TemplateLayout layout, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
    context = layout.getContext();
    //见6.2.3的布局末尾
    footerStub = layout.findManagedViewById(R.id.suc_layout_footer);

>2.inflateFooter

less 复制代码
  protected View inflateFooter(@LayoutRes int footer) {
    if (Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
      LayoutInflater inflater =
          LayoutInflater.from(
              new ContextThemeWrapper(context, R.style.SucPartnerCustomizationButtonBar_Stackable));
      footerStub.setLayoutInflater(inflater);
    }
    footerStub.setLayoutResource(footer);
    return footerStub.inflate();
  }

9.5.autoSetButtonBarVisibility

按钮的可见性处理

ini 复制代码
  private void autoSetButtonBarVisibility() {
    Button primaryButton = getPrimaryButtonView();
    Button secondaryButton = getSecondaryButtonView();
    boolean primaryVisible = primaryButton != null && primaryButton.getVisibility() == View.VISIBLE;
    boolean secondaryVisible =
        secondaryButton != null && secondaryButton.getVisibility() == View.VISIBLE;

    if (buttonContainer != null) {
      buttonContainer.setVisibility(
          primaryVisible || secondaryVisible
              ? View.VISIBLE
              : removeFooterBarWhenEmpty ? View.GONE : View.INVISIBLE);
    }
  }
相关推荐
zhangphil2 小时前
Android ValueAnimator ImageView animate() rotation,Kotlin
android·kotlin
徊忆羽菲2 小时前
CentOS7使用源码安装PHP8教程整理
android
编程、小哥哥3 小时前
python操作mysql
android·python
Couvrir洪荒猛兽4 小时前
Android实训十 数据存储和访问
android
五味香6 小时前
Java学习,List 元素替换
android·java·开发语言·python·学习·golang·kotlin
十二测试录7 小时前
【自动化测试】—— Appium使用保姆教程
android·经验分享·测试工具·程序人生·adb·appium·自动化
Couvrir洪荒猛兽8 小时前
Android实训九 数据存储和访问
android
aloneboyooo9 小时前
Android Studio安装配置
android·ide·android studio
Jacob程序员9 小时前
leaflet绘制室内平面图
android·开发语言·javascript
2401_8979078610 小时前
10天学会flutter DAY2 玩转dart 类
android·flutter