Round thumbnail for individual picker
This CL configures IndividualPickerFragment to use a round thumbnail for the applied wallpaper.
Screenshots
* Day - https://screenshot.googleplex.com/3tzed4USi4VyR38.png
* Night - https://screenshot.googleplex.com/mcF6nM4NQwWuWTk.png
Bug: 178745862
Fixes: 178745862
Test: added in IndividualPickerActivityTest.java
Change-Id: Icb9142d2c3d43e312be8c9c0b1647eedededef87
diff --git a/res/layout/grid_item_image.xml b/res/layout/grid_item_image.xml
index 45a327c..97315b4 100755
--- a/res/layout/grid_item_image.xml
+++ b/res/layout/grid_item_image.xml
@@ -43,6 +43,7 @@
android:layout_height="match_parent"
android:contentDescription="@string/wallpaper_thumbnail"
app:cardCornerRadius="?android:dialogCornerRadius"
+ app:cardBackgroundColor="@android:color/transparent"
app:cardElevation="0dp">
<RelativeLayout
@@ -50,11 +51,10 @@
android:layout_height="match_parent"
android:foreground="@drawable/wallpaper_option_border">
- <ImageView
+ <com.android.wallpaper.picker.individual.CircularImageView
android:id="@+id/thumbnail"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="@color/secondary_color"
android:scaleType="centerCrop" />
<ImageView
@@ -67,7 +67,6 @@
android:id="@+id/loading_indicator_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="@color/black_67_alpha"
android:visibility="gone">
<ProgressBar
@@ -81,15 +80,5 @@
</FrameLayout>
</RelativeLayout>
- <ImageView
- android:id="@+id/check_circle"
- android:layout_width="@dimen/grid_item_individual_wallpaper_check_circle_size"
- android:layout_height="@dimen/grid_item_individual_wallpaper_check_circle_size"
- android:layout_gravity="bottom|right"
- android:layout_marginEnd="@dimen/grid_item_individual_wallpaper_check_circle_offset"
- android:layout_marginBottom="@dimen/grid_item_individual_wallpaper_check_circle_offset"
- android:src="@drawable/check_circle_accent_24dp"
- android:visibility="gone" />
-
</androidx.cardview.widget.CardView>
</LinearLayout>
diff --git a/res/layout/grid_item_my_photos.xml b/res/layout/grid_item_my_photos.xml
index b2f93dd..9c62a36 100755
--- a/res/layout/grid_item_my_photos.xml
+++ b/res/layout/grid_item_my_photos.xml
@@ -27,11 +27,10 @@
android:focusable="true"
android:foreground="?attr/selectableItemBackground">
- <ImageView
+ <com.android.wallpaper.picker.individual.CircularImageView
android:id="@+id/thumbnail"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="@color/secondary_color"
android:scaleType="centerCrop" />
<ImageView
diff --git a/res/layout/grid_item_rotation_desktop.xml b/res/layout/grid_item_rotation_desktop.xml
index 6e6df84..531ccc0 100755
--- a/res/layout/grid_item_rotation_desktop.xml
+++ b/res/layout/grid_item_rotation_desktop.xml
@@ -27,11 +27,10 @@
android:focusable="true"
android:foreground="?attr/selectableItemBackground">
- <ImageView
+ <com.android.wallpaper.picker.individual.CircularImageView
android:id="@+id/thumbnail"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="@color/secondary_color"
android:scaleType="centerCrop" />
<FrameLayout
@@ -69,13 +68,4 @@
</FrameLayout>
- <ImageView
- android:id="@+id/check_circle"
- android:layout_width="@dimen/grid_item_individual_wallpaper_check_circle_size"
- android:layout_height="@dimen/grid_item_individual_wallpaper_check_circle_size"
- android:layout_marginStart="@dimen/grid_item_individual_wallpaper_check_circle_offset"
- android:layout_marginTop="@dimen/grid_item_individual_wallpaper_check_circle_offset"
- android:src="@drawable/check_circle_accent_24dp"
- android:visibility="gone" />
-
</FrameLayout>
diff --git a/src/com/android/wallpaper/picker/individual/CheckmarkSelectionAnimator.java b/src/com/android/wallpaper/picker/individual/CheckmarkSelectionAnimator.java
deleted file mode 100755
index db33857..0000000
--- a/src/com/android/wallpaper/picker/individual/CheckmarkSelectionAnimator.java
+++ /dev/null
@@ -1,283 +0,0 @@
-/*
- * Copyright (C) 2017 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.wallpaper.picker.individual;
-
-import android.animation.Animator;
-import android.animation.Animator.AnimatorListener;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.content.Context;
-import android.os.Handler;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.View.OnHoverListener;
-import android.widget.ImageView;
-
-import com.android.wallpaper.R;
-
-/**
- * Implementation of {@code SelectionAnimator} which uses a checkmark and inset around the tile to
- * indicate a selected state.
- */
-public class CheckmarkSelectionAnimator implements SelectionAnimator {
- private static final int HOVER_TIMEOUT_MS = 200;
- private static final float HOVER_CHECK_CIRCLE_OPACITY = 0.67f;
-
- private Context mAppContext;
-
- private View mTile;
- private ImageView mCheckCircle;
- private View mLoadingIndicatorContainer;
- private boolean mIsSelected;
- private boolean mIsHovered;
- private Handler mHoverHandler;
-
- private Runnable mHoverEnterRunnable = new Runnable() {
- @Override
- public void run() {
- mIsHovered = true;
-
- mCheckCircle.setImageDrawable(mAppContext.getDrawable(
- R.drawable.material_ic_check_circle_white_24));
- mCheckCircle.setVisibility(View.VISIBLE);
- ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(
- mCheckCircle, "alpha", 0f, HOVER_CHECK_CIRCLE_OPACITY);
- alphaAnimator.start();
-
- alphaAnimator.addListener(new AnimatorListener() {
- @Override
- public void onAnimationStart(Animator animation) {
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- mIsHovered = true;
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- }
-
- @Override
- public void onAnimationRepeat(Animator animation) {
- }
- });
- }
- };
-
- private Runnable mHoverExitRunnable = new Runnable() {
- @Override
- public void run() {
- mIsHovered = false;
-
- ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(
- mCheckCircle, "alpha", HOVER_CHECK_CIRCLE_OPACITY, 0f);
- alphaAnimator.addListener(new AnimatorListener() {
- @Override
- public void onAnimationStart(Animator animation) {
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- mCheckCircle.setVisibility(View.GONE);
- mIsHovered = false;
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- }
-
- @Override
- public void onAnimationRepeat(Animator animation) {
- }
- });
-
- alphaAnimator.start();
- }
- };
-
- public CheckmarkSelectionAnimator(Context appContext, View itemView) {
- mAppContext = appContext;
-
- mTile = itemView.findViewById(R.id.tile);
- mCheckCircle = (ImageView) itemView.findViewById(R.id.check_circle);
- mLoadingIndicatorContainer = itemView.findViewById(R.id.loading_indicator_container);
- mHoverHandler = new Handler();
-
- mTile.setOnHoverListener(new OnHoverListener() {
- @Override
- public boolean onHover(View v, MotionEvent event) {
- // If this ViewHolder is already selected, then don't change the state of the check circle.
- if (mIsSelected) {
- return false;
- }
-
- int actionMasked = event.getActionMasked();
-
- switch (actionMasked) {
- case MotionEvent.ACTION_HOVER_ENTER:
- animateHoverEnter();
- break;
- case MotionEvent.ACTION_HOVER_EXIT:
- animateHoverExit();
- break;
- default:
- // fall out
- }
-
- return false;
- }
- });
- }
-
- @Override
- public void selectImmediately() {
- mIsSelected = true;
- int insetPx = mAppContext.getResources().getDimensionPixelSize(
- R.dimen.grid_item_individual_wallpaper_selected_inset);
- mTile.setPadding(insetPx, insetPx, insetPx, insetPx);
- mCheckCircle.setImageDrawable(
- mAppContext.getDrawable(R.drawable.check_circle_accent_24dp));
- mCheckCircle.setVisibility(View.VISIBLE);
- mCheckCircle.setAlpha(1f);
- mLoadingIndicatorContainer.setVisibility(View.GONE);
- }
-
- @Override
- public void deselectImmediately() {
- mIsSelected = false;
- mCheckCircle.setAlpha(0f);
- mCheckCircle.setVisibility(View.GONE);
- mTile.setPadding(0, 0, 0, 0);
- mLoadingIndicatorContainer.setVisibility(View.GONE);
- }
-
- @Override
- public void animateSelected() {
- // If already selected, do nothing.
- if (mIsSelected) {
- return;
- }
-
- mLoadingIndicatorContainer.setVisibility(View.GONE);
-
- int[][] values = new int[2][4];
- values[0] = new int[]{0, 0, 0, 0};
- int insetPx = mAppContext.getResources().getDimensionPixelSize(
- R.dimen.grid_item_individual_wallpaper_selected_inset);
- values[1] = new int[]{insetPx, insetPx, insetPx, insetPx};
-
- ObjectAnimator paddingAnimator = ObjectAnimator.ofMultiInt(mTile, "padding", values);
- ObjectAnimator checkCircleAlphaAnimator = ObjectAnimator.ofFloat(mCheckCircle, "alpha", 0f, 1f);
-
- mCheckCircle.setImageDrawable(
- mAppContext.getDrawable(R.drawable.check_circle_accent_24dp));
-
- AnimatorSet animatorSet = new AnimatorSet();
- animatorSet.playTogether(paddingAnimator, checkCircleAlphaAnimator);
- animatorSet.setDuration(200);
- animatorSet.start();
-
- mCheckCircle.setVisibility(View.VISIBLE);
-
- mIsSelected = true;
- }
-
- @Override
- public void animateDeselected() {
- mLoadingIndicatorContainer.setVisibility(View.GONE);
-
- // If already deselected, do nothing.
- if (!mIsSelected) {
- return;
- }
-
- int[][] values = new int[2][4];
- int insetPx = mAppContext.getResources().getDimensionPixelSize(
- R.dimen.grid_item_individual_wallpaper_selected_inset);
- values[0] = new int[]{insetPx, insetPx, insetPx, insetPx};
- values[1] = new int[]{0, 0, 0, 0};
-
- ObjectAnimator paddingAnimator = ObjectAnimator.ofMultiInt(mTile, "padding", values);
- ObjectAnimator checkCircleAlphaAnimator = ObjectAnimator.ofFloat(mCheckCircle, "alpha", 1f, 0f);
-
- AnimatorSet animatorSet = new AnimatorSet();
- animatorSet.playTogether(paddingAnimator, checkCircleAlphaAnimator);
- animatorSet.setDuration(200);
- animatorSet.start();
-
- checkCircleAlphaAnimator.addListener(new AnimatorListener() {
- @Override
- public void onAnimationStart(Animator animation) {
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- mCheckCircle.setVisibility(View.GONE);
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- }
-
- @Override
- public void onAnimationRepeat(Animator animation) {
- }
- });
-
- mIsSelected = false;
- }
-
- @Override
- public void showLoading() {
- mLoadingIndicatorContainer.setVisibility(View.VISIBLE);
- }
-
- @Override
- public void showNotLoading() {
- mLoadingIndicatorContainer.setVisibility(View.GONE);
- }
-
- private void animateHoverEnter() {
- removeHoverHandlerCallbacks();
-
- if (mIsHovered) {
- return;
- }
-
- mHoverHandler.postDelayed(mHoverEnterRunnable, HOVER_TIMEOUT_MS);
- }
-
- private void animateHoverExit() {
- removeHoverHandlerCallbacks();
-
- if (!mIsHovered) {
- return;
- }
-
- mHoverHandler.postDelayed(mHoverExitRunnable, HOVER_TIMEOUT_MS);
- }
-
- @Override
- public boolean isSelected() {
- return mIsSelected;
- }
-
- private void removeHoverHandlerCallbacks() {
- mHoverHandler.removeCallbacks(mHoverEnterRunnable);
- mHoverHandler.removeCallbacks(mHoverExitRunnable);
- }
-}
diff --git a/src/com/android/wallpaper/picker/individual/CircularImageView.java b/src/com/android/wallpaper/picker/individual/CircularImageView.java
new file mode 100644
index 0000000..86bf394
--- /dev/null
+++ b/src/com/android/wallpaper/picker/individual/CircularImageView.java
@@ -0,0 +1,84 @@
+/*
+ * 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.
+ */
+package com.android.wallpaper.picker.individual;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Path;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+
+/**
+ * A view where the image can be optionally clipped to have a circular border.
+ */
+public class CircularImageView extends ImageView {
+ private boolean mClipped = false;
+
+ private boolean mPathSet = false;
+ private Path mPath;
+
+ public CircularImageView(Context context) {
+ super(context);
+ }
+
+ public CircularImageView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public CircularImageView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ /**
+ * Returns whether the image is clipped with a circular boundary.
+ */
+ public boolean getClipped() {
+ return mClipped;
+ }
+
+ /**
+ * Modifies how the image is clipped. When called with true, the image
+ * is clipped with a circular boundary; with false, the default boundary.
+ *
+ * @param clippedValue Whether the image is clipped with a circular
+ * boundary.
+ */
+ public void setClipped(boolean clippedValue) {
+ mClipped = clippedValue;
+ invalidate();
+ requestLayout();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ if (mClipped) {
+ if (!mPathSet) {
+ // Computes path.
+ mPath = new Path();
+ mPath.addCircle(
+ getWidth() / 2,
+ getHeight() / 2,
+ getHeight() / 2,
+ Path.Direction.CW);
+ mPathSet = true;
+ }
+ canvas.clipPath(mPath);
+ }
+
+ super.onDraw(canvas);
+ }
+}
+
diff --git a/src/com/android/wallpaper/picker/individual/IndividualPickerFragment.java b/src/com/android/wallpaper/picker/individual/IndividualPickerFragment.java
index 71d70e3..3d23e50 100755
--- a/src/com/android/wallpaper/picker/individual/IndividualPickerFragment.java
+++ b/src/com/android/wallpaper/picker/individual/IndividualPickerFragment.java
@@ -1008,7 +1008,8 @@
? index + 1 : index;
ViewHolder holder = mImageGrid.findViewHolderForAdapterPosition(index);
if (holder != null) {
- holder.itemView.setActivated(isActivated);
+ CircularImageView thumbnail = holder.itemView.findViewById(R.id.thumbnail);
+ thumbnail.setClipped(isActivated);
} else {
// Item is not visible, make sure the item is re-bound when it becomes visible.
mAdapter.notifyItemChanged(index);
@@ -1023,10 +1024,7 @@
index = (shouldShowRotationTile() || mCategory.supportsCustomPhotos())
? index + 1 : index;
ViewHolder holder = mImageGrid.findViewHolderForAdapterPosition(index);
- if (holder != null) {
- holder.itemView.findViewById(R.id.check_circle)
- .setVisibility(isApplied ? View.VISIBLE : View.GONE);
- } else {
+ if (holder == null) {
// Item is not visible, make sure the item is re-bound when it becomes visible.
mAdapter.notifyItemChanged(index);
}
@@ -1075,6 +1073,45 @@
return mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP && isRotationEnabled();
}
+ class EmptySelectionAnimator implements SelectionAnimator{
+ EmptySelectionAnimator() {}
+
+ public boolean isSelected() {
+ return false;
+ }
+
+ /**
+ * Sets the UI to selected immediately with no animation.
+ */
+ public void selectImmediately() {}
+
+ /**
+ * Sets the UI to deselected immediately with no animation.
+ */
+ public void deselectImmediately() {}
+
+ /**
+ * Sets the UI to selected with a smooth animation.
+ */
+ public void animateSelected() {}
+
+ /**
+ * Sets the UI to deselected with a smooth animation.
+ */
+ public void animateDeselected() {}
+
+ /**
+ * Sets the UI to show a loading indicator.
+ */
+ public void showLoading() {}
+
+ /**
+ * Sets the UI to hide the loading indicator.
+ */
+ public void showNotLoading() {}
+
+ }
+
/**
* RecyclerView Adapter subclass for the wallpaper tiles in the RecyclerView.
*/
@@ -1154,8 +1191,7 @@
private ViewHolder createRotationHolder(ViewGroup parent) {
LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
View view = layoutInflater.inflate(R.layout.grid_item_rotation_desktop, parent, false);
- SelectionAnimator selectionAnimator =
- new CheckmarkSelectionAnimator(getActivity(), view);
+ SelectionAnimator selectionAnimator = new EmptySelectionAnimator();
return new DesktopRotationHolder(getActivity(), mTileSizePx.y, view, selectionAnimator,
IndividualPickerFragment.this);
}
@@ -1165,8 +1201,7 @@
View view = layoutInflater.inflate(R.layout.grid_item_image, parent, false);
if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
- SelectionAnimator selectionAnimator =
- new CheckmarkSelectionAnimator(getActivity(), view);
+ SelectionAnimator selectionAnimator = new EmptySelectionAnimator();
return new SetIndividualHolder(
getActivity(), mTileSizePx.y, view,
selectionAnimator,
@@ -1319,10 +1354,8 @@
mAppliedWallpaperInfo = wallpaper;
}
- holder.itemView.setActivated(
- (isWallpaperApplied && !hasUserSelectedWallpaper) || isWallpaperSelected);
- holder.itemView.findViewById(R.id.check_circle).setVisibility(
- isWallpaperApplied ? View.VISIBLE : View.GONE);
+ CircularImageView thumbnail = holder.itemView.findViewById(R.id.thumbnail);
+ thumbnail.setClipped(isWallpaperApplied);
}
}
diff --git a/tests/src/com/android/wallpaper/picker/individual/IndividualPickerActivityTest.java b/tests/src/com/android/wallpaper/picker/individual/IndividualPickerActivityTest.java
index a4decf4..902a42c 100644
--- a/tests/src/com/android/wallpaper/picker/individual/IndividualPickerActivityTest.java
+++ b/tests/src/com/android/wallpaper/picker/individual/IndividualPickerActivityTest.java
@@ -61,6 +61,7 @@
import com.android.wallpaper.testing.TestInjector;
import com.android.wallpaper.testing.TestWallpaperCategory;
import com.android.wallpaper.testing.TestWallpaperInfo;
+import com.android.wallpaper.testing.TestWallpaperPreferences;
import com.android.wallpaper.testing.TestWallpaperRotationInitializer;
import org.hamcrest.Matcher;
@@ -97,6 +98,9 @@
private TestWallpaperCategory mTestCategory;
+ private TestWallpaperPreferences mPreferences;
+ private ArrayList<WallpaperInfo> mWallpapers;
+
@Rule
public ActivityTestRule<IndividualPickerActivity> mActivityRule =
new ActivityTestRule<>(IndividualPickerActivity.class, false, false);
@@ -114,6 +118,15 @@
sWallpaperInfo1.setAttributions(Arrays.asList(
"Attribution 0", "Attribution 1", "Attribution 2"));
+
+ sWallpaperInfo1.setCollectionId("collection");
+
+ mPreferences = (TestWallpaperPreferences) mInjector.getPreferences(context);
+
+ mWallpapers = new ArrayList<>();
+ mWallpapers.add(sWallpaperInfo1);
+ mWallpapers.add(sWallpaperInfo2);
+ mWallpapers.add(sWallpaperInfo3);
}
@After
@@ -134,15 +147,8 @@
private void setActivityWithMockWallpapers(boolean isRotationEnabled,
@RotationInitializationState int rotationState) {
- sWallpaperInfo1.setCollectionId("collection");
-
- ArrayList<WallpaperInfo> wallpapers = new ArrayList<>();
- wallpapers.add(sWallpaperInfo1);
- wallpapers.add(sWallpaperInfo2);
- wallpapers.add(sWallpaperInfo3);
-
mTestCategory = new TestWallpaperCategory(
- "Test category", "collection", wallpapers, 0 /* priority */);
+ "Test category", "collection", mWallpapers, 0 /* priority */);
mTestCategory.setIsRotationEnabled(isRotationEnabled);
mTestCategory.setRotationInitializationState(rotationState);
@@ -388,4 +394,50 @@
assertEquals("Attribution 0", holder.itemView.findViewById(R.id.tile)
.getContentDescription());
}
+
+ /**
+ * Tests whether the selected wallpaper has a clipped thumbnail: first wallpaper.
+ */
+ @Test
+ public void testSelectFirstWallpaper_ShowsClippedThumbnail() {
+ runSelectWallpaperTest(0);
+ }
+
+ /**
+ * Tests whether the selected wallpaper has a clipped thumbnail: second wallpaper.
+ */
+ @Test
+ public void testSelectSecondWallpaper_ShowsClippedThumbnail() {
+ runSelectWallpaperTest(1);
+ }
+
+ /**
+ * Tests whether the selected wallpaper has a clipped thumbnail: third wallpaper.
+ */
+ @Test
+ public void testSelectThirdWallpaper_ShowsClippedThumbnail() {
+ runSelectWallpaperTest(2);
+ }
+
+ private void runSelectWallpaperTest(int selectedWallpaperIndex) {
+ mPreferences.setHomeWallpaperRemoteId(
+ mWallpapers.get(selectedWallpaperIndex).getWallpaperId());
+
+ setActivityWithMockWallpapers(false /* isRotationEnabled */,
+ WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED);
+ IndividualPickerActivity activity = getActivity();
+
+ RecyclerView recyclerView = activity.findViewById(R.id.wallpaper_grid);
+
+ for (int index = 0; index < 3; index++) {
+ assertNotNull(recyclerView.findViewHolderForAdapterPosition(index));
+
+ CircularImageView thumbnail =
+ recyclerView.findViewHolderForAdapterPosition(index)
+ .itemView.findViewById(R.id.thumbnail);
+
+ // Assert that only the selected wallpaper has a clipped thumbnail.
+ assertEquals(thumbnail.getClipped(), index == selectedWallpaperIndex);
+ }
+ }
}