Loading libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/shared/bubbles/DragZoneFactoryScreenshotTest.kt +1 −0 Original line number Diff line number Diff line Loading @@ -91,6 +91,7 @@ class DragZoneFactoryScreenshotTest(private val param: Param) { is DraggedObject.Bubble -> "bubble" is DraggedObject.BubbleBar -> "bubbleBar" is DraggedObject.ExpandedView -> "expandedView" is DraggedObject.LauncherIcon -> "launcherIcon" } private val splitScreenModeName = Loading libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZone.kt +14 −5 Original line number Diff line number Diff line Loading @@ -35,6 +35,9 @@ sealed interface DragZone { /** The bounds of the drop target associated with this drag zone. */ val dropTarget: DropTargetRect? /** The bounds of the second drop target associated with this drag zone. */ val secondDropTarget: DropTargetRect? fun contains(x: Int, y: Int) = bounds.contains(x, y) sealed interface Bounds { Loading @@ -54,39 +57,45 @@ sealed interface DragZone { /** Represents the bubble drag area on the screen. */ sealed class Bubble( override val bounds: Bounds.RectZone, override val dropTarget: DropTargetRect override val dropTarget: DropTargetRect, ) : DragZone { data class Left( override val bounds: Bounds.RectZone, override val dropTarget: DropTargetRect override val dropTarget: DropTargetRect, override val secondDropTarget: DropTargetRect? = null, ) : Bubble(bounds, dropTarget) data class Right( override val bounds: Bounds.RectZone, override val dropTarget: DropTargetRect override val dropTarget: DropTargetRect, override val secondDropTarget: DropTargetRect? = null, ) : Bubble(bounds, dropTarget) } /** Represents dragging to Desktop Window. */ data class DesktopWindow( override val bounds: Bounds.RectZone, override val dropTarget: DropTargetRect override val dropTarget: DropTargetRect, override val secondDropTarget: DropTargetRect? = null, ) : DragZone /** Represents dragging to Full Screen. */ data class FullScreen( override val bounds: Bounds.RectZone, override val dropTarget: DropTargetRect override val dropTarget: DropTargetRect, override val secondDropTarget: DropTargetRect? = null, ) : DragZone /** Represents dragging to dismiss. */ data class Dismiss(override val bounds: Bounds.CircleZone) : DragZone { override val dropTarget: DropTargetRect? = null override val secondDropTarget: DropTargetRect? = null } /** Represents dragging to enter Split or replace a Split app. */ sealed class Split(override val bounds: Bounds.RectZone) : DragZone { override val dropTarget: DropTargetRect? = null override val secondDropTarget: DropTargetRect? = null data class Left(override val bounds: Bounds.RectZone) : Split(bounds) Loading libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZoneFactory.kt +7 −1 Original line number Diff line number Diff line Loading @@ -237,6 +237,10 @@ class DragZoneFactory( } dragZones.addAll(createBubbleHalfScreenDragZones(forBubbleBar = false)) } is DraggedObject.LauncherIcon -> { val showSecondDropTarget = !draggedObject.bubbleBarHasBubbles dragZones.addAll(createBubbleCornerDragZones(showSecondDropTarget)) } } return dragZones } Loading @@ -252,7 +256,7 @@ class DragZoneFactory( ) } private fun createBubbleCornerDragZones(): List<DragZone> { private fun createBubbleCornerDragZones(showSecondDropTarget: Boolean = false): List<DragZone> { val dragZoneSize = if (deviceConfig.isSmallTablet) { bubbleDragZoneFoldableSize Loading @@ -271,6 +275,7 @@ class DragZoneFactory( ), ), dropTarget = expandedViewDropTargetLeft, secondDropTarget = if (showSecondDropTarget) bubbleBarDropTargetLeft else null ), DragZone.Bubble.Right( bounds = Loading @@ -283,6 +288,7 @@ class DragZoneFactory( ), ), dropTarget = expandedViewDropTargetRight, secondDropTarget = if (showSecondDropTarget) bubbleBarDropTargetRight else null ) ) } Loading libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DraggedObject.kt +8 −5 Original line number Diff line number Diff line Loading @@ -18,10 +18,13 @@ package com.android.wm.shell.shared.bubbles /** A Bubble object being dragged. */ sealed interface DraggedObject { /** The initial location of the object at the start of the drag gesture. */ val initialLocation: BubbleBarLocation data class Bubble(override val initialLocation: BubbleBarLocation) : DraggedObject data class BubbleBar(override val initialLocation: BubbleBarLocation) : DraggedObject data class ExpandedView(override val initialLocation: BubbleBarLocation) : DraggedObject data class Bubble(val initialLocation: BubbleBarLocation) : DraggedObject data class BubbleBar(val initialLocation: BubbleBarLocation) : DraggedObject data class ExpandedView(val initialLocation: BubbleBarLocation) : DraggedObject data class LauncherIcon(val bubbleBarHasBubbles: Boolean, val onDropAction: Runnable) : DraggedObject } libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetManager.kt +95 −36 Original line number Diff line number Diff line Loading @@ -19,12 +19,18 @@ package com.android.wm.shell.shared.bubbles import android.content.Context import android.graphics.RectF import android.util.TypedValue import android.view.View import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.widget.FrameLayout import androidx.annotation.VisibleForTesting import androidx.core.animation.Animator import androidx.core.animation.AnimatorListenerAdapter import androidx.core.animation.ValueAnimator import com.android.wm.shell.shared.bubbles.DragZone.DropTargetRect import com.android.wm.shell.shared.bubbles.DraggedObject.Bubble import com.android.wm.shell.shared.bubbles.DraggedObject.BubbleBar import com.android.wm.shell.shared.bubbles.DraggedObject.ExpandedView import com.android.wm.shell.shared.bubbles.DraggedObject.LauncherIcon /** * Manages animating drop targets in response to dragging bubble icons or bubble expanded views Loading @@ -37,10 +43,12 @@ class DropTargetManager( ) { private var state: DragState? = null private val dropTargetView = DropTargetView(context) private var animator: ValueAnimator? = null @VisibleForTesting val dropTargetView = DropTargetView(context) @VisibleForTesting var secondDropTargetView: DropTargetView? = null private var morphRect: RectF = RectF(0f, 0f, 0f, 0f) private val isLayoutRtl = container.isLayoutRtl private val viewAnimatorsMap = mutableMapOf<View, ValueAnimator>() private companion object { const val MORPH_ANIM_DURATION = 250L Loading @@ -54,26 +62,38 @@ class DropTargetManager( val state = DragState(dragZones, draggedObject) dragZoneChangedListener.onInitialDragZoneSet(state.initialDragZone) this.state = state animator?.cancel() setupDropTarget() viewAnimatorsMap.values.forEach { it.cancel() } setupDropTarget(dropTargetView) if (dragZones.any { it.secondDropTarget != null }) { secondDropTargetView = secondDropTargetView ?: DropTargetView(context) setupDropTarget(secondDropTargetView) } else { secondDropTargetView?.let { container.removeView(it) } secondDropTargetView = null } } private fun setupDropTarget() { if (dropTargetView.parent != null) container.removeView(dropTargetView) container.addView(dropTargetView, 0) dropTargetView.alpha = 0f private fun setupDropTarget(view: View?) { if (view == null) return if (view.parent != null) container.removeView(view) container.addView(view, 0) view.alpha = 0f dropTargetView.elevation = TypedValue.applyDimension( view.elevation = TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, DROP_TARGET_ELEVATION_DP, context.resources.displayMetrics ) // Match parent and the target is drawn within the view dropTargetView.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT) view.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT) } /** Called when the user drags to a new location. */ fun onDragUpdated(x: Int, y: Int) { val state = state ?: return /** * Called when the user drags to a new location. * * @return DragZone that matches provided x and y coordinates. */ fun onDragUpdated(x: Int, y: Int): DragZone? { val state = state ?: return null val oldDragZone = state.currentDragZone val newDragZone = state.getMatchingDragZone(x = x, y = y) state.currentDragZone = newDragZone Loading @@ -81,50 +101,78 @@ class DropTargetManager( dragZoneChangedListener.onDragZoneChanged( draggedObject = state.draggedObject, from = oldDragZone, to = newDragZone to = newDragZone, ) updateDropTarget() } return newDragZone } /** Called when the drag ended. */ fun onDragEnded() { val dropState = state ?: return startFadeAnimation(from = dropTargetView.alpha, to = 0f) { container.removeView(dropTargetView) startFadeAnimation(dropTargetView, to = 0f) { container.removeView(dropTargetView) } startFadeAnimation(secondDropTargetView, to = 0f) { container.removeView(secondDropTargetView) secondDropTargetView = null } dragZoneChangedListener.onDragEnded(dropState.currentDragZone) state = null } private fun updateDropTarget() { val currentDragZone = state?.currentDragZone ?: return val dropState = state ?: return val currentDragZone = dropState.currentDragZone if (currentDragZone == null) { startFadeAnimation(dropTargetView, to = 0f) startFadeAnimation(secondDropTargetView, to = 0f) { container.removeView(secondDropTargetView) } return } val dropTargetRect = currentDragZone.dropTarget when { dropTargetRect == null -> startFadeAnimation(from = dropTargetView.alpha, to = 0f) dropTargetRect == null -> startFadeAnimation(dropTargetView, to = 0f) dropTargetView.alpha == 0f -> { dropTargetView.update(RectF(dropTargetRect.rect), dropTargetRect.cornerRadius) startFadeAnimation(from = 0f, to = 1f) startFadeAnimation(dropTargetView, to = 1f) } else -> startMorphAnimation(dropTargetRect) } val secondDropTargetRect = currentDragZone.secondDropTarget when { secondDropTargetRect == null -> startFadeAnimation(secondDropTargetView, to = 0f) else -> { val secondDropTargetView = secondDropTargetView ?: return secondDropTargetView.update( RectF(secondDropTargetRect.rect), secondDropTargetRect.cornerRadius, ) startFadeAnimation(secondDropTargetView, to = 1f) } } } private fun startFadeAnimation(from: Float, to: Float, onEnd: (() -> Unit)? = null) { animator?.cancel() private fun startFadeAnimation(view: View?, to: Float, onEnd: (() -> Unit)? = null) { if (view == null) return val from = view.alpha viewAnimatorsMap[view]?.cancel() val duration = if (from < to) DROP_TARGET_ALPHA_IN_DURATION else DROP_TARGET_ALPHA_OUT_DURATION val animator = ValueAnimator.ofFloat(from, to).setDuration(duration) animator.addUpdateListener { _ -> dropTargetView.alpha = animator.animatedValue as Float } animator.addUpdateListener { _ -> view.alpha = animator.animatedValue as Float } if (onEnd != null) { animator.doOnEnd(onEnd) } this.animator = animator viewAnimatorsMap[view] = animator animator.start() } private fun startMorphAnimation(dropTargetRect: DropTargetRect) { animator?.cancel() viewAnimatorsMap[dropTargetView]?.cancel() val startAlpha = dropTargetView.alpha val startRect = dropTargetView.getRect() val endRect = dropTargetRect.rect Loading @@ -139,38 +187,49 @@ class DropTargetManager( morphRect.bottom = (startRect.bottom + (endRect.bottom - startRect.bottom) * fraction) dropTargetView.update(morphRect, dropTargetRect.cornerRadius) } this.animator = animator viewAnimatorsMap[dropTargetView] = animator animator.start() } /** Stores the current drag state. */ private inner class DragState( private val dragZones: List<DragZone>, val draggedObject: DraggedObject val draggedObject: DraggedObject, ) { val initialDragZone = if (draggedObject.initialLocation.isOnLeft(isLayoutRtl)) { draggedObject.initialLocation?.let { if (it.isOnLeft(isLayoutRtl)) { dragZones.filterIsInstance<DragZone.Bubble.Left>().first() } else { dragZones.filterIsInstance<DragZone.Bubble.Right>().first() } var currentDragZone: DragZone = initialDragZone } var currentDragZone: DragZone? = initialDragZone fun getMatchingDragZone(x: Int, y: Int): DragZone { return dragZones.firstOrNull { it.contains(x, y) } ?: currentDragZone fun getMatchingDragZone(x: Int, y: Int): DragZone? { return dragZones.firstOrNull { it.contains(x, y) } } } private val DraggedObject.initialLocation: BubbleBarLocation? get() = when (this) { is Bubble -> initialLocation is BubbleBar -> initialLocation is ExpandedView -> initialLocation is LauncherIcon -> null } /** An interface to be notified when drag zones change. */ interface DragZoneChangedListener { /** An initial drag zone was set. Called when a drag starts. */ fun onInitialDragZoneSet(dragZone: DragZone) fun onInitialDragZoneSet(dragZone: DragZone?) /** Called when the object was dragged to a different drag zone. */ fun onDragZoneChanged(draggedObject: DraggedObject, from: DragZone, to: DragZone) fun onDragZoneChanged(draggedObject: DraggedObject, from: DragZone?, to: DragZone?) /** Called when the drag has ended with the zone it ended in. */ fun onDragEnded(zone: DragZone) fun onDragEnded(zone: DragZone?) } private fun Animator.doOnEnd(onEnd: () -> Unit) { Loading Loading
libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/shared/bubbles/DragZoneFactoryScreenshotTest.kt +1 −0 Original line number Diff line number Diff line Loading @@ -91,6 +91,7 @@ class DragZoneFactoryScreenshotTest(private val param: Param) { is DraggedObject.Bubble -> "bubble" is DraggedObject.BubbleBar -> "bubbleBar" is DraggedObject.ExpandedView -> "expandedView" is DraggedObject.LauncherIcon -> "launcherIcon" } private val splitScreenModeName = Loading
libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZone.kt +14 −5 Original line number Diff line number Diff line Loading @@ -35,6 +35,9 @@ sealed interface DragZone { /** The bounds of the drop target associated with this drag zone. */ val dropTarget: DropTargetRect? /** The bounds of the second drop target associated with this drag zone. */ val secondDropTarget: DropTargetRect? fun contains(x: Int, y: Int) = bounds.contains(x, y) sealed interface Bounds { Loading @@ -54,39 +57,45 @@ sealed interface DragZone { /** Represents the bubble drag area on the screen. */ sealed class Bubble( override val bounds: Bounds.RectZone, override val dropTarget: DropTargetRect override val dropTarget: DropTargetRect, ) : DragZone { data class Left( override val bounds: Bounds.RectZone, override val dropTarget: DropTargetRect override val dropTarget: DropTargetRect, override val secondDropTarget: DropTargetRect? = null, ) : Bubble(bounds, dropTarget) data class Right( override val bounds: Bounds.RectZone, override val dropTarget: DropTargetRect override val dropTarget: DropTargetRect, override val secondDropTarget: DropTargetRect? = null, ) : Bubble(bounds, dropTarget) } /** Represents dragging to Desktop Window. */ data class DesktopWindow( override val bounds: Bounds.RectZone, override val dropTarget: DropTargetRect override val dropTarget: DropTargetRect, override val secondDropTarget: DropTargetRect? = null, ) : DragZone /** Represents dragging to Full Screen. */ data class FullScreen( override val bounds: Bounds.RectZone, override val dropTarget: DropTargetRect override val dropTarget: DropTargetRect, override val secondDropTarget: DropTargetRect? = null, ) : DragZone /** Represents dragging to dismiss. */ data class Dismiss(override val bounds: Bounds.CircleZone) : DragZone { override val dropTarget: DropTargetRect? = null override val secondDropTarget: DropTargetRect? = null } /** Represents dragging to enter Split or replace a Split app. */ sealed class Split(override val bounds: Bounds.RectZone) : DragZone { override val dropTarget: DropTargetRect? = null override val secondDropTarget: DropTargetRect? = null data class Left(override val bounds: Bounds.RectZone) : Split(bounds) Loading
libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZoneFactory.kt +7 −1 Original line number Diff line number Diff line Loading @@ -237,6 +237,10 @@ class DragZoneFactory( } dragZones.addAll(createBubbleHalfScreenDragZones(forBubbleBar = false)) } is DraggedObject.LauncherIcon -> { val showSecondDropTarget = !draggedObject.bubbleBarHasBubbles dragZones.addAll(createBubbleCornerDragZones(showSecondDropTarget)) } } return dragZones } Loading @@ -252,7 +256,7 @@ class DragZoneFactory( ) } private fun createBubbleCornerDragZones(): List<DragZone> { private fun createBubbleCornerDragZones(showSecondDropTarget: Boolean = false): List<DragZone> { val dragZoneSize = if (deviceConfig.isSmallTablet) { bubbleDragZoneFoldableSize Loading @@ -271,6 +275,7 @@ class DragZoneFactory( ), ), dropTarget = expandedViewDropTargetLeft, secondDropTarget = if (showSecondDropTarget) bubbleBarDropTargetLeft else null ), DragZone.Bubble.Right( bounds = Loading @@ -283,6 +288,7 @@ class DragZoneFactory( ), ), dropTarget = expandedViewDropTargetRight, secondDropTarget = if (showSecondDropTarget) bubbleBarDropTargetRight else null ) ) } Loading
libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DraggedObject.kt +8 −5 Original line number Diff line number Diff line Loading @@ -18,10 +18,13 @@ package com.android.wm.shell.shared.bubbles /** A Bubble object being dragged. */ sealed interface DraggedObject { /** The initial location of the object at the start of the drag gesture. */ val initialLocation: BubbleBarLocation data class Bubble(override val initialLocation: BubbleBarLocation) : DraggedObject data class BubbleBar(override val initialLocation: BubbleBarLocation) : DraggedObject data class ExpandedView(override val initialLocation: BubbleBarLocation) : DraggedObject data class Bubble(val initialLocation: BubbleBarLocation) : DraggedObject data class BubbleBar(val initialLocation: BubbleBarLocation) : DraggedObject data class ExpandedView(val initialLocation: BubbleBarLocation) : DraggedObject data class LauncherIcon(val bubbleBarHasBubbles: Boolean, val onDropAction: Runnable) : DraggedObject }
libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetManager.kt +95 −36 Original line number Diff line number Diff line Loading @@ -19,12 +19,18 @@ package com.android.wm.shell.shared.bubbles import android.content.Context import android.graphics.RectF import android.util.TypedValue import android.view.View import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.widget.FrameLayout import androidx.annotation.VisibleForTesting import androidx.core.animation.Animator import androidx.core.animation.AnimatorListenerAdapter import androidx.core.animation.ValueAnimator import com.android.wm.shell.shared.bubbles.DragZone.DropTargetRect import com.android.wm.shell.shared.bubbles.DraggedObject.Bubble import com.android.wm.shell.shared.bubbles.DraggedObject.BubbleBar import com.android.wm.shell.shared.bubbles.DraggedObject.ExpandedView import com.android.wm.shell.shared.bubbles.DraggedObject.LauncherIcon /** * Manages animating drop targets in response to dragging bubble icons or bubble expanded views Loading @@ -37,10 +43,12 @@ class DropTargetManager( ) { private var state: DragState? = null private val dropTargetView = DropTargetView(context) private var animator: ValueAnimator? = null @VisibleForTesting val dropTargetView = DropTargetView(context) @VisibleForTesting var secondDropTargetView: DropTargetView? = null private var morphRect: RectF = RectF(0f, 0f, 0f, 0f) private val isLayoutRtl = container.isLayoutRtl private val viewAnimatorsMap = mutableMapOf<View, ValueAnimator>() private companion object { const val MORPH_ANIM_DURATION = 250L Loading @@ -54,26 +62,38 @@ class DropTargetManager( val state = DragState(dragZones, draggedObject) dragZoneChangedListener.onInitialDragZoneSet(state.initialDragZone) this.state = state animator?.cancel() setupDropTarget() viewAnimatorsMap.values.forEach { it.cancel() } setupDropTarget(dropTargetView) if (dragZones.any { it.secondDropTarget != null }) { secondDropTargetView = secondDropTargetView ?: DropTargetView(context) setupDropTarget(secondDropTargetView) } else { secondDropTargetView?.let { container.removeView(it) } secondDropTargetView = null } } private fun setupDropTarget() { if (dropTargetView.parent != null) container.removeView(dropTargetView) container.addView(dropTargetView, 0) dropTargetView.alpha = 0f private fun setupDropTarget(view: View?) { if (view == null) return if (view.parent != null) container.removeView(view) container.addView(view, 0) view.alpha = 0f dropTargetView.elevation = TypedValue.applyDimension( view.elevation = TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, DROP_TARGET_ELEVATION_DP, context.resources.displayMetrics ) // Match parent and the target is drawn within the view dropTargetView.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT) view.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT) } /** Called when the user drags to a new location. */ fun onDragUpdated(x: Int, y: Int) { val state = state ?: return /** * Called when the user drags to a new location. * * @return DragZone that matches provided x and y coordinates. */ fun onDragUpdated(x: Int, y: Int): DragZone? { val state = state ?: return null val oldDragZone = state.currentDragZone val newDragZone = state.getMatchingDragZone(x = x, y = y) state.currentDragZone = newDragZone Loading @@ -81,50 +101,78 @@ class DropTargetManager( dragZoneChangedListener.onDragZoneChanged( draggedObject = state.draggedObject, from = oldDragZone, to = newDragZone to = newDragZone, ) updateDropTarget() } return newDragZone } /** Called when the drag ended. */ fun onDragEnded() { val dropState = state ?: return startFadeAnimation(from = dropTargetView.alpha, to = 0f) { container.removeView(dropTargetView) startFadeAnimation(dropTargetView, to = 0f) { container.removeView(dropTargetView) } startFadeAnimation(secondDropTargetView, to = 0f) { container.removeView(secondDropTargetView) secondDropTargetView = null } dragZoneChangedListener.onDragEnded(dropState.currentDragZone) state = null } private fun updateDropTarget() { val currentDragZone = state?.currentDragZone ?: return val dropState = state ?: return val currentDragZone = dropState.currentDragZone if (currentDragZone == null) { startFadeAnimation(dropTargetView, to = 0f) startFadeAnimation(secondDropTargetView, to = 0f) { container.removeView(secondDropTargetView) } return } val dropTargetRect = currentDragZone.dropTarget when { dropTargetRect == null -> startFadeAnimation(from = dropTargetView.alpha, to = 0f) dropTargetRect == null -> startFadeAnimation(dropTargetView, to = 0f) dropTargetView.alpha == 0f -> { dropTargetView.update(RectF(dropTargetRect.rect), dropTargetRect.cornerRadius) startFadeAnimation(from = 0f, to = 1f) startFadeAnimation(dropTargetView, to = 1f) } else -> startMorphAnimation(dropTargetRect) } val secondDropTargetRect = currentDragZone.secondDropTarget when { secondDropTargetRect == null -> startFadeAnimation(secondDropTargetView, to = 0f) else -> { val secondDropTargetView = secondDropTargetView ?: return secondDropTargetView.update( RectF(secondDropTargetRect.rect), secondDropTargetRect.cornerRadius, ) startFadeAnimation(secondDropTargetView, to = 1f) } } } private fun startFadeAnimation(from: Float, to: Float, onEnd: (() -> Unit)? = null) { animator?.cancel() private fun startFadeAnimation(view: View?, to: Float, onEnd: (() -> Unit)? = null) { if (view == null) return val from = view.alpha viewAnimatorsMap[view]?.cancel() val duration = if (from < to) DROP_TARGET_ALPHA_IN_DURATION else DROP_TARGET_ALPHA_OUT_DURATION val animator = ValueAnimator.ofFloat(from, to).setDuration(duration) animator.addUpdateListener { _ -> dropTargetView.alpha = animator.animatedValue as Float } animator.addUpdateListener { _ -> view.alpha = animator.animatedValue as Float } if (onEnd != null) { animator.doOnEnd(onEnd) } this.animator = animator viewAnimatorsMap[view] = animator animator.start() } private fun startMorphAnimation(dropTargetRect: DropTargetRect) { animator?.cancel() viewAnimatorsMap[dropTargetView]?.cancel() val startAlpha = dropTargetView.alpha val startRect = dropTargetView.getRect() val endRect = dropTargetRect.rect Loading @@ -139,38 +187,49 @@ class DropTargetManager( morphRect.bottom = (startRect.bottom + (endRect.bottom - startRect.bottom) * fraction) dropTargetView.update(morphRect, dropTargetRect.cornerRadius) } this.animator = animator viewAnimatorsMap[dropTargetView] = animator animator.start() } /** Stores the current drag state. */ private inner class DragState( private val dragZones: List<DragZone>, val draggedObject: DraggedObject val draggedObject: DraggedObject, ) { val initialDragZone = if (draggedObject.initialLocation.isOnLeft(isLayoutRtl)) { draggedObject.initialLocation?.let { if (it.isOnLeft(isLayoutRtl)) { dragZones.filterIsInstance<DragZone.Bubble.Left>().first() } else { dragZones.filterIsInstance<DragZone.Bubble.Right>().first() } var currentDragZone: DragZone = initialDragZone } var currentDragZone: DragZone? = initialDragZone fun getMatchingDragZone(x: Int, y: Int): DragZone { return dragZones.firstOrNull { it.contains(x, y) } ?: currentDragZone fun getMatchingDragZone(x: Int, y: Int): DragZone? { return dragZones.firstOrNull { it.contains(x, y) } } } private val DraggedObject.initialLocation: BubbleBarLocation? get() = when (this) { is Bubble -> initialLocation is BubbleBar -> initialLocation is ExpandedView -> initialLocation is LauncherIcon -> null } /** An interface to be notified when drag zones change. */ interface DragZoneChangedListener { /** An initial drag zone was set. Called when a drag starts. */ fun onInitialDragZoneSet(dragZone: DragZone) fun onInitialDragZoneSet(dragZone: DragZone?) /** Called when the object was dragged to a different drag zone. */ fun onDragZoneChanged(draggedObject: DraggedObject, from: DragZone, to: DragZone) fun onDragZoneChanged(draggedObject: DraggedObject, from: DragZone?, to: DragZone?) /** Called when the drag has ended with the zone it ended in. */ fun onDragEnded(zone: DragZone) fun onDragEnded(zone: DragZone?) } private fun Animator.doOnEnd(onEnd: () -> Unit) { Loading