Add ripple effect when a header in widget picker is selected.

We need to use setSelected in order to add a ripple effect when a header is selected in tablet landscape mode, and to set the style through XML rather than programmatically the way it was done before in the method `setLargeScreenTheme`.

Fix: 268558453
Test: Turn on LARGE_SCREEN_WIDGET_PICKER and verify that when selecting a header there's a ripple effect on that header
Change-Id: I6cab920258a57118430d1e07416508ef36faa56d
This commit is contained in:
Federico Baron 2023-02-13 17:03:18 -08:00
parent 192e7afc3c
commit e8217b3c1b
15 changed files with 245 additions and 90 deletions

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2023 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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#EFF1F2" android:state_expanded="false" />
<item android:color="#191C1D" android:state_expanded="true" />
</selector>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2023 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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?android:attr/textColorSecondary"/>
</selector>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2023 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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#EFF1F2" android:state_expanded="false" />
<item android:color="#191C1D" android:state_expanded="true" />
</selector>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2023 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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?android:attr/textColorPrimary"/>
</selector>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2023 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.
-->
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:insetTop="@dimen/widget_list_entry_spacing" >
<ripple
android:color="@color/accent_ripple_color"
android:paddingTop="@dimen/widget_list_header_view_vertical_padding"
android:paddingBottom="@dimen/widget_list_header_view_vertical_padding" >
<item android:id="@android:id/mask"
android:drawable="@drawable/bg_widgets_header_states_large_screen" />
<item android:drawable="@drawable/bg_widgets_header_states_large_screen" />
</ripple>
</inset>

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2023 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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_expanded="true">
<shape android:shape="rectangle">
<solid android:color="@color/widget_picker_background_selected" />
<corners android:radius="@dimen/widget_list_top_bottom_corner_radius" />
</shape>
</item>
<item android:state_expanded="false">
<shape android:shape="rectangle">
<solid android:color="@android:color/transparent" />
<corners android:radius="@dimen/widget_list_top_bottom_corner_radius" />
</shape>
</item>
</selector>

View File

@ -0,0 +1,69 @@
<?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.
-->
<com.android.launcher3.widget.picker.WidgetsListHeader xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:launcher="http://schemas.android.com/apk/res-auto"
android:id="@+id/widgets_list_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:importantForAccessibility="yes"
android:focusable="true"
launcher:appIconSize="48dp"
android:descendantFocusability="afterDescendants"
android:background="@drawable/bg_widgets_header_large_screen" >
<ImageView
android:id="@+id/app_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:importantForAccessibility="no"
tools:src="@drawable/ic_corp"/>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:orientation="vertical"
android:duplicateParentState="true">
<TextView
android:id="@+id/app_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?attr/widgetPickerHeaderAppTitleColor"
android:textSize="16sp"
android:duplicateParentState="true"
tools:text="App name" />
<TextView
android:id="@+id/app_subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?attr/widgetPickerHeaderAppSubtitleColor"
android:alpha="0.7"
android:duplicateParentState="true"
tools:text="m widgets, n shortcuts" />
</LinearLayout>
</com.android.launcher3.widget.picker.WidgetsListHeader>

View File

@ -55,6 +55,8 @@
<attr name="dropTargetHoverTextColor" format="color" />
<attr name="preloadIconAccentColor" format="color" />
<attr name="preloadIconBackgroundColor" format="color" />
<attr name="widgetPickerHeaderAppTitleColor" format="color"/>
<attr name="widgetPickerHeaderAppSubtitleColor" format="color"/>
<!-- BubbleTextView specific attributes. -->
<declare-styleable name="BubbleTextView">

View File

