mirror of
https://github.com/LawnchairLauncher/lawnchair.git
synced 2026-02-19 10:48:19 +00:00
Merge "Bubble bar drag to dismiss" into udc-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
e3d4a69749
27
quickstep/res/drawable/bg_bubble_dismiss_circle.xml
Normal file
27
quickstep/res/drawable/bg_bubble_dismiss_circle.xml
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
|
||||
<stroke
|
||||
android:width="2dp"
|
||||
android:color="@android:color/system_accent1_600" />
|
||||
|
||||
<solid android:color="@android:color/system_accent1_600" />
|
||||
</shape>
|
||||
25
quickstep/res/drawable/ic_bubble_dismiss_white.xml
Normal file
25
quickstep/res/drawable/ic_bubble_dismiss_white.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="20dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:pathData="M19.000000,6.400000l-1.400000,-1.400000 -5.600000,5.600000 -5.600000,-5.600000 -1.400000,1.400000 5.600000,5.600000 -5.600000,5.600000 1.400000,1.400000 5.600000,-5.600000 5.600000,5.600000 1.400000,-1.400000 -5.600000,-5.600000z"
|
||||
android:fillColor="@android:color/system_neutral1_50"/>
|
||||
</vector>
|
||||
@@ -367,6 +367,13 @@
|
||||
<dimen name="bubblebar_icon_spacing">3dp</dimen>
|
||||
<dimen name="bubblebar_icon_elevation">1dp</dimen>
|
||||
|
||||
<!-- Bubble bar dismiss view -->
|
||||
<dimen name="bubblebar_dismiss_target_size">96dp</dimen>
|
||||
<dimen name="bubblebar_dismiss_target_small_size">60dp</dimen>
|
||||
<dimen name="bubblebar_dismiss_target_icon_size">24dp</dimen>
|
||||
<dimen name="bubblebar_dismiss_target_bottom_margin">50dp</dimen>
|
||||
<dimen name="bubblebar_dismiss_floating_gradient_height">548dp</dimen>
|
||||
|
||||
<!-- Launcher splash screen -->
|
||||
<!-- Note: keep this value in sync with the WindowManager/Shell dimens.xml -->
|
||||
<!-- starting_surface_exit_animation_window_shift_length -->
|
||||
|
||||
@@ -90,6 +90,8 @@ import com.android.launcher3.taskbar.bubbles.BubbleBarController;
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleBarView;
|
||||
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.BubbleStashController;
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleStashedHandleViewController;
|
||||
import com.android.launcher3.taskbar.overlay.TaskbarOverlayController;
|
||||
@@ -216,7 +218,9 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
new BubbleBarController(this, bubbleBarView),
|
||||
new BubbleBarViewController(this, bubbleBarView),
|
||||
new BubbleStashController(this),
|
||||
new BubbleStashedHandleViewController(this, bubbleHandleView)));
|
||||
new BubbleStashedHandleViewController(this, bubbleHandleView),
|
||||
new BubbleDragController(this),
|
||||
new BubbleDismissController(this, mDragLayer)));
|
||||
}
|
||||
|
||||
// Construct controllers.
|
||||
|
||||
@@ -95,6 +95,8 @@ public class BubbleBarView extends FrameLayout {
|
||||
private View.OnClickListener mOnClickListener;
|
||||
|
||||
private final Rect mTempRect = new Rect();
|
||||
private float mRelativePivotX = 1f;
|
||||
private float mRelativePivotY = 1f;
|
||||
|
||||
// An animator that represents the expansion state of the bubble bar, where 0 corresponds to the
|
||||
// collapsed state and 1 to the fully expanded state.
|
||||
@@ -109,6 +111,9 @@ public class BubbleBarView extends FrameLayout {
|
||||
@Nullable
|
||||
private Consumer<String> mUpdateSelectedBubbleAfterCollapse;
|
||||
|
||||
@Nullable
|
||||
private BubbleView mDraggedBubbleView;
|
||||
|
||||
public BubbleBarView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
@@ -181,9 +186,10 @@ public class BubbleBarView extends FrameLayout {
|
||||
mBubbleBarBounds.right = right;
|
||||
mBubbleBarBounds.bottom = bottom;
|
||||
|
||||
// The bubble bar handle is aligned to the bottom edge of the screen so scale towards that.
|
||||
setPivotX(getWidth());
|
||||
setPivotY(getHeight());
|
||||
// The bubble bar handle is aligned according to the relative pivot,
|
||||
// by default it's aligned to the bottom edge of the screen so scale towards that
|
||||
setPivotX(mRelativePivotX * getWidth());
|
||||
setPivotY(mRelativePivotY * getHeight());
|
||||
|
||||
// Position the views
|
||||
updateChildrenRenderNodeProperties();
|
||||
@@ -198,6 +204,32 @@ public class BubbleBarView extends FrameLayout {
|
||||
return mBubbleBarBounds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set bubble bar relative pivot value for X and Y, applied as a fraction of view width/height
|
||||
* respectively. If the value is not in range of 0 to 1 it will be normalized.
|
||||
* @param x relative X pivot value in range 0..1
|
||||
* @param y relative Y pivot value in range 0..1
|
||||
*/
|
||||
public void setRelativePivot(float x, float y) {
|
||||
mRelativePivotX = Float.max(Float.min(x, 1), 0);
|
||||
mRelativePivotY = Float.max(Float.min(y, 1), 0);
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current relative pivot for X axis
|
||||
*/
|
||||
public float getRelativePivotX() {
|
||||
return mRelativePivotX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current relative pivot for Y axis
|
||||
*/
|
||||
public float getRelativePivotY() {
|
||||
return mRelativePivotY;
|
||||
}
|
||||
|
||||
// TODO: (b/280605790) animate it
|
||||
@Override
|
||||
public void addView(View child, int index, ViewGroup.LayoutParams params) {
|
||||
@@ -254,9 +286,9 @@ public class BubbleBarView extends FrameLayout {
|
||||
// where the bubble will end up when the animation ends
|
||||
final float targetX = currentWidth - expandedWidth + expandedX;
|
||||
bv.setTranslationX(widthState * (targetX - collapsedX) + collapsedX);
|
||||
// if we're fully expanded, set the z level to 0
|
||||
// if we're fully expanded, set the z level to 0 or to bubble elevation if dragged
|
||||
if (widthState == 1f) {
|
||||
bv.setZ(0);
|
||||
bv.setZ(bv == mDraggedBubbleView ? mBubbleElevation : 0);
|
||||
}
|
||||
// When we're expanded, we're not stacked so we're not behind the stack
|
||||
bv.setBehindStack(false, animate);
|
||||
@@ -328,6 +360,14 @@ public class BubbleBarView extends FrameLayout {
|
||||
updateArrowForSelected(/* shouldAnimate= */ true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the dragged bubble view to correctly apply Z order. Dragged view should appear on top
|
||||
*/
|
||||
public void setDraggedBubble(@Nullable BubbleView view) {
|
||||
mDraggedBubbleView = view;
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the arrow position to match the selected bubble.
|
||||
*
|
||||
|
||||
@@ -24,6 +24,8 @@ import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.anim.AnimatedFloat;
|
||||
import com.android.launcher3.taskbar.TaskbarActivityContext;
|
||||
@@ -54,6 +56,7 @@ public class BubbleBarViewController {
|
||||
// Initialized in init.
|
||||
private BubbleStashController mBubbleStashController;
|
||||
private BubbleBarController mBubbleBarController;
|
||||
private BubbleDragController mBubbleDragController;
|
||||
private TaskbarStashController mTaskbarStashController;
|
||||
private TaskbarInsetsController mTaskbarInsetsController;
|
||||
private View.OnClickListener mBubbleClickListener;
|
||||
@@ -85,6 +88,7 @@ public class BubbleBarViewController {
|
||||
public void init(TaskbarControllers controllers, BubbleControllers bubbleControllers) {
|
||||
mBubbleStashController = bubbleControllers.bubbleStashController;
|
||||
mBubbleBarController = bubbleControllers.bubbleBarController;
|
||||
mBubbleDragController = bubbleControllers.bubbleDragController;
|
||||
mTaskbarStashController = controllers.taskbarStashController;
|
||||
mTaskbarInsetsController = controllers.taskbarInsetsController;
|
||||
|
||||
@@ -95,6 +99,7 @@ public class BubbleBarViewController {
|
||||
mBubbleBarScale.updateValue(1f);
|
||||
mBubbleClickListener = v -> onBubbleClicked(v);
|
||||
mBubbleBarClickListener = v -> setExpanded(true);
|
||||
mBubbleDragController.setupBubbleBarView(mBarView);
|
||||
mBarView.setOnClickListener(mBubbleBarClickListener);
|
||||
mBarView.addOnLayoutChangeListener((view, i, i1, i2, i3, i4, i5, i6, i7) ->
|
||||
mTaskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged()
|
||||
@@ -256,6 +261,7 @@ public class BubbleBarViewController {
|
||||
if (b != null) {
|
||||
mBarView.addView(b.getView(), 0, new FrameLayout.LayoutParams(mIconSize, mIconSize));
|
||||
b.getView().setOnClickListener(mBubbleClickListener);
|
||||
mBubbleDragController.setupBubbleView(b.getView());
|
||||
} else {
|
||||
Log.w(TAG, "addBubble, bubble was null!");
|
||||
}
|
||||
@@ -307,4 +313,41 @@ public class BubbleBarViewController {
|
||||
mBubbleStashController.showBubbleBar(true /* expand the bubbles */);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the dragged bubble view in the bubble bar view, and notifies SystemUI
|
||||
* to collapse the selected bubble while it's dragged.
|
||||
* @param bubbleView dragged bubble view
|
||||
*/
|
||||
public void onDragStart(@NonNull BubbleView bubbleView) {
|
||||
mBarView.setDraggedBubble(bubbleView);
|
||||
if (bubbleView.getBubble() == null) return;
|
||||
mSystemUiProxy.collapseWhileDragging(bubbleView.getBubble().getKey(), true /* collapse */);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the dragged bubble view in the bubble bar view, and notifies SystemUI
|
||||
* to expand the selected bubble when dragging finished.
|
||||
* @param bubbleView dragged bubble view
|
||||
*/
|
||||
public void onDragEnd(@NonNull BubbleView bubbleView) {
|
||||
mBarView.setDraggedBubble(null);
|
||||
if (bubbleView.getBubble() == null) return;
|
||||
mSystemUiProxy.collapseWhileDragging(bubbleView.getBubble().getKey(), false /* collapse */);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when bubble was dragged into the dismiss target. Notifies System
|
||||
* @param bubble dismissed bubble item
|
||||
*/
|
||||
public void onDismissBubbleWhileDragging(@NonNull BubbleBarItem bubble) {
|
||||
mSystemUiProxy.removeBubble(bubble.getKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when bubble stack was dragged into the dismiss target
|
||||
*/
|
||||
public void onDismissAllBubblesWhileDragging() {
|
||||
mSystemUiProxy.removeAllBubbles();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ public class BubbleControllers {
|
||||
public final BubbleBarViewController bubbleBarViewController;
|
||||
public final BubbleStashController bubbleStashController;
|
||||
public final BubbleStashedHandleViewController bubbleStashedHandleViewController;
|
||||
public final BubbleDragController bubbleDragController;
|
||||
public final BubbleDismissController bubbleDismissController;
|
||||
|
||||
private final RunnableList mPostInitRunnables = new RunnableList();
|
||||
|
||||
@@ -39,11 +41,15 @@ public class BubbleControllers {
|
||||
BubbleBarController bubbleBarController,
|
||||
BubbleBarViewController bubbleBarViewController,
|
||||
BubbleStashController bubbleStashController,
|
||||
BubbleStashedHandleViewController bubbleStashedHandleViewController) {
|
||||
BubbleStashedHandleViewController bubbleStashedHandleViewController,
|
||||
BubbleDragController bubbleDragController,
|
||||
BubbleDismissController bubbleDismissController) {
|
||||
this.bubbleBarController = bubbleBarController;
|
||||
this.bubbleBarViewController = bubbleBarViewController;
|
||||
this.bubbleStashController = bubbleStashController;
|
||||
this.bubbleStashedHandleViewController = bubbleStashedHandleViewController;
|
||||
this.bubbleDragController = bubbleDragController;
|
||||
this.bubbleDismissController = bubbleDismissController;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -56,6 +62,8 @@ public class BubbleControllers {
|
||||
bubbleBarViewController.init(taskbarControllers, this);
|
||||
bubbleStashedHandleViewController.init(taskbarControllers, this);
|
||||
bubbleStashController.init(taskbarControllers, this);
|
||||
bubbleDragController.init(/* bubbleControllers = */ this);
|
||||
bubbleDismissController.init(/* bubbleControllers = */ this);
|
||||
|
||||
mPostInitRunnables.executeAllAndDestroy();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,276 @@
|
||||
/*
|
||||
* 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.taskbar.bubbles;
|
||||
|
||||
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.res.Resources;
|
||||
import android.os.SystemProperties;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.dynamicanimation.animation.DynamicAnimation;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.taskbar.TaskbarActivityContext;
|
||||
import com.android.launcher3.taskbar.TaskbarDragLayer;
|
||||
import com.android.wm.shell.common.bubbles.DismissView;
|
||||
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
|
||||
|
||||
/**
|
||||
* Controls dismiss view presentation for the bubble bar dismiss functionality.
|
||||
* Provides the dragged view snapping to the target dismiss area and animates it.
|
||||
* When the dragged bubble/bubble stack is realised inside of the target area, it gets dismissed.
|
||||
*
|
||||
* @see BubbleDragController
|
||||
*/
|
||||
public class BubbleDismissController {
|
||||
private static final float FLING_TO_DISMISS_MIN_VELOCITY = 6000f;
|
||||
public static final boolean ENABLE_FLING_TO_DISMISS_BUBBLE =
|
||||
SystemProperties.getBoolean("persist.wm.debug.fling_to_dismiss_bubble", true);
|
||||
private final TaskbarActivityContext mActivity;
|
||||
private final TaskbarDragLayer mDragLayer;
|
||||
@Nullable
|
||||
private BubbleBarViewController mBubbleBarViewController;
|
||||
|
||||
// Dismiss view that's attached to drag layer. It consists of the scrim view and the circular
|
||||
// dismiss view used as a dismiss target.
|
||||
@Nullable
|
||||
private DismissView mDismissView;
|
||||
|
||||
// The currently magnetized object, which is being dragged and will be attracted to the magnetic
|
||||
// dismiss target. This is either the stack itself, or an individual bubble.
|
||||
@Nullable
|
||||
private MagnetizedObject<View> mMagnetizedObject;
|
||||
|
||||
// The MagneticTarget instance for our circular dismiss view. This is added to the
|
||||
// MagnetizedObject instances for the stack and any dragged-out bubbles.
|
||||
@Nullable
|
||||
private MagnetizedObject.MagneticTarget mMagneticTarget;
|
||||
@Nullable
|
||||
private ValueAnimator mDismissAnimator;
|
||||
|
||||
public BubbleDismissController(TaskbarActivityContext activity, TaskbarDragLayer dragLayer) {
|
||||
mActivity = activity;
|
||||
mDragLayer = dragLayer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes dependencies when bubble controllers are created.
|
||||
* Should be careful to only access things that were created in constructors for now, as some
|
||||
* controllers may still be waiting for init().
|
||||
*/
|
||||
public void init(@NonNull BubbleControllers bubbleControllers) {
|
||||
mBubbleBarViewController = bubbleControllers.bubbleBarViewController;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the dismiss view and magnetized object that will be attracted to magnetic target.
|
||||
* Should be called before handling events or showing/hiding dismiss view.
|
||||
* @param magnetizedView the view to be pulled into target dismiss area
|
||||
*/
|
||||
public void setupDismissView(@NonNull View magnetizedView) {
|
||||
setupDismissView();
|
||||
setupMagnetizedObject(magnetizedView);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the touch event and pass it to the magnetized object.
|
||||
* It should be called after {@code setupDismissView}
|
||||
*/
|
||||
public boolean handleTouchEvent(@NonNull MotionEvent event) {
|
||||
return mMagnetizedObject != null && mMagnetizedObject.maybeConsumeMotionEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show dismiss view with animation
|
||||
* It should be called after {@code setupDismissView}
|
||||
*/
|
||||
public void showDismissView() {
|
||||
if (mDismissView == null) return;
|
||||
mDismissView.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide dismiss view with animation
|
||||
* It should be called after {@code setupDismissView}
|
||||
*/
|
||||
public void hideDismissView() {
|
||||
if (mDismissView == null) return;
|
||||
mDismissView.hide();
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismiss magnetized object when it's released in the dismiss target area
|
||||
*/
|
||||
private void dismissMagnetizedObject() {
|
||||
if (mMagnetizedObject == null || mBubbleBarViewController == null) return;
|
||||
if (mMagnetizedObject.getUnderlyingObject() instanceof BubbleView) {
|
||||
BubbleView bubbleView = (BubbleView) mMagnetizedObject.getUnderlyingObject();
|
||||
if (bubbleView.getBubble() != null) {
|
||||
mBubbleBarViewController.onDismissBubbleWhileDragging(bubbleView.getBubble());
|
||||
}
|
||||
} else if (mMagnetizedObject.getUnderlyingObject() instanceof BubbleBarView) {
|
||||
mBubbleBarViewController.onDismissAllBubblesWhileDragging();
|
||||
}
|
||||
cleanUpAnimatedViews();
|
||||
}
|
||||
|
||||
/**
|
||||
* Animate dismiss view when magnetized object gets stuck in the magnetic target
|
||||
* @param view captured view
|
||||
*/
|
||||
private void animateDismissCaptured(@NonNull View view) {
|
||||
cancelAnimations();
|
||||
mDismissAnimator = createDismissAnimator(view);
|
||||
mDismissAnimator.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Animate dismiss view when magnetized object gets unstuck from the magnetic target
|
||||
*/
|
||||
private void animateDismissReleased() {
|
||||
if (mDismissAnimator == null) return;
|
||||
mDismissAnimator.removeAllListeners();
|
||||
mDismissAnimator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
cancelAnimations();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
cancelAnimations();
|
||||
}
|
||||
});
|
||||
mDismissAnimator.reverse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel and clear dismiss animations and reset view states
|
||||
*/
|
||||
private void cancelAnimations() {
|
||||
if (mDismissAnimator == null) return;
|
||||
ValueAnimator animator = mDismissAnimator;
|
||||
mDismissAnimator = null;
|
||||
animator.cancel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up views changed during animation
|
||||
*/
|
||||
private void cleanUpAnimatedViews() {
|
||||
// Cancel animations
|
||||
cancelAnimations();
|
||||
// Reset dismiss view
|
||||
if (mDismissView != null) {
|
||||
mDismissView.getCircle().setScaleX(1f);
|
||||
mDismissView.getCircle().setScaleY(1f);
|
||||
}
|
||||
// Reset magnetized view
|
||||
if (mMagnetizedObject != null) {
|
||||
mMagnetizedObject.getUnderlyingObject().setAlpha(1f);
|
||||
mMagnetizedObject.getUnderlyingObject().setScaleX(1f);
|
||||
mMagnetizedObject.getUnderlyingObject().setScaleY(1f);
|
||||
}
|
||||
}
|
||||
|
||||
private void setupDismissView() {
|
||||
if (mDismissView != null) return;
|
||||
mDismissView = new DismissView(mActivity.getApplicationContext());
|
||||
BubbleDismissViewUtils.setup(mDismissView);
|
||||
mDragLayer.addView(mDismissView, /* index = */ 0,
|
||||
new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
|
||||
setupMagneticTarget(mDismissView.getCircle());
|
||||
}
|
||||
|
||||
private void setupMagneticTarget(@NonNull View view) {
|
||||
int magneticFieldRadius = mActivity.getResources().getDimensionPixelSize(
|
||||
R.dimen.bubblebar_dismiss_target_size);
|
||||
mMagneticTarget = new MagnetizedObject.MagneticTarget(view, magneticFieldRadius);
|
||||
}
|
||||
|
||||
private void setupMagnetizedObject(@NonNull View magnetizedView) {
|
||||
mMagnetizedObject = new MagnetizedObject<>(mActivity.getApplicationContext(),
|
||||
magnetizedView, DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y) {
|
||||
@Override
|
||||
public float getWidth(@NonNull View underlyingObject) {
|
||||
return underlyingObject.getWidth();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getHeight(@NonNull View underlyingObject) {
|
||||
return underlyingObject.getHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getLocationOnScreen(@NonNull View underlyingObject, @NonNull int[] loc) {
|
||||
underlyingObject.getLocationOnScreen(loc);
|
||||
}
|
||||
};
|
||||
|
||||
mMagnetizedObject.setHapticsEnabled(true);
|
||||
mMagnetizedObject.setFlingToTargetEnabled(ENABLE_FLING_TO_DISMISS_BUBBLE);
|
||||
mMagnetizedObject.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY);
|
||||
if (mMagneticTarget != null) {
|
||||
mMagnetizedObject.addTarget(mMagneticTarget);
|
||||
}
|
||||
mMagnetizedObject.setMagnetListener(new MagnetizedObject.MagnetListener() {
|
||||
@Override
|
||||
public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
|
||||
animateDismissCaptured(magnetizedView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
|
||||
float velX, float velY, boolean wasFlungOut) {
|
||||
animateDismissReleased();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
|
||||
dismissMagnetizedObject();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private ValueAnimator createDismissAnimator(@NonNull View magnetizedView) {
|
||||
Resources resources = mActivity.getResources();
|
||||
int expandedSize = resources.getDimensionPixelSize(R.dimen.bubblebar_dismiss_target_size);
|
||||
int collapsedSize = resources.getDimensionPixelSize(
|
||||
R.dimen.bubblebar_dismiss_target_small_size);
|
||||
float minScale = (float) collapsedSize / expandedSize;
|
||||
ValueAnimator animator = ValueAnimator.ofFloat(1f, minScale);
|
||||
animator.addUpdateListener(animation -> {
|
||||
if (mDismissView == null) return;
|
||||
final float animatedValue = (float) animation.getAnimatedValue();
|
||||
mDismissView.getCircle().setScaleX(animatedValue);
|
||||
mDismissView.getCircle().setScaleY(animatedValue);
|
||||
magnetizedView.setAlpha(animatedValue);
|
||||
if (magnetizedView instanceof BubbleBarView) {
|
||||
magnetizedView.setScaleX(animatedValue);
|
||||
magnetizedView.setScaleY(animatedValue);
|
||||
}
|
||||
});
|
||||
return animator;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
@file:JvmName("BubbleDismissViewUtils")
|
||||
|
||||
package com.android.launcher3.taskbar.bubbles
|
||||
|
||||
import com.android.launcher3.R
|
||||
import com.android.wm.shell.common.bubbles.DismissView
|
||||
|
||||
/**
|
||||
* Dismiss view is shared from WMShell. It requires setup with local resources.
|
||||
*
|
||||
* Usage:
|
||||
* - Kotlin `dismissView.setup()`
|
||||
* - Java `BubbleDismissViewUtils.setup(dismissView)`
|
||||
*/
|
||||
fun DismissView.setup() {
|
||||
setup(
|
||||
DismissView.Config(
|
||||
targetSizeResId = R.dimen.bubblebar_dismiss_target_size,
|
||||
iconSizeResId = R.dimen.bubblebar_dismiss_target_icon_size,
|
||||
bottomMarginResId = R.dimen.bubblebar_dismiss_target_bottom_margin,
|
||||
floatingGradientHeightResId = R.dimen.bubblebar_dismiss_floating_gradient_height,
|
||||
floatingGradientColorResId = android.R.color.system_neutral1_900,
|
||||
backgroundResId = R.drawable.bg_bubble_dismiss_circle,
|
||||
iconResId = R.drawable.ic_bubble_dismiss_white
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* 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.taskbar.bubbles;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.graphics.PointF;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.launcher3.taskbar.TaskbarActivityContext;
|
||||
import com.android.wm.shell.common.bubbles.RelativeTouchListener;
|
||||
|
||||
/**
|
||||
* Controls bubble bar drag to dismiss interaction.
|
||||
* Interacts with {@link BubbleDismissController}, used by {@link BubbleBarViewController}.
|
||||
* Supported interactions:
|
||||
* - Drag a single bubble view into dismiss target to remove it.
|
||||
* - Drag the bubble stack into dismiss target to remove all.
|
||||
* Restores initial position of dragged view if released outside of the dismiss target.
|
||||
*/
|
||||
public class BubbleDragController {
|
||||
private final TaskbarActivityContext mActivity;
|
||||
private BubbleBarViewController mBubbleBarViewController;
|
||||
private BubbleDismissController mBubbleDismissController;
|
||||
|
||||
public BubbleDragController(TaskbarActivityContext activity) {
|
||||
mActivity = activity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes dependencies when bubble controllers are created.
|
||||
* Should be careful to only access things that were created in constructors for now, as some
|
||||
* controllers may still be waiting for init().
|
||||
*/
|
||||
public void init(@NonNull BubbleControllers bubbleControllers) {
|
||||
mBubbleBarViewController = bubbleControllers.bubbleBarViewController;
|
||||
mBubbleDismissController = bubbleControllers.bubbleDismissController;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the bubble view for dragging and attach touch listener to it
|
||||
*/
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
public void setupBubbleView(@NonNull BubbleView bubbleView) {
|
||||
// Don't setup dragging for overflow bubble view
|
||||
if (bubbleView.getBubble() == null
|
||||
|| !(bubbleView.getBubble() instanceof BubbleBarBubble)) return;
|
||||
bubbleView.setOnTouchListener(new BaseDragListener() {
|
||||
@Override
|
||||
protected void onDragStart() {
|
||||
super.onDragStart();
|
||||
mBubbleBarViewController.onDragStart(bubbleView);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDragEnd() {
|
||||
super.onDragEnd();
|
||||
mBubbleBarViewController.onDragEnd(bubbleView);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the bubble bar view for dragging and attach touch listener to it
|
||||
*/
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
public void setupBubbleBarView(@NonNull BubbleBarView bubbleBarView) {
|
||||
PointF initialRelativePivot = new PointF();
|
||||
bubbleBarView.setOnTouchListener(new BaseDragListener() {
|
||||
@Override
|
||||
public boolean onDown(@NonNull View view, @NonNull MotionEvent event) {
|
||||
if (bubbleBarView.isExpanded()) return false;
|
||||
// Setup dragging only when bubble bar is collapsed
|
||||
return super.onDown(view, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDragStart() {
|
||||
super.onDragStart();
|
||||
initialRelativePivot.set(bubbleBarView.getRelativePivotX(),
|
||||
bubbleBarView.getRelativePivotY());
|
||||
bubbleBarView.setRelativePivot(/* x = */ 0.5f, /* y = */ 0.5f);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDragEnd() {
|
||||
super.onDragEnd();
|
||||
bubbleBarView.setRelativePivot(initialRelativePivot.x, initialRelativePivot.y);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Base drag listener for handling a single bubble view or bubble bar view dragging.
|
||||
* Controls dragging interaction and interacts with {@link BubbleDismissController}
|
||||
* to coordinate dismiss view presentation.
|
||||
* Lifecycle methods can be overridden do add extra setup/clean up steps
|
||||
*/
|
||||
private class BaseDragListener extends RelativeTouchListener {
|
||||
private boolean mHandling;
|
||||
private boolean mDragging;
|
||||
|
||||
@Override
|
||||
public boolean onDown(@NonNull View view, @NonNull MotionEvent event) {
|
||||
mHandling = true;
|
||||
mActivity.setTaskbarWindowFullscreen(true);
|
||||
mBubbleDismissController.setupDismissView(view);
|
||||
mBubbleDismissController.handleTouchEvent(event);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMove(@NonNull View view, @NonNull MotionEvent event, float viewInitialX,
|
||||
float viewInitialY, float dx, float dy) {
|
||||
if (!mHandling) return;
|
||||
if (!mDragging) {
|
||||
// Start dragging
|
||||
mDragging = true;
|
||||
onDragStart();
|
||||
}
|
||||
if (!mBubbleDismissController.handleTouchEvent(event)) {
|
||||
// Drag the view if not processed by dismiss controller
|
||||
view.setTranslationX(viewInitialX + dx);
|
||||
view.setTranslationY(viewInitialY + dy);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUp(@NonNull View view, @NonNull MotionEvent event, float viewInitialX,
|
||||
float viewInitialY, float dx, float dy, float velX, float velY) {
|
||||
onComplete(view, event, viewInitialX, viewInitialY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel(@NonNull View view, @NonNull MotionEvent event, float viewInitialX,
|
||||
float viewInitialY, float dx, float dy) {
|
||||
onComplete(view, event, viewInitialX, viewInitialY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares dismiss view for dragging.
|
||||
* This method can be overridden to add extra setup on drag start
|
||||
*/
|
||||
protected void onDragStart() {
|
||||
mBubbleDismissController.showDismissView();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up dismiss view after dragging.
|
||||
* This method can be overridden to add extra setup on drag end
|
||||
*/
|
||||
protected void onDragEnd() {
|
||||
mBubbleDismissController.hideDismissView();
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete drag handling and clean up dependencies
|
||||
*/
|
||||
private void onComplete(@NonNull View view, @NonNull MotionEvent event,
|
||||
float viewInitialX, float viewInitialY) {
|
||||
if (!mHandling) return;
|
||||
if (mDragging) {
|
||||
// Stop dragging
|
||||
mDragging = false;
|
||||
view.setTranslationX(viewInitialX);
|
||||
view.setTranslationY(viewInitialY);
|
||||
onDragEnd();
|
||||
}
|
||||
mBubbleDismissController.handleTouchEvent(event);
|
||||
mActivity.setTaskbarWindowFullscreen(false);
|
||||
mHandling = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -661,6 +661,31 @@ public class SystemUiProxy implements ISystemUiProxy {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells SysUI to remove the bubble with the provided key.
|
||||
* @param key the key of the bubble to show.
|
||||
*/
|
||||
public void removeBubble(String key) {
|
||||
if (mBubbles == null) return;
|
||||
try {
|
||||
mBubbles.removeBubble(key);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed call removeBubble");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells SysUI to remove all bubbles.
|
||||
*/
|
||||
public void removeAllBubbles() {
|
||||
if (mBubbles == null) return;
|
||||
try {
|
||||
mBubbles.removeAllBubbles();
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed call removeAllBubbles");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells SysUI to collapse the bubbles.
|
||||
*/
|
||||
@@ -674,6 +699,21 @@ public class SystemUiProxy implements ISystemUiProxy {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells SysUI to collapse/expand selected bubble view while it's dragged.
|
||||
* Should be called only when the bubble bar is expanded.
|
||||
* @param bubbleKey the key of the bubble to collapse/expand
|
||||
* @param collapse whether to collapse/expand selected bubble
|
||||
*/
|
||||
public void collapseWhileDragging(@Nullable String bubbleKey, boolean collapse) {
|
||||
if (mBubbles == null) return;
|
||||
try {
|
||||
mBubbles.collapseWhileDragging(bubbleKey, collapse);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed call collapseWhileDragging");
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Splitscreen
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user