App Pairs: Launch animation
[App Pairs 7/?]
This patch implements the app pair launch animation from icon. Adds a new function, composeFadeInSplitLaunchAnimator(), in SplitAnimationController, that builds the combined launcher + shell animation.
Bug: 309618233
Flag: ACONFIG com.android.wm.shell.enable_app_pairs DEVELOPMENT
Test: Manual
Change-Id: I8e95f629ae2a71f1bd6cbb356f5e33233e5c2906
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index e301bdb..031ca5b 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -3212,7 +3212,7 @@
* Handles an app pair launch; overridden in
* {@link com.android.launcher3.uioverrides.QuickstepLauncher}
*/
- public void launchAppPair(WorkspaceItemInfo app1, WorkspaceItemInfo app2) {
+ public void launchAppPair(AppPairIcon appPairIcon) {
// Overridden
}
diff --git a/src/com/android/launcher3/apppairs/AppPairIcon.java b/src/com/android/launcher3/apppairs/AppPairIcon.java
index 46932fb..d201b8f 100644
--- a/src/com/android/launcher3/apppairs/AppPairIcon.java
+++ b/src/com/android/launcher3/apppairs/AppPairIcon.java
@@ -17,11 +17,10 @@
package com.android.launcher3.apppairs;
import android.content.Context;
-import android.graphics.Canvas;
import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
+import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
@@ -33,7 +32,6 @@
import com.android.launcher3.Reorderable;
import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.model.data.FolderInfo;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.MultiTranslateDelegate;
import com.android.launcher3.views.ActivityContext;
@@ -47,33 +45,8 @@
* member apps are set into these rectangles.
*/
public class AppPairIcon extends FrameLayout implements DraggableView, Reorderable {
- /**
- * Design specs -- the below ratios are in relation to the size of a standard app icon.
- */
- private static final float OUTER_PADDING_SCALE = 1 / 30f;
- private static final float INNER_PADDING_SCALE = 1 / 24f;
- private static final float MEMBER_ICON_SCALE = 11 / 30f;
- private static final float CENTER_CHANNEL_SCALE = 1 / 30f;
- private static final float BIG_RADIUS_SCALE = 1 / 5f;
- private static final float SMALL_RADIUS_SCALE = 1 / 15f;
-
- // App pair icons are slightly smaller than regular icons, so we pad the icon by this much on
- // each side.
- float mOuterPadding;
- // Inside of the icon, the two member apps are padded by this much.
- float mInnerPadding;
- // The two member apps have icons that are this big (in diameter).
- float mMemberIconSize;
- // The size of the center channel.
- float mCenterChannelSize;
- // The large outer radius of the background rectangles.
- float mBigRadius;
- // The small inner radius of the background rectangles.
- float mSmallRadius;
- // The app pairs icon appears differently in portrait and landscape.
- boolean mIsLandscape;
-
- private ActivityContext mActivity;
+ // A view that holds the app pair icon graphic.
+ private AppPairIconGraphic mIconGraphic;
// A view that holds the app pair's title.
private BubbleTextView mAppPairName;
// The underlying ItemInfo that stores info about the app pair members, etc.
@@ -109,7 +82,10 @@
icon.setTag(appPairInfo);
icon.setOnClickListener(activity.getItemOnClickListener());
icon.mInfo = appPairInfo;
- icon.mActivity = activity;
+
+ // Set up icon drawable area
+ icon.mIconGraphic = icon.findViewById(R.id.app_pair_icon_graphic);
+ icon.mIconGraphic.init(activity.getDeviceProfile(), icon);
// Set up app pair title
icon.mAppPairName = icon.findViewById(R.id.app_pair_icon_name);
@@ -127,85 +103,6 @@
return icon;
}
- @Override
- protected void dispatchDraw(Canvas canvas) {
- super.dispatchDraw(canvas);
-
- // Calculate device-specific measurements
- DeviceProfile grid = mActivity.getDeviceProfile();
- int defaultIconSize = grid.iconSizePx;
- mOuterPadding = OUTER_PADDING_SCALE * defaultIconSize;
- mInnerPadding = INNER_PADDING_SCALE * defaultIconSize;
- mMemberIconSize = MEMBER_ICON_SCALE * defaultIconSize;
- mCenterChannelSize = CENTER_CHANNEL_SCALE * defaultIconSize;
- mBigRadius = BIG_RADIUS_SCALE * defaultIconSize;
- mSmallRadius = SMALL_RADIUS_SCALE * defaultIconSize;
- mIsLandscape = grid.isLeftRightSplit;
-
- // Calculate drawable area position
- float leftBound = (canvas.getWidth() / 2f) - (defaultIconSize / 2f);
- float topBound = getPaddingTop();
-
- // Prepare to draw app pair icon background
- Drawable background = new AppPairIconBackground(getContext(), this);
- background.setBounds(0, 0, defaultIconSize, defaultIconSize);
-
- // Draw background
- canvas.save();
- canvas.translate(leftBound, topBound);
- background.draw(canvas);
- canvas.restore();
-
- // Prepare to draw icons
- WorkspaceItemInfo app1 = mInfo.contents.get(0);
- WorkspaceItemInfo app2 = mInfo.contents.get(1);
- Drawable app1Icon = app1.newIcon(getContext());
- Drawable app2Icon = app2.newIcon(getContext());
- app1Icon.setBounds(0, 0, defaultIconSize, defaultIconSize);
- app2Icon.setBounds(0, 0, defaultIconSize, defaultIconSize);
-
- // Draw first icon
- canvas.save();
- canvas.translate(leftBound, topBound);
- // The app icons are placed differently depending on device orientation.
- if (mIsLandscape) {
- canvas.translate(
- (defaultIconSize / 2f) - (mCenterChannelSize / 2f) - mInnerPadding
- - mMemberIconSize,
- (defaultIconSize / 2f) - (mMemberIconSize / 2f)
- );
- } else {
- canvas.translate(
- (defaultIconSize / 2f) - (mMemberIconSize / 2f),
- (defaultIconSize / 2f) - (mCenterChannelSize / 2f) - mInnerPadding
- - mMemberIconSize
- );
-
- }
- canvas.scale(MEMBER_ICON_SCALE, MEMBER_ICON_SCALE);
- app1Icon.draw(canvas);
- canvas.restore();
-
- // Draw second icon
- canvas.save();
- canvas.translate(leftBound, topBound);
- // The app icons are placed differently depending on device orientation.
- if (mIsLandscape) {
- canvas.translate(
- (defaultIconSize / 2f) + (mCenterChannelSize / 2f) + mInnerPadding,
- (defaultIconSize / 2f) - (mMemberIconSize / 2f)
- );
- } else {
- canvas.translate(
- (defaultIconSize / 2f) - (mMemberIconSize / 2f),
- (defaultIconSize / 2f) + (mCenterChannelSize / 2f) + mInnerPadding
- );
- }
- canvas.scale(MEMBER_ICON_SCALE, MEMBER_ICON_SCALE);
- app2Icon.draw(canvas);
- canvas.restore();
- }
-
/**
* Returns a formatted accessibility title for app pairs.
*/
@@ -257,4 +154,8 @@
public FolderInfo getInfo() {
return mInfo;
}
+
+ public View getIconDrawableArea() {
+ return mIconGraphic;
+ }
}
diff --git a/src/com/android/launcher3/apppairs/AppPairIconBackground.java b/src/com/android/launcher3/apppairs/AppPairIconBackground.java
index 735c82f..4e60ece 100644
--- a/src/com/android/launcher3/apppairs/AppPairIconBackground.java
+++ b/src/com/android/launcher3/apppairs/AppPairIconBackground.java
@@ -32,8 +32,8 @@
* A Drawable for the background behind the twin app icons (looks like two rectangles).
*/
class AppPairIconBackground extends Drawable {
- // The icon that we will draw this background on.
- private final AppPairIcon icon;
+ // The underlying view that we are drawing this background on.
+ private final AppPairIconGraphic icon;
private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
/**
@@ -44,8 +44,8 @@
private static final RectF EMPTY_RECT = new RectF();
private static final float[] ARRAY_OF_ZEROES = new float[8];
- AppPairIconBackground(Context context, AppPairIcon appPairIcon) {
- icon = appPairIcon;
+ AppPairIconBackground(Context context, AppPairIconGraphic iconGraphic) {
+ icon = iconGraphic;
// Set up background paint color
TypedArray ta = context.getTheme().obtainStyledAttributes(R.styleable.FolderIconPreview);
mBackgroundPaint.setStyle(Paint.Style.FILL);
@@ -56,7 +56,7 @@
@Override
public void draw(Canvas canvas) {
- if (icon.mIsLandscape) {
+ if (icon.isLeftRightSplit()) {
drawLeftRightSplit(canvas);
} else {
drawTopBottomSplit(canvas);
@@ -73,29 +73,29 @@
// The left half of the background image, excluding center channel
RectF leftSide = new RectF(
- icon.mOuterPadding,
- icon.mOuterPadding,
- (width / 2f) - (icon.mCenterChannelSize / 2f),
- height - icon.mOuterPadding
+ 0,
+ 0,
+ (width / 2f) - (icon.getCenterChannelSize() / 2f),
+ height
);
// The right half of the background image, excluding center channel
RectF rightSide = new RectF(
- (width / 2f) + (icon.mCenterChannelSize / 2f),
- icon.mOuterPadding,
- width - icon.mOuterPadding,
- height - icon.mOuterPadding
+ (width / 2f) + (icon.getCenterChannelSize() / 2f),
+ 0,
+ width,
+ height
);
drawCustomRoundedRect(canvas, leftSide, new float[]{
- icon.mBigRadius, icon.mBigRadius,
- icon.mSmallRadius, icon.mSmallRadius,
- icon.mSmallRadius, icon.mSmallRadius,
- icon.mBigRadius, icon.mBigRadius});
+ icon.getBigRadius(), icon.getBigRadius(),
+ icon.getSmallRadius(), icon.getSmallRadius(),
+ icon.getSmallRadius(), icon.getSmallRadius(),
+ icon.getBigRadius(), icon.getBigRadius()});
drawCustomRoundedRect(canvas, rightSide, new float[]{
- icon.mSmallRadius, icon.mSmallRadius,
- icon.mBigRadius, icon.mBigRadius,
- icon.mBigRadius, icon.mBigRadius,
- icon.mSmallRadius, icon.mSmallRadius});
+ icon.getSmallRadius(), icon.getSmallRadius(),
+ icon.getBigRadius(), icon.getBigRadius(),
+ icon.getBigRadius(), icon.getBigRadius(),
+ icon.getSmallRadius(), icon.getSmallRadius()});
}
/**
@@ -108,29 +108,29 @@
// The top half of the background image, excluding center channel
RectF topSide = new RectF(
- icon.mOuterPadding,
- icon.mOuterPadding,
- width - icon.mOuterPadding,
- (height / 2f) - (icon.mCenterChannelSize / 2f)
+ 0,
+ 0,
+ width,
+ (height / 2f) - (icon.getCenterChannelSize() / 2f)
);
// The bottom half of the background image, excluding center channel
RectF bottomSide = new RectF(
- icon.mOuterPadding,
- (height / 2f) + (icon.mCenterChannelSize / 2f),
- width - icon.mOuterPadding,
- height - icon.mOuterPadding
+ 0,
+ (height / 2f) + (icon.getCenterChannelSize() / 2f),
+ width,
+ height
);
drawCustomRoundedRect(canvas, topSide, new float[]{
- icon.mBigRadius, icon.mBigRadius,
- icon.mBigRadius, icon.mBigRadius,
- icon.mSmallRadius, icon.mSmallRadius,
- icon.mSmallRadius, icon.mSmallRadius});
+ icon.getBigRadius(), icon.getBigRadius(),
+ icon.getBigRadius(), icon.getBigRadius(),
+ icon.getSmallRadius(), icon.getSmallRadius(),
+ icon.getSmallRadius(), icon.getSmallRadius()});
drawCustomRoundedRect(canvas, bottomSide, new float[]{
- icon.mSmallRadius, icon.mSmallRadius,
- icon.mSmallRadius, icon.mSmallRadius,
- icon.mBigRadius, icon.mBigRadius,
- icon.mBigRadius, icon.mBigRadius});
+ icon.getSmallRadius(), icon.getSmallRadius(),
+ icon.getSmallRadius(), icon.getSmallRadius(),
+ icon.getBigRadius(), icon.getBigRadius(),
+ icon.getBigRadius(), icon.getBigRadius()});
}
/**
@@ -146,7 +146,7 @@
c.drawDoubleRoundRect(rect, radii, EMPTY_RECT, ARRAY_OF_ZEROES, mBackgroundPaint);
} else {
// Fallback rectangle with uniform rounded corners
- c.drawRoundRect(rect, icon.mBigRadius, icon.mBigRadius, mBackgroundPaint);
+ c.drawRoundRect(rect, icon.getBigRadius(), icon.getBigRadius(), mBackgroundPaint);
}
}
diff --git a/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt b/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt
new file mode 100644
index 0000000..34467ec
--- /dev/null
+++ b/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2023 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.apppairs
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.drawable.Drawable
+import android.util.AttributeSet
+import android.view.Gravity
+import android.widget.FrameLayout
+import com.android.launcher3.DeviceProfile
+
+/**
+ * A FrameLayout marking the area on an [AppPairIcon] where the visual icon will be drawn. One of
+ * two child UI elements on an [AppPairIcon], along with a BubbleTextView holding the text title.
+ */
+class AppPairIconGraphic
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null) : FrameLayout(context, attrs) {
+ companion object {
+ // Design specs -- the below ratios are in relation to the size of a standard app icon.
+ private const val OUTER_PADDING_SCALE = 1 / 30f
+ private const val INNER_PADDING_SCALE = 1 / 24f
+ private const val MEMBER_ICON_SCALE = 11 / 30f
+ private const val CENTER_CHANNEL_SCALE = 1 / 30f
+ private const val BIG_RADIUS_SCALE = 1 / 5f
+ private const val SMALL_RADIUS_SCALE = 1 / 15f
+ }
+
+ // App pair icons are slightly smaller than regular icons, so we pad the icon by this much on
+ // each side.
+ private var outerPadding = 0f
+ // Inside of the icon, the two member apps are padded by this much.
+ private var innerPadding = 0f
+ // The colored background (two rectangles in a square area) is this big.
+ private var backgroundSize = 0f
+ // The two member apps have icons that are this big (in diameter).
+ private var memberIconSize = 0f
+ // The size of the center channel.
+ var centerChannelSize = 0f
+ // The large outer radius of the background rectangles.
+ var bigRadius = 0f
+ // The small inner radius of the background rectangles.
+ var smallRadius = 0f
+ // The app pairs icon appears differently in portrait and landscape.
+ var isLeftRightSplit = false
+
+ private lateinit var appPairBackground: Drawable
+ private lateinit var appIcon1: Drawable
+ private lateinit var appIcon2: Drawable
+
+ fun init(grid: DeviceProfile, icon: AppPairIcon) {
+ // Calculate device-specific measurements
+ val defaultIconSize = grid.iconSizePx
+ outerPadding = OUTER_PADDING_SCALE * defaultIconSize
+ innerPadding = INNER_PADDING_SCALE * defaultIconSize
+ backgroundSize = defaultIconSize - outerPadding * 2
+ memberIconSize = MEMBER_ICON_SCALE * defaultIconSize
+ centerChannelSize = CENTER_CHANNEL_SCALE * defaultIconSize
+ bigRadius = BIG_RADIUS_SCALE * defaultIconSize
+ smallRadius = SMALL_RADIUS_SCALE * defaultIconSize
+ isLeftRightSplit = grid.isLeftRightSplit
+
+ appPairBackground = AppPairIconBackground(context, this)
+ appPairBackground.setBounds(0, 0, backgroundSize.toInt(), backgroundSize.toInt())
+ appIcon1 = icon.info.contents[0].newIcon(context)
+ appIcon2 = icon.info.contents[1].newIcon(context)
+ appIcon1.setBounds(0, 0, memberIconSize.toInt(), memberIconSize.toInt())
+ appIcon2.setBounds(0, 0, memberIconSize.toInt(), memberIconSize.toInt())
+ }
+
+ override fun dispatchDraw(canvas: Canvas) {
+ super.dispatchDraw(canvas)
+
+ // Center the drawable area in the larger icon canvas
+ val lp: LayoutParams = layoutParams as LayoutParams
+ lp.gravity = Gravity.CENTER_HORIZONTAL
+ lp.topMargin = outerPadding.toInt()
+ lp.height = backgroundSize.toInt()
+ lp.width = backgroundSize.toInt()
+ layoutParams = lp
+
+ // Draw background
+ appPairBackground.draw(canvas)
+
+ // Draw first icon
+ canvas.save()
+ // The app icons are placed differently depending on device orientation.
+ if (isLeftRightSplit) {
+ canvas.translate(innerPadding, height / 2f - memberIconSize / 2f)
+ } else {
+ canvas.translate(width / 2f - memberIconSize / 2f, innerPadding)
+ }
+ appIcon1.draw(canvas)
+ canvas.restore()
+
+ // Draw second icon
+ canvas.save()
+ // The app icons are placed differently depending on device orientation.
+ if (isLeftRightSplit) {
+ canvas.translate(
+ width - (innerPadding + memberIconSize),
+ height / 2f - memberIconSize / 2f
+ )
+ } else {
+ canvas.translate(
+ width / 2f - memberIconSize / 2f,
+ height - (innerPadding + memberIconSize)
+ )
+ }
+ appIcon2.draw(canvas)
+ canvas.restore()
+ }
+}
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index 3bce377..a9c2a2e 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -145,8 +145,8 @@
*/
private static void onClickAppPairIcon(View v) {
Launcher launcher = Launcher.getLauncher(v.getContext());
- FolderInfo folderInfo = ((AppPairIcon) v).getInfo();
- launcher.launchAppPair(folderInfo.contents.get(0), folderInfo.contents.get(1));
+ AppPairIcon appPairIcon = (AppPairIcon) v;
+ launcher.launchAppPair(appPairIcon);
}
/**