Refine layouts for large screen

- Support dynamic paddings depending on app's screen width
- Add round corners to homepage ripple effect to improve the transition
  of being highlighted
- Add an interface to support dynamic split layout for suggestion cards

Bug: 223300824
Test: robotest, manual
Change-Id: Iaca6b4fd3f7369179416ef084a800d7eb2ee4640
This commit is contained in:
Jason Chiu
2022-03-24 16:58:31 +08:00
parent c4c923d1eb
commit 680fce3acd
17 changed files with 441 additions and 98 deletions

View File

@@ -14,13 +14,10 @@
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
--> -->
<inset xmlns:android="http://schemas.android.com/apk/res/android" <shape xmlns:android="http://schemas.android.com/apk/res/android"
android:insetLeft="@dimen/homepage_menu_entry_padding_horizontal" android:shape="rectangle">
android:insetRight="@dimen/homepage_menu_entry_padding_horizontal">
<shape android:shape="rectangle">
<solid <solid
android:color="?android:attr/textColorPrimary" /> android:color="?android:attr/textColorPrimary" />
<corners <corners
android:radius="@dimen/homepage_menu_entry_corner_radius" /> android:radius="@dimen/homepage_preference_corner_radius" />
</shape> </shape>
</inset>

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
-->
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?android:attr/colorControlHighlight">
<item android:id="@android:id/mask">
<shape android:shape="rectangle">
<solid
android:color="@android:color/white" />
<corners
android:radius="@dimen/homepage_preference_corner_radius" />
</shape>
</item>
</ripple>

View File

@@ -20,10 +20,8 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeightSmall" android:minHeight="@dimen/homepage_preference_min_height"
android:gravity="center_vertical" android:gravity="center_vertical"
android:paddingStart="@dimen/homepage_menu_entry_padding_horizontal"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:background="?android:attr/selectableItemBackground" android:background="?android:attr/selectableItemBackground"
android:clipToPadding="false" android:clipToPadding="false"
android:baselineAligned="false"> android:baselineAligned="false">
@@ -32,11 +30,10 @@
android:id="@+id/icon_frame" android:id="@+id/icon_frame"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:minWidth="56dp" android:minWidth="48dp"
android:gravity="center" android:gravity="end|center_vertical"
android:orientation="horizontal" android:orientation="horizontal"
android:paddingStart="8dp" android:paddingStart="@dimen/homepage_preference_icon_padding_start"
android:paddingEnd="8dp"
android:paddingTop="4dp" android:paddingTop="4dp"
android:paddingBottom="4dp"> android:paddingBottom="4dp">
@@ -44,18 +41,20 @@
android:id="@android:id/icon" android:id="@android:id/icon"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:maxWidth="40dp" app:maxWidth="48dp"
app:maxHeight="40dp"/> app:maxHeight="48dp"/>
</LinearLayout> </LinearLayout>
<RelativeLayout <RelativeLayout
android:id="@+id/text_frame"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:paddingTop="16dp" android:paddingTop="16dp"
android:paddingBottom="16dp" android:paddingBottom="16dp"
android:paddingEnd="16dp"> android:paddingStart="@dimen/homepage_preference_text_padding_start"
android:paddingEnd="24dp">
<TextView <TextView
android:id="@android:id/title" android:id="@android:id/title"

View File

@@ -31,7 +31,8 @@
android:id="@+id/search_action_bar" android:id="@+id/search_action_bar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/search_bar_height" android:layout_height="@dimen/search_bar_height"
android:paddingStart="4dp" android:paddingStart="@dimen/search_bar_padding_start"
android:paddingEnd="@dimen/search_bar_padding_end"
android:background="@drawable/search_bar_selected_background" android:background="@drawable/search_bar_selected_background"
android:contentInsetStartWithNavigation="@dimen/search_bar_content_inset" android:contentInsetStartWithNavigation="@dimen/search_bar_content_inset"
android:navigationIcon="@drawable/ic_homepage_search"> android:navigationIcon="@drawable/ic_homepage_search">
@@ -40,7 +41,7 @@
style="@style/TextAppearance.SearchBar" style="@style/TextAppearance.SearchBar"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingStart="-4dp" android:paddingStart="@dimen/search_bar_title_padding_start"
android:layout_gravity="start" android:layout_gravity="start"
android:text="@string/search_menu"/> android:text="@string/search_menu"/>
</Toolbar> </Toolbar>

View File

