Diagnosing android.graphics.Rect Issues in Android Views
Fix zero-width Rects, false contains() results, and missed intersections by checking coordinate order, layout timing, and coordinate space.
28 Feb 2026, 02:34 UTC

A custom view ignores taps in an area that clearly overlaps a drawn rectangle, or a collision check reports no overlap between two shapes that look intersecting on screen. The usual cause is not the touch handler or the drawing code—it is an android.graphics.Rect whose bounds are inverted, empty, or built before the view has been laid out.
Takeaway: verify that left <= right and top <= bottom, confirm the rectangle lives in the same coordinate space as the point you are testing, and rebuild it after onLayout before calling contains or intersects.
android.graphics.Rect stores four integers: left, top, right, and bottom. width() returns right minus left, and height() returns bottom minus top. isEmpty() returns true when left >= right or top >= bottom, which means many operations treat the rectangle as if it has no area.
Symptom and likely cause table
| Symptom | Likely cause | Quick check |
|---|---|---|
| Rect reports zero width or height | left > right or top > bottom, or all sides equal | Log rect.width() and rect.height() |
| contains(x, y) returns false for an obvious point | Rect built with view coordinates while the point uses screen coordinates, or Rect built before layout | Compare event.getRawX() against view bounds; log Rect after onLayout |
| intersects(other) returns false for overlapping rectangles | One Rect has negative width or height from bad set() arguments | Validate both rectangles before calling intersects |
| Rect is empty after a set() call | Identical coordinates for all sides, or an earlier setEmpty() | Inspect the arguments passed to set() |
Ordered diagnostic checks
- Check coordinate ordering. Log rect.left, rect.top, rect.right, and rect.bottom. If left > right or top > bottom, the rectangle is inverted and most operations behave as if it is empty.
- Confirm layout timing. A Rect created in a View constructor or in onFinishInflate sees width and height of zero. Rebuild it in onLayout(changed, l, t, r, b) or after the first layout pass.
- Match coordinate spaces. View touch events use coordinates relative to the view. If your Rect was filled with screen coordinates, for example from getGlobalVisibleRect, convert the touch point with event.getRawX() and event.getRawY(), or translate the Rect into view coordinates.
- Inspect extreme values. Coordinates near Integer.MAX_VALUE can overflow when width or height is computed. Constrain values to a safe range before constructing the Rect.
- Look for setEmpty(). If a shared Rect instance is reused, another component may have called setEmpty() between your build and your check.
Fixes tied to the finding
Rebuild the Rect after layout
Run this inside a custom View subclass. No special permissions are required.
class TapAreaView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : View(context, attrs) {
private val activeArea = Rect()
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
super.onLayout(changed, l, t, r, b)
activeArea.set(
paddingLeft,
paddingTop,
width - paddingRight,
height - paddingBottom
)
}
override fun onTouchEvent(event: MotionEvent): Boolean {
val x = event.x.toInt()
val y = event.y.toInt()
if (activeArea.contains(x, y)) {
performClick()
return true
}
return super.onTouchEvent(event)
}
}
Expected check: after the first layout, activeArea.width() should equal width minus paddingLeft minus paddingRight. If it is zero, the Rect was likely built before onLayout ran.
Align coordinate spaces
Use screen coordinates consistently when testing against a global Rect.
val globalRect = Rect()
view.getGlobalVisibleRect(globalRect)
val rawX = event.rawX.toInt()
val rawY = event.rawY.toInt()
if (globalRect.contains(rawX, rawY)) {
// handle
}
Risk: mixing event.x, which is view-relative, with globalRect, which is screen-relative, produces false negatives even when the finger is visually inside the rectangle.
Guard against inverted rectangles
fun safeRect(left: Int, top: Int, right: Int, bottom: Int): Rect {
return Rect(
min(left, right),
min(top, bottom),
max(left, right),
max(top, bottom)
)
}
Use this helper when coordinates may arrive in either order, such as from two drag points.
Detect overflow before computing dimensions
If coordinates come from untrusted input or sensor data, clamp them:
val safeLeft = left.coerceIn(0, Int.MAX_VALUE - 1)
val safeRight = right.coerceIn(0, Int.MAX_VALUE)
val rect = Rect(safeLeft, top, safeRight, bottom)
Escalation criteria
Escalate when:
- The Rect passes all checks above but contains still returns the wrong result.
- A parent view applies a Matrix, scale, or translation that changes the visual position without changing the Rect.
- Clipping from a parent ViewGroup or Canvas.clipRect hides the area you expect to be interactive.
Include the API level, the Rect values, the point being tested, and the expected versus actual result in the bug report.
Limitations and verification
This guide covers android.graphics.Rect only. It does not apply to android.graphics.RectF, which uses floating-point coordinates, or to custom rectangle utilities. The behavior described assumes API level 26 or higher.
Practical verification: write a unit test that constructs Rect objects with known coordinates and asserts width, height, contains, and intersects results. In a running app, log the Rect immediately before and after each layout pass and compare the values against the view’s measured bounds.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.