Fix Resolver dragging animation

Adds a new ResolverDrawerLayout XML property, ignoreOffsetTopLimit, that
takes a direct child view’s id. The specified view will serve as a top
limit for all child views below it with layout_ignoreOffset attribute
set to true i.e. they will be pushed down by the view when the drawer
is moving.

Fix: 240481385
Test: atest FrameworksCoreTests:ResolverActivityTest
Test: atest FrameworksCoreTests:ChooserActivityTest
Test: Manual testing with some injected code that simulates various
conditions:
  * various property values or property absence
  * simultaneous layout and drawer moving
  * one-time layout while an "ignoreOffset" view has been moved out of
    place with a consequent release.

Change-Id: Ib9aa4f11d35c2dede06b0913091e7b146438e57e
This commit is contained in:
Andrey Epin
2022-08-23 12:42:03 -07:00
parent f12d6c2894
commit 2e1805e892
3 changed files with 66 additions and 1 deletions

View File

@@ -17,6 +17,9 @@
package com.android.internal.widget;
import static android.content.res.Resources.ID_NULL;
import android.annotation.IdRes;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
@@ -96,6 +99,8 @@ public class ResolverDrawerLayout extends ViewGroup {
private int mTopOffset;
private boolean mShowAtTop;
@IdRes
private int mIgnoreOffsetTopLimitViewId = ID_NULL;
private boolean mIsDragging;
private boolean mOpenOnClick;
@@ -156,6 +161,10 @@ public class ResolverDrawerLayout extends ViewGroup {
mIsMaxCollapsedHeightSmallExplicit =
a.hasValue(R.styleable.ResolverDrawerLayout_maxCollapsedHeightSmall);
mShowAtTop = a.getBoolean(R.styleable.ResolverDrawerLayout_showAtTop, false);
if (a.hasValue(R.styleable.ResolverDrawerLayout_ignoreOffsetTopLimit)) {
mIgnoreOffsetTopLimitViewId = a.getResourceId(
R.styleable.ResolverDrawerLayout_ignoreOffsetTopLimit, ID_NULL);
}
a.recycle();
mScrollIndicatorDrawable = mContext.getDrawable(R.drawable.scroll_indicator_material);
@@ -577,12 +586,32 @@ public class ResolverDrawerLayout extends ViewGroup {
dy -= 1.0f;
}
boolean isIgnoreOffsetLimitSet = false;
int ignoreOffsetLimit = 0;
View ignoreOffsetLimitView = findIgnoreOffsetLimitView();
if (ignoreOffsetLimitView != null) {
LayoutParams lp = (LayoutParams) ignoreOffsetLimitView.getLayoutParams();
ignoreOffsetLimit = ignoreOffsetLimitView.getBottom() + lp.bottomMargin;
isIgnoreOffsetLimitSet = true;
}
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == View.GONE) {
continue;
}
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (!lp.ignoreOffset) {
child.offsetTopAndBottom((int) dy);
} else if (isIgnoreOffsetLimitSet) {
int top = child.getTop();
int targetTop = Math.max(
(int) (ignoreOffsetLimit + lp.topMargin + dy),
lp.mFixedTop);
if (top != targetTop) {
child.offsetTopAndBottom(targetTop - top);
}
ignoreOffsetLimit = child.getBottom() + lp.bottomMargin;
}
}
final boolean isCollapsedOld = mCollapseOffset != 0;
@@ -1024,6 +1053,8 @@ public class ResolverDrawerLayout extends ViewGroup {
final int rightEdge = width - getPaddingRight();
final int widthAvailable = rightEdge - leftEdge;
boolean isIgnoreOffsetLimitSet = false;
int ignoreOffsetLimit = 0;
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
@@ -1036,9 +1067,24 @@ public class ResolverDrawerLayout extends ViewGroup {
continue;
}
if (mIgnoreOffsetTopLimitViewId != ID_NULL && !isIgnoreOffsetLimitSet) {
if (mIgnoreOffsetTopLimitViewId == child.getId()) {
ignoreOffsetLimit = child.getBottom() + lp.bottomMargin;
isIgnoreOffsetLimitSet = true;
}
}
int top = ypos + lp.topMargin;
if (lp.ignoreOffset) {
top -= mCollapseOffset;
if (!isDragging()) {
lp.mFixedTop = (int) (top - mCollapseOffset);
}
if (isIgnoreOffsetLimitSet) {
top = Math.max(ignoreOffsetLimit + lp.topMargin, (int) (top - mCollapseOffset));
ignoreOffsetLimit = top + child.getMeasuredHeight() + lp.bottomMargin;
} else {
top -= mCollapseOffset;
}
}
final int bottom = top + child.getMeasuredHeight();
@@ -1102,11 +1148,23 @@ public class ResolverDrawerLayout extends ViewGroup {
mCollapsibleHeightReserved = ss.mCollapsibleHeightReserved;
}
private View findIgnoreOffsetLimitView() {
if (mIgnoreOffsetTopLimitViewId == ID_NULL) {
return null;
}
View v = findViewById(mIgnoreOffsetTopLimitViewId);
if (v != null && v != this && v.getParent() == this && v.getVisibility() != View.GONE) {
return v;
}
return null;
}
public static class LayoutParams extends MarginLayoutParams {
public boolean alwaysShow;
public boolean ignoreOffset;
public boolean hasNestedScrollIndicator;
public int maxHeight;
int mFixedTop;
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);

View File

@@ -23,6 +23,7 @@
android:maxWidth="@dimen/resolver_max_width"
android:maxCollapsedHeight="@dimen/resolver_max_collapsed_height"
android:maxCollapsedHeightSmall="56dp"
android:ignoreOffsetTopLimit="@id/title_container"
android:id="@id/contentPanel">
<RelativeLayout

View File

@@ -9560,6 +9560,12 @@
<attr name="maxCollapsedHeightSmall" format="dimension" />
<!-- Whether the Drawer should be positioned at the top rather than at the bottom. -->
<attr name="showAtTop" format="boolean" />
<!-- By default `ResolverDrawerLayout`’s children views with `layout_ignoreOffset` property
set to true have a fixed position in the layout that won’t be affected by the drawer’s
movements. This property alternates that behavior. It specifies a child view’s id that
will push all ignoreOffset siblings below it when the drawer is moved i.e. setting the
top limit the ignoreOffset elements. -->
<attr name="ignoreOffsetTopLimit" format="reference" />
</declare-styleable>
<declare-styleable name="MessagingLinearLayout">