@@ -21,20 +21,23 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:layout_margin="@dimen/search_bar_margin"> android:layout_marginEnd="@dimen/search_bar_margin"
android:layout_marginVertical="@dimen/search_bar_margin">
<Toolbar <Toolbar
android:id="@+id/search_action_bar_two_pane" android:id="@+id/search_action_bar_two_pane"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/search_bar_height" android:layout_height="@dimen/search_bar_height"
android:paddingStart="4dp" android:paddingStart="@dimen/search_bar_padding_start_two_pane"
android:paddingEnd="@dimen/search_bar_padding_end_two_pane"
android:background="@drawable/search_bar_selected_background" android:background="@drawable/search_bar_selected_background"
android:contentInsetStartWithNavigation="@dimen/search_bar_content_inset" android:contentInsetStartWithNavigation="@dimen/search_bar_content_inset"
android:navigationIcon="@drawable/ic_homepage_search"> android:navigationIcon="@drawable/ic_homepage_search">
<TextView <TextView
android:id="@+id/search_bar_title"
style="@style/TextAppearance.SearchBar" style="@style/TextAppearance.SearchBar"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingStart="-4dp" android:paddingStart="@dimen/search_bar_title_padding_start_regular_two_pane"
android:layout_gravity="start" android:layout_gravity="start"
android:text="@string/search_menu"/> android:text="@string/search_menu"/>
</Toolbar> </Toolbar>

View File

@@ -20,7 +20,6 @@
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:background="?androidprv:attr/colorSurface" android:background="?androidprv:attr/colorSurface"
android:orientation="vertical"> android:orientation="vertical">
@@ -41,7 +40,6 @@
android:layout_width="@dimen/avatar_length" android:layout_width="@dimen/avatar_length"
android:layout_height="@dimen/avatar_length" android:layout_height="@dimen/avatar_length"
android:layout_gravity="center" android:layout_gravity="center"
android:layout_marginEnd="16dp"
android:contentDescription="@string/search_bar_account_avatar_content_description"/> android:contentDescription="@string/search_bar_account_avatar_content_description"/>
</LinearLayout> </LinearLayout>

View File

@@ -35,9 +35,6 @@
<!-- SwitchBar sub settings margin start / end --> <!-- SwitchBar sub settings margin start / end -->
<dimen name="switchbar_subsettings_margin_start">80dp</dimen> <dimen name="switchbar_subsettings_margin_start">80dp</dimen>
<dimen name="search_bar_height">64dp</dimen>
<dimen name="search_bar_half_height">32dp</dimen>
<!-- Dimensions for Wifi Assistant Card --> <!-- Dimensions for Wifi Assistant Card -->
<dimen name="wifi_assistant_padding_top_bottom">24dp</dimen> <dimen name="wifi_assistant_padding_top_bottom">24dp</dimen>
<dimen name="wifi_assistant_padding_start_end">24dp</dimen> <dimen name="wifi_assistant_padding_start_end">24dp</dimen>

View File

@@ -129,6 +129,12 @@
<dimen name="search_bar_text_size">20sp</dimen> <dimen name="search_bar_text_size">20sp</dimen>
<dimen name="search_bar_corner_radius">28dp</dimen> <dimen name="search_bar_corner_radius">28dp</dimen>
<dimen name="search_bar_content_inset">56dp</dimen> <dimen name="search_bar_content_inset">56dp</dimen>
<dimen name="search_bar_padding_start">4dp</dimen>
<dimen name="search_bar_padding_start_two_pane">8dp</dimen>
<dimen name="search_bar_padding_end">16dp</dimen>
<dimen name="search_bar_padding_end_two_pane">24dp</dimen>
<dimen name="search_bar_title_padding_start">-4dp</dimen>
<dimen name="search_bar_title_padding_start_regular_two_pane">8dp</dimen>
<!-- Avatar --> <!-- Avatar -->
<dimen name="avatar_length">48dp</dimen> <dimen name="avatar_length">48dp</dimen>
@@ -136,13 +142,16 @@
<dimen name="avatar_margin_end">24dp</dimen> <dimen name="avatar_margin_end">24dp</dimen>
<dimen name="multiple_users_avatar_size">20dp</dimen> <dimen name="multiple_users_avatar_size">20dp</dimen>
<!-- Homepage title --> <!-- Homepage -->
<dimen name="homepage_title_margin_bottom">8dp</dimen> <dimen name="homepage_title_margin_bottom">8dp</dimen>
<dimen name="homepage_title_margin_horizontal">24dp</dimen> <dimen name="homepage_title_margin_horizontal">24dp</dimen>
<dimen name="homepage_padding_horizontal_two_pane">24dp</dimen>
<!-- Homepage menu entry --> <dimen name="homepage_preference_corner_radius">28dp</dimen>
<dimen name="homepage_menu_entry_padding_horizontal">16dp</dimen> <dimen name="homepage_preference_min_height">88sp</dimen>
<dimen name="homepage_menu_entry_corner_radius">28dp</dimen> <dimen name="homepage_preference_icon_padding_start">32dp</dimen>
<dimen name="homepage_preference_icon_padding_start_two_pane">8dp</dimen>
<dimen name="homepage_preference_text_padding_start">16dp</dimen>
<dimen name="homepage_preference_text_padding_start_two_pane">24dp</dimen>
<!-- Dimensions for Wifi Assistant Card --> <!-- Dimensions for Wifi Assistant Card -->
<dimen name="wifi_assistant_padding_top_bottom">16dp</dimen> <dimen name="wifi_assistant_padding_top_bottom">16dp</dimen>

