How can we draw something between the background and the contents of an arbitrary View instance that we don’t own?

Summary

Drawing between a View’s background and its contents in Android is challenging when you don’t own the View or Drawable. The key issue is the lack of direct hooks or listeners to intercept the drawing process at the desired z-order. Common solutions like subclassing, wrapping, or using Drawable callbacks are not feasible due to constraints.

Root Cause

  • No direct access to the drawing pipeline: Android’s View and Drawable APIs do not provide a way to insert custom draw operations between the background and content.
  • Runtime constraints: Extending classes or modifying existing Drawables is prohibited.
  • Limited z-order control: View.setForeground() and overlays draw above the content, not between background and content.

Why This Happens in Real Systems

  • Encapsulation: Android’s framework prioritizes encapsulation, limiting direct manipulation of internal drawing processes.
  • Performance considerations: Allowing arbitrary draw interceptions could introduce inefficiencies or inconsistencies.
  • API design: The View and Drawable APIs were not designed with this specific use case in mind.

Real-World Impact

  • Inability to achieve desired visual effects: Libraries or apps requiring precise z-order control (e.g., highlighting text in a TextView) are blocked.
  • Workarounds are intrusive: Solutions like wrapping Views or modifying Drawables can break user code or interfere with interactions.
  • Limited library functionality: Runtime appearance modification libraries face significant limitations in Android.

Example or Code (if necessary and relevant)

// Example of a failed attempt using View.setForeground()
val view = findViewById(R.id.textView)
val foregroundDrawable = ColorDrawable(Color.YELLOW)
view.foreground = foregroundDrawable // Draws above content, not between background and content

How Senior Engineers Fix It

  • Leverage View hierarchy: Use a transparent ViewGroup with custom drawing in its dispatchDraw() method to intercept and modify the drawing process.
  • Reflective access: Use reflection to access and modify internal Drawable states or drawing methods (caution: not recommended due to instability).
  • Custom View wrapper: Create a non-intrusive wrapper that captures and modifies the drawing canvas, though this may violate constraints.

Why Juniors Miss It

  • Overlooking dispatchDraw(): Juniors often focus on onDraw() and miss the opportunity to intercept drawing in a parent ViewGroup.
  • Misunderstanding z-order: Confusion between View.setForeground(), overlays, and the actual drawing order.
  • Ignoring framework limitations: Underestimating the constraints imposed by Android’s encapsulation and API design.

Leave a Comment