Merge "Drag bubble in bubble bar to other side" into main
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ 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.
|
||||
-->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="@dimen/bubble_expanded_view_drop_target_corner_radius" />
|
||||
<solid android:color="@color/bubblebar_drop_target_bg_color" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="?androidprv:attr/materialColorPrimaryContainer" />
|
||||
</shape>
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<!-- TODO(b/330585402): replace 600dp height with calculated value -->
|
||||
<View xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="@dimen/bubble_expanded_view_drop_target_width"
|
||||
android:layout_height="600dp"
|
||||
android:layout_margin="@dimen/bubble_expanded_view_drop_target_margin"
|
||||
android:background="@drawable/bg_bubble_expanded_view_drop_target"
|
||||
android:elevation="@dimen/bubblebar_elevation" />
|
||||
@@ -456,6 +456,9 @@
|
||||
|
||||
<!-- Bubble bar drop target -->
|
||||
<dimen name="bubblebar_drop_target_corner_radius">36dp</dimen>
|
||||
<dimen name="bubble_expanded_view_drop_target_corner_radius">16dp</dimen>
|
||||
<dimen name="bubble_expanded_view_drop_target_width">412dp</dimen>
|
||||
<dimen name="bubble_expanded_view_drop_target_margin">16dp</dimen>
|
||||
|
||||
<!-- Launcher splash screen -->
|
||||
<!-- Note: keep this value in sync with the WindowManager/Shell dimens.xml -->
|
||||
|
||||
@@ -106,6 +106,7 @@ import com.android.launcher3.taskbar.bubbles.BubbleBarViewController;
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleControllers;
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleDismissController;
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleDragController;
|
||||
import com.android.launcher3.taskbar.bubbles.BubblePinController;
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleStashController;
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleStashedHandleViewController;
|
||||
import com.android.launcher3.taskbar.navbutton.NearestTouchFrame;
|
||||
@@ -259,6 +260,8 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
new BubbleDragController(this),
|
||||
new BubbleDismissController(this, mDragLayer),
|
||||
new BubbleBarPinController(this, mDragLayer,
|
||||
() -> getDeviceProfile().getDisplayInfo().currentSize),
|
||||
new BubblePinController(this, mDragLayer,
|
||||
() -> getDeviceProfile().getDisplayInfo().currentSize)
|
||||
));
|
||||
}
|
||||
|
||||
@@ -131,21 +131,15 @@ class BubbleBarBackground(context: Context, private var backgroundHeight: Float)
|
||||
|
||||
// Draw background.
|
||||
val radius = backgroundHeight / 2f
|
||||
val left = if (anchorLeft) 0f else bounds.width().toFloat() - width
|
||||
val right = if (anchorLeft) width else bounds.width().toFloat()
|
||||
canvas.drawRoundRect(
|
||||
left,
|
||||
pointerVisibleHeight,
|
||||
right,
|
||||
bounds.height().toFloat(),
|
||||
radius,
|
||||
radius,
|
||||
paint
|
||||
)
|
||||
val left = bounds.left + (if (anchorLeft) 0f else bounds.width().toFloat() - width)
|
||||
val right = bounds.left + (if (anchorLeft) width else bounds.width().toFloat())
|
||||
val top = bounds.top + pointerVisibleHeight
|
||||
val bottom = bounds.top + bounds.height().toFloat()
|
||||
canvas.drawRoundRect(left, top, right, bottom, radius, radius, paint)
|
||||
|
||||
if (showingArrow) {
|
||||
// Draw arrow.
|
||||
val transX = arrowPositionX - pointerWidth / 2f
|
||||
val transX = bounds.left + arrowPositionX - pointerWidth / 2f
|
||||
canvas.translate(transX, 0f)
|
||||
arrowDrawable.draw(canvas)
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package com.android.launcher3.taskbar.bubbles;
|
||||
|
||||
import static com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE;
|
||||
import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
|
||||
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
|
||||
|
||||
import android.animation.Animator;
|
||||
@@ -29,6 +30,7 @@ import android.content.Context;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.FloatProperty;
|
||||
import android.util.LayoutDirection;
|
||||
import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
@@ -79,6 +81,7 @@ public class BubbleBarView extends FrameLayout {
|
||||
// TODO: (b/273594744) calculate the amount of space we have and base the max on that
|
||||
// if it's smaller than 5.
|
||||
private static final int MAX_BUBBLES = 5;
|
||||
private static final int MAX_VISIBLE_BUBBLES_COLLAPSED = 2;
|
||||
private static final int ARROW_POSITION_ANIMATION_DURATION_MS = 200;
|
||||
private static final int WIDTH_ANIMATION_DURATION_MS = 200;
|
||||
|
||||
@@ -94,6 +97,40 @@ public class BubbleBarView extends FrameLayout {
|
||||
// During fade in animation we shift the bubble bar 1/60th of the screen width
|
||||
private static final float FADE_IN_ANIM_POSITION_SHIFT = 1 / 60f;
|
||||
|
||||
/**
|
||||
* Custom property to set translationX value for the bar view while a bubble is being dragged.
|
||||
* Skips applying translation to the dragged bubble.
|
||||
*/
|
||||
private static final FloatProperty<BubbleBarView> BUBBLE_DRAG_TRANSLATION_X =
|
||||
new FloatProperty<>("bubbleDragTranslationX") {
|
||||
@Override
|
||||
public void setValue(BubbleBarView bubbleBarView, float translationX) {
|
||||
bubbleBarView.setTranslationXDuringBubbleDrag(translationX);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float get(BubbleBarView bubbleBarView) {
|
||||
return bubbleBarView.mTranslationXDuringDrag;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Custom property to set alpha value for the bar view while a bubble is being dragged.
|
||||
* Skips applying alpha to the dragged bubble.
|
||||
*/
|
||||
private static final FloatProperty<BubbleBarView> BUBBLE_DRAG_ALPHA =
|
||||
new FloatProperty<>("bubbleDragAlpha") {
|
||||
@Override
|
||||
public void setValue(BubbleBarView bubbleBarView, float alpha) {
|
||||
bubbleBarView.setAlphaDuringBubbleDrag(alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float get(BubbleBarView bubbleBarView) {
|
||||
return bubbleBarView.mAlphaDuringDrag;
|
||||
}
|
||||
};
|
||||
|
||||
private final BubbleBarBackground mBubbleBarBackground;
|
||||
|
||||
private boolean mIsAnimatingNewBubble = false;
|
||||
@@ -117,6 +154,8 @@ public class BubbleBarView extends FrameLayout {
|
||||
private final float mDragElevation;
|
||||
private final int mPointerSize;
|
||||
|
||||
private final Rect mTempBackgroundBounds = new Rect();
|
||||
|
||||
// Whether the bar is expanded (i.e. the bubble activity is being displayed).
|
||||
private boolean mIsBarExpanded = false;
|
||||
// The currently selected bubble view.
|
||||
@@ -150,6 +189,8 @@ public class BubbleBarView extends FrameLayout {
|
||||
|
||||
@Nullable
|
||||
private BubbleView mDraggedBubbleView;
|
||||
private float mTranslationXDuringDrag = 0f;
|
||||
private float mAlphaDuringDrag = 0f;
|
||||
|
||||
private int mPreviousLayoutDirection = LayoutDirection.UNDEFINED;
|
||||
|
||||
@@ -259,8 +300,10 @@ public class BubbleBarView extends FrameLayout {
|
||||
setPivotX(mRelativePivotX * getWidth());
|
||||
setPivotY(mRelativePivotY * getHeight());
|
||||
|
||||
// Position the views
|
||||
updateChildrenRenderNodeProperties(mBubbleBarLocation);
|
||||
if (!mDragging) {
|
||||
// Position the views when not dragging
|
||||
updateChildrenRenderNodeProperties(mBubbleBarLocation);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -302,17 +345,15 @@ public class BubbleBarView extends FrameLayout {
|
||||
mBubbleBarLocationAnimator.cancel();
|
||||
mBubbleBarLocationAnimator = null;
|
||||
}
|
||||
setTranslationX(0f);
|
||||
setAlpha(1f);
|
||||
resetDragAnimation();
|
||||
if (bubbleBarLocation != mBubbleBarLocation) {
|
||||
mBubbleBarLocation = bubbleBarLocation;
|
||||
onBubbleBarLocationChanged();
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether this view is being currently being dragged
|
||||
* Set whether this view is currently being dragged
|
||||
*/
|
||||
public void setIsDragging(boolean dragging) {
|
||||
if (mDragging == dragging) {
|
||||
@@ -326,7 +367,7 @@ public class BubbleBarView extends FrameLayout {
|
||||
* Get translation for bubble bar when drag is released and it needs to animate back to the
|
||||
* resting position.
|
||||
* Resting position is based on the supplied location. If the supplied location is different
|
||||
* from the internal location that was used to lay out the bubble bar, translation values are
|
||||
* from the internal location that was used during bubble bar layout, translation values are
|
||||
* calculated to position the bar at the desired location.
|
||||
*
|
||||
* @param initialTranslation initial bubble bar translation at the start of drag
|
||||
@@ -353,6 +394,30 @@ public class BubbleBarView extends FrameLayout {
|
||||
return dragEndTranslation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get translation for a bubble when drag is released and it needs to animate back to the
|
||||
* resting position.
|
||||
* Resting position is based on the supplied location. If the supplied location is different
|
||||
* from the internal location that was used during bubble bar layout, translation values are
|
||||
* calculated to position the bar at the desired location.
|
||||
*
|
||||
* @param initialTranslation initial bubble bar translation at the start of drag
|
||||
* @param location desired location of the bubble bar when drag is released
|
||||
* @return point with x and y values representing translation on x and y-axis
|
||||
*/
|
||||
public PointF getDraggedBubbleReleaseTranslation(PointF initialTranslation,
|
||||
BubbleBarLocation location) {
|
||||
// Start with bubble bar translation
|
||||
final PointF dragEndTranslation = new PointF(
|
||||
getBubbleBarDragReleaseTranslation(initialTranslation, location));
|
||||
// Apply individual bubble translation, as the order may have changed
|
||||
int viewIndex = indexOfChild(mDraggedBubbleView);
|
||||
dragEndTranslation.x += getExpandedBubbleTranslationX(viewIndex,
|
||||
getChildCount(),
|
||||
location.isOnLeft(isLayoutRtl()));
|
||||
return dragEndTranslation;
|
||||
}
|
||||
|
||||
private float getDistanceFromOtherSide() {
|
||||
// Calculate the shift needed to position the bubble bar on the other side
|
||||
int displayWidth = getResources().getDisplayMetrics().widthPixels;
|
||||
@@ -393,17 +458,18 @@ public class BubbleBarView extends FrameLayout {
|
||||
mBubbleBarLocationAnimator.start();
|
||||
}
|
||||
|
||||
private Animator getLocationUpdateFadeOutAnimator(BubbleBarLocation bubbleBarLocation) {
|
||||
private Animator getLocationUpdateFadeOutAnimator(BubbleBarLocation newLocation) {
|
||||
final FloatProperty<? super BubbleBarView> txProp = getLocationAnimTranslationXProperty();
|
||||
final float shift =
|
||||
getResources().getDisplayMetrics().widthPixels * FADE_OUT_ANIM_POSITION_SHIFT;
|
||||
final boolean onLeft = bubbleBarLocation.isOnLeft(isLayoutRtl());
|
||||
final float tx = getTranslationX() + (onLeft ? shift : -shift);
|
||||
final boolean onLeft = newLocation.isOnLeft(isLayoutRtl());
|
||||
final float tx = txProp.get(this) + (onLeft ? -shift : shift);
|
||||
|
||||
ObjectAnimator positionAnim = ObjectAnimator.ofFloat(this, TRANSLATION_X, tx)
|
||||
.setDuration(FADE_OUT_ANIM_POSITION_DURATION_MS);
|
||||
ObjectAnimator positionAnim = ObjectAnimator.ofFloat(this, txProp, tx).setDuration(
|
||||
FADE_OUT_ANIM_POSITION_DURATION_MS);
|
||||
positionAnim.setInterpolator(EMPHASIZED_ACCELERATE);
|
||||
|
||||
ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(this, ALPHA, 0f)
|
||||
ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(this, getLocationAnimAlphaProperty(), 0f)
|
||||
.setDuration(FADE_OUT_ANIM_ALPHA_DURATION_MS);
|
||||
alphaAnim.setStartDelay(FADE_OUT_ANIM_ALPHA_DELAY_MS);
|
||||
|
||||
@@ -412,14 +478,14 @@ public class BubbleBarView extends FrameLayout {
|
||||
return animatorSet;
|
||||
}
|
||||
|
||||
private Animator getLocationUpdateFadeInAnimator(BubbleBarLocation animatedLocation) {
|
||||
private Animator getLocationUpdateFadeInAnimator(BubbleBarLocation newLocation) {
|
||||
final float shift =
|
||||
getResources().getDisplayMetrics().widthPixels * FADE_IN_ANIM_POSITION_SHIFT;
|
||||
|
||||
final boolean onLeft = animatedLocation.isOnLeft(isLayoutRtl());
|
||||
final boolean onLeft = newLocation.isOnLeft(isLayoutRtl());
|
||||
final float startTx;
|
||||
final float finalTx;
|
||||
if (animatedLocation == mBubbleBarLocation) {
|
||||
if (newLocation == mBubbleBarLocation) {
|
||||
// Animated location matches layout location.
|
||||
finalTx = 0;
|
||||
} else {
|
||||
@@ -439,9 +505,9 @@ public class BubbleBarView extends FrameLayout {
|
||||
.setEndValue(finalTx)
|
||||
.setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
|
||||
.setStiffness(FADE_IN_ANIM_POSITION_SPRING_STIFFNESS)
|
||||
.build(this, VIEW_TRANSLATE_X);
|
||||
.build(this, getLocationAnimTranslationXProperty());
|
||||
|
||||
ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(this, ALPHA, 1f)
|
||||
ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(this, getLocationAnimAlphaProperty(), 1f)
|
||||
.setDuration(FADE_IN_ANIM_ALPHA_DURATION_MS);
|
||||
|
||||
AnimatorSet animatorSet = new AnimatorSet();
|
||||
@@ -449,6 +515,84 @@ public class BubbleBarView extends FrameLayout {
|
||||
return animatorSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get property that can be used to animate the translation-x value for the bar.
|
||||
* When a bubble is being dragged, uses {@link #BUBBLE_DRAG_TRANSLATION_X}.
|
||||
* Falls back to {@link com.android.launcher3.LauncherAnimUtils#VIEW_TRANSLATE_X} otherwise.
|
||||
*/
|
||||
private FloatProperty<? super BubbleBarView> getLocationAnimTranslationXProperty() {
|
||||
return mDraggedBubbleView == null ? VIEW_TRANSLATE_X : BUBBLE_DRAG_TRANSLATION_X;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get property that can be used to animate the alpha value for the bar.
|
||||
* When a bubble is being dragged, uses {@link #BUBBLE_DRAG_ALPHA}.
|
||||
* Falls back to {@link com.android.launcher3.LauncherAnimUtils#VIEW_ALPHA} otherwise.
|
||||
*/
|
||||
private FloatProperty<? super BubbleBarView> getLocationAnimAlphaProperty() {
|
||||
return mDraggedBubbleView == null ? VIEW_ALPHA : BUBBLE_DRAG_ALPHA;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set translation-x value for the bar while a bubble is being dragged.
|
||||
* We can not update translation on the bar directly because the dragged bubble would be
|
||||
* affected as well. As it is a child view.
|
||||
* Instead, while a bubble is being dragged, set translation on each child view, that is not the
|
||||
* dragged view. And set a translation on the background.
|
||||
* This allows for the dragged bubble view to remain in position while the bar moves during
|
||||
* animation.
|
||||
*/
|
||||
private void setTranslationXDuringBubbleDrag(float translationX) {
|
||||
mTranslationXDuringDrag = translationX;
|
||||
final int childCount = getChildCount();
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
BubbleView view = (BubbleView) getChildAt(i);
|
||||
if (view != mDraggedBubbleView) {
|
||||
view.setBubbleBarTranslationX(translationX);
|
||||
}
|
||||
}
|
||||
if (mBubbleBarBackground != null) {
|
||||
mTempBackgroundBounds.set(mBubbleBarBackground.getBounds());
|
||||
mTempBackgroundBounds.offsetTo((int) translationX, 0);
|
||||
mBubbleBarBackground.setBounds(mTempBackgroundBounds);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set alpha value for the bar while a bubble is being dragged.
|
||||
* We can not update the alpha on the bar directly because the dragged bubble would be affected
|
||||
* as well. As it is a child view.
|
||||
* Instead, while a bubble is being dragged, set alpha on each child view, that is not the
|
||||
* dragged view. And set an alpha on the background.
|
||||
* This allows for the dragged bubble to remain visible while the bar is hidden during
|
||||
* animation.
|
||||
*/
|
||||
private void setAlphaDuringBubbleDrag(float alpha) {
|
||||
mAlphaDuringDrag = alpha;
|
||||
final int childCount = getChildCount();
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
View view = getChildAt(i);
|
||||
if (view != mDraggedBubbleView) {
|
||||
view.setAlpha(alpha);
|
||||
}
|
||||
}
|
||||
if (mBubbleBarBackground != null) {
|
||||
mBubbleBarBackground.setAlpha((int) (255 * alpha));
|
||||
}
|
||||
}
|
||||
|
||||
private void resetDragAnimation() {
|
||||
if (mBubbleBarLocationAnimator != null) {
|
||||
mBubbleBarLocationAnimator.removeAllListeners();
|
||||
mBubbleBarLocationAnimator.cancel();
|
||||
mBubbleBarLocationAnimator = null;
|
||||
}
|
||||
setTranslationXDuringBubbleDrag(0f);
|
||||
setAlphaDuringBubbleDrag(1f);
|
||||
setTranslationX(0f);
|
||||
setAlpha(1f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the bounds with translation that may have been applied and returns the result.
|
||||
*/
|
||||
@@ -558,30 +702,21 @@ public class BubbleBarView extends FrameLayout {
|
||||
float elevationState = (1 - widthState);
|
||||
for (int i = 0; i < bubbleCount; i++) {
|
||||
BubbleView bv = (BubbleView) getChildAt(i);
|
||||
if (bv == mDraggedBubbleView) {
|
||||
// Skip the dragged bubble. Its translation is managed by the drag controller.
|
||||
continue;
|
||||
}
|
||||
bv.setTranslationY(ty);
|
||||
|
||||
// the position of the bubble when the bar is fully expanded
|
||||
final float expandedX;
|
||||
final float expandedX = getExpandedBubbleTranslationX(i, bubbleCount, onLeft);
|
||||
// the position of the bubble when the bar is fully collapsed
|
||||
final float collapsedX;
|
||||
if (onLeft) {
|
||||
// If bar is on the left, bubbles are ordered right to left
|
||||
expandedX = (bubbleCount - i - 1) * (mIconSize + mExpandedBarIconsSpacing);
|
||||
// Shift the first bubble only if there are more bubbles in addition to overflow
|
||||
collapsedX = i == 0 && bubbleCount > 2 ? mIconOverlapAmount : 0;
|
||||
} else {
|
||||
// Bubbles ordered left to right, don't move the first bubble
|
||||
expandedX = i * (mIconSize + mExpandedBarIconsSpacing);
|
||||
collapsedX = i == 0 ? 0 : mIconOverlapAmount;
|
||||
}
|
||||
if (bv == mDraggedBubbleView) {
|
||||
// if bubble is dragged set the elevation to bubble drag elevation
|
||||
bv.setZ(mDragElevation);
|
||||
} else {
|
||||
// otherwise slowly animate elevation while keeping correct Z ordering
|
||||
float fullElevationForChild = (MAX_BUBBLES * mBubbleElevation) - i;
|
||||
bv.setZ(fullElevationForChild * elevationState);
|
||||
}
|
||||
final float collapsedX = getCollapsedBubbleTranslationX(i, bubbleCount, onLeft);
|
||||
|
||||
// slowly animate elevation while keeping correct Z ordering
|
||||
float fullElevationForChild = (MAX_BUBBLES * mBubbleElevation) - i;
|
||||
bv.setZ(fullElevationForChild * elevationState);
|
||||
|
||||
if (mIsBarExpanded) {
|
||||
// If bar is on the right, account for bubble bar expanding and shifting left
|
||||
final float expandedBarShift = onLeft ? 0 : currentWidth - expandedWidth;
|
||||
@@ -601,9 +736,10 @@ public class BubbleBarView extends FrameLayout {
|
||||
// If we're fully collapsed, hide all bubbles except for the first 2. If there are
|
||||
// only 2 bubbles, hide the second bubble as well because it's the overflow.
|
||||
if (widthState == 0) {
|
||||
if (i > 1) {
|
||||
if (i > MAX_VISIBLE_BUBBLES_COLLAPSED - 1) {
|
||||
bv.setAlpha(0);
|
||||
} else if (i == 1 && bubbleCount == 2) {
|
||||
} else if (i == MAX_VISIBLE_BUBBLES_COLLAPSED - 1
|
||||
&& bubbleCount == MAX_VISIBLE_BUBBLES_COLLAPSED) {
|
||||
bv.setAlpha(0);
|
||||
}
|
||||
}
|
||||
@@ -636,6 +772,34 @@ public class BubbleBarView extends FrameLayout {
|
||||
mBubbleBarBackground.setWidth(interpolatedWidth);
|
||||
}
|
||||
|
||||
private float getExpandedBubbleTranslationX(int bubbleIndex, int bubbleCount,
|
||||
boolean onLeft) {
|
||||
if (bubbleIndex < 0 || bubbleIndex >= bubbleCount) {
|
||||
return 0;
|
||||
}
|
||||
if (onLeft) {
|
||||
// If bar is on the left, bubbles are ordered right to left
|
||||
return (bubbleCount - bubbleIndex - 1) * (mIconSize + mExpandedBarIconsSpacing);
|
||||
} else {
|
||||
// Bubbles ordered left to right, don't move the first bubble
|
||||
return bubbleIndex * (mIconSize + mExpandedBarIconsSpacing);
|
||||
}
|
||||
}
|
||||
|
||||
private float getCollapsedBubbleTranslationX(int bubbleIndex, int bubbleCount,
|
||||
boolean onLeft) {
|
||||
if (bubbleIndex < 0 || bubbleIndex >= bubbleCount) {
|
||||
return 0;
|
||||
}
|
||||
if (onLeft) {
|
||||
// Shift the first bubble only if there are more bubbles in addition to overflow
|
||||
return bubbleIndex == 0 && bubbleCount > MAX_VISIBLE_BUBBLES_COLLAPSED
|
||||
? mIconOverlapAmount : 0;
|
||||
} else {
|
||||
return bubbleIndex == 0 ? 0 : mIconOverlapAmount;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reorders the views to match the provided list.
|
||||
*/
|
||||
@@ -685,8 +849,18 @@ public class BubbleBarView extends FrameLayout {
|
||||
* Sets the dragged bubble view to correctly apply Z order. Dragged view should appear on top
|
||||
*/
|
||||
public void setDraggedBubble(@Nullable BubbleView view) {
|
||||
if (mDraggedBubbleView != null) {
|
||||
mDraggedBubbleView.setZ(0);
|
||||
if (view == null) {
|
||||
// We are clearing the dragged bubble, reset drag
|
||||
resetDragAnimation();
|
||||
}
|
||||
}
|
||||
mDraggedBubbleView = view;
|
||||
requestLayout();
|
||||
if (view != null) {
|
||||
view.setZ(mDragElevation);
|
||||
}
|
||||
setIsDragging(view != null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -745,7 +919,7 @@ public class BubbleBarView extends FrameLayout {
|
||||
if (bubbleBarLocation.isOnLeft(isLayoutRtl())) {
|
||||
// Bubble positions are reversed. First bubble may be shifted, if there are more
|
||||
// bubbles than the current bubble and overflow.
|
||||
bubblePosition = index == 0 && getChildCount() > 2 ? 1 : 0;
|
||||
bubblePosition = index == 0 && getChildCount() > MAX_VISIBLE_BUBBLES_COLLAPSED ? 1 : 0;
|
||||
} else {
|
||||
bubblePosition = index;
|
||||
}
|
||||
@@ -807,7 +981,7 @@ public class BubbleBarView extends FrameLayout {
|
||||
final int horizontalPadding = getPaddingStart() + getPaddingEnd();
|
||||
// If there are more than 2 bubbles, the first 2 should be visible when collapsed.
|
||||
// Otherwise just the first bubble should be visible because we don't show the overflow.
|
||||
return childCount > 2
|
||||
return childCount > MAX_VISIBLE_BUBBLES_COLLAPSED
|
||||
? mIconSize + mIconOverlapAmount + horizontalPadding
|
||||
: mIconSize + horizontalPadding;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import static android.view.View.VISIBLE;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Rect;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
@@ -468,7 +469,8 @@ public class BubbleBarViewController {
|
||||
*/
|
||||
public void onDragStart(@NonNull BubbleView bubbleView) {
|
||||
if (bubbleView.getBubble() == null) return;
|
||||
mSystemUiProxy.onBubbleDrag(bubbleView.getBubble().getKey(), /* isBeingDragged = */ true);
|
||||
|
||||
mSystemUiProxy.startBubbleDrag(bubbleView.getBubble().getKey());
|
||||
mBarView.setDraggedBubble(bubbleView);
|
||||
}
|
||||
|
||||
@@ -476,18 +478,45 @@ public class BubbleBarViewController {
|
||||
* Notifies SystemUI to expand the selected bubble when the bubble is released.
|
||||
* @param bubbleView dragged bubble view
|
||||
*/
|
||||
public void onDragRelease(@NonNull BubbleView bubbleView) {
|
||||
public void onDragRelease(@NonNull BubbleView bubbleView, BubbleBarLocation location) {
|
||||
if (bubbleView.getBubble() == null) return;
|
||||
mSystemUiProxy.onBubbleDrag(bubbleView.getBubble().getKey(), /* isBeingDragged = */ false);
|
||||
// TODO(b/330585402): send new bubble bar bounds to shell for the animation
|
||||
mSystemUiProxy.stopBubbleDrag(bubbleView.getBubble().getKey(), location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the dragged bubble view in the bubble bar view
|
||||
* Notifies {@link BubbleBarView} that drag and all animations are finished.
|
||||
*/
|
||||
public void onDragEnd() {
|
||||
mBarView.setDraggedBubble(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get translation for bubble bar when drag is released.
|
||||
*
|
||||
* @see BubbleBarView#getBubbleBarDragReleaseTranslation(PointF, BubbleBarLocation)
|
||||
*/
|
||||
public PointF getBubbleBarDragReleaseTranslation(PointF initialTranslation,
|
||||
BubbleBarLocation location) {
|
||||
if (location == mBarView.getBubbleBarLocation()) {
|
||||
return initialTranslation;
|
||||
}
|
||||
return mBarView.getBubbleBarDragReleaseTranslation(initialTranslation, location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get translation for bubble view when drag is released.
|
||||
*
|
||||
* @see BubbleBarView#getDraggedBubbleReleaseTranslation(PointF, BubbleBarLocation)
|
||||
*/
|
||||
public PointF getDraggedBubbleReleaseTranslation(PointF initialTranslation,
|
||||
BubbleBarLocation location) {
|
||||
if (location == mBarView.getBubbleBarLocation()) {
|
||||
return initialTranslation;
|
||||
}
|
||||
return mBarView.getDraggedBubbleReleaseTranslation(initialTranslation, location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when bubble was dragged into the dismiss target. Notifies System
|
||||
* @param bubble dismissed bubble item
|
||||
|
||||
@@ -30,6 +30,7 @@ public class BubbleControllers {
|
||||
public final BubbleDragController bubbleDragController;
|
||||
public final BubbleDismissController bubbleDismissController;
|
||||
public final BubbleBarPinController bubbleBarPinController;
|
||||
public final BubblePinController bubblePinController;
|
||||
|
||||
private final RunnableList mPostInitRunnables = new RunnableList();
|
||||
|
||||
@@ -45,7 +46,8 @@ public class BubbleControllers {
|
||||
BubbleStashedHandleViewController bubbleStashedHandleViewController,
|
||||
BubbleDragController bubbleDragController,
|
||||
BubbleDismissController bubbleDismissController,
|
||||
BubbleBarPinController bubbleBarPinController) {
|
||||
BubbleBarPinController bubbleBarPinController,
|
||||
BubblePinController bubblePinController) {
|
||||
this.bubbleBarController = bubbleBarController;
|
||||
this.bubbleBarViewController = bubbleBarViewController;
|
||||
this.bubbleStashController = bubbleStashController;
|
||||
@@ -53,6 +55,7 @@ public class BubbleControllers {
|
||||
this.bubbleDragController = bubbleDragController;
|
||||
this.bubbleDismissController = bubbleDismissController;
|
||||
this.bubbleBarPinController = bubbleBarPinController;
|
||||
this.bubblePinController = bubblePinController;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -68,6 +71,7 @@ public class BubbleControllers {
|
||||
bubbleDragController.init(/* bubbleControllers = */ this);
|
||||
bubbleDismissController.init(/* bubbleControllers = */ this);
|
||||
bubbleBarPinController.init(this);
|
||||
bubblePinController.init(this);
|
||||
|
||||
mPostInitRunnables.executeAllAndDestroy();
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ public class BubbleDragController {
|
||||
private BubbleBarViewController mBubbleBarViewController;
|
||||
private BubbleDismissController mBubbleDismissController;
|
||||
private BubbleBarPinController mBubbleBarPinController;
|
||||
private BubblePinController mBubblePinController;
|
||||
|
||||
public BubbleDragController(TaskbarActivityContext activity) {
|
||||
mActivity = activity;
|
||||
@@ -58,8 +59,12 @@ public class BubbleDragController {
|
||||
mBubbleBarViewController = bubbleControllers.bubbleBarViewController;
|
||||
mBubbleDismissController = bubbleControllers.bubbleDismissController;
|
||||
mBubbleBarPinController = bubbleControllers.bubbleBarPinController;
|
||||
mBubblePinController = bubbleControllers.bubblePinController;
|
||||
mBubbleDismissController.setListener(
|
||||
stuck -> mBubbleBarPinController.setDropTargetHidden(stuck));
|
||||
stuck -> {
|
||||
mBubbleBarPinController.setDropTargetHidden(stuck);
|
||||
mBubblePinController.setDropTargetHidden(stuck);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,19 +78,58 @@ public class BubbleDragController {
|
||||
}
|
||||
|
||||
bubbleView.setOnTouchListener(new BubbleTouchListener() {
|
||||
|
||||
private BubbleBarLocation mReleasedLocation = BubbleBarLocation.DEFAULT;
|
||||
|
||||
private final LocationChangeListener mLocationChangeListener =
|
||||
new LocationChangeListener() {
|
||||
@Override
|
||||
public void onChange(@NonNull BubbleBarLocation location) {
|
||||
mBubbleBarController.animateBubbleBarLocation(location);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRelease(@NonNull BubbleBarLocation location) {
|
||||
mReleasedLocation = location;
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
void onDragStart() {
|
||||
mBubblePinController.setListener(mLocationChangeListener);
|
||||
mBubbleBarViewController.onDragStart(bubbleView);
|
||||
mBubblePinController.onDragStart(
|
||||
mBubbleBarViewController.getBubbleBarLocation().isOnLeft(
|
||||
bubbleView.isLayoutRtl()));
|
||||
}
|
||||
|
||||
@Override
|
||||
void onDragEnd() {
|
||||
mBubbleBarViewController.onDragEnd();
|
||||
protected void onDragUpdate(float x, float y) {
|
||||
mBubblePinController.onDragUpdate(x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDragRelease() {
|
||||
mBubbleBarViewController.onDragRelease(bubbleView);
|
||||
mBubblePinController.onDragEnd();
|
||||
mBubbleBarViewController.onDragRelease(bubbleView, mReleasedLocation);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDragDismiss() {
|
||||
mBubblePinController.onDragEnd();
|
||||
}
|
||||
|
||||
@Override
|
||||
void onDragEnd() {
|
||||
mBubbleBarController.updateBubbleBarLocation(mReleasedLocation);
|
||||
mBubbleBarViewController.onDragEnd();
|
||||
mBubblePinController.setListener(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PointF getRestingPosition() {
|
||||
return mBubbleBarViewController.getDraggedBubbleReleaseTranslation(
|
||||
getInitialPosition(), mReleasedLocation);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -98,8 +142,7 @@ public class BubbleDragController {
|
||||
PointF initialRelativePivot = new PointF();
|
||||
bubbleBarView.setOnTouchListener(new BubbleTouchListener() {
|
||||
|
||||
@Nullable
|
||||
private BubbleBarLocation mReleasedLocation;
|
||||
private BubbleBarLocation mReleasedLocation = BubbleBarLocation.DEFAULT;
|
||||
|
||||
private final LocationChangeListener mLocationChangeListener =
|
||||
new LocationChangeListener() {
|
||||
@@ -145,20 +188,18 @@ public class BubbleDragController {
|
||||
|
||||
@Override
|
||||
void onDragEnd() {
|
||||
// Make sure to update location as the first thing. Pivot update causes a relayout
|
||||
mBubbleBarController.updateBubbleBarLocation(mReleasedLocation);
|
||||
bubbleBarView.setIsDragging(false);
|
||||
// Restoring the initial pivot for the bubble bar view
|
||||
bubbleBarView.setRelativePivot(initialRelativePivot.x, initialRelativePivot.y);
|
||||
bubbleBarView.setIsDragging(false);
|
||||
mBubbleBarController.updateBubbleBarLocation(mReleasedLocation);
|
||||
mBubbleBarPinController.setListener(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PointF getRestingPosition() {
|
||||
if (mReleasedLocation == null
|
||||
|| mReleasedLocation == bubbleBarView.getBubbleBarLocation()) {
|
||||
return getInitialPosition();
|
||||
}
|
||||
return bubbleBarView.getBubbleBarDragReleaseTranslation(getInitialPosition(),
|
||||
mReleasedLocation);
|
||||
return mBubbleBarViewController.getBubbleBarDragReleaseTranslation(
|
||||
getInitialPosition(), mReleasedLocation);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* 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.taskbar.bubbles
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.graphics.Point
|
||||
import android.view.Gravity.BOTTOM
|
||||
import android.view.Gravity.LEFT
|
||||
import android.view.Gravity.RIGHT
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import com.android.launcher3.R
|
||||
import com.android.wm.shell.common.bubbles.BaseBubblePinController
|
||||
import com.android.wm.shell.common.bubbles.BubbleBarLocation
|
||||
|
||||
/** Controller to manage pinning bubble bar to left or right when dragging starts from a bubble */
|
||||
class BubblePinController(
|
||||
private val context: Context,
|
||||
private val container: FrameLayout,
|
||||
screenSizeProvider: () -> Point
|
||||
) : BaseBubblePinController(screenSizeProvider) {
|
||||
|
||||
private lateinit var bubbleBarViewController: BubbleBarViewController
|
||||
private lateinit var bubbleStashController: BubbleStashController
|
||||
private var exclRectWidth: Float = 0f
|
||||
private var exclRectHeight: Float = 0f
|
||||
|
||||
private var dropTargetView: View? = null
|
||||
private var dropTargetMargin: Int = 0
|
||||
|
||||
fun init(bubbleControllers: BubbleControllers) {
|
||||
bubbleBarViewController = bubbleControllers.bubbleBarViewController
|
||||
bubbleStashController = bubbleControllers.bubbleStashController
|
||||
exclRectWidth = context.resources.getDimension(R.dimen.bubblebar_dismiss_zone_width)
|
||||
exclRectHeight = context.resources.getDimension(R.dimen.bubblebar_dismiss_zone_height)
|
||||
dropTargetMargin =
|
||||
context.resources.getDimensionPixelSize(R.dimen.bubble_expanded_view_drop_target_margin)
|
||||
}
|
||||
|
||||
override fun getExclusionRectWidth(): Float {
|
||||
return exclRectWidth
|
||||
}
|
||||
|
||||
override fun getExclusionRectHeight(): Float {
|
||||
return exclRectHeight
|
||||
}
|
||||
|
||||
override fun getDropTargetView(): View? {
|
||||
return dropTargetView
|
||||
}
|
||||
|
||||
override fun removeDropTargetView(view: View) {
|
||||
container.removeView(view)
|
||||
dropTargetView = null
|
||||
}
|
||||
|
||||
override fun createDropTargetView(): View {
|
||||
return LayoutInflater.from(context)
|
||||
.inflate(R.layout.bubble_expanded_view_drop_target, container, false)
|
||||
.also { view ->
|
||||
// TODO(b/330585402): dynamic height for the drop target based on actual height
|
||||
dropTargetView = view
|
||||
container.addView(view)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("RtlHardcoded")
|
||||
override fun updateLocation(location: BubbleBarLocation) {
|
||||
val onLeft = location.isOnLeft(container.isLayoutRtl)
|
||||
|
||||
val bubbleBarBounds = bubbleBarViewController.bubbleBarBounds
|
||||
dropTargetView?.updateLayoutParams<FrameLayout.LayoutParams> {
|
||||
gravity = BOTTOM or (if (onLeft) LEFT else RIGHT)
|
||||
bottomMargin =
|
||||
-bubbleStashController.bubbleBarTranslationY.toInt() +
|
||||
bubbleBarBounds.height() +
|
||||
dropTargetMargin
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,6 +66,9 @@ public class BubbleView extends ConstraintLayout {
|
||||
private final ImageView mAppIcon;
|
||||
private final int mBubbleSize;
|
||||
|
||||
private float mBubbleBarTranslationX = 0f;
|
||||
private float mTranslationX = 0f;
|
||||
|
||||
private DotRenderer mDotRenderer;
|
||||
private DotRenderer.DrawParams mDrawParams;
|
||||
private int mDotColor;
|
||||
@@ -126,6 +129,35 @@ public class BubbleView extends ConstraintLayout {
|
||||
outline.setOval(inset, inset, inset + normalizedSize, inset + normalizedSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTranslationX(float translationX) {
|
||||
// Overriding setting translationX as it can be a combination of the parent translation
|
||||
// and current view translation.
|
||||
// When a BubbleView is being dragged to pin the bubble bar to other side, we animate the
|
||||
// bar to the new location during the drag.
|
||||
// One part of the animation is updating the translation of the bubble bar. But doing
|
||||
// that also updates the translation for the child views, like the dragged bubble.
|
||||
// To get around that, we instead apply translation on each child view of bubble bar. It
|
||||
// is applied as bubble bar translation. This results in BubbleView's translation being a
|
||||
// sum of the translation it has and the parent bubble bar translation.
|
||||
mTranslationX = translationX;
|
||||
applyTranslation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Translation of the bubble bar that hosts this bubble.
|
||||
* Is applied together with translation applied on the view through
|
||||
* {@link #setTranslationX(float)}.
|
||||
*/
|
||||
void setBubbleBarTranslationX(float translationX) {
|
||||
mBubbleBarTranslationX = translationX;
|
||||
applyTranslation();
|
||||
}
|
||||
|
||||
private void applyTranslation() {
|
||||
super.setTranslationX(mTranslationX + mBubbleBarTranslationX);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatchDraw(Canvas canvas) {
|
||||
super.dispatchDraw(canvas);
|
||||
|
||||
@@ -800,15 +800,29 @@ public class SystemUiProxy implements ISystemUiProxy, NavHandle, SafeCloseable {
|
||||
/**
|
||||
* Tells SysUI when the bubble is being dragged.
|
||||
* Should be called only when the bubble bar is expanded.
|
||||
* @param bubbleKey the key of the bubble to collapse/expand
|
||||
* @param isBeingDragged whether the bubble is being dragged
|
||||
* @param bubbleKey key of the bubble being dragged
|
||||
*/
|
||||
public void onBubbleDrag(@Nullable String bubbleKey, boolean isBeingDragged) {
|
||||
public void startBubbleDrag(@Nullable String bubbleKey) {
|
||||
if (mBubbles == null) return;
|
||||
try {
|
||||
mBubbles.onBubbleDrag(bubbleKey, isBeingDragged);
|
||||
mBubbles.startBubbleDrag(bubbleKey);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed call onBubbleDrag");
|
||||
Log.w(TAG, "Failed call startBubbleDrag");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells SysUI when the bubble stops being dragged.
|
||||
* Should be called only when the bubble bar is expanded.
|
||||
* @param bubbleKey key of the bubble being dragged
|
||||
* @param location location of the bubble bar
|
||||
*/
|
||||
public void stopBubbleDrag(@Nullable String bubbleKey, BubbleBarLocation location) {
|
||||
if (mBubbles == null) return;
|
||||
try {
|
||||
mBubbles.stopBubbleDrag(bubbleKey, location);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed call stopBubbleDrag");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user