View File

@@ -16,6 +16,7 @@
package com.android.settings.activityembedding; package com.android.settings.activityembedding;
import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
import android.util.FeatureFlagUtils; import android.util.FeatureFlagUtils;
@@ -33,6 +34,8 @@ public class ActivityEmbeddingUtils {
// The smallest value of the smallest-width (sw) of the window in any rotation when // The smallest value of the smallest-width (sw) of the window in any rotation when
// the split should be used. // the split should be used.
private static final float MIN_SMALLEST_SCREEN_SPLIT_WIDTH_DP = 600f; private static final float MIN_SMALLEST_SCREEN_SPLIT_WIDTH_DP = 600f;
// The minimum width of the activity to show the regular homepage layout.
private static final float MIN_REGULAR_HOMEPAGE_LAYOUT_WIDTH_DP = 380f;
private static final String TAG = "ActivityEmbeddingUtils"; private static final String TAG = "ActivityEmbeddingUtils";
/** Get the smallest pixel value of width of the window when the split should be used. */ /** Get the smallest pixel value of width of the window when the split should be used. */
@@ -71,4 +74,11 @@ public class ActivityEmbeddingUtils {
return isFlagEnabled && isSplitSupported; return isFlagEnabled && isSplitSupported;
} }
/** Whether to show the regular or simplified homepage layout. */
public static boolean isRegularHomepageLayout(Activity activity) {
DisplayMetrics dm = activity.getResources().getDisplayMetrics();
return dm.widthPixels >= (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, MIN_REGULAR_HOMEPAGE_LAYOUT_WIDTH_DP, dm);
}
} }

View File