@ -176,10 +176,14 @@
<item name="android:colorPrimaryDark">#E8EAED</item>
<item name="android:textColorSecondary">?android:attr/textColorPrimary</item>
<item name="android:colorEdgeEffect">?android:attr/textColorSecondary</item>
<item name="widgetPickerHeaderAppTitleColor">@color/app_title_text_light</item>
<item name="widgetPickerHeaderAppSubtitleColor">@color/app_subtitle_text_light</item>
</style>
<style name="WidgetContainerTheme.Dark" parent="AppTheme.Dark">
<item name="android:colorEdgeEffect">?android:attr/textColorSecondary</item>
<item name="android:colorPrimaryDark">#616161</item> <!-- Gray 700 -->
<item name="widgetPickerHeaderAppTitleColor">@color/app_title_text_dark</item>
<item name="widgetPickerHeaderAppSubtitleColor">@color/app_subtitle_text_dark</item>
</style>
<style name="FastScrollerPopup" parent="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle">

View File

@ -291,7 +291,9 @@ public class WidgetsFullSheet extends BaseWidgetSheet
// Inflate the suggestions header.
mSuggestedWidgetsHeader = (WidgetsListHeader) layoutInflater.inflate(
R.layout.widgets_list_row_header, mSuggestedWidgetsContainer, false);
R.layout.widgets_list_row_header_two_pane,
mSuggestedWidgetsContainer,
false);
mSuggestedWidgetsHeader.setExpanded(true);
PackageItemInfo packageItemInfo = new PackageItemInfo(
@ -312,8 +314,8 @@ public class WidgetsFullSheet extends BaseWidgetSheet
mSuggestedWidgetsHeader.applyFromItemInfoWithIcon(widgetsListHeaderEntry);
mSuggestedWidgetsHeader.setIcon(
getContext().getDrawable(R.drawable.widget_suggestions_icon));
mSuggestedWidgetsHeader.setOnExpandChangeListener(isExpanded -> {
mSuggestedWidgetsHeader.setExpanded(isExpanded);
mSuggestedWidgetsHeader.setOnClickListener(view -> {
mSuggestedWidgetsHeader.setExpanded(true);
resetExpandedHeaders();
mRightPane.removeAllViews();
mRightPane.addView(mRecommendedWidgetsTable);

View File

@ -115,7 +115,8 @@ public class WidgetsListAdapter extends Adapter<ViewHolder> implements OnHeaderC
mViewHolderBinders.put(
VIEW_TYPE_WIDGETS_HEADER,
new WidgetsListHeaderViewHolderBinder(
layoutInflater, /* onHeaderClickListener= */ this));
layoutInflater, /* onHeaderClickListener= */ this,
headerChangeListener != null));
mViewHolderBinders.put(
VIEW_TYPE_WIDGETS_SPACE,
new WidgetsSpaceViewHolderBinder(emptySpaceHeightProvider));

View File

@ -21,9 +21,7 @@ package com.android.launcher3.widget.picker;
*/
enum WidgetsListDrawableState {
FIRST(new int[]{android.R.attr.state_first}),
FIRST_EXPANDED(new int[]{android.R.attr.state_first, android.R.attr.state_expanded}),
MIDDLE(new int[]{android.R.attr.state_middle}),
MIDDLE_EXPANDED(new int[]{android.R.attr.state_middle, android.R.attr.state_expanded}),
LAST(new int[]{android.R.attr.state_last}),
SINGLE(new int[]{android.R.attr.state_single});
@ -33,12 +31,10 @@ enum WidgetsListDrawableState {
mStateSet = stateSet;
}
static WidgetsListDrawableState obtain(boolean isFirst, boolean isLast, boolean isExpanded) {
static WidgetsListDrawableState obtain(boolean isFirst, boolean isLast) {
if (isFirst && isLast) return SINGLE;
if (isFirst && isExpanded) return FIRST_EXPANDED;
if (isFirst) return FIRST;
if (isLast) return LAST;
if (isExpanded) return MIDDLE_EXPANDED;
return MIDDLE;
}
}

View File

@ -15,17 +15,12 @@
*/
package com.android.launcher3.widget.picker;
import static com.android.launcher3.config.FeatureFlags.LARGE_SCREEN_WIDGET_PICKER;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.ImageView;
@ -38,13 +33,11 @@ import androidx.annotation.UiThread;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
import com.android.launcher3.icons.PlaceHolderIconDrawable;
import com.android.launcher3.icons.cache.HandlerRunnable;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
@ -56,8 +49,9 @@ import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
*/
public final class WidgetsListHeader extends LinearLayout implements ItemInfoUpdateReceiver {
private static final int[] EXPANDED_DRAWABLE_STATE = new int[] {android.R.attr.state_expanded};
private final int mIconSize;
private final boolean mIsTwoPane;
@Nullable private HandlerRunnable mIconLoadRequest;
@Nullable private Drawable mIconDrawable;
@ -65,7 +59,6 @@ public final class WidgetsListHeader extends LinearLayout implements ItemInfoUpd
private ImageView mAppIcon;
private TextView mTitle;
private TextView mSubtitle;
private GradientDrawable mBackground;
private boolean mEnableIconUpdateAnimation = false;
private boolean mIsExpanded = false;
@ -86,11 +79,6 @@ public final class WidgetsListHeader extends LinearLayout implements ItemInfoUpd
R.styleable.WidgetsListRowHeader, defStyleAttr, /* defStyleRes= */ 0);
mIconSize = a.getDimensionPixelSize(R.styleable.WidgetsListRowHeader_appIconSize,
grid.iconSizePx);
mIsTwoPane = grid.isLandscape && grid.isTablet && LARGE_SCREEN_WIDGET_PICKER.get();
if (mIsTwoPane) {
setLargeScreenTheme();
}
}
@Override
@ -99,9 +87,6 @@ public final class WidgetsListHeader extends LinearLayout implements ItemInfoUpd
mAppIcon = findViewById(R.id.app_icon);
mTitle = findViewById(R.id.app_title);
mSubtitle = findViewById(R.id.app_subtitle);
if (mIsTwoPane) {
findViewById(R.id.toggle).setVisibility(GONE);
}
setAccessibilityDelegate(new AccessibilityDelegate() {
@Override
@ -130,58 +115,18 @@ public final class WidgetsListHeader extends LinearLayout implements ItemInfoUpd
});
}
/**
* Sets a {@link OnExpansionChangeListener} to get a callback when this app widgets section
* expands / collapses.
*/
@UiThread
public void setOnExpandChangeListener(
@Nullable OnExpansionChangeListener onExpandChangeListener) {
// Use the entire touch area of this view to expand / collapse an app widgets section.
setOnClickListener(view -> {
setExpanded(mIsTwoPane || !mIsExpanded);
if (onExpandChangeListener != null) {
onExpandChangeListener.onExpansionChange(mIsExpanded);
}
});
}
/** Sets the expand toggle to expand / collapse. */
@UiThread
public void setExpanded(boolean isExpanded) {
this.mIsExpanded = isExpanded;
if (mIsTwoPane) {
if (Utilities.isDarkTheme(getContext())) {
if (mIsExpanded) {
mTitle.setTextColor(Color.BLACK);
mSubtitle.setTextColor(Color.BLACK);
} else {
mTitle.setTextColor(Color.WHITE);
mSubtitle.setTextColor(Themes.getAttrColor(getContext(),
android.R.attr.textColorSecondary));
}
}
setLargeScreenTheme();
}
refreshDrawableState();
}
/**
* Sets the style for the header when we are using large screens in landscape.
*/
private void setLargeScreenTheme() {
if (mBackground == null) {
mBackground = new GradientDrawable();
mBackground.setCornerRadius((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
28,
getContext().getResources().getDisplayMetrics()));
}
mBackground.setColor(mIsExpanded
? getResources().getColor(R.color.widget_picker_background_selected)
: Color.TRANSPARENT);
this.setBackground(mBackground);
/** @return true if this header is expanded. */
public boolean isExpanded() {
return mIsExpanded;
}
/** Sets the {@link WidgetsListDrawableState} and refreshes the background drawable. */
@UiThread
public void setListDrawableState(WidgetsListDrawableState state) {
@ -260,12 +205,15 @@ public final class WidgetsListHeader extends LinearLayout implements ItemInfoUpd
@Override
protected int[] onCreateDrawableState(int extraSpace) {
if (mListDrawableState == null) return super.onCreateDrawableState(extraSpace);
// Augment the state set from the super implementation with the custom states from
// mListDrawableState.
int[] drawableState =
super.onCreateDrawableState(extraSpace + mListDrawableState.mStateSet.length);
mergeDrawableStates(drawableState, mListDrawableState.mStateSet);
// We create a drawable state with an additional two spaces to be able to fit expanded state
// and the list drawable state.
int[] drawableState = super.onCreateDrawableState(extraSpace + 2);
if (mIsExpanded) {
mergeDrawableStates(drawableState, EXPANDED_DRAWABLE_STATE);
}
if (mListDrawableState != null) {
mergeDrawableStates(drawableState, mListDrawableState.mStateSet);
}
return drawableState;
}
@ -283,10 +231,4 @@ public final class WidgetsListHeader extends LinearLayout implements ItemInfoUpd
}
}
}
/** A listener for the widget section expansion / collapse events. */
public interface OnExpansionChangeListener {
/** Notifies that the widget section is expanded or collapsed. */
void onExpansionChange(boolean isExpanded);
}
}

