Loading libs/WindowManager/Shell/shared/res/values/dimen.xml +2 −0 Original line number Original line Diff line number Diff line Loading @@ -39,6 +39,8 @@ <!-- Bubble drop target dimensions --> <!-- Bubble drop target dimensions --> <dimen name="drop_target_elevation">1dp</dimen> <dimen name="drop_target_elevation">1dp</dimen> <dimen name="drop_target_radius">28dp</dimen> <dimen name="drop_target_stroke">1dp</dimen> <dimen name="drop_target_full_screen_padding">20dp</dimen> <dimen name="drop_target_full_screen_padding">20dp</dimen> <dimen name="drop_target_desktop_window_padding_small">100dp</dimen> <dimen name="drop_target_desktop_window_padding_small">100dp</dimen> <dimen name="drop_target_desktop_window_padding_large">130dp</dimen> <dimen name="drop_target_desktop_window_padding_large">130dp</dimen> Loading libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetManager.kt +25 −29 Original line number Original line Diff line number Diff line Loading @@ -18,29 +18,34 @@ package com.android.wm.shell.shared.bubbles import android.content.Context import android.content.Context import android.graphics.Rect import android.graphics.Rect import android.view.View import android.graphics.RectF import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.widget.FrameLayout import android.widget.FrameLayout import androidx.core.animation.Animator import androidx.core.animation.Animator import androidx.core.animation.AnimatorListenerAdapter import androidx.core.animation.AnimatorListenerAdapter import androidx.core.animation.ValueAnimator import androidx.core.animation.ValueAnimator import com.android.wm.shell.shared.R /** /** * Manages animating drop targets in response to dragging bubble icons or bubble expanded views * Manages animating drop targets in response to dragging bubble icons or bubble expanded views * across different drag zones. * across different drag zones. */ */ class DropTargetManager( class DropTargetManager( context: Context, private val context: Context, private val container: FrameLayout, private val container: FrameLayout, private val isLayoutRtl: Boolean, private val isLayoutRtl: Boolean, private val dragZoneChangedListener: DragZoneChangedListener, private val dragZoneChangedListener: DragZoneChangedListener, ) { ) { private var state: DragState? = null private var state: DragState? = null private val dropTargetView = View(context) private val dropTargetView = DropTargetView(context) private var animator: ValueAnimator? = null private var animator: ValueAnimator? = null private var morphRect: RectF = RectF(0f, 0f, 0f, 0f) private companion object { private companion object { const val ANIMATION_DURATION_MS = 250L const val MORPH_ANIM_DURATION = 250L const val DROP_TARGET_ALPHA_IN_DURATION = 150L const val DROP_TARGET_ALPHA_OUT_DURATION = 100L } } /** Must be called when a drag gesture is starting. */ /** Must be called when a drag gesture is starting. */ Loading @@ -55,15 +60,10 @@ class DropTargetManager( private fun setupDropTarget() { private fun setupDropTarget() { if (dropTargetView.parent != null) container.removeView(dropTargetView) if (dropTargetView.parent != null) container.removeView(dropTargetView) container.addView(dropTargetView, 0) container.addView(dropTargetView, 0) // TODO b/393173014: set elevation and background dropTargetView.alpha = 0f dropTargetView.alpha = 0f dropTargetView.scaleX = 1f dropTargetView.elevation = context.resources.getDimension(R.dimen.drop_target_elevation) dropTargetView.scaleY = 1f // Match parent and the target is drawn within the view dropTargetView.translationX = 0f dropTargetView.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT) dropTargetView.translationY = 0f // the drop target is added with a width and height of 1 pixel. when it gets resized, we use // set its scale to the width and height of the bounds it should have to avoid layout passes dropTargetView.layoutParams = FrameLayout.LayoutParams(/* width= */ 1, /* height= */ 1) } } /** Called when the user drags to a new location. */ /** Called when the user drags to a new location. */ Loading Loading @@ -92,10 +92,7 @@ class DropTargetManager( when { when { dropTargetBounds == null -> startFadeAnimation(from = dropTargetView.alpha, to = 0f) dropTargetBounds == null -> startFadeAnimation(from = dropTargetView.alpha, to = 0f) dropTargetView.alpha == 0f -> { dropTargetView.alpha == 0f -> { dropTargetView.translationX = dropTargetBounds.exactCenterX() dropTargetView.update(RectF(dropTargetBounds)) dropTargetView.translationY = dropTargetBounds.exactCenterY() dropTargetView.scaleX = dropTargetBounds.width().toFloat() dropTargetView.scaleY = dropTargetBounds.height().toFloat() startFadeAnimation(from = 0f, to = 1f) startFadeAnimation(from = 0f, to = 1f) } } else -> startMorphAnimation(dropTargetBounds) else -> startMorphAnimation(dropTargetBounds) Loading @@ -104,7 +101,9 @@ class DropTargetManager( private fun startFadeAnimation(from: Float, to: Float, onEnd: (() -> Unit)? = null) { private fun startFadeAnimation(from: Float, to: Float, onEnd: (() -> Unit)? = null) { animator?.cancel() animator?.cancel() val animator = ValueAnimator.ofFloat(from, to).setDuration(ANIMATION_DURATION_MS) 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 { _ -> dropTargetView.alpha = animator.animatedValue as Float } if (onEnd != null) { if (onEnd != null) { animator.doOnEnd(onEnd) animator.doOnEnd(onEnd) Loading @@ -113,23 +112,20 @@ class DropTargetManager( animator.start() animator.start() } } private fun startMorphAnimation(bounds: Rect) { private fun startMorphAnimation(endBounds: Rect) { animator?.cancel() animator?.cancel() val startAlpha = dropTargetView.alpha val startAlpha = dropTargetView.alpha val startTx = dropTargetView.translationX val startRect = dropTargetView.getRect() val startTy = dropTargetView.translationY val animator = ValueAnimator.ofFloat(0f, 1f).setDuration(MORPH_ANIM_DURATION) val startScaleX = dropTargetView.scaleX val startScaleY = dropTargetView.scaleY val animator = ValueAnimator.ofFloat(0f, 1f).setDuration(ANIMATION_DURATION_MS) animator.addUpdateListener { _ -> animator.addUpdateListener { _ -> val fraction = animator.animatedValue as Float val fraction = animator.animatedValue as Float dropTargetView.alpha = startAlpha + (1 - startAlpha) * fraction dropTargetView.alpha = startAlpha + (1 - startAlpha) * fraction dropTargetView.translationX = startTx + (bounds.exactCenterX() - startTx) * fraction dropTargetView.translationY = startTy + (bounds.exactCenterY() - startTy) * fraction morphRect.left = (startRect.left + (endBounds.left - startRect.left) * fraction) dropTargetView.scaleX = morphRect.top = (startRect.top + (endBounds.top - startRect.top) * fraction) startScaleX + (bounds.width().toFloat() - startScaleX) * fraction morphRect.right = (startRect.right + (endBounds.right - startRect.right) * fraction) dropTargetView.scaleY = morphRect.bottom = (startRect.bottom + (endBounds.bottom - startRect.bottom) * fraction) startScaleY + (bounds.height().toFloat() - startScaleY) * fraction dropTargetView.update(morphRect) } } this.animator = animator this.animator = animator animator.start() animator.start() Loading libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetView.kt 0 → 100644 +63 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2025 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.wm.shell.shared.bubbles import android.content.Context import android.graphics.Canvas import android.graphics.Paint import android.graphics.RectF import android.view.View import com.android.wm.shell.shared.R /** * Shows a drop target within this view. */ class DropTargetView(context: Context) : View(context) { private val rectPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { color = context.getColor(com.android.internal.R.color.materialColorPrimaryContainer) style = Paint.Style.FILL alpha = (0.35f * 255).toInt() } private val strokePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { color = context.getColor(com.android.internal.R.color.materialColorPrimaryContainer) style = Paint.Style.STROKE strokeWidth = context.resources.getDimensionPixelSize(R.dimen.drop_target_stroke).toFloat() } private val cornerRadius = context.resources.getDimensionPixelSize( R.dimen.drop_target_radius).toFloat() private val rect = RectF(0f, 0f, 0f, 0f) override fun onDraw(canvas: Canvas) { canvas.save() canvas.drawRoundRect(rect, cornerRadius, cornerRadius, rectPaint) canvas.drawRoundRect(rect, cornerRadius, cornerRadius, strokePaint) canvas.restore() } fun update(positionRect: RectF) { rect.set(positionRect) invalidate() } fun getRect(): RectF { return RectF(rect) } } No newline at end of file libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DropTargetManagerTest.kt +9 −9 Original line number Original line Diff line number Diff line Loading @@ -18,7 +18,6 @@ package com.android.wm.shell.shared.bubbles import android.content.Context import android.content.Context import android.graphics.Rect import android.graphics.Rect import android.view.View import android.widget.FrameLayout import android.widget.FrameLayout import androidx.core.animation.AnimatorTestRule import androidx.core.animation.AnimatorTestRule import androidx.test.core.app.ApplicationProvider.getApplicationContext import androidx.test.core.app.ApplicationProvider.getApplicationContext Loading Loading @@ -58,8 +57,8 @@ class DropTargetManagerTest { private val bubbleRightDragZone = private val bubbleRightDragZone = DragZone.Bubble.Right(bounds = Rect(200, 0, 300, 100), dropTarget = Rect(200, 0, 280, 150)) DragZone.Bubble.Right(bounds = Rect(200, 0, 300, 100), dropTarget = Rect(200, 0, 280, 150)) private val dropTargetView: View private val dropTargetView: DropTargetView get() = container.getChildAt(0) get() = container.getChildAt(0) as DropTargetView @Before @Before fun setUp() { fun setUp() { Loading Loading @@ -238,8 +237,9 @@ class DropTargetManagerTest { InstrumentationRegistry.getInstrumentation().runOnMainSync { InstrumentationRegistry.getInstrumentation().runOnMainSync { dropTargetManager.onDragEnded() dropTargetManager.onDragEnded() // advance the timer by 100ms so the animation doesn't complete // advance the timer by 50ms so the animation doesn't complete animatorTestRule.advanceTimeBy(100) // needs to be < DropTargetManager.DROP_TARGET_ALPHA_OUT_DURATION animatorTestRule.advanceTimeBy(50) } } assertThat(container.childCount).isEqualTo(1) assertThat(container.childCount).isEqualTo(1) Loading Loading @@ -320,10 +320,10 @@ class DropTargetManagerTest { } } private fun verifyDropTargetPosition(rect: Rect) { private fun verifyDropTargetPosition(rect: Rect) { assertThat(dropTargetView.scaleX).isEqualTo(rect.width()) assertThat(dropTargetView.getRect().left).isEqualTo(rect.left) assertThat(dropTargetView.scaleY).isEqualTo(rect.height()) assertThat(dropTargetView.getRect().top).isEqualTo(rect.top) assertThat(dropTargetView.translationX).isEqualTo(rect.exactCenterX()) assertThat(dropTargetView.getRect().right).isEqualTo(rect.right) assertThat(dropTargetView.translationY).isEqualTo(rect.exactCenterY()) assertThat(dropTargetView.getRect().bottom).isEqualTo(rect.bottom) } } private class FakeDragZoneChangedListener : DropTargetManager.DragZoneChangedListener { private class FakeDragZoneChangedListener : DropTargetManager.DragZoneChangedListener { Loading Loading
libs/WindowManager/Shell/shared/res/values/dimen.xml +2 −0 Original line number Original line Diff line number Diff line Loading @@ -39,6 +39,8 @@ <!-- Bubble drop target dimensions --> <!-- Bubble drop target dimensions --> <dimen name="drop_target_elevation">1dp</dimen> <dimen name="drop_target_elevation">1dp</dimen> <dimen name="drop_target_radius">28dp</dimen> <dimen name="drop_target_stroke">1dp</dimen> <dimen name="drop_target_full_screen_padding">20dp</dimen> <dimen name="drop_target_full_screen_padding">20dp</dimen> <dimen name="drop_target_desktop_window_padding_small">100dp</dimen> <dimen name="drop_target_desktop_window_padding_small">100dp</dimen> <dimen name="drop_target_desktop_window_padding_large">130dp</dimen> <dimen name="drop_target_desktop_window_padding_large">130dp</dimen> Loading
libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetManager.kt +25 −29 Original line number Original line Diff line number Diff line Loading @@ -18,29 +18,34 @@ package com.android.wm.shell.shared.bubbles import android.content.Context import android.content.Context import android.graphics.Rect import android.graphics.Rect import android.view.View import android.graphics.RectF import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.widget.FrameLayout import android.widget.FrameLayout import androidx.core.animation.Animator import androidx.core.animation.Animator import androidx.core.animation.AnimatorListenerAdapter import androidx.core.animation.AnimatorListenerAdapter import androidx.core.animation.ValueAnimator import androidx.core.animation.ValueAnimator import com.android.wm.shell.shared.R /** /** * Manages animating drop targets in response to dragging bubble icons or bubble expanded views * Manages animating drop targets in response to dragging bubble icons or bubble expanded views * across different drag zones. * across different drag zones. */ */ class DropTargetManager( class DropTargetManager( context: Context, private val context: Context, private val container: FrameLayout, private val container: FrameLayout, private val isLayoutRtl: Boolean, private val isLayoutRtl: Boolean, private val dragZoneChangedListener: DragZoneChangedListener, private val dragZoneChangedListener: DragZoneChangedListener, ) { ) { private var state: DragState? = null private var state: DragState? = null private val dropTargetView = View(context) private val dropTargetView = DropTargetView(context) private var animator: ValueAnimator? = null private var animator: ValueAnimator? = null private var morphRect: RectF = RectF(0f, 0f, 0f, 0f) private companion object { private companion object { const val ANIMATION_DURATION_MS = 250L const val MORPH_ANIM_DURATION = 250L const val DROP_TARGET_ALPHA_IN_DURATION = 150L const val DROP_TARGET_ALPHA_OUT_DURATION = 100L } } /** Must be called when a drag gesture is starting. */ /** Must be called when a drag gesture is starting. */ Loading @@ -55,15 +60,10 @@ class DropTargetManager( private fun setupDropTarget() { private fun setupDropTarget() { if (dropTargetView.parent != null) container.removeView(dropTargetView) if (dropTargetView.parent != null) container.removeView(dropTargetView) container.addView(dropTargetView, 0) container.addView(dropTargetView, 0) // TODO b/393173014: set elevation and background dropTargetView.alpha = 0f dropTargetView.alpha = 0f dropTargetView.scaleX = 1f dropTargetView.elevation = context.resources.getDimension(R.dimen.drop_target_elevation) dropTargetView.scaleY = 1f // Match parent and the target is drawn within the view dropTargetView.translationX = 0f dropTargetView.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT) dropTargetView.translationY = 0f // the drop target is added with a width and height of 1 pixel. when it gets resized, we use // set its scale to the width and height of the bounds it should have to avoid layout passes dropTargetView.layoutParams = FrameLayout.LayoutParams(/* width= */ 1, /* height= */ 1) } } /** Called when the user drags to a new location. */ /** Called when the user drags to a new location. */ Loading Loading @@ -92,10 +92,7 @@ class DropTargetManager( when { when { dropTargetBounds == null -> startFadeAnimation(from = dropTargetView.alpha, to = 0f) dropTargetBounds == null -> startFadeAnimation(from = dropTargetView.alpha, to = 0f) dropTargetView.alpha == 0f -> { dropTargetView.alpha == 0f -> { dropTargetView.translationX = dropTargetBounds.exactCenterX() dropTargetView.update(RectF(dropTargetBounds)) dropTargetView.translationY = dropTargetBounds.exactCenterY() dropTargetView.scaleX = dropTargetBounds.width().toFloat() dropTargetView.scaleY = dropTargetBounds.height().toFloat() startFadeAnimation(from = 0f, to = 1f) startFadeAnimation(from = 0f, to = 1f) } } else -> startMorphAnimation(dropTargetBounds) else -> startMorphAnimation(dropTargetBounds) Loading @@ -104,7 +101,9 @@ class DropTargetManager( private fun startFadeAnimation(from: Float, to: Float, onEnd: (() -> Unit)? = null) { private fun startFadeAnimation(from: Float, to: Float, onEnd: (() -> Unit)? = null) { animator?.cancel() animator?.cancel() val animator = ValueAnimator.ofFloat(from, to).setDuration(ANIMATION_DURATION_MS) 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 { _ -> dropTargetView.alpha = animator.animatedValue as Float } if (onEnd != null) { if (onEnd != null) { animator.doOnEnd(onEnd) animator.doOnEnd(onEnd) Loading @@ -113,23 +112,20 @@ class DropTargetManager( animator.start() animator.start() } } private fun startMorphAnimation(bounds: Rect) { private fun startMorphAnimation(endBounds: Rect) { animator?.cancel() animator?.cancel() val startAlpha = dropTargetView.alpha val startAlpha = dropTargetView.alpha val startTx = dropTargetView.translationX val startRect = dropTargetView.getRect() val startTy = dropTargetView.translationY val animator = ValueAnimator.ofFloat(0f, 1f).setDuration(MORPH_ANIM_DURATION) val startScaleX = dropTargetView.scaleX val startScaleY = dropTargetView.scaleY val animator = ValueAnimator.ofFloat(0f, 1f).setDuration(ANIMATION_DURATION_MS) animator.addUpdateListener { _ -> animator.addUpdateListener { _ -> val fraction = animator.animatedValue as Float val fraction = animator.animatedValue as Float dropTargetView.alpha = startAlpha + (1 - startAlpha) * fraction dropTargetView.alpha = startAlpha + (1 - startAlpha) * fraction dropTargetView.translationX = startTx + (bounds.exactCenterX() - startTx) * fraction dropTargetView.translationY = startTy + (bounds.exactCenterY() - startTy) * fraction morphRect.left = (startRect.left + (endBounds.left - startRect.left) * fraction) dropTargetView.scaleX = morphRect.top = (startRect.top + (endBounds.top - startRect.top) * fraction) startScaleX + (bounds.width().toFloat() - startScaleX) * fraction morphRect.right = (startRect.right + (endBounds.right - startRect.right) * fraction) dropTargetView.scaleY = morphRect.bottom = (startRect.bottom + (endBounds.bottom - startRect.bottom) * fraction) startScaleY + (bounds.height().toFloat() - startScaleY) * fraction dropTargetView.update(morphRect) } } this.animator = animator this.animator = animator animator.start() animator.start() Loading
libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetView.kt 0 → 100644 +63 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2025 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.wm.shell.shared.bubbles import android.content.Context import android.graphics.Canvas import android.graphics.Paint import android.graphics.RectF import android.view.View import com.android.wm.shell.shared.R /** * Shows a drop target within this view. */ class DropTargetView(context: Context) : View(context) { private val rectPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { color = context.getColor(com.android.internal.R.color.materialColorPrimaryContainer) style = Paint.Style.FILL alpha = (0.35f * 255).toInt() } private val strokePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { color = context.getColor(com.android.internal.R.color.materialColorPrimaryContainer) style = Paint.Style.STROKE strokeWidth = context.resources.getDimensionPixelSize(R.dimen.drop_target_stroke).toFloat() } private val cornerRadius = context.resources.getDimensionPixelSize( R.dimen.drop_target_radius).toFloat() private val rect = RectF(0f, 0f, 0f, 0f) override fun onDraw(canvas: Canvas) { canvas.save() canvas.drawRoundRect(rect, cornerRadius, cornerRadius, rectPaint) canvas.drawRoundRect(rect, cornerRadius, cornerRadius, strokePaint) canvas.restore() } fun update(positionRect: RectF) { rect.set(positionRect) invalidate() } fun getRect(): RectF { return RectF(rect) } } No newline at end of file
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DropTargetManagerTest.kt +9 −9 Original line number Original line Diff line number Diff line Loading @@ -18,7 +18,6 @@ package com.android.wm.shell.shared.bubbles import android.content.Context import android.content.Context import android.graphics.Rect import android.graphics.Rect import android.view.View import android.widget.FrameLayout import android.widget.FrameLayout import androidx.core.animation.AnimatorTestRule import androidx.core.animation.AnimatorTestRule import androidx.test.core.app.ApplicationProvider.getApplicationContext import androidx.test.core.app.ApplicationProvider.getApplicationContext Loading Loading @@ -58,8 +57,8 @@ class DropTargetManagerTest { private val bubbleRightDragZone = private val bubbleRightDragZone = DragZone.Bubble.Right(bounds = Rect(200, 0, 300, 100), dropTarget = Rect(200, 0, 280, 150)) DragZone.Bubble.Right(bounds = Rect(200, 0, 300, 100), dropTarget = Rect(200, 0, 280, 150)) private val dropTargetView: View private val dropTargetView: DropTargetView get() = container.getChildAt(0) get() = container.getChildAt(0) as DropTargetView @Before @Before fun setUp() { fun setUp() { Loading Loading @@ -238,8 +237,9 @@ class DropTargetManagerTest { InstrumentationRegistry.getInstrumentation().runOnMainSync { InstrumentationRegistry.getInstrumentation().runOnMainSync { dropTargetManager.onDragEnded() dropTargetManager.onDragEnded() // advance the timer by 100ms so the animation doesn't complete // advance the timer by 50ms so the animation doesn't complete animatorTestRule.advanceTimeBy(100) // needs to be < DropTargetManager.DROP_TARGET_ALPHA_OUT_DURATION animatorTestRule.advanceTimeBy(50) } } assertThat(container.childCount).isEqualTo(1) assertThat(container.childCount).isEqualTo(1) Loading Loading @@ -320,10 +320,10 @@ class DropTargetManagerTest { } } private fun verifyDropTargetPosition(rect: Rect) { private fun verifyDropTargetPosition(rect: Rect) { assertThat(dropTargetView.scaleX).isEqualTo(rect.width()) assertThat(dropTargetView.getRect().left).isEqualTo(rect.left) assertThat(dropTargetView.scaleY).isEqualTo(rect.height()) assertThat(dropTargetView.getRect().top).isEqualTo(rect.top) assertThat(dropTargetView.translationX).isEqualTo(rect.exactCenterX()) assertThat(dropTargetView.getRect().right).isEqualTo(rect.right) assertThat(dropTargetView.translationY).isEqualTo(rect.exactCenterY()) assertThat(dropTargetView.getRect().bottom).isEqualTo(rect.bottom) } } private class FakeDragZoneChangedListener : DropTargetManager.DragZoneChangedListener { private class FakeDragZoneChangedListener : DropTargetManager.DragZoneChangedListener { Loading