@@ -86,6 +86,7 @@ public class SettingsHomepageActivity extends FragmentActivity implements
private TopLevelSettings mMainFragment; private TopLevelSettings mMainFragment;
private View mHomepageView; private View mHomepageView;
private View mAppBar;
private View mSuggestionView; private View mSuggestionView;
private View mTwoPaneSuggestionView; private View mTwoPaneSuggestionView;
private CategoryMixin mCategoryMixin; private CategoryMixin mCategoryMixin;
@@ -93,6 +94,8 @@ public class SettingsHomepageActivity extends FragmentActivity implements
private SplitController mSplitController; private SplitController mSplitController;
private boolean mIsEmbeddingActivityEnabled; private boolean mIsEmbeddingActivityEnabled;
private boolean mIsTwoPane; private boolean mIsTwoPane;
// A regular layout shows icons on homepage, whereas a simplified layout doesn't.
private boolean mIsRegularLayout = true;
/** A listener receiving homepage loaded events. */ /** A listener receiving homepage loaded events. */
public interface HomepageLoadedListener { public interface HomepageLoadedListener {
@@ -100,8 +103,11 @@ public class SettingsHomepageActivity extends FragmentActivity implements
void onHomepageLoaded(); void onHomepageLoaded();
} }
private interface FragmentBuilder<T extends Fragment> { private interface FragmentCreator<T extends Fragment> {
T build(); T create();
/** To initialize after {@link #create} */
default void init(Fragment fragment) {}
} }
/** /**
@@ -145,6 +151,11 @@ public class SettingsHomepageActivity extends FragmentActivity implements
return mMainFragment; return mMainFragment;
} }
/** Whether the activity is showing in two-pane */
public boolean isTwoPane() {
return mIsTwoPane;
}
@Override @Override
public CategoryMixin getCategoryMixin() { public CategoryMixin getCategoryMixin() {
return mCategoryMixin; return mCategoryMixin;
@@ -160,8 +171,8 @@ public class SettingsHomepageActivity extends FragmentActivity implements
mSplitController = SplitController.getInstance(); mSplitController = SplitController.getInstance();
mIsTwoPane = mSplitController.isActivityEmbedded(this); mIsTwoPane = mSplitController.isActivityEmbedded(this);
final View appBar = findViewById(R.id.app_bar_container); mAppBar = findViewById(R.id.app_bar_container);
appBar.setMinimumHeight(getSearchBoxHeight()); mAppBar.setMinimumHeight(getSearchBoxHeight());
initHomepageContainer(); initHomepageContainer();
updateHomepageAppBar(); updateHomepageAppBar();
updateHomepageBackground(); updateHomepageBackground();
@@ -195,6 +206,8 @@ public class SettingsHomepageActivity extends FragmentActivity implements
// Launch the intent from deep link for large screen devices. // Launch the intent from deep link for large screen devices.
launchDeepLinkIntentToRight(); launchDeepLinkIntentToRight();
updateHomepagePaddings();
updateSplitLayout();
} }
@Override @Override
@@ -226,7 +239,42 @@ public class SettingsHomepageActivity extends FragmentActivity implements
mIsTwoPane = newTwoPaneState; mIsTwoPane = newTwoPaneState;
updateHomepageAppBar(); updateHomepageAppBar();
updateHomepageBackground(); updateHomepageBackground();
updateHomepagePaddings();
} }
updateSplitLayout();
}
private void updateSplitLayout() {
if (!mIsEmbeddingActivityEnabled) {
return;
}
if (mIsTwoPane) {
if (mIsRegularLayout == ActivityEmbeddingUtils.isRegularHomepageLayout(this)) {
// Layout unchanged
return;
}
} else if (mIsRegularLayout) {
// One pane mode with the regular layout, not needed to change
return;
}
mIsRegularLayout = !mIsRegularLayout;
// Update search title padding
View searchTitle = findViewById(R.id.search_bar_title);
if (searchTitle != null) {
int paddingStart = getResources().getDimensionPixelSize(
mIsRegularLayout
? R.dimen.search_bar_title_padding_start_regular_two_pane
: R.dimen.search_bar_title_padding_start);
searchTitle.setPaddingRelative(paddingStart, 0, 0, 0);
}
// Notify fragments
getSupportFragmentManager().getFragments().forEach(fragment -> {
if (fragment instanceof SplitLayoutListener) {
((SplitLayoutListener) fragment).onSplitLayoutChanged(mIsRegularLayout);
}
});
} }
private void setupEdgeToEdge() { private void setupEdgeToEdge() {
@@ -303,29 +351,25 @@ public class SettingsHomepageActivity extends FragmentActivity implements
// Schedule a timer to show the homepage and hide the suggestion on timeout. // Schedule a timer to show the homepage and hide the suggestion on timeout.
mHomepageView.postDelayed(() -> showHomepageWithSuggestion(false), mHomepageView.postDelayed(() -> showHomepageWithSuggestion(false),
HOMEPAGE_LOADING_TIMEOUT_MS); HOMEPAGE_LOADING_TIMEOUT_MS);
final FragmentBuilder<?> fragmentBuilder = () -> { showFragment(new SuggestionFragCreator(fragmentClass, /* isTwoPaneLayout= */ false),
try { R.id.suggestion_content);
return fragmentClass.getConstructor().newInstance();
} catch (Exception e) {
Log.w(TAG, "Cannot show fragment", e);
}
return null;
};
showFragment(fragmentBuilder, R.id.suggestion_content);
if (mIsEmbeddingActivityEnabled) { if (mIsEmbeddingActivityEnabled) {
showFragment(fragmentBuilder, R.id.two_pane_suggestion_content); showFragment(new SuggestionFragCreator(fragmentClass, /* isTwoPaneLayout= */ true),
R.id.two_pane_suggestion_content);
} }
} }
private <T extends Fragment> T showFragment(FragmentBuilder<T> fragmentBuilder, int id) { private <T extends Fragment> T showFragment(FragmentCreator<T> fragmentCreator, int id) {
final FragmentManager fragmentManager = getSupportFragmentManager(); final FragmentManager fragmentManager = getSupportFragmentManager();
final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
T showFragment = (T) fragmentManager.findFragmentById(id); T showFragment = (T) fragmentManager.findFragmentById(id);
if (showFragment == null) { if (showFragment == null) {
showFragment = fragmentBuilder.build(); showFragment = fragmentCreator.create();
fragmentCreator.init(showFragment);
fragmentTransaction.add(id, showFragment); fragmentTransaction.add(id, showFragment);
} else { } else {
fragmentCreator.init(showFragment);
fragmentTransaction.show(showFragment); fragmentTransaction.show(showFragment);
} }
fragmentTransaction.commit(); fragmentTransaction.commit();
@@ -447,9 +491,54 @@ public class SettingsHomepageActivity extends FragmentActivity implements
} }
} }
private void updateHomepagePaddings() {
if (!mIsEmbeddingActivityEnabled) {
return;
}
if (mIsTwoPane) {
int padding = getResources().getDimensionPixelSize(
R.dimen.homepage_padding_horizontal_two_pane);
mAppBar.setPaddingRelative(padding, 0, padding, 0);
mMainFragment.setPaddingHorizontal(padding);
} else {
mAppBar.setPaddingRelative(0, 0, 0, 0);
mMainFragment.setPaddingHorizontal(0);
}
mMainFragment.updatePreferencePadding(mIsTwoPane);
}
private int getSearchBoxHeight() { private int getSearchBoxHeight() {
final int searchBarHeight = getResources().getDimensionPixelSize(R.dimen.search_bar_height); final int searchBarHeight = getResources().getDimensionPixelSize(R.dimen.search_bar_height);
final int searchBarMargin = getResources().getDimensionPixelSize(R.dimen.search_bar_margin); final int searchBarMargin = getResources().getDimensionPixelSize(R.dimen.search_bar_margin);
return searchBarHeight + searchBarMargin * 2; return searchBarHeight + searchBarMargin * 2;
} }
private static class SuggestionFragCreator implements FragmentCreator {
private final Class<? extends Fragment> mClass;
private final boolean mIsTwoPaneLayout;
SuggestionFragCreator(Class<? extends Fragment> clazz, boolean isTwoPaneLayout) {
mClass = clazz;
mIsTwoPaneLayout = isTwoPaneLayout;
}
@Override
public Fragment create() {
try {
Fragment fragment = mClass.getConstructor().newInstance();
return fragment;
} catch (Exception e) {
Log.w(TAG, "Cannot show fragment", e);
}
return null;
}
@Override
public void init(Fragment fragment) {
if (fragment instanceof SplitLayoutListener) {
((SplitLayoutListener) fragment).setSplitLayoutSupported(mIsTwoPaneLayout);
}
}
}
} }