View File

@ -32,17 +32,23 @@ public final class WidgetsListHeaderViewHolderBinder implements
ViewHolderBinder<WidgetsListHeaderEntry, WidgetsListHeaderHolder> {
private final LayoutInflater mLayoutInflater;
private final OnHeaderClickListener mOnHeaderClickListener;
private final boolean mIsTwoPane;
public WidgetsListHeaderViewHolderBinder(LayoutInflater layoutInflater,
OnHeaderClickListener onHeaderClickListener) {
OnHeaderClickListener onHeaderClickListener, boolean isTwoPane) {
mLayoutInflater = layoutInflater;
mOnHeaderClickListener = onHeaderClickListener;
mIsTwoPane = isTwoPane;
}
@Override
public WidgetsListHeaderHolder newViewHolder(ViewGroup parent) {
return new WidgetsListHeaderHolder((WidgetsListHeader) mLayoutInflater.inflate(
R.layout.widgets_list_row_header, parent, false));
mIsTwoPane
? R.layout.widgets_list_row_header_two_pane
: R.layout.widgets_list_row_header,
parent,
false));
}
@Override
@ -54,10 +60,11 @@ public final class WidgetsListHeaderViewHolderBinder implements
widgetsListHeader.setListDrawableState(
WidgetsListDrawableState.obtain(
(position & POSITION_FIRST) != 0,
(position & POSITION_LAST) != 0,
/* isExpanded= */ data.isWidgetListShown()));
widgetsListHeader.setOnExpandChangeListener(isExpanded ->
mOnHeaderClickListener.onHeaderClicked(isExpanded,
PackageUserKey.fromPackageItemInfo(data.mPkgItem)));
(position & POSITION_LAST) != 0));
widgetsListHeader.setOnClickListener(view -> {
widgetsListHeader.setExpanded(mIsTwoPane || !widgetsListHeader.isExpanded());
mOnHeaderClickListener.onHeaderClicked(widgetsListHeader.isExpanded(),
PackageUserKey.fromPackageItemInfo(data.mPkgItem));
});
}
}

View File

@ -90,7 +90,8 @@ public final class WidgetsListHeaderViewHolderBinderTest {
}).when(mIconCache).getTitleNoCache(any());
mViewHolderBinder = new WidgetsListHeaderViewHolderBinder(
LayoutInflater.from(mContext),
mOnHeaderClickListener);
mOnHeaderClickListener,
false);
}
@Test