Decouple the scaling / translation of widget views necessary in split-screen
=> We pull apart the scale and translation that are set in a fairly
static way due to split-screen vs. the general translation and scale
properties that might be used more dynamically, in this case
for re-order animations
=> This allows removal of some code that breaks reorder animations
due to the accrual of translations / scales in certain edge cases.
=> TODO in future CL: address other translation cases and make the throw
case for calling base setTranslationX/Y for Workspace Items unconditional
issue 149438360
test: manual
Change-Id: Ic3fde172f669e215cd25db0fcd4e1c3c873d314f
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 93247ab..e8e88c4 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -29,6 +29,7 @@
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
@@ -70,7 +71,7 @@
* too aggressive.
*/
public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, OnResumeCallback,
- IconLabelDotView, DraggableView {
+ IconLabelDotView, DraggableView, Reorderable {
private static final int DISPLAY_WORKSPACE = 0;
private static final int DISPLAY_ALL_APPS = 1;
@@ -78,6 +79,8 @@
private static final int[] STATE_PRESSED = new int[] {android.R.attr.state_pressed};
+ private final PointF mTranslationForReorder = new PointF(0, 0);
+ private float mScaleForReorder = 1f;
private static final Property<BubbleTextView, Float> DOT_SCALE_PROPERTY
= new Property<BubbleTextView, Float>(Float.TYPE, "dotScale") {
@@ -672,6 +675,30 @@
return mIconSize;
}
+ public void setReorderOffset(float x, float y) {
+ mTranslationForReorder.set(x, y);
+ super.setTranslationX(x);
+ super.setTranslationY(y);
+ }
+
+ public void getReorderOffset(PointF offset) {
+ offset.set(mTranslationForReorder);
+ }
+
+ public void setReorderScale(float scale) {
+ mScaleForReorder = scale;
+ super.setScaleX(scale);
+ super.setScaleY(scale);
+ }
+
+ public float getReorderScale() {
+ return mScaleForReorder;
+ }
+
+ public View getView() {
+ return this;
+ }
+
@Override
public int getViewType() {
return DRAGGABLE_ICON;
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 99416c4..71a787f 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -33,6 +33,7 @@
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
@@ -54,7 +55,6 @@
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.anim.PropertyListBuilder;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.folder.PreviewBackground;
@@ -66,7 +66,6 @@
import com.android.launcher3.util.Themes;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.views.ActivityContext;
-import com.android.launcher3.widget.LauncherAppWidgetHostView;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -99,6 +98,7 @@
// return an (x, y) value from helper functions. Do NOT use them to maintain other state.
@Thunk final int[] mTmpPoint = new int[2];
@Thunk final int[] mTempLocation = new int[2];
+ final PointF mTmpPointF = new PointF();
// Used to visualize / debug the Grid of the CellLayout
private static final boolean VISUALIZE_GRID = false;
@@ -136,7 +136,7 @@
private final Paint mDragOutlinePaint = new Paint();
@Thunk final ArrayMap<LayoutParams, Animator> mReorderAnimators = new ArrayMap<>();
- @Thunk final ArrayMap<View, ReorderPreviewAnimation> mShakeAnimators = new ArrayMap<>();
+ @Thunk final ArrayMap<Reorderable, ReorderPreviewAnimation> mShakeAnimators = new ArrayMap<>();
private boolean mItemPlacementDirty = false;
@@ -1868,10 +1868,11 @@
boolean skip = mode == ReorderPreviewAnimation.MODE_HINT && solution.intersectingViews
!= null && !solution.intersectingViews.contains(child);
+
LayoutParams lp = (LayoutParams) child.getLayoutParams();
- if (c != null && !skip) {
- ReorderPreviewAnimation rha = new ReorderPreviewAnimation(child, mode, lp.cellX,
- lp.cellY, c.cellX, c.cellY, c.spanX, c.spanY);
+ if (c != null && !skip && (child instanceof Reorderable)) {
+ ReorderPreviewAnimation rha = new ReorderPreviewAnimation((Reorderable) child,
+ mode, lp.cellX, lp.cellY, c.cellX, c.cellY, c.spanX, c.spanY);
rha.animate();
}
}
@@ -1893,7 +1894,7 @@
// Class which represents the reorder preview animations. These animations show that an item is
// in a temporary state, and hint at where the item will return to.
class ReorderPreviewAnimation {
- final View child;
+ final Reorderable child;
float finalDeltaX;
float finalDeltaY;
float initDeltaX;
@@ -1913,8 +1914,8 @@
float animationProgress = 0;
ValueAnimator a;
- public ReorderPreviewAnimation(View child, int mode, int cellX0, int cellY0, int cellX1,
- int cellY1, int spanX, int spanY) {
+ public ReorderPreviewAnimation(Reorderable child, int mode, int cellX0, int cellY0,
+ int cellX1, int cellY1, int spanX, int spanY) {
regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
final int x0 = mTmpPoint[0];
final int y0 = mTmpPoint[1];
@@ -1926,63 +1927,60 @@
this.child = child;
this.mode = mode;
+ finalDeltaX = 0;
+ finalDeltaY = 0;
- // TODO issue!
- setInitialAnimationValues(false);
- finalScale = (mChildScale - (CHILD_DIVIDEND / child.getWidth())) * initScale;
- finalDeltaX = initDeltaX;
- finalDeltaY = initDeltaY;
+ child.getReorderOffset(mTmpPointF);
+ initDeltaX = mTmpPointF.x;
+ initDeltaY = mTmpPointF.y;
+ initScale = child.getReorderScale();
+ finalScale = mChildScale - (CHILD_DIVIDEND / child.getView().getWidth()) * initScale;
+
int dir = mode == MODE_HINT ? -1 : 1;
if (dX == dY && dX == 0) {
} else {
if (dY == 0) {
- finalDeltaX += - dir * Math.signum(dX) * mReorderPreviewAnimationMagnitude;
+ finalDeltaX = -dir * Math.signum(dX) * mReorderPreviewAnimationMagnitude;
} else if (dX == 0) {
- finalDeltaY += - dir * Math.signum(dY) * mReorderPreviewAnimationMagnitude;
+ finalDeltaY = -dir * Math.signum(dY) * mReorderPreviewAnimationMagnitude;
} else {
double angle = Math.atan( (float) (dY) / dX);
- finalDeltaX += (int) (- dir * Math.signum(dX) *
- Math.abs(Math.cos(angle) * mReorderPreviewAnimationMagnitude));
- finalDeltaY += (int) (- dir * Math.signum(dY) *
- Math.abs(Math.sin(angle) * mReorderPreviewAnimationMagnitude));
+ finalDeltaX = (int) (-dir * Math.signum(dX)
+ * Math.abs(Math.cos(angle) * mReorderPreviewAnimationMagnitude));
+ finalDeltaY = (int) (-dir * Math.signum(dY)
+ * Math.abs(Math.sin(angle) * mReorderPreviewAnimationMagnitude));
}
}
}
- void setInitialAnimationValues(boolean restoreOriginalValues) {
- if (restoreOriginalValues) {
- if (child instanceof LauncherAppWidgetHostView) {
- LauncherAppWidgetHostView lahv = (LauncherAppWidgetHostView) child;
- initScale = lahv.getScaleToFit();
- initDeltaX = lahv.getTranslationForCentering().x;
- initDeltaY = lahv.getTranslationForCentering().y;
- } else {
- initScale = mChildScale;
- initDeltaX = 0;
- initDeltaY = 0;
- }
- } else {
- initScale = child.getScaleX();
- initDeltaX = child.getTranslationX();
- initDeltaY = child.getTranslationY();
- }
+ void setInitialAnimationValuesToBaseline() {
+ initScale = mChildScale;
+ initDeltaX = 0;
+ initDeltaY = 0;
}
void animate() {
- boolean noMovement = (finalDeltaX == initDeltaX) && (finalDeltaY == initDeltaY);
+ boolean noMovement = (finalDeltaX == 0) && (finalDeltaY == 0);
if (mShakeAnimators.containsKey(child)) {
ReorderPreviewAnimation oldAnimation = mShakeAnimators.get(child);
- oldAnimation.cancel();
mShakeAnimators.remove(child);
+
if (noMovement) {
- completeAnimationImmediately();
+ // A previous animation for this item exists, and no new animation will exist.
+ // Finish the old animation smoothly.
+ oldAnimation.finishAnimation();
return;
+ } else {
+ // A previous animation for this item exists, and a new one will exist. Stop
+ // the old animation in its tracks, and proceed with the new one.
+ oldAnimation.cancel();
}
}
if (noMovement) {
return;
}
+
ValueAnimator va = ObjectAnimator.ofFloat(this, ANIMATION_PROGRESS, 0, 1);
a = va;
@@ -1999,7 +1997,7 @@
va.addListener(new AnimatorListenerAdapter() {
public void onAnimationRepeat(Animator animation) {
// We make sure to end only after a full period
- setInitialAnimationValues(true);
+ setInitialAnimationValuesToBaseline();
repeating = true;
}
});
@@ -2012,11 +2010,9 @@
float r1 = (mode == MODE_HINT && repeating) ? 1.0f : animationProgress;
float x = r1 * finalDeltaX + (1 - r1) * initDeltaX;
float y = r1 * finalDeltaY + (1 - r1) * initDeltaY;
- child.setTranslationX(x);
- child.setTranslationY(y);
+ child.setReorderOffset(x, y);
float s = animationProgress * finalScale + (1 - animationProgress) * initScale;
- child.setScaleX(s);
- child.setScaleY(s);
+ child.setReorderScale(s);
}
private void cancel() {
@@ -2025,27 +2021,27 @@
}
}
- @Thunk void completeAnimationImmediately() {
+ /**
+ * Smoothly returns the item to its baseline position / scale
+ */
+ @Thunk void finishAnimation() {
if (a != null) {
a.cancel();
}
- setInitialAnimationValues(true);
- a = new PropertyListBuilder()
- .scale(initScale)
- .translationX(initDeltaX)
- .translationY(initDeltaY)
- .build(child)
- .setDuration(REORDER_ANIMATION_DURATION);
- Launcher.cast(mActivity).getDragController().addFirstFrameAnimationHelper(a);
+ setInitialAnimationValuesToBaseline();
+ ValueAnimator va = ObjectAnimator.ofFloat(this, ANIMATION_PROGRESS,
+ animationProgress, 0);
+ a = va;
a.setInterpolator(DEACCEL_1_5);
+ a.setDuration(REORDER_ANIMATION_DURATION);
a.start();
}
}
private void completeAndClearReorderPreviewAnimations() {
for (ReorderPreviewAnimation a: mShakeAnimators.values()) {
- a.completeAnimationImmediately();
+ a.finishAnimation();
}
mShakeAnimators.clear();
}
diff --git a/src/com/android/launcher3/Reorderable.java b/src/com/android/launcher3/Reorderable.java
new file mode 100644
index 0000000..5112eaf
--- /dev/null
+++ b/src/com/android/launcher3/Reorderable.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2020 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.graphics.PointF;
+import android.view.View;
+
+public interface Reorderable {
+
+ /**
+ * Set the offset related to reorder hint and "bounce" animations
+ */
+ void setReorderOffset(float x, float y);
+
+ void getReorderOffset(PointF offset);
+
+ /**
+ * Set the scale related to reorder hint and "bounce" animations
+ */
+ void setReorderScale(float scale);
+ float getReorderScale();
+
+ /**
+ * Get the com.android.view related to this object
+ */
+ View getView();
+}
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index e29971e..96bdc2a 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -26,6 +26,7 @@
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Canvas;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
@@ -49,6 +50,7 @@
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.OnAlarmListener;
import com.android.launcher3.R;
+import com.android.launcher3.Reorderable;
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
import com.android.launcher3.anim.Interpolators;
@@ -79,7 +81,7 @@
* An icon that can appear on in the workspace representing an {@link Folder}.
*/
public class FolderIcon extends FrameLayout implements FolderListener, IconLabelDotView,
- DraggableView {
+ DraggableView, Reorderable {
@Thunk ActivityContext mActivity;
@Thunk Folder mFolder;
@@ -119,6 +121,9 @@
private float mDotScale;
private Animator mDotScaleAnim;
+ private final PointF mTranslationForReorder = new PointF(0, 0);
+ private float mScaleForReorder = 1f;
+
private static final Property<FolderIcon, Float> DOT_SCALE_PROPERTY
= new Property<FolderIcon, Float>(Float.TYPE, "dotScale") {
@Override
@@ -226,16 +231,6 @@
mBackground.getBounds(outBounds);
}
- @Override
- public int getViewType() {
- return DRAGGABLE_ICON;
- }
-
- @Override
- public void getVisualDragBounds(Rect bounds) {
- getPreviewBounds(bounds);
- }
-
public float getBackgroundStrokeWidth() {
return mBackground.getStrokeWidth();
}
@@ -716,4 +711,39 @@
public void onFolderClose(int currentPage) {
mPreviewItemManager.onFolderClose(currentPage);
}
+
+
+ public void setReorderOffset(float x, float y) {
+ mTranslationForReorder.set(x, y);
+ super.setTranslationX(x);
+ super.setTranslationY(y);
+ }
+
+ public void getReorderOffset(PointF offset) {
+ offset.set(mTranslationForReorder);
+ }
+
+ public void setReorderScale(float scale) {
+ mScaleForReorder = scale;
+ super.setScaleX(scale);
+ super.setScaleY(scale);
+ }
+
+ public float getReorderScale() {
+ return mScaleForReorder;
+ }
+
+ public View getView() {
+ return this;
+ }
+
+ @Override
+ public int getViewType() {
+ return DRAGGABLE_ICON;
+ }
+
+ @Override
+ public void getVisualDragBounds(Rect bounds) {
+ getPreviewBounds(bounds);
+ }
}
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index 5d33f13..6f2e179 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -19,8 +19,6 @@
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
import android.content.res.Configuration;
-import android.graphics.PointF;
-import android.graphics.Rect;
import android.os.Handler;
import android.os.SystemClock;
import android.util.SparseBooleanArray;
@@ -40,7 +38,6 @@
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.dragndrop.DragLayer;
-import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.util.Executors;
@@ -51,7 +48,7 @@
* {@inheritDoc}
*/
public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView
- implements TouchCompleteListener, View.OnLongClickListener, DraggableView {
+ implements TouchCompleteListener, View.OnLongClickListener {
// Related to the auto-advancing of widgets
private static final long ADVANCE_INTERVAL = 20000;
@@ -73,15 +70,7 @@
private boolean mIsAutoAdvanceRegistered;
private Runnable mAutoAdvanceRunnable;
- /**
- * The scaleX and scaleY value such that the widget fits within its cellspans, scaleX = scaleY.
- */
- private float mScaleToFit = 1f;
- /**
- * The translation values to center the widget within its cellspans.
- */
- private final PointF mTranslationForCentering = new PointF(0, 0);
public LauncherAppWidgetHostView(Context context) {
super(context);
@@ -307,26 +296,6 @@
scheduleNextAdvance();
}
- public void setScaleToFit(float scale) {
- mScaleToFit = scale;
- setScaleX(scale);
- setScaleY(scale);
- }
-
- public float getScaleToFit() {
- return mScaleToFit;
- }
-
- public void setTranslationForCentering(float x, float y) {
- mTranslationForCentering.set(x, y);
- setTranslationX(x);
- setTranslationY(y);
- }
-
- public PointF getTranslationForCentering() {
- return mTranslationForCentering;
- }
-
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
@@ -357,17 +326,4 @@
}
return false;
}
-
- @Override
- public int getViewType() {
- return DRAGGABLE_WIDGET;
- }
-
- @Override
- public void getVisualDragBounds(Rect bounds) {
- int width = (int) (getMeasuredWidth() * mScaleToFit);
- int height = (int) (getMeasuredHeight() * mScaleToFit);
-
- bounds.set(0, 0 , width, height);
- }
}
diff --git a/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java b/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
index 104ad77..5ea01d9 100644
--- a/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
@@ -18,12 +18,14 @@
import android.appwidget.AppWidgetHostView;
import android.content.Context;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
+import com.android.launcher3.Reorderable;
import com.android.launcher3.dragndrop.DraggableView;
import java.util.ArrayList;
@@ -32,7 +34,20 @@
* Extension of AppWidgetHostView with support for controlled keyboard navigation.
*/
public abstract class NavigableAppWidgetHostView extends AppWidgetHostView
- implements DraggableView {
+ implements DraggableView, Reorderable {
+
+ /**
+ * The scaleX and scaleY value such that the widget fits within its cellspans, scaleX = scaleY.
+ */
+ private float mScaleToFit = 1f;
+
+ /**
+ * The translation values to center the widget within its cellspans.
+ */
+ private final PointF mTranslationForCentering = new PointF(0, 0);
+
+ private final PointF mTranslationForReorder = new PointF(0, 0);
+ private float mScaleForReorder = 1f;
@ViewDebug.ExportedProperty(category = "launcher")
private boolean mChildrenFocused;
@@ -137,6 +152,47 @@
setSelected(childIsFocused);
}
+ public void setScaleToFit(float scale) {
+ mScaleToFit = scale;
+ super.setScaleX(scale * mScaleForReorder);
+ super.setScaleY(scale * mScaleForReorder);
+ }
+
+ public float getScaleToFit() {
+ return mScaleToFit;
+ }
+
+ public View getView() {
+ return this;
+ }
+
+
+ public void setTranslationForCentering(float x, float y) {
+ mTranslationForCentering.set(x, y);
+ super.setTranslationX(x + mTranslationForReorder.x);
+ super.setTranslationY(y + mTranslationForReorder.y);
+ }
+
+ public void setReorderOffset(float x, float y) {
+ mTranslationForReorder.set(x, y);
+ super.setTranslationX(mTranslationForCentering.x + x);
+ super.setTranslationY(mTranslationForCentering.y + y);
+ }
+
+ public void getReorderOffset(PointF offset) {
+ offset.set(mTranslationForReorder);
+ }
+
+ public void setReorderScale(float scale) {
+ mScaleForReorder = scale;
+ super.setScaleX(mScaleToFit * scale);
+ super.setScaleY(mScaleToFit * scale);
+ }
+
+ public float getReorderScale() {
+ return mScaleForReorder;
+ }
+
@Override
public int getViewType() {
return DRAGGABLE_WIDGET;
@@ -144,6 +200,9 @@
@Override
public void getVisualDragBounds(Rect bounds) {
- bounds.set(0, 0 , getMeasuredWidth(), getMeasuredHeight());
+ int width = (int) (getMeasuredWidth() * mScaleToFit);
+ int height = (int) (getMeasuredHeight() * mScaleToFit);
+
+ bounds.set(0, 0 , width, height);
}
}