From 3774824ceaafc367e06158f34fa1c99806fcde68 Mon Sep 17 00:00:00 2001 From: Fengjiang Li Date: Thu, 12 Jan 2023 10:31:24 -0800 Subject: [PATCH] Predictive swipe: show extra app icons at bottom of All Apps's RecyclerViews The maximum center scale of All Apps to Home is 90%. It means we should add 5% height to All Apps's RecyclerView to render extra app icons. Test: manual bug: b/264906511 Change-Id: I2e970580810220e25d7fc3a86c19abaf87ba2c6e --- res/values/id.xml | 2 + .../launcher3/allapps/AllAppsGridAdapter.java | 14 +++ .../allapps/AllAppsTransitionController.java | 95 ++++++++++++++++++- 3 files changed, 108 insertions(+), 3 deletions(-) diff --git a/res/values/id.xml b/res/values/id.xml index 9fc0ff8277..52a7e98f89 100644 --- a/res/values/id.xml +++ b/res/values/id.xml @@ -37,4 +37,6 @@ + + diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java index 63e6d13a5c..112d47ed50 100644 --- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java +++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java @@ -21,6 +21,7 @@ import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; +import androidx.annotation.Px; import androidx.core.view.accessibility.AccessibilityEventCompat; import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; import androidx.core.view.accessibility.AccessibilityRecordCompat; @@ -143,6 +144,19 @@ public class AllAppsGridAdapter extends cic.isSelected())); } + /** + * We need to extend all apps' RecyclerView's bottom by 5% of view height to ensure extra + * roll(s) of app icons is rendered at the bottom, so that they can fill the bottom gap + * created during predictive back's scale animation from all apps to home. + */ + @Override + protected void calculateExtraLayoutSpace(RecyclerView.State state, int[] extraLayoutSpace) { + super.calculateExtraLayoutSpace(state, extraLayoutSpace); + @Px int extraSpacePx = (int) (getHeight() + * (1 - AllAppsTransitionController.SWIPE_ALL_APPS_TO_HOME_MIN_SCALE) / 2); + extraLayoutSpace[1] = Math.max(extraLayoutSpace[1], extraSpacePx); + } + /** * Returns the number of rows before {@param adapterPosition}, including this position * which should not be counted towards the collection info. diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java index 24bfedb037..6f6f86b105 100644 --- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java +++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java @@ -32,14 +32,18 @@ import android.animation.ObjectAnimator; import android.util.FloatProperty; import android.view.HapticFeedbackConstants; import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; import android.view.animation.Interpolator; import androidx.annotation.FloatRange; +import androidx.annotation.Nullable; import com.android.launcher3.DeviceProfile; import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; +import com.android.launcher3.R; import com.android.launcher3.anim.AnimatedFloat; import com.android.launcher3.anim.AnimatorListeners; import com.android.launcher3.anim.Interpolators; @@ -66,7 +70,7 @@ public class AllAppsTransitionController implements StateHandler, OnDeviceProfileChangeListener { // This constant should match the second derivative of the animator interpolator. public static final float INTERP_COEFF = 1.7f; - private static final float SWIPE_ALL_APPS_TO_HOME_MIN_SCALE = 0.9f; + public static final float SWIPE_ALL_APPS_TO_HOME_MIN_SCALE = 0.9f; private static final int REVERT_SWIPE_ALL_APPS_TO_HOME_ANIMATION_DURATION_MS = 200; public static final FloatProperty ALL_APPS_PROGRESS = @@ -168,6 +172,8 @@ public class AllAppsTransitionController private boolean mIsTablet; + private boolean mHasScaleEffect; + public AllAppsTransitionController(Launcher l) { mLauncher = l; DeviceProfile dp = mLauncher.getDeviceProfile(); @@ -276,8 +282,18 @@ public class AllAppsTransitionController rv.getScrollbar().setVisibility(scaleProgress < 1f ? View.INVISIBLE : View.VISIBLE); } - // TODO(b/264906511): We need to disable view clipping on all apps' parent views so - // that the extra roll of app icons are displayed. + // Disable view clipping from all apps' RecyclerView up to all apps view during scale + // animation, and vice versa. The goal is to display extra roll(s) app icons (rendered in + // {@link AppsGridLayoutManager#calculateExtraLayoutSpace}) during scale animation. + boolean hasScaleEffect = scaleProgress < 1f; + if (hasScaleEffect != mHasScaleEffect) { + mHasScaleEffect = hasScaleEffect; + if (mHasScaleEffect) { + setClipChildrenOnViewTree(rv, mLauncher.getAppsView(), false); + } else { + restoreClipChildrenOnViewTree(rv, mLauncher.getAppsView()); + } + } } private void animateAllAppsToNoScale() { @@ -380,6 +396,79 @@ public class AllAppsTransitionController mShouldControlKeyboard = !mLauncher.getSearchConfig().isKeyboardSyncEnabled(); } + /** + * Recursively call {@link ViewGroup#setClipChildren(boolean)} from {@link View} to ts parent + * (direct or indirect) inclusive. This method will also save the old clipChildren value on each + * view with {@link View#setTag(int, Object)}, which can be restored in + * {@link #restoreClipChildrenOnViewTree(View, ViewParent)}. + * + * Note that if parent is null or not a parent of the view, this method will be applied all the + * way to root view. + * + * @param v child view + * @param parent direct or indirect parent of child view + * @param clipChildren whether we should clip children + */ + private static void setClipChildrenOnViewTree( + @Nullable View v, + @Nullable ViewParent parent, + boolean clipChildren) { + if (v == null) { + return; + } + + if (v instanceof ViewGroup) { + ViewGroup viewGroup = (ViewGroup) v; + boolean oldClipChildren = viewGroup.getClipChildren(); + if (oldClipChildren != clipChildren) { + v.setTag(R.id.saved_clip_children_tag_id, oldClipChildren); + viewGroup.setClipChildren(clipChildren); + } + } + + if (v == parent) { + return; + } + + if (v.getParent() instanceof View) { + setClipChildrenOnViewTree((View) v.getParent(), parent, clipChildren); + } + } + + /** + * Recursively call {@link ViewGroup#setClipChildren(boolean)} to restore clip children value + * set in {@link #setClipChildrenOnViewTree(View, ViewParent, boolean)} on view to its parent + * (direct or indirect) inclusive. + * + * Note that if parent is null or not a parent of the view, this method will be applied all the + * way to root view. + * + * @param v child view + * @param parent direct or indirect parent of child view + */ + private static void restoreClipChildrenOnViewTree( + @Nullable View v, @Nullable ViewParent parent) { + if (v == null) { + return; + } + if (v instanceof ViewGroup) { + ViewGroup viewGroup = (ViewGroup) v; + Object viewTag = viewGroup.getTag(R.id.saved_clip_children_tag_id); + if (viewTag instanceof Boolean) { + viewGroup.setClipChildren((boolean) viewTag); + viewGroup.setTag(R.id.saved_clip_children_tag_id, null); + } + } + + if (v == parent) { + return; + } + + if (v.getParent() instanceof View) { + restoreClipChildrenOnViewTree((View) v.getParent(), parent); + } + } + /** * Updates the total scroll range but does not update the UI. */