SettingsLib synced by Copybara

CollapsingToolbarBaseActivity
 - Add CollapsingToolbarAppCompatActivity to support AppCompatActivity
 - Adjust minSDK back to 21
MainSwitchPreference
 - support iconSpaceReserved before S

Test: manual & robotest
Change-Id: I184764c476cb0142a7f8c967fb09588d8ff6879d
This commit is contained in:
Edgar Wang
2022-12-22 16:24:41 +08:00
parent 37f3eda749
commit c4859863ec
15 changed files with 330 additions and 92 deletions

View File

@@ -18,6 +18,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.settingslib.widget">
<uses-sdk android:minSdkVersion="29" />
<uses-sdk android:minSdkVersion="21" />
</manifest>

View File

@@ -29,6 +29,12 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?android:attr/actionBarTheme" />
<androidx.appcompat.widget.Toolbar
android:id="@+id/support_action_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?android:attr/actionBarTheme"
android:visibility="gone" />
<FrameLayout
android:id="@+id/content_frame"
android:layout_width="match_parent"

View File

@@ -0,0 +1,179 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.collapsingtoolbar;
import android.app.ActionBar;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toolbar;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.android.settingslib.utils.BuildCompatUtils;
import com.android.settingslib.widget.R;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.appbar.CollapsingToolbarLayout;
import com.google.android.material.color.DynamicColors;
/**
* A base Activity that has a collapsing toolbar layout is used for the activities intending to
* enable the collapsing toolbar function.
*/
public class CollapsingToolbarAppCompatActivity extends AppCompatActivity {
private class DelegateCallback implements CollapsingToolbarDelegate.HostCallback {
@Nullable
@Override
public ActionBar setActionBar(Toolbar toolbar) {
return null;
}
@Nullable
@Override
public androidx.appcompat.app.ActionBar setActionBar(
androidx.appcompat.widget.Toolbar toolbar) {
CollapsingToolbarAppCompatActivity.super.setSupportActionBar(toolbar);
return CollapsingToolbarAppCompatActivity.super.getSupportActionBar();
}
@Override
public void setOuterTitle(CharSequence title) {
CollapsingToolbarAppCompatActivity.super.setTitle(title);
}
}
private CollapsingToolbarDelegate mToolbardelegate;
private int mCustomizeLayoutResId = 0;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (BuildCompatUtils.isAtLeastS()) {
DynamicColors.applyToActivityIfAvailable(this);
}
setTheme(R.style.Theme_SubSettingsBase);
if (mCustomizeLayoutResId > 0 && !BuildCompatUtils.isAtLeastS()) {
super.setContentView(mCustomizeLayoutResId);
return;
}
View view = getToolbarDelegate().onCreateView(getLayoutInflater(), null, this);
super.setContentView(view);
}
@Override
public void setContentView(int layoutResID) {
final ViewGroup parent = (mToolbardelegate == null) ? findViewById(R.id.content_frame)
: mToolbardelegate.getContentFrameLayout();
if (parent != null) {
parent.removeAllViews();
}
LayoutInflater.from(this).inflate(layoutResID, parent);
}
@Override
public void setContentView(View view) {
final ViewGroup parent = (mToolbardelegate == null) ? findViewById(R.id.content_frame)
: mToolbardelegate.getContentFrameLayout();
if (parent != null) {
parent.addView(view);
}
}
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
final ViewGroup parent = (mToolbardelegate == null) ? findViewById(R.id.content_frame)
: mToolbardelegate.getContentFrameLayout();
if (parent != null) {
parent.addView(view, params);
}
}
/**
* This method allows an activity to replace the default layout with a customize layout. Notice
* that it will no longer apply the features being provided by this class when this method
* gets called.
*/
protected void setCustomizeContentView(int layoutResId) {
mCustomizeLayoutResId = layoutResId;
}
@Override
public void setTitle(CharSequence title) {
getToolbarDelegate().setTitle(title);
}
@Override
public void setTitle(int titleId) {
setTitle(getText(titleId));
}
@Override
public boolean onSupportNavigateUp() {
if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
getSupportFragmentManager().popBackStackImmediate();
}
// Closes the activity if there is no fragment inside the stack. Otherwise the activity will
// has a blank screen since there is no any fragment.
if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
finishAfterTransition();
}
return true;
}
@Override
public void onBackPressed() {
super.onBackPressed();
// Closes the activity if there is no fragment inside the stack. Otherwise the activity will
// has a blank screen since there is no any fragment. onBackPressed() in Activity.java only
// handles popBackStackImmediate(). This will close activity to avoid a blank screen.
if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
finishAfterTransition();
}
}
/**
* Returns an instance of collapsing toolbar.
*/
@Nullable
public CollapsingToolbarLayout getCollapsingToolbarLayout() {
return getToolbarDelegate().getCollapsingToolbarLayout();
}
/**
* Return an instance of app bar.
*/
@Nullable
public AppBarLayout getAppBarLayout() {
return getToolbarDelegate().getAppBarLayout();
}
private CollapsingToolbarDelegate getToolbarDelegate() {
if (mToolbardelegate == null) {
mToolbardelegate = new CollapsingToolbarDelegate(new DelegateCallback());
}
return mToolbardelegate;
}
}

View File

@@ -117,12 +117,30 @@ public class CollapsingToolbarBaseActivity extends FragmentActivity {
@Override
public boolean onNavigateUp() {
if (!super.onNavigateUp()) {
if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
getSupportFragmentManager().popBackStackImmediate();
}
// Closes the activity if there is no fragment inside the stack. Otherwise the activity will
// has a blank screen since there is no any fragment.
if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
finishAfterTransition();
}
return true;
}
@Override
public void onBackPressed() {
super.onBackPressed();
// Closes the activity if there is no fragment inside the stack. Otherwise the activity will
// has a blank screen since there is no any fragment. onBackPressed() in Activity.java only
// handles popBackStackImmediate(). This will close activity to avoid a blank screen.
if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
finishAfterTransition();
}
}
/**
* Returns an instance of collapsing toolbar.
*/

View File

@@ -19,9 +19,11 @@ package com.android.settingslib.collapsingtoolbar;
import static android.text.Layout.HYPHENATION_FREQUENCY_NORMAL_FAST;
import android.app.ActionBar;
import android.app.Activity;
import android.content.res.Configuration;
import android.graphics.text.LineBreakConfig;
import android.os.Build;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -30,6 +32,7 @@ import android.widget.Toolbar;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import com.android.settingslib.widget.R;
@@ -42,7 +45,7 @@ import com.google.android.material.appbar.CollapsingToolbarLayout;
* extend from {@link CollapsingToolbarBaseActivity} or from {@link CollapsingToolbarBaseFragment}.
*/
public class CollapsingToolbarDelegate {
private static final String TAG = "CTBdelegate";
/** Interface to be implemented by the host of the Collapsing Toolbar. */
public interface HostCallback {
/**
@@ -53,6 +56,13 @@ public class CollapsingToolbarDelegate {
@Nullable
ActionBar setActionBar(Toolbar toolbar);
/** Sets support tool bar and return support action bar, this is for AppCompatActivity. */
@Nullable
default androidx.appcompat.app.ActionBar setActionBar(
androidx.appcompat.widget.Toolbar toolbar) {
return null;
}
/** Sets a title on the host. */
void setOuterTitle(CharSequence title);
}
@@ -79,6 +89,13 @@ public class CollapsingToolbarDelegate {
/** Method to call that creates the root view of the collapsing toolbar. */
@SuppressWarnings("RestrictTo")
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container) {
return onCreateView(inflater, container, null);
}
/** Method to call that creates the root view of the collapsing toolbar. */
@SuppressWarnings("RestrictTo")
View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
Activity activity) {
final View view =
inflater.inflate(R.layout.collapsing_toolbar_base_layout, container, false);
if (view instanceof CoordinatorLayout) {
@@ -99,17 +116,57 @@ public class CollapsingToolbarDelegate {
}
}
autoSetCollapsingToolbarLayoutScrolling();
mToolbar = view.findViewById(R.id.action_bar);
mContentFrameLayout = view.findViewById(R.id.content_frame);
final ActionBar actionBar = mHostCallback.setActionBar(mToolbar);
if (activity instanceof AppCompatActivity) {
Log.d(TAG, "onCreateView: from AppCompatActivity and sub-class.");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
initSupportActionBar(inflater);
} else {
initRSupportActionBar(view);
}
} else {
Log.d(TAG, "onCreateView: from NonAppCompatActivity.");
mToolbar = view.findViewById(R.id.action_bar);
final ActionBar actionBar = mHostCallback.setActionBar(mToolbar);
// Enable title and home button by default
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setHomeButtonEnabled(true);
actionBar.setDisplayShowTitleEnabled(true);
}
}
return view;
}
// Enable title and home button by default
private void initSupportActionBar(@NonNull LayoutInflater inflater) {
if (mCollapsingToolbarLayout == null) {
return;
}
mCollapsingToolbarLayout.removeAllViews();
inflater.inflate(R.layout.support_toolbar, mCollapsingToolbarLayout);
final androidx.appcompat.widget.Toolbar supportToolbar =
mCollapsingToolbarLayout.findViewById(R.id.support_action_bar);
final androidx.appcompat.app.ActionBar actionBar =
mHostCallback.setActionBar(supportToolbar);
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setHomeButtonEnabled(true);
actionBar.setDisplayShowTitleEnabled(true);
}
}
private void initRSupportActionBar(View view) {
view.findViewById(R.id.action_bar).setVisibility(View.GONE);
final androidx.appcompat.widget.Toolbar supportToolbar =
view.findViewById(R.id.support_action_bar);
supportToolbar.setVisibility(View.VISIBLE);
final androidx.appcompat.app.ActionBar actionBar =
mHostCallback.setActionBar(supportToolbar);
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setHomeButtonEnabled(true);
actionBar.setDisplayShowTitleEnabled(true);
}
return view;
}
/** Return an instance of CoordinatorLayout. */
@@ -160,9 +217,13 @@ public class CollapsingToolbarDelegate {
new AppBarLayout.Behavior.DragCallback() {
@Override
public boolean canDrag(@NonNull AppBarLayout appBarLayout) {
// Header can be scrolling while device in landscape mode.
return appBarLayout.getResources().getConfiguration().orientation
== Configuration.ORIENTATION_LANDSCAPE;
// Header can be scrolling while device in landscape mode and SDK > 33
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.TIRAMISU) {
return false;
} else {
return appBarLayout.getResources().getConfiguration().orientation
== Configuration.ORIENTATION_LANDSCAPE;
}
}
});
params.setBehavior(behavior);

View File

@@ -49,7 +49,7 @@ import com.google.android.material.appbar.CollapsingToolbarLayout;
*/
@RequiresApi(Build.VERSION_CODES.S)
public class CollapsingCoordinatorLayout extends CoordinatorLayout {
private static final String TAG = "CollapsingCoordinatorLayout";
private static final String TAG = "CollapsingCoordinator";
private static final float TOOLBAR_LINE_SPACING_MULTIPLIER = 1.1f;
private CharSequence mToolbarTitle;
@@ -255,9 +255,13 @@ public class CollapsingCoordinatorLayout extends CoordinatorLayout {
new AppBarLayout.Behavior.DragCallback() {
@Override
public boolean canDrag(@NonNull AppBarLayout appBarLayout) {
// Header can be scrolling while device in landscape mode.
return appBarLayout.getResources().getConfiguration().orientation
== Configuration.ORIENTATION_LANDSCAPE;
// Header can be scrolling while device in landscape mode and SDK > 33
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.TIRAMISU) {
return false;
} else {
return appBarLayout.getResources().getConfiguration().orientation
== Configuration.ORIENTATION_LANDSCAPE;
}
}
});
params.setBehavior(behavior);

View File

@@ -18,7 +18,11 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content"
android:layout_width="match_parent">
android:layout_width="match_parent"
android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingRight="?android:attr/listPreferredItemPaddingRight"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
<TextView
android:id="@+id/switch_text"
@@ -50,7 +54,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginEnd="@dimen/settingslib_switchbar_subsettings_margin_end"
android:focusable="false"
android:clickable="false"
android:theme="@style/SwitchBar.Switch.Settingslib"/>

View File

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2021 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<!-- SwitchBar sub settings margin start / end -->
<dimen name="settingslib_switchbar_subsettings_margin_start">80dp</dimen>
</resources>

View File

@@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2021 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<!-- SwitchBar sub settings margin start / end -->
<dimen name="settingslib_switchbar_subsettings_margin_start">128dp</dimen>
<dimen name="settingslib_switchbar_subsettings_margin_end">128dp</dimen>
</resources>

View File

@@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2021 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<!-- SwitchBar sub settings margin start / end -->
<dimen name="settingslib_switchbar_subsettings_margin_start">80dp</dimen>
<dimen name="settingslib_switchbar_subsettings_margin_end">80dp</dimen>
</resources>

View File

@@ -27,6 +27,6 @@
<dimen name="settingslib_switch_title_margin">24dp</dimen>
<!-- SwitchBar sub settings margin start / end -->
<dimen name="settingslib_switchbar_subsettings_margin_start">72dp</dimen>
<dimen name="settingslib_switchbar_subsettings_margin_start">56dp</dimen>
<dimen name="settingslib_switchbar_subsettings_margin_end">16dp</dimen>
</resources>

View File

@@ -160,6 +160,19 @@ public class MainSwitchBar extends LinearLayout implements CompoundButton.OnChec
}
}
/**
* Set icon space reserved for title
*/
public void setIconSpaceReserved(boolean iconSpaceReserved) {
if (mTextView != null && !BuildCompatUtils.isAtLeastS()) {
LayoutParams params = (LayoutParams) mTextView.getLayoutParams();
int iconSpace = getContext().getResources().getDimensionPixelSize(
R.dimen.settingslib_switchbar_subsettings_margin_start);
params.setMarginStart(iconSpaceReserved ? iconSpace : 0);
mTextView.setLayoutParams(params);
}
}
/**
* Show the MainSwitchBar
*/

View File

@@ -37,7 +37,6 @@ public class MainSwitchPreference extends TwoStatePreference implements OnMainSw
private final List<OnMainSwitchChangeListener> mSwitchChangeListeners = new ArrayList<>();
private MainSwitchBar mMainSwitchBar;
private CharSequence mTitle;
public MainSwitchPreference(Context context) {
super(context);
@@ -68,6 +67,10 @@ public class MainSwitchPreference extends TwoStatePreference implements OnMainSw
holder.setDividerAllowedBelow(false);
mMainSwitchBar = (MainSwitchBar) holder.findViewById(R.id.settingslib_main_switch_bar);
// To support onPreferenceChange callback, it needs to call callChangeListener() when
// MainSwitchBar is clicked.
mMainSwitchBar.setOnClickListener((view) -> callChangeListener(isChecked()));
setIconSpaceReserved(isIconSpaceReserved());
updateStatus(isChecked());
registerListenerToSwitchBar();
}
@@ -82,6 +85,10 @@ public class MainSwitchPreference extends TwoStatePreference implements OnMainSw
final CharSequence title = a.getText(
androidx.preference.R.styleable.Preference_android_title);
setTitle(title);
final boolean bIconSpaceReserved = a.getBoolean(
androidx.preference.R.styleable.Preference_android_iconSpaceReserved, true);
setIconSpaceReserved(bIconSpaceReserved);
a.recycle();
}
}
@@ -96,9 +103,17 @@ public class MainSwitchPreference extends TwoStatePreference implements OnMainSw
@Override
public void setTitle(CharSequence title) {
mTitle = title;
super.setTitle(title);
if (mMainSwitchBar != null) {
mMainSwitchBar.setTitle(mTitle);
mMainSwitchBar.setTitle(title);
}
}
@Override
public void setIconSpaceReserved(boolean iconSpaceReserved) {
super.setIconSpaceReserved(iconSpaceReserved);
if (mMainSwitchBar != null) {
mMainSwitchBar.setIconSpaceReserved(iconSpaceReserved);
}
}
@@ -113,7 +128,7 @@ public class MainSwitchPreference extends TwoStatePreference implements OnMainSw
public void updateStatus(boolean checked) {
setChecked(checked);
if (mMainSwitchBar != null) {
mMainSwitchBar.setTitle(mTitle);
mMainSwitchBar.setTitle(getTitle());
mMainSwitchBar.show();
}
}
@@ -125,6 +140,7 @@ public class MainSwitchPreference extends TwoStatePreference implements OnMainSw
if (!mSwitchChangeListeners.contains(listener)) {
mSwitchChangeListeners.add(listener);
}
if (mMainSwitchBar != null) {
mMainSwitchBar.addOnSwitchChangeListener(listener);
}

View File

@@ -17,6 +17,7 @@
<resources>
<style name="PreferenceTheme.SettingsLib" parent="@style/PreferenceThemeOverlay">
<item name="preferenceCategoryTitleTextAppearance">@style/TextAppearance.CategoryTitle.SettingsLib</item>
<item name="preferenceScreenStyle">@style/SettingsPreferenceScreen.SettingsLib</item>
<item name="preferenceCategoryStyle">@style/SettingsCategoryPreference.SettingsLib</item>
<item name="preferenceStyle">@style/SettingsPreference.SettingsLib</item>
<item name="checkBoxPreferenceStyle">@style/SettingsCheckBoxPreference.SettingsLib</item>
@@ -28,6 +29,11 @@
<item name="footerPreferenceStyle">@style/Preference.Material</item>
</style>
<style name="SettingsPreferenceScreen.SettingsLib" parent="@style/Preference.PreferenceScreen.Material">
<item name="layout">@layout/settingslib_preference</item>
<item name="iconSpaceReserved">@bool/settingslib_config_icon_space_reserved</item>
</style>
<style name="SettingsCategoryPreference.SettingsLib" parent="@style/Preference.Category.Material">
<item name="iconSpaceReserved">@bool/settingslib_config_icon_space_reserved</item>
<item name="allowDividerAbove">@bool/settingslib_config_allow_divider</item>

View File

@@ -28,19 +28,19 @@
</style>
<style name="TextAppearance.TopIntroText"
parent="@android:style/TextAppearance.DeviceDefault">
parent="@android:style/TextAppearance.DeviceDefault">
<item name="android:textSize">14sp</item>
<item name="android:textColor">?android:attr/textColorSecondary</item>
</style>
<style name="TextAppearance.EntityHeaderTitle"
parent="@android:style/TextAppearance.DeviceDefault.WindowTitle">
parent="@android:style/TextAppearance.DeviceDefault.WindowTitle">
<item name="android:textColor">?android:attr/textColorPrimary</item>
<item name="android:textSize">20sp</item>
</style>
<style name="TextAppearance.EntityHeaderSummary"
parent="@android:style/TextAppearance.DeviceDefault">
parent="@android:style/TextAppearance.DeviceDefault">
<item name="android:textAlignment">viewStart</item>
<item name="android:textColor">?android:attr/textColorSecondary</item>
<item name="android:singleLine">true</item>