Merge "Adds the FloatingContentCoordinator."
This commit is contained in:
committed by
Android (Google) Code Review
commit
de25f7f1ff
@@ -0,0 +1,320 @@
|
||||
package com.android.systemui.util
|
||||
|
||||
import android.graphics.Rect
|
||||
import android.util.Log
|
||||
import com.android.systemui.util.FloatingContentCoordinator.FloatingContent
|
||||
import java.util.HashMap
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/** Tag for debug logging. */
|
||||
private const val TAG = "FloatingCoordinator"
|
||||
|
||||
/**
|
||||
* Coordinates the positions and movement of floating content, such as PIP and Bubbles, to ensure
|
||||
* that they don't overlap. If content does overlap due to content appearing or moving, the
|
||||
* coordinator will ask content to move to resolve the conflict.
|
||||
*
|
||||
* After implementing [FloatingContent], content should call [onContentAdded] to begin coordination.
|
||||
* Subsequently, call [onContentMoved] whenever the content moves, and the coordinator will move
|
||||
* other content out of the way. [onContentRemoved] should be called when the content is removed or
|
||||
* no longer visible.
|
||||
*/
|
||||
@Singleton
|
||||
class FloatingContentCoordinator @Inject constructor() {
|
||||
|
||||
/**
|
||||
* Represents a piece of floating content, such as PIP or the Bubbles stack. Provides methods
|
||||
* that allow the [FloatingContentCoordinator] to determine the current location of the content,
|
||||
* as well as the ability to ask it to move out of the way of other content.
|
||||
*
|
||||
* The default implementation of [calculateNewBoundsOnOverlap] moves the content up or down,
|
||||
* depending on the position of the conflicting content. You can override this method if you
|
||||
* want your own custom conflict resolution logic.
|
||||
*/
|
||||
interface FloatingContent {
|
||||
|
||||
/**
|
||||
* Return the bounds claimed by this content. This should include the bounds occupied by the
|
||||
* content itself, as well as any padding, if desired. The coordinator will ensure that no
|
||||
* other content is located within these bounds.
|
||||
*
|
||||
* If the content is animating, this method should return the bounds to which the content is
|
||||
* animating. If that animation is cancelled, or updated, be sure that your implementation
|
||||
* of this method returns the appropriate bounds, and call [onContentMoved] so that the
|
||||
* coordinator moves other content out of the way.
|
||||
*/
|
||||
fun getFloatingBoundsOnScreen(): Rect
|
||||
|
||||
/**
|
||||
* Return the area within which this floating content is allowed to move. When resolving
|
||||
* conflicts, the coordinator will never ask your content to move to a position where any
|
||||
* part of the content would be out of these bounds.
|
||||
*/
|
||||
fun getAllowedFloatingBoundsRegion(): Rect
|
||||
|
||||
/**
|
||||
* Called when the coordinator needs this content to move to the given bounds. It's up to
|
||||
* you how to do that.
|
||||
*
|
||||
* Note that if you start an animation to these bounds, [getFloatingBoundsOnScreen] should
|
||||
* return the destination bounds, not the in-progress animated bounds. This is so the
|
||||
* coordinator knows where floating content is going to be and can resolve conflicts
|
||||
* accordingly.
|
||||
*/
|
||||
fun moveToBounds(bounds: Rect)
|
||||
|
||||
/**
|
||||
* Called by the coordinator when it needs to find a new home for this floating content,
|
||||
* because a new or moving piece of content is now overlapping with it.
|
||||
*
|
||||
* [findAreaForContentVertically] and [findAreaForContentAboveOrBelow] are helpful utility
|
||||
* functions that will find new bounds for your content automatically. Unless you require
|
||||
* specific conflict resolution logic, these should be sufficient. By default, this method
|
||||
* delegates to [findAreaForContentVertically].
|
||||
*
|
||||
* @param overlappingContentBounds The bounds of the other piece of content, which
|
||||
* necessitated this content's relocation. Your new position must not overlap with these
|
||||
* bounds.
|
||||
* @param otherContentBounds The bounds of any other pieces of floating content. Your new
|
||||
* position must not overlap with any of these either. These bounds are guaranteed to be
|
||||
* non-overlapping.
|
||||
* @return The new bounds for this content.
|
||||
*/
|
||||
@JvmDefault
|
||||
fun calculateNewBoundsOnOverlap(
|
||||
overlappingContentBounds: Rect,
|
||||
otherContentBounds: List<Rect>
|
||||
): Rect {
|
||||
return findAreaForContentVertically(
|
||||
getFloatingBoundsOnScreen(),
|
||||
overlappingContentBounds,
|
||||
otherContentBounds,
|
||||
getAllowedFloatingBoundsRegion())
|
||||
}
|
||||
}
|
||||
|
||||
/** The bounds of all pieces of floating content added to the coordinator. */
|
||||
private val allContentBounds: MutableMap<FloatingContent, Rect> = HashMap()
|
||||
|
||||
/**
|
||||
* Makes the coordinator aware of a new piece of floating content, and moves any existing
|
||||
* content out of the way, if necessary.
|
||||
*
|
||||
* If you don't want your new content to move existing content, use [getOccupiedBounds] to find
|
||||
* an unoccupied area, and move the content there before calling this method.
|
||||
*/
|
||||
fun onContentAdded(newContent: FloatingContent) {
|
||||
updateContentBounds()
|
||||
allContentBounds[newContent] = newContent.getFloatingBoundsOnScreen()
|
||||
maybeMoveConflictingContent(newContent)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to notify the coordinator that a piece of floating content has moved (or is animating)
|
||||
* to a new position, and that any conflicting floating content should be moved out of the way.
|
||||
*
|
||||
* The coordinator will call [FloatingContent.getFloatingBoundsOnScreen] to find the new bounds
|
||||
* for the moving content. If you're animating the content, be sure that your implementation of
|
||||
* getFloatingBoundsOnScreen returns the bounds to which it's animating, not the content's
|
||||
* current bounds.
|
||||
*
|
||||
* If the animation moving this content is cancelled or updated, you'll need to call this method
|
||||
* again, to ensure that content is moved out of the way of the latest bounds.
|
||||
*
|
||||
* @param content The content that has moved.
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun onContentMoved(content: FloatingContent) {
|
||||
if (!allContentBounds.containsKey(content)) {
|
||||
Log.wtf(TAG, "Received onContentMoved call before onContentAdded! " +
|
||||
"This should never happen.")
|
||||
return
|
||||
}
|
||||
|
||||
updateContentBounds()
|
||||
maybeMoveConflictingContent(content)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to notify the coordinator that a piece of floating content has been removed or is no
|
||||
* longer visible.
|
||||
*/
|
||||
fun onContentRemoved(removedContent: FloatingContent) {
|
||||
allContentBounds.remove(removedContent)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a set of Rects that represent the bounds of all of the floating content on the
|
||||
* screen.
|
||||
*
|
||||
* [onContentAdded] will move existing content out of the way if the added content intersects
|
||||
* existing content. That's fine - but if your specific starting position is not important, you
|
||||
* can use this function to find unoccupied space for your content before calling
|
||||
* [onContentAdded], so that moving existing content isn't necessary.
|
||||
*/
|
||||
fun getOccupiedBounds(): Collection<Rect> {
|
||||
return allContentBounds.values
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies any pieces of content that are now overlapping with the given content, and asks
|
||||
* them to move out of the way.
|
||||
*/
|
||||
private fun maybeMoveConflictingContent(fromContent: FloatingContent) {
|
||||
val conflictingNewBounds = allContentBounds[fromContent]!!
|
||||
allContentBounds
|
||||
// Filter to content that intersects with the new bounds. That's content that needs
|
||||
// to move.
|
||||
.filter { (content, bounds) ->
|
||||
content != fromContent && Rect.intersects(conflictingNewBounds, bounds) }
|
||||
// Tell that content to get out of the way, and save the bounds it says it's moving
|
||||
// (or animating) to.
|
||||
.forEach { (content, bounds) ->
|
||||
content.moveToBounds(
|
||||
content.calculateNewBoundsOnOverlap(
|
||||
conflictingNewBounds,
|
||||
// Pass all of the content bounds except the bounds of the
|
||||
// content we're asking to move, and the conflicting new bounds
|
||||
// (since those are passed separately).
|
||||
otherContentBounds = allContentBounds.values
|
||||
.minus(bounds)
|
||||
.minus(conflictingNewBounds)))
|
||||
allContentBounds[content] = content.getFloatingBoundsOnScreen()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update [allContentBounds] by calling [FloatingContent.getFloatingBoundsOnScreen] for all
|
||||
* content and saving the result.
|
||||
*/
|
||||
private fun updateContentBounds() {
|
||||
allContentBounds.keys.forEach { allContentBounds[it] = it.getFloatingBoundsOnScreen() }
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Finds new bounds for the given content, either above or below its current position. The
|
||||
* new bounds won't intersect with the newly overlapping rect or the exclusion rects, and
|
||||
* will be within the allowed bounds unless no possible position exists.
|
||||
*
|
||||
* You can use this method to help find a new position for your content when the coordinator
|
||||
* calls [FloatingContent.moveToAreaExcluding].
|
||||
*
|
||||
* @param contentRect The bounds of the content for which we're finding a new home.
|
||||
* @param newlyOverlappingRect The bounds of the content that forced this relocation by
|
||||
* intersecting with the content we now need to move. If the overlapping content is
|
||||
* overlapping the top half of this content, we'll try to move this content downward if
|
||||
* possible (since the other content is 'pushing' it down), and vice versa.
|
||||
* @param exclusionRects Any other areas that we need to avoid when finding a new home for
|
||||
* the content. These areas must be non-overlapping with each other.
|
||||
* @param allowedBounds The area within which we're allowed to find new bounds for the
|
||||
* content.
|
||||
* @return New bounds for the content that don't intersect the exclusion rects or the
|
||||
* newly overlapping rect, and that is within bounds unless no possible in-bounds position
|
||||
* exists.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun findAreaForContentVertically(
|
||||
contentRect: Rect,
|
||||
newlyOverlappingRect: Rect,
|
||||
exclusionRects: Collection<Rect>,
|
||||
allowedBounds: Rect
|
||||
): Rect {
|
||||
// If the newly overlapping Rect's center is above the content's center, we'll prefer to
|
||||
// find a space for this content that is below the overlapping content, since it's
|
||||
// 'pushing' it down. This may not be possible due to to screen bounds, in which case
|
||||
// we'll find space in the other direction.
|
||||
val overlappingContentPushingDown =
|
||||
newlyOverlappingRect.centerY() < contentRect.centerY()
|
||||
|
||||
// Filter to exclusion rects that are above or below the content that we're finding a
|
||||
// place for. Then, split into two lists - rects above the content, and rects below it.
|
||||
var (rectsToAvoidAbove, rectsToAvoidBelow) = exclusionRects
|
||||
.filter { rectToAvoid -> rectsIntersectVertically(rectToAvoid, contentRect) }
|
||||
.partition { rectToAvoid -> rectToAvoid.top < contentRect.top }
|
||||
|
||||
// Lazily calculate the closest possible new tops for the content, above and below its
|
||||
// current location.
|
||||
val newContentBoundsAbove by lazy { findAreaForContentAboveOrBelow(
|
||||
contentRect,
|
||||
exclusionRects = rectsToAvoidAbove.plus(newlyOverlappingRect),
|
||||
findAbove = true) }
|
||||
val newContentBoundsBelow by lazy { findAreaForContentAboveOrBelow(
|
||||
contentRect,
|
||||
exclusionRects = rectsToAvoidBelow.plus(newlyOverlappingRect),
|
||||
findAbove = false) }
|
||||
|
||||
val positionAboveInBounds by lazy { allowedBounds.contains(newContentBoundsAbove) }
|
||||
val positionBelowInBounds by lazy { allowedBounds.contains(newContentBoundsBelow) }
|
||||
|
||||
// Use the 'below' position if the content is being overlapped from the top, unless it's
|
||||
// out of bounds. Also use it if the content is being overlapped from the bottom, but
|
||||
// the 'above' position is out of bounds. Otherwise, use the 'above' position.
|
||||
val usePositionBelow =
|
||||
overlappingContentPushingDown && positionBelowInBounds ||
|
||||
!overlappingContentPushingDown && !positionAboveInBounds
|
||||
|
||||
// Return the content rect, but offset to reflect the new position.
|
||||
return if (usePositionBelow) newContentBoundsBelow else newContentBoundsAbove
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a new position for the given content, either above or below its current position
|
||||
* depending on whether [findAbove] is true or false, respectively. This new position will
|
||||
* not intersect with any of the [exclusionRects].
|
||||
*
|
||||
* This method is useful as a helper method for implementing your own conflict resolution
|
||||
* logic. Otherwise, you'd want to use [findAreaForContentVertically], which takes screen
|
||||
* bounds and conflicting bounds' location into account when deciding whether to move to new
|
||||
* bounds above or below the current bounds.
|
||||
*
|
||||
* @param contentRect The content we're finding an area for.
|
||||
* @param exclusionRects The areas we need to avoid when finding a new area for the content.
|
||||
* These areas must be non-overlapping with each other.
|
||||
* @param findAbove Whether we are finding an area above the content's current position,
|
||||
* rather than an area below it.
|
||||
*/
|
||||
fun findAreaForContentAboveOrBelow(
|
||||
contentRect: Rect,
|
||||
exclusionRects: Collection<Rect>,
|
||||
findAbove: Boolean
|
||||
): Rect {
|
||||
// Sort the rects, since we want to move the content as little as possible. We'll
|
||||
// start with the rects closest to the content and move outward. If we're finding an
|
||||
// area above the content, that means we sort in reverse order to search the rects
|
||||
// from highest to lowest y-value.
|
||||
val sortedExclusionRects =
|
||||
exclusionRects.sortedBy { if (findAbove) -it.top else it.top }
|
||||
|
||||
val proposedNewBounds = Rect(contentRect)
|
||||
for (exclusionRect in sortedExclusionRects) {
|
||||
// If the proposed new bounds don't intersect with this exclusion rect, that
|
||||
// means there's room for the content here. We know this because the rects are
|
||||
// sorted and non-overlapping, so any subsequent exclusion rects would be higher
|
||||
// (or lower) than this one and can't possibly intersect if this one doesn't.
|
||||
if (!Rect.intersects(proposedNewBounds, exclusionRect)) {
|
||||
break
|
||||
} else {
|
||||
// Otherwise, we need to keep searching for new bounds. If we're finding an
|
||||
// area above, propose new bounds that place the content just above the
|
||||
// exclusion rect. If we're finding an area below, propose new bounds that
|
||||
// place the content just below the exclusion rect.
|
||||
val verticalOffset =
|
||||
if (findAbove) -contentRect.height() else exclusionRect.height()
|
||||
proposedNewBounds.offsetTo(
|
||||
proposedNewBounds.left,
|
||||
exclusionRect.top + verticalOffset)
|
||||
}
|
||||
}
|
||||
|
||||
return proposedNewBounds
|
||||
}
|
||||
|
||||
/** Returns whether or not the two Rects share any of the same space on the X axis. */
|
||||
private fun rectsIntersectVertically(r1: Rect, r2: Rect): Boolean {
|
||||
return (r1.left >= r2.left && r1.left <= r2.right) ||
|
||||
(r1.right <= r2.right && r1.right >= r2.left)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
package com.android.systemui.util
|
||||
|
||||
import android.graphics.Rect
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.testing.TestableLooper
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@TestableLooper.RunWithLooper
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@SmallTest
|
||||
class FloatingContentCoordinatorTest : SysuiTestCase() {
|
||||
|
||||
private val screenBounds = Rect(0, 0, 1000, 1000)
|
||||
|
||||
private val rect100px = Rect()
|
||||
private val rect100pxFloating = FloatingRect(rect100px)
|
||||
|
||||
private val rect200px = Rect()
|
||||
private val rect200pxFloating = FloatingRect(rect200px)
|
||||
|
||||
private val rect300px = Rect()
|
||||
private val rect300pxFloating = FloatingRect(rect300px)
|
||||
|
||||
private val floatingCoordinator = FloatingContentCoordinator()
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
rect100px.set(0, 0, 100, 100)
|
||||
rect200px.set(0, 0, 200, 200)
|
||||
rect300px.set(0, 0, 300, 300)
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
// We need to remove this stuff since it's a singleton object and it'll be there for the
|
||||
// next test.
|
||||
floatingCoordinator.onContentRemoved(rect100pxFloating)
|
||||
floatingCoordinator.onContentRemoved(rect200pxFloating)
|
||||
floatingCoordinator.onContentRemoved(rect300pxFloating)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnContentAdded() {
|
||||
// Add rect1, and verify that the coordinator didn't move it.
|
||||
floatingCoordinator.onContentAdded(rect100pxFloating)
|
||||
assertEquals(rect100px.top, 0)
|
||||
|
||||
// Add rect2, which intersects rect1. Verify that rect2 was not moved, since newly added
|
||||
// content is allowed to remain where it is. rect1 should have been moved below rect2
|
||||
// since it was in the way.
|
||||
floatingCoordinator.onContentAdded(rect200pxFloating)
|
||||
assertEquals(rect200px.top, 0)
|
||||
assertEquals(rect100px.top, 200)
|
||||
|
||||
verifyRectSizes()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnContentRemoved() {
|
||||
// Add rect1, and remove it. Then add rect2. Since rect1 was removed before that, it should
|
||||
// no longer be considered in the way, so it shouldn't move when rect2 is added.
|
||||
floatingCoordinator.onContentAdded(rect100pxFloating)
|
||||
floatingCoordinator.onContentRemoved(rect100pxFloating)
|
||||
floatingCoordinator.onContentAdded(rect200pxFloating)
|
||||
|
||||
assertEquals(rect100px.top, 0)
|
||||
assertEquals(rect200px.top, 0)
|
||||
|
||||
verifyRectSizes()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnContentMoved_twoRects() {
|
||||
// Add rect1, which is at y = 0.
|
||||
floatingCoordinator.onContentAdded(rect100pxFloating)
|
||||
|
||||
// Move rect2 down to 500px, where it won't conflict with rect1.
|
||||
rect200px.offsetTo(0, 500)
|
||||
floatingCoordinator.onContentAdded(rect200pxFloating)
|
||||
|
||||
// Then, move it to 0px where it will absolutely conflict with rect1.
|
||||
rect200px.offsetTo(0, 0)
|
||||
floatingCoordinator.onContentMoved(rect200pxFloating)
|
||||
|
||||
// The coordinator should have left rect2 alone, and moved rect1 below it. rect1 should now
|
||||
// be at y = 200.
|
||||
assertEquals(rect200px.top, 0)
|
||||
assertEquals(rect100px.top, 200)
|
||||
|
||||
verifyRectSizes()
|
||||
|
||||
// Move rect2 to y = 275px. Since this puts it at the bottom half of rect1, it should push
|
||||
// rect1 upward and leave rect2 alone.
|
||||
rect200px.offsetTo(0, 275)
|
||||
floatingCoordinator.onContentMoved(rect200pxFloating)
|
||||
|
||||
assertEquals(rect200px.top, 275)
|
||||
assertEquals(rect100px.top, 175)
|
||||
|
||||
verifyRectSizes()
|
||||
|
||||
// Move rect2 to y = 110px. This makes it intersect rect1 again, but above its center of
|
||||
// mass. That means rect1 should be pushed downward.
|
||||
rect200px.offsetTo(0, 110)
|
||||
floatingCoordinator.onContentMoved(rect200pxFloating)
|
||||
|
||||
assertEquals(rect200px.top, 110)
|
||||
assertEquals(rect100px.top, 310)
|
||||
|
||||
verifyRectSizes()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnContentMoved_threeRects() {
|
||||
floatingCoordinator.onContentAdded(rect100pxFloating)
|
||||
|
||||
// Add rect2, which should displace rect1 to y = 200
|
||||
floatingCoordinator.onContentAdded(rect200pxFloating)
|
||||
assertEquals(rect200px.top, 0)
|
||||
assertEquals(rect100px.top, 200)
|
||||
|
||||
// Add rect3, which should completely cover both rect1 and rect2. That should cause them to
|
||||
// move away. The order in which they do so is non-deterministic, so just make sure none of
|
||||
// the three Rects intersect.
|
||||
floatingCoordinator.onContentAdded(rect300pxFloating)
|
||||
|
||||
assertFalse(Rect.intersects(rect100px, rect200px))
|
||||
assertFalse(Rect.intersects(rect100px, rect300px))
|
||||
assertFalse(Rect.intersects(rect200px, rect300px))
|
||||
|
||||
// Move rect2 to intersect both rect1 and rect3.
|
||||
rect200px.offsetTo(0, 150)
|
||||
floatingCoordinator.onContentMoved(rect200pxFloating)
|
||||
|
||||
assertFalse(Rect.intersects(rect100px, rect200px))
|
||||
assertFalse(Rect.intersects(rect100px, rect300px))
|
||||
assertFalse(Rect.intersects(rect200px, rect300px))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnContentMoved_respectsUpperBounds() {
|
||||
// Add rect1, which is at y = 0.
|
||||
floatingCoordinator.onContentAdded(rect100pxFloating)
|
||||
|
||||
// Move rect2 down to 500px, where it won't conflict with rect1.
|
||||
rect200px.offsetTo(0, 500)
|
||||
floatingCoordinator.onContentAdded(rect200pxFloating)
|
||||
|
||||
// Then, move it to 90px where it will conflict with rect1, but with a center of mass below
|
||||
// that of rect1's. This would normally mean that rect1 moves upward. However, since it's at
|
||||
// the top of the screen, it should go downward instead.
|
||||
rect200px.offsetTo(0, 90)
|
||||
floatingCoordinator.onContentMoved(rect200pxFloating)
|
||||
|
||||
// rect2 should have been left alone, rect1 is now below rect2 at y = 290px even though it
|
||||
// was intersected from below.
|
||||
assertEquals(rect200px.top, 90)
|
||||
assertEquals(rect100px.top, 290)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnContentMoved_respectsLowerBounds() {
|
||||
// Put rect1 at the bottom of the screen and add it.
|
||||
rect100px.offsetTo(0, screenBounds.bottom - 100)
|
||||
floatingCoordinator.onContentAdded(rect100pxFloating)
|
||||
|
||||
// Put rect2 at the bottom as well. Since its center of mass is above rect1's, rect1 would
|
||||
// normally move downward. Since it's at the bottom of the screen, it should go upward
|
||||
// instead.
|
||||
rect200px.offsetTo(0, 800)
|
||||
floatingCoordinator.onContentAdded(rect200pxFloating)
|
||||
|
||||
assertEquals(rect200px.top, 800)
|
||||
assertEquals(rect100px.top, 700)
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the rect sizes didn't change when the coordinator manipulated them. This allows us
|
||||
* to assert only the value of rect.top in tests, since if top, width, and height are correct,
|
||||
* that means top/left/right/bottom are all correct.
|
||||
*/
|
||||
private fun verifyRectSizes() {
|
||||
assertEquals(100, rect100px.width())
|
||||
assertEquals(200, rect200px.width())
|
||||
assertEquals(300, rect300px.width())
|
||||
|
||||
assertEquals(100, rect100px.height())
|
||||
assertEquals(200, rect200px.height())
|
||||
assertEquals(300, rect300px.height())
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class that uses [floatingCoordinator.findAreaForContentVertically] to move a
|
||||
* Rect when needed.
|
||||
*/
|
||||
inner class FloatingRect(
|
||||
private val underlyingRect: Rect
|
||||
) : FloatingContentCoordinator.FloatingContent {
|
||||
override fun moveToBounds(bounds: Rect) {
|
||||
underlyingRect.set(bounds)
|
||||
}
|
||||
|
||||
override fun getAllowedFloatingBoundsRegion(): Rect {
|
||||
return screenBounds
|
||||
}
|
||||
|
||||
override fun getFloatingBoundsOnScreen(): Rect {
|
||||
return underlyingRect
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user