View File

@@ -0,0 +1,34 @@
/*
* 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.settings.homepage;
/** A listener receiving the spilt layout change */
public interface SplitLayoutListener {
/**
* Called when the spilt layout is changed.
*
* @param isRegularLayout whether the layout should be regular or simplified
*/
void onSplitLayoutChanged(boolean isRegularLayout);
/**
* Notifies the listener whether the split layout is supported.
*/
default void setSplitLayoutSupported(boolean supported) {
}
}

View File

@@ -27,6 +27,8 @@ import android.graphics.drawable.Drawable;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.preference.Preference; import androidx.preference.Preference;
@@ -44,12 +46,13 @@ import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.support.SupportPreferenceController; import com.android.settings.support.SupportPreferenceController;
import com.android.settings.widget.HomepagePreference; import com.android.settings.widget.HomepagePreference;
import com.android.settings.widget.HomepagePreferenceLayoutHelper.HomepagePreferenceLayout;
import com.android.settingslib.core.instrumentation.Instrumentable; import com.android.settingslib.core.instrumentation.Instrumentable;
import com.android.settingslib.drawer.Tile; import com.android.settingslib.drawer.Tile;
import com.android.settingslib.search.SearchIndexable; import com.android.settingslib.search.SearchIndexable;
@SearchIndexable(forTarget = MOBILE) @SearchIndexable(forTarget = MOBILE)
public class TopLevelSettings extends DashboardFragment implements public class TopLevelSettings extends DashboardFragment implements SplitLayoutListener,
PreferenceFragmentCompat.OnPreferenceStartFragmentCallback { PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
private static final String TAG = "TopLevelSettings"; private static final String TAG = "TopLevelSettings";
@@ -58,6 +61,7 @@ public class TopLevelSettings extends DashboardFragment implements
private boolean mIsEmbeddingActivityEnabled; private boolean mIsEmbeddingActivityEnabled;
private TopLevelHighlightMixin mHighlightMixin; private TopLevelHighlightMixin mHighlightMixin;
private int mPaddingHorizontal;
private boolean mScrollNeeded = true; private boolean mScrollNeeded = true;
private boolean mFirstStarted = true; private boolean mFirstStarted = true;
@@ -177,23 +181,13 @@ public class TopLevelSettings extends DashboardFragment implements
@Override @Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
super.onCreatePreferences(savedInstanceState, rootKey); super.onCreatePreferences(savedInstanceState, rootKey);
final PreferenceScreen screen = getPreferenceScreen(); int tintColor = Utils.getHomepageIconColor(getContext());
if (screen == null) { iteratePreferences(preference -> {
return; Drawable icon = preference.getIcon();
}
// Tint the homepage icons
final int tintColor = Utils.getHomepageIconColor(getContext());
final int count = screen.getPreferenceCount();
for (int i = 0; i < count; i++) {
final Preference preference = screen.getPreference(i);
if (preference == null) {
break;
}
final Drawable icon = preference.getIcon();
if (icon != null) { if (icon != null) {
icon.setTint(tintColor); icon.setTint(tintColor);
} }
} });
} }
@Override @Override
@@ -202,6 +196,15 @@ public class TopLevelSettings extends DashboardFragment implements
highlightPreferenceIfNeeded(); highlightPreferenceIfNeeded();
} }
@Override
public void onSplitLayoutChanged(boolean isRegularLayout) {
iteratePreferences(preference -> {
if (preference instanceof HomepagePreferenceLayout) {
((HomepagePreferenceLayout) preference).getHelper().setIconVisible(isRegularLayout);
}
});
}
@Override @Override
public void highlightPreferenceIfNeeded() { public void highlightPreferenceIfNeeded() {
if (mHighlightMixin != null) { if (mHighlightMixin != null) {
@@ -209,6 +212,52 @@ public class TopLevelSettings extends DashboardFragment implements
} }
} }
@Override
public RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent,
Bundle savedInstanceState) {
RecyclerView recyclerView = super.onCreateRecyclerView(inflater, parent,
savedInstanceState);
recyclerView.setPadding(mPaddingHorizontal, 0, mPaddingHorizontal, 0);
return recyclerView;
}
/** Sets the horizontal padding */
public void setPaddingHorizontal(int padding) {
mPaddingHorizontal = padding;
RecyclerView recyclerView = getListView();
if (recyclerView != null) {
recyclerView.setPadding(padding, 0, padding, 0);
}
}
/** Updates the preference internal paddings */
public void updatePreferencePadding(boolean isTwoPane) {
iteratePreferences(new PreferenceJob() {
private int mIconPaddingStart;
private int mTextPaddingStart;
@Override
public void init() {
mIconPaddingStart = getResources().getDimensionPixelSize(isTwoPane
? R.dimen.homepage_preference_icon_padding_start_two_pane
: R.dimen.homepage_preference_icon_padding_start);
mTextPaddingStart = getResources().getDimensionPixelSize(isTwoPane
? R.dimen.homepage_preference_text_padding_start_two_pane
: R.dimen.homepage_preference_text_padding_start);
}
@Override
public void doForEach(Preference preference) {
if (preference instanceof HomepagePreferenceLayout) {
((HomepagePreferenceLayout) preference).getHelper()
.setIconPaddingStart(mIconPaddingStart);
((HomepagePreferenceLayout) preference).getHelper()
.setTextPaddingStart(mTextPaddingStart);
}
}
});
}
/** Returns a {@link TopLevelHighlightMixin} that performs highlighting */ /** Returns a {@link TopLevelHighlightMixin} that performs highlighting */
public TopLevelHighlightMixin getHighlightMixin() { public TopLevelHighlightMixin getHighlightMixin() {
return mHighlightMixin; return mHighlightMixin;
@@ -261,6 +310,31 @@ public class TopLevelSettings extends DashboardFragment implements
} }
} }
private void iteratePreferences(PreferenceJob job) {
if (job == null || getPreferenceManager() == null) {
return;
}
PreferenceScreen screen = getPreferenceScreen();
if (screen == null) {
return;
}
job.init();
int count = screen.getPreferenceCount();
for (int i = 0; i < count; i++) {
Preference preference = screen.getPreference(i);
if (preference == null) {
break;
}
job.doForEach(preference);
}
}
private interface PreferenceJob {
default void init() {}
void doForEach(Preference preference);
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.top_level_settings) { new BaseSearchIndexProvider(R.xml.top_level_settings) {

View File

@@ -21,7 +21,6 @@ import android.graphics.drawable.Drawable;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.util.SparseArray; import android.util.SparseArray;
import android.util.TypedValue;
import android.view.View; import android.view.View;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
@@ -46,6 +45,10 @@ public class HighlightableTopLevelPreferenceAdapter extends PreferenceGroupAdapt
private static final String TAG = "HighlightableTopLevelAdapter"; private static final String TAG = "HighlightableTopLevelAdapter";
static final long DELAY_HIGHLIGHT_DURATION_MILLIS = 100L; static final long DELAY_HIGHLIGHT_DURATION_MILLIS = 100L;
private static final int RES_NORMAL_BACKGROUND =
R.drawable.homepage_selectable_item_background;
private static final int RES_HIGHLIGHTED_BACKGROUND =
R.drawable.homepage_highlighted_item_background;
private final int mTitleColorNormal; private final int mTitleColorNormal;
private final int mTitleColorHighlight; private final int mTitleColorHighlight;
@@ -54,11 +57,8 @@ public class HighlightableTopLevelPreferenceAdapter extends PreferenceGroupAdapt
private final int mIconColorNormal; private final int mIconColorNormal;
private final int mIconColorHighlight; private final int mIconColorHighlight;
private final Context mContext;
private final SettingsHomepageActivity mHomepageActivity; private final SettingsHomepageActivity mHomepageActivity;
private final RecyclerView mRecyclerView; private final RecyclerView mRecyclerView;
private final int mNormalBackgroundRes;
private final int mHighlightBackgroundRes;
private String mHighlightKey; private String mHighlightKey;
private int mHighlightPosition = RecyclerView.NO_POSITION; private int mHighlightPosition = RecyclerView.NO_POSITION;
private int mScrollPosition = RecyclerView.NO_POSITION; private int mScrollPosition = RecyclerView.NO_POSITION;
@@ -74,23 +74,18 @@ public class HighlightableTopLevelPreferenceAdapter extends PreferenceGroupAdapt
mHighlightKey = key; mHighlightKey = key;
mScrolled = !scrollNeeded; mScrolled = !scrollNeeded;
mViewHolders = new SparseArray<>(); mViewHolders = new SparseArray<>();
mContext = preferenceGroup.getContext();
mHomepageActivity = homepageActivity; mHomepageActivity = homepageActivity;
final TypedValue outValue = new TypedValue(); Context context = preferenceGroup.getContext();
mContext.getTheme().resolveAttribute(android.R.attr.selectableItemBackground, mTitleColorNormal = Utils.getColorAttrDefaultColor(context,
outValue, true /* resolveRefs */);
mNormalBackgroundRes = outValue.resourceId;
mHighlightBackgroundRes = R.drawable.homepage_highlighted_item_background;
mTitleColorNormal = Utils.getColorAttrDefaultColor(mContext,
android.R.attr.textColorPrimary); android.R.attr.textColorPrimary);
mTitleColorHighlight = Utils.getColorAttrDefaultColor(mContext, mTitleColorHighlight = Utils.getColorAttrDefaultColor(context,
android.R.attr.textColorPrimaryInverse); android.R.attr.textColorPrimaryInverse);
mSummaryColorNormal = Utils.getColorAttrDefaultColor(mContext, mSummaryColorNormal = Utils.getColorAttrDefaultColor(context,
android.R.attr.textColorSecondary); android.R.attr.textColorSecondary);
mSummaryColorHighlight = Utils.getColorAttrDefaultColor(mContext, mSummaryColorHighlight = Utils.getColorAttrDefaultColor(context,
android.R.attr.textColorSecondaryInverse); android.R.attr.textColorSecondaryInverse);
mIconColorNormal = Utils.getHomepageIconColor(mContext); mIconColorNormal = Utils.getHomepageIconColor(context);
mIconColorHighlight = Utils.getHomepageIconColorHighlight(mContext); mIconColorHighlight = Utils.getHomepageIconColorHighlight(context);
} }
@Override @Override
@@ -236,7 +231,7 @@ public class HighlightableTopLevelPreferenceAdapter extends PreferenceGroupAdapt
private void addHighlightBackground(PreferenceViewHolder holder) { private void addHighlightBackground(PreferenceViewHolder holder) {
final View v = holder.itemView; final View v = holder.itemView;
v.setBackgroundResource(mHighlightBackgroundRes); v.setBackgroundResource(RES_HIGHLIGHTED_BACKGROUND);
((TextView) v.findViewById(android.R.id.title)).setTextColor(mTitleColorHighlight); ((TextView) v.findViewById(android.R.id.title)).setTextColor(mTitleColorHighlight);
((TextView) v.findViewById(android.R.id.summary)).setTextColor(mSummaryColorHighlight); ((TextView) v.findViewById(android.R.id.summary)).setTextColor(mSummaryColorHighlight);
final Drawable drawable = ((ImageView) v.findViewById(android.R.id.icon)).getDrawable(); final Drawable drawable = ((ImageView) v.findViewById(android.R.id.icon)).getDrawable();
@@ -247,7 +242,7 @@ public class HighlightableTopLevelPreferenceAdapter extends PreferenceGroupAdapt
private void removeHighlightBackground(PreferenceViewHolder holder) { private void removeHighlightBackground(PreferenceViewHolder holder) {
final View v = holder.itemView; final View v = holder.itemView;
v.setBackgroundResource(mNormalBackgroundRes); v.setBackgroundResource(RES_NORMAL_BACKGROUND);
((TextView) v.findViewById(android.R.id.title)).setTextColor(mTitleColorNormal); ((TextView) v.findViewById(android.R.id.title)).setTextColor(mTitleColorNormal);
((TextView) v.findViewById(android.R.id.summary)).setTextColor(mSummaryColorNormal); ((TextView) v.findViewById(android.R.id.summary)).setTextColor(mSummaryColorNormal);
final Drawable drawable = ((ImageView) v.findViewById(android.R.id.icon)).getDrawable(); final Drawable drawable = ((ImageView) v.findViewById(android.R.id.icon)).getDrawable();

View File

@@ -20,29 +20,43 @@ import android.content.Context;
import android.util.AttributeSet; import android.util.AttributeSet;
import androidx.preference.Preference; import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
import com.android.settings.R;
/** A customized layout for homepage preference. */ /** A customized layout for homepage preference. */
public class HomepagePreference extends Preference { public class HomepagePreference extends Preference implements
HomepagePreferenceLayoutHelper.HomepagePreferenceLayout {
private final HomepagePreferenceLayoutHelper mHelper;
public HomepagePreference(Context context, AttributeSet attrs, int defStyleAttr, public HomepagePreference(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) { int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes); super(context, attrs, defStyleAttr, defStyleRes);
setLayoutResource(R.layout.homepage_preference); mHelper = new HomepagePreferenceLayoutHelper(this);
} }
public HomepagePreference(Context context, AttributeSet attrs, int defStyleAttr) { public HomepagePreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr); super(context, attrs, defStyleAttr);
setLayoutResource(R.layout.homepage_preference); mHelper = new HomepagePreferenceLayoutHelper(this);
} }
public HomepagePreference(Context context, AttributeSet attrs) { public HomepagePreference(Context context, AttributeSet attrs) {
super(context, attrs); super(context, attrs);
setLayoutResource(R.layout.homepage_preference); mHelper = new HomepagePreferenceLayoutHelper(this);
} }
public HomepagePreference(Context context) { public HomepagePreference(Context context) {
super(context); super(context);
setLayoutResource(R.layout.homepage_preference); mHelper = new HomepagePreferenceLayoutHelper(this);
}
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
mHelper.onBindViewHolder(holder);
}
@Override
public HomepagePreferenceLayoutHelper getHelper() {
return mHelper;
} }
} }

