Merge "[Predictive Back] Refactor setClipChildrenOnViewTree/restoreClipChildrenOnViewTree to support clipToPadding" into main
This commit is contained in:
@@ -68,8 +68,6 @@ import android.util.TypedValue;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewParent;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import androidx.annotation.ChecksSdkIntAtLeast;
|
||||
@@ -774,136 +772,6 @@ public final class Utilities {
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public 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
|
||||
*/
|
||||
public 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to {@link #setClipChildrenOnViewTree(View, ViewParent, boolean)} but is calling
|
||||
* {@link ViewGroup#setClipToPadding}.
|
||||
*/
|
||||
public static void setClipToPaddingOnViewTree(
|
||||
@Nullable View v,
|
||||
@Nullable ViewParent parent,
|
||||
boolean clipToPadding) {
|
||||
if (v == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (v instanceof ViewGroup) {
|
||||
ViewGroup viewGroup = (ViewGroup) v;
|
||||
boolean oldClipToPadding = viewGroup.getClipToPadding();
|
||||
if (oldClipToPadding != clipToPadding) {
|
||||
v.setTag(R.id.saved_clip_to_padding_tag_id, oldClipToPadding);
|
||||
viewGroup.setClipToPadding(clipToPadding);
|
||||
}
|
||||
}
|
||||
|
||||
if (v == parent) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (v.getParent() instanceof View) {
|
||||
setClipToPaddingOnViewTree((View) v.getParent(), parent, clipToPadding);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to {@link #restoreClipChildrenOnViewTree(View, ViewParent)} but is calling
|
||||
* {@link ViewGroup#setClipToPadding}.
|
||||
*/
|
||||
public static void restoreClipToPaddingOnViewTree(
|
||||
@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_to_padding_tag_id);
|
||||
if (viewTag instanceof Boolean) {
|
||||
viewGroup.setClipToPadding((boolean) viewTag);
|
||||
viewGroup.setTag(R.id.saved_clip_to_padding_tag_id, null);
|
||||
}
|
||||
}
|
||||
|
||||
if (v == parent) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (v.getParent() instanceof View) {
|
||||
restoreClipToPaddingOnViewTree((View) v.getParent(), parent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates the {@code targetView} so that it overlaps with {@code exclusionBounds} as little
|
||||
* as possible, while remaining within {@code inclusionBounds}.
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.launcher3
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewParent
|
||||
|
||||
object UtilitiesKt {
|
||||
|
||||
/**
|
||||
* Modify [ViewGroup]'s attribute with type [T]. The overridden attribute is saved by calling
|
||||
* [View.setTag] and can be later restored by [View.getTag].
|
||||
*
|
||||
* @param <T> type of [ViewGroup] attribute. For example, [T] is [Boolean] if modifying
|
||||
* [ViewGroup.setClipChildren]
|
||||
*/
|
||||
abstract class ViewGroupAttrModifier<T>(
|
||||
private val targetAttrValue: T,
|
||||
private val tagKey: Int
|
||||
) {
|
||||
/**
|
||||
* If [targetAttrValue] is different from existing view attribute returned from
|
||||
* [getAttribute], this method will save existing attribute by calling [ViewGroup.setTag].
|
||||
* Then call [setAttribute] to set attribute with [targetAttrValue].
|
||||
*/
|
||||
fun saveAndChangeAttribute(viewGroup: ViewGroup) {
|
||||
val oldAttrValue = getAttribute(viewGroup)
|
||||
if (oldAttrValue !== targetAttrValue) {
|
||||
viewGroup.setTag(tagKey, oldAttrValue)
|
||||
setAttribute(viewGroup, targetAttrValue)
|
||||
}
|
||||
}
|
||||
|
||||
/** Restore saved attribute in [saveAndChangeAttribute] by calling [ViewGroup.getTag]. */
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun restoreAttribute(viewGroup: ViewGroup) {
|
||||
val oldAttrValue: T = viewGroup.getTag(tagKey) as T ?: return
|
||||
setAttribute(viewGroup, oldAttrValue)
|
||||
viewGroup.setTag(tagKey, null)
|
||||
}
|
||||
|
||||
/** Subclass will override this method to decide how to get [ViewGroup] attribute. */
|
||||
abstract fun getAttribute(viewGroup: ViewGroup): T
|
||||
|
||||
/** Subclass will override this method to decide how to set [ViewGroup] attribute. */
|
||||
abstract fun setAttribute(viewGroup: ViewGroup, attr: T)
|
||||
}
|
||||
|
||||
/** [ViewGroupAttrModifier] to call [ViewGroup.setClipChildren] to false. */
|
||||
@JvmField
|
||||
val CLIP_CHILDREN_FALSE_MODIFIER: ViewGroupAttrModifier<Boolean> =
|
||||
object : ViewGroupAttrModifier<Boolean>(false, R.id.saved_clip_children_tag_id) {
|
||||
override fun getAttribute(viewGroup: ViewGroup): Boolean {
|
||||
return viewGroup.clipChildren
|
||||
}
|
||||
|
||||
override fun setAttribute(viewGroup: ViewGroup, clipChildren: Boolean) {
|
||||
viewGroup.clipChildren = clipChildren
|
||||
}
|
||||
}
|
||||
|
||||
/** [ViewGroupAttrModifier] to call [ViewGroup.setClipToPadding] to false. */
|
||||
@JvmField
|
||||
val CLIP_TO_PADDING_FALSE_MODIFIER: ViewGroupAttrModifier<Boolean> =
|
||||
object : ViewGroupAttrModifier<Boolean>(false, R.id.saved_clip_to_padding_tag_id) {
|
||||
override fun getAttribute(viewGroup: ViewGroup): Boolean {
|
||||
return viewGroup.clipToPadding
|
||||
}
|
||||
|
||||
override fun setAttribute(viewGroup: ViewGroup, clipToPadding: Boolean) {
|
||||
viewGroup.clipToPadding = clipToPadding
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively call [ViewGroupAttrModifier.saveAndChangeAttribute] from [View] to its parent
|
||||
* (direct or indirect) inclusive.
|
||||
*
|
||||
* [ViewGroupAttrModifier.saveAndChangeAttribute] will save the existing attribute value on each
|
||||
* view with [View.setTag], which can be restored in [restoreAttributesOnViewTree].
|
||||
*
|
||||
* 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 modifiers list of [ViewGroupAttrModifier] to modify view attribute
|
||||
*/
|
||||
@JvmStatic
|
||||
fun modifyAttributesOnViewTree(
|
||||
v: View?,
|
||||
parent: ViewParent?,
|
||||
vararg modifiers: ViewGroupAttrModifier<*>
|
||||
) {
|
||||
if (v == null) {
|
||||
return
|
||||
}
|
||||
if (v is ViewGroup) {
|
||||
for (modifier in modifiers) {
|
||||
modifier.saveAndChangeAttribute(v)
|
||||
}
|
||||
}
|
||||
if (v === parent) {
|
||||
return
|
||||
}
|
||||
if (v.parent is View) {
|
||||
modifyAttributesOnViewTree(v.parent as View, parent, *modifiers)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively call [ViewGroupAttrModifier.restoreAttribute]} to restore view attributes
|
||||
* previously saved in [ViewGroupAttrModifier.saveAndChangeAttribute] 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
|
||||
* @param modifiers list of [ViewGroupAttrModifier] to restore view attributes
|
||||
*/
|
||||
@JvmStatic
|
||||
fun restoreAttributesOnViewTree(
|
||||
v: View?,
|
||||
parent: ViewParent?,
|
||||
vararg modifiers: ViewGroupAttrModifier<*>
|
||||
) {
|
||||
if (v == null) {
|
||||
return
|
||||
}
|
||||
if (v is ViewGroup) {
|
||||
for (modifier in modifiers) {
|
||||
modifier.restoreAttribute(v)
|
||||
}
|
||||
}
|
||||
if (v === parent) {
|
||||
return
|
||||
}
|
||||
if (v.parent is View) {
|
||||
restoreAttributesOnViewTree(v.parent as View, parent, *modifiers)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,8 +24,9 @@ import static com.android.launcher3.LauncherState.ALL_APPS;
|
||||
import static com.android.launcher3.LauncherState.ALL_APPS_CONTENT;
|
||||
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
|
||||
import static com.android.launcher3.LauncherState.NORMAL;
|
||||
import static com.android.launcher3.Utilities.restoreClipChildrenOnViewTree;
|
||||
import static com.android.launcher3.Utilities.setClipChildrenOnViewTree;
|
||||
import static com.android.launcher3.UtilitiesKt.CLIP_CHILDREN_FALSE_MODIFIER;
|
||||
import static com.android.launcher3.UtilitiesKt.modifyAttributesOnViewTree;
|
||||
import static com.android.launcher3.UtilitiesKt.restoreAttributesOnViewTree;
|
||||
import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
|
||||
import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_BOTTOM_SHEET_FADE;
|
||||
import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
|
||||
@@ -305,9 +306,11 @@ public class AllAppsTransitionController
|
||||
if (hasScaleEffect != mHasScaleEffect) {
|
||||
mHasScaleEffect = hasScaleEffect;
|
||||
if (mHasScaleEffect) {
|
||||
setClipChildrenOnViewTree(rv, mLauncher.getAppsView(), false);
|
||||
modifyAttributesOnViewTree(rv, mLauncher.getAppsView(),
|
||||
CLIP_CHILDREN_FALSE_MODIFIER);
|
||||
} else {
|
||||
restoreClipChildrenOnViewTree(rv, mLauncher.getAppsView());
|
||||
restoreAttributesOnViewTree(rv, mLauncher.getAppsView(),
|
||||
CLIP_CHILDREN_FALSE_MODIFIER);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,10 +17,10 @@ package com.android.launcher3.widget.picker;
|
||||
|
||||
import static com.android.launcher3.Flags.enableCategorizedWidgetSuggestions;
|
||||
import static com.android.launcher3.Flags.enableUnfoldedTwoPanePicker;
|
||||
import static com.android.launcher3.Utilities.restoreClipChildrenOnViewTree;
|
||||
import static com.android.launcher3.Utilities.restoreClipToPaddingOnViewTree;
|
||||
import static com.android.launcher3.Utilities.setClipChildrenOnViewTree;
|
||||
import static com.android.launcher3.Utilities.setClipToPaddingOnViewTree;
|
||||
import static com.android.launcher3.UtilitiesKt.CLIP_CHILDREN_FALSE_MODIFIER;
|
||||
import static com.android.launcher3.UtilitiesKt.CLIP_TO_PADDING_FALSE_MODIFIER;
|
||||
import static com.android.launcher3.UtilitiesKt.modifyAttributesOnViewTree;
|
||||
import static com.android.launcher3.UtilitiesKt.restoreAttributesOnViewTree;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Outline;
|
||||
@@ -162,13 +162,15 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet {
|
||||
}
|
||||
mOldIsBackSwipeProgressing = isBackSwipeProgressing;
|
||||
if (isBackSwipeProgressing) {
|
||||
setClipChildrenOnViewTree(mPrimaryWidgetListView, (ViewParent) mContent, false);
|
||||
setClipChildrenOnViewTree(mRightPaneScrollView, (ViewParent) mContent, false);
|
||||
setClipToPaddingOnViewTree(mRightPaneScrollView, (ViewParent) mContent, false);
|
||||
modifyAttributesOnViewTree(mPrimaryWidgetListView, (ViewParent) mContent,
|
||||
CLIP_CHILDREN_FALSE_MODIFIER);
|
||||
modifyAttributesOnViewTree(mRightPaneScrollView, (ViewParent) mContent,
|
||||
CLIP_CHILDREN_FALSE_MODIFIER, CLIP_TO_PADDING_FALSE_MODIFIER);
|
||||
} else {
|
||||
restoreClipChildrenOnViewTree(mPrimaryWidgetListView, mContent);
|
||||
restoreClipChildrenOnViewTree(mRightPaneScrollView, mContent);
|
||||
restoreClipToPaddingOnViewTree(mRightPaneScrollView, mContent);
|
||||
restoreAttributesOnViewTree(mPrimaryWidgetListView, mContent,
|
||||
CLIP_CHILDREN_FALSE_MODIFIER);
|
||||
restoreAttributesOnViewTree(mRightPaneScrollView, mContent,
|
||||
CLIP_CHILDREN_FALSE_MODIFIER, CLIP_TO_PADDING_FALSE_MODIFIER);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.launcher3
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.launcher3.UtilitiesKt.CLIP_CHILDREN_FALSE_MODIFIER
|
||||
import com.android.launcher3.UtilitiesKt.CLIP_TO_PADDING_FALSE_MODIFIER
|
||||
import com.android.launcher3.UtilitiesKt.modifyAttributesOnViewTree
|
||||
import com.android.launcher3.UtilitiesKt.restoreAttributesOnViewTree
|
||||
import com.android.launcher3.util.ActivityContextWrapper
|
||||
import com.android.launcher3.views.WidgetsEduView
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class UtilitiesKtTest {
|
||||
|
||||
val context: Context = ActivityContextWrapper(ApplicationProvider.getApplicationContext())
|
||||
|
||||
private lateinit var rootView: WidgetsEduView
|
||||
private lateinit var midView: ViewGroup
|
||||
private lateinit var childView: View
|
||||
@Before
|
||||
fun setup() {
|
||||
rootView =
|
||||
LayoutInflater.from(context).inflate(R.layout.widgets_edu, null) as WidgetsEduView
|
||||
midView = rootView.requireViewById(R.id.edu_view)
|
||||
childView = rootView.requireViewById(R.id.edu_header)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun set_clipChildren_false() {
|
||||
assertThat(rootView.clipChildren).isTrue()
|
||||
assertThat(midView.clipChildren).isTrue()
|
||||
|
||||
modifyAttributesOnViewTree(childView, rootView, CLIP_CHILDREN_FALSE_MODIFIER)
|
||||
|
||||
assertThat(rootView.clipChildren).isFalse()
|
||||
assertThat(midView.clipChildren).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun restore_clipChildren_true() {
|
||||
assertThat(rootView.clipChildren).isTrue()
|
||||
assertThat(midView.clipChildren).isTrue()
|
||||
modifyAttributesOnViewTree(childView, rootView, CLIP_CHILDREN_FALSE_MODIFIER)
|
||||
assertThat(rootView.clipChildren).isFalse()
|
||||
assertThat(midView.clipChildren).isFalse()
|
||||
|
||||
restoreAttributesOnViewTree(childView, rootView, CLIP_CHILDREN_FALSE_MODIFIER)
|
||||
|
||||
assertThat(rootView.clipChildren).isTrue()
|
||||
assertThat(midView.clipChildren).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun restore_clipChildren_skipRestoreMidView() {
|
||||
assertThat(rootView.clipChildren).isTrue()
|
||||
assertThat(midView.clipChildren).isTrue()
|
||||
rootView.clipChildren = false
|
||||
modifyAttributesOnViewTree(childView, rootView, CLIP_CHILDREN_FALSE_MODIFIER)
|
||||
assertThat(rootView.clipChildren).isFalse()
|
||||
assertThat(midView.clipChildren).isFalse()
|
||||
|
||||
restoreAttributesOnViewTree(childView, rootView, CLIP_CHILDREN_FALSE_MODIFIER)
|
||||
|
||||
assertThat(rootView.clipChildren).isFalse()
|
||||
assertThat(midView.clipChildren).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun set_clipToPadding_false() {
|
||||
assertThat(rootView.clipToPadding).isTrue()
|
||||
assertThat(midView.clipToPadding).isTrue()
|
||||
|
||||
modifyAttributesOnViewTree(childView, rootView, CLIP_TO_PADDING_FALSE_MODIFIER)
|
||||
|
||||
assertThat(rootView.clipToPadding).isFalse()
|
||||
assertThat(midView.clipToPadding).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun restore_clipToPadding_true() {
|
||||
assertThat(rootView.clipToPadding).isTrue()
|
||||
assertThat(midView.clipToPadding).isTrue()
|
||||
modifyAttributesOnViewTree(childView, rootView, CLIP_TO_PADDING_FALSE_MODIFIER)
|
||||
assertThat(rootView.clipToPadding).isFalse()
|
||||
assertThat(midView.clipToPadding).isFalse()
|
||||
|
||||
restoreAttributesOnViewTree(childView, rootView, CLIP_TO_PADDING_FALSE_MODIFIER)
|
||||
|
||||
assertThat(rootView.clipToPadding).isTrue()
|
||||
assertThat(midView.clipToPadding).isTrue()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user