View File

@@ -0,0 +1,78 @@
/*
* 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.settings.widget;
import android.view.View;
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
import com.android.settings.R;
/** Helper for homepage preference to manage layout. */
public class HomepagePreferenceLayoutHelper {
private View mIcon;
private View mText;
private boolean mIconVisible = true;
private int mIconPaddingStart = -1;
private int mTextPaddingStart = -1;
/** The interface for managing preference layouts on homepage */
public interface HomepagePreferenceLayout {
/** Returns a {@link HomepagePreferenceLayoutHelper} */
HomepagePreferenceLayoutHelper getHelper();
}
public HomepagePreferenceLayoutHelper(Preference preference) {
preference.setLayoutResource(R.layout.homepage_preference);
}
/** Sets whether the icon should be visible */
public void setIconVisible(boolean visible) {
mIconVisible = visible;
if (mIcon != null) {
mIcon.setVisibility(visible ? View.VISIBLE : View.GONE);
}
}
/** Sets the icon padding start */
public void setIconPaddingStart(int paddingStart) {
mIconPaddingStart = paddingStart;
if (mIcon != null && paddingStart >= 0) {
mIcon.setPaddingRelative(paddingStart, mIcon.getPaddingTop(), mIcon.getPaddingEnd(),
mIcon.getPaddingBottom());
}
}
/** Sets the text padding start */
public void setTextPaddingStart(int paddingStart) {
mTextPaddingStart = paddingStart;
if (mText != null && paddingStart >= 0) {
mText.setPaddingRelative(paddingStart, mText.getPaddingTop(), mText.getPaddingEnd(),
mText.getPaddingBottom());
}
}
void onBindViewHolder(PreferenceViewHolder holder) {
mIcon = holder.findViewById(R.id.icon_frame);
mText = holder.findViewById(R.id.text_frame);
setIconVisible(mIconVisible);
setIconPaddingStart(mIconPaddingStart);
setTextPaddingStart(mTextPaddingStart);
}
}

View File

@@ -19,29 +19,45 @@ package com.android.settings.widget;
import android.content.Context; import android.content.Context;
import android.util.AttributeSet; import android.util.AttributeSet;
import com.android.settings.R; import androidx.preference.PreferenceViewHolder;
import com.android.settingslib.RestrictedTopLevelPreference; import com.android.settingslib.RestrictedTopLevelPreference;
/** Homepage preference that can be disabled by a device admin using a user restriction. */ /** Homepage preference that can be disabled by a device admin using a user restriction. */
public class RestrictedHomepagePreference extends RestrictedTopLevelPreference { public class RestrictedHomepagePreference extends RestrictedTopLevelPreference implements
HomepagePreferenceLayoutHelper.HomepagePreferenceLayout {
private final HomepagePreferenceLayoutHelper mHelper;
public RestrictedHomepagePreference(Context context, AttributeSet attrs, int defStyleAttr, public RestrictedHomepagePreference(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) { int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes); super(context, attrs, defStyleAttr, defStyleRes);
setLayoutResource(R.layout.homepage_preference); mHelper = new HomepagePreferenceLayoutHelper(this);
} }
public RestrictedHomepagePreference(Context context, AttributeSet attrs, int defStyleAttr) { public RestrictedHomepagePreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr); super(context, attrs, defStyleAttr);
setLayoutResource(R.layout.homepage_preference); mHelper = new HomepagePreferenceLayoutHelper(this);
} }
public RestrictedHomepagePreference(Context context, AttributeSet attrs) { public RestrictedHomepagePreference(Context context, AttributeSet attrs) {
super(context, attrs); super(context, attrs);
setLayoutResource(R.layout.homepage_preference); mHelper = new HomepagePreferenceLayoutHelper(this);
} }
public RestrictedHomepagePreference(Context context) { public RestrictedHomepagePreference(Context context) {
super(context); super(context);
setLayoutResource(R.layout.homepage_preference); mHelper = new HomepagePreferenceLayoutHelper(this);
}
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
mHelper.onBindViewHolder(holder);
}
@Override
public HomepagePreferenceLayoutHelper getHelper() {
return mHelper;
} }
} }

View File

@@ -30,6 +30,7 @@ import android.graphics.drawable.Drawable;
import android.os.Bundle; import android.os.Bundle;
import androidx.preference.Preference; import androidx.preference.Preference;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen; import androidx.preference.PreferenceScreen;
import com.android.settings.R; import com.android.settings.R;
@@ -73,6 +74,7 @@ public class TopLevelSettingsTest {
doReturn(1).when(screen).getPreferenceCount(); doReturn(1).when(screen).getPreferenceCount();
doReturn(preference).when(screen).getPreference(anyInt()); doReturn(preference).when(screen).getPreference(anyInt());
doReturn(screen).when(mSettings).getPreferenceScreen(); doReturn(screen).when(mSettings).getPreferenceScreen();
doReturn(new PreferenceManager(mContext)).when(mSettings).getPreferenceManager();
doReturn(0).when(mSettings).getPreferenceScreenResId(); doReturn(0).when(mSettings).getPreferenceScreenResId();
mSettings.onCreatePreferences(new Bundle(), "rootKey"); mSettings.onCreatePreferences(new Bundle(), "rootKey");