Mobile Development 11 min read

Using Custom Drawable to Simplify Complex Android Views and Implement a Moving Basketball Example

This article explains how to leverage Android's custom Drawable to separate drawing logic from View interaction, demonstrates extracting reusable Drawable components for complex charts, and provides a complete Kotlin example that draws a basketball and animates it to the user's touch point.

Sohu Tech Products
Sohu Tech Products
Sohu Tech Products
Using Custom Drawable to Simplify Complex Android Views and Implement a Moving Basketball Example

In Android development, simple icons are often provided by designers, but complex graphics such as stock charts or line graphs require custom drawing logic. The article starts by describing the limitations of using static images and the need for custom Views when dealing with intricate visual requirements.

It then introduces the concept of Drawable as an abstract class dedicated solely to drawing, without handling user interaction, making it ideal for background or decorative rendering. The four abstract methods that must be implemented— draw(Canvas) , setAlpha(int) , setColorFilter(ColorFilter?) , and getOpacity() —are listed and explained.

A brief code excerpt shows the definition of the Drawable class:

public abstract class Drawable {
    public abstract void draw(@NonNull Canvas var1);
    public abstract void setAlpha(int var1);
    public abstract void setColorFilter(@Nullable ColorFilter var1);
    public abstract int getOpacity();
    // ... other members omitted
}

The article then analyses a real‑world case: a fund chart with multiple layers (coordinates, time‑sharing line, order tags, and specific points). Each layer is extracted into its own custom Drawable (e.g., CoordinateDrawable , TimeSharingDrawable , OrderTagDrawable , SpecificPointDrawable ) and drawn sequentially in onDraw() .

Next, a complete Kotlin example demonstrates how to create a custom Drawable that draws a basketball, complete with the orange fill, black lines, and curved seams. The full implementation is provided, preserving all method overrides and drawing logic:

class BallDrawable : Drawable() {
    private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        style = Paint.Style.FILL
        color = Color.parseColor("#D2691E")
    }
    private val linePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        style = Paint.Style.STROKE
        strokeWidth = 1f.px
        color = Color.BLACK
    }
    override fun draw(canvas: Canvas) {
        val radius = bounds.width().toFloat() / 2
        canvas.drawCircle(bounds.width() / 2f, bounds.height() / 2f, radius, paint)
        // draw vertical and horizontal lines, then curves ...
        canvas.drawPath(path, linePaint)
    }
    override fun setAlpha(alpha: Int) { paint.alpha = alpha }
    override fun getOpacity(): Int = when (paint.alpha) {
        0xff -> PixelFormat.OPAQUE
        0x00 -> PixelFormat.TRANSPARENT
        else -> PixelFormat.TRANSLUCENT
    }
    override fun setColorFilter(colorFilter: ColorFilter?) { paint.colorFilter = colorFilter }
}

A custom View ( CustomBallMovingSiteView ) hosts the drawable, captures touch events, and uses property animations ( ObjectAnimator and AnimatorSet ) to move and rotate the basketball to the tapped location while drawing a ripple effect. The relevant code snippets are included verbatim:

class CustomBallMovingSiteView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : FrameLayout(context, attrs, defStyleAttr) {
    private lateinit var ballContainerIv: ImageView
    private val ballDrawable = BallDrawable()
    private val radius = 50
    // ... other fields omitted
    init { initView(context, attrs) }
    private fun initView(context: Context, attrs: AttributeSet?) {
        ballContainerIv = ImageView(context).apply {
            layoutParams = LayoutParams(radius * 2, radius * 2).apply { gravity = Gravity.CENTER }
            setImageDrawable(ballDrawable)
        }
        addView(ballContainerIv)
        setWillNotDraw(false)
    }
    override fun onTouchEvent(event: MotionEvent?): Boolean {
        // calculate touch coordinates, start ripple animation, move & rotate ball
        val path = Path().apply { moveTo(lastTouchEventX, lastTouchEventY); quadTo(lastTouchEventX, lastTouchEventY, touchEventX, touchEventY) }
        val oaMoving = ObjectAnimator.ofFloat(ballContainerIv, "x", "y", path)
        val oaRotating = ObjectAnimator.ofFloat(ballContainerIv, "rotation", 0f, 360f)
        AnimatorSet().apply { duration = 1000; playTogether(oaMoving, oaRotating); start() }
        return super.onTouchEvent(event)
    }
    fun setRippleValue(currentValue: Float) { /* update ripple radius and alpha */ }
    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        // draw ripple circle
        canvas?.drawCircle(rawTouchEventX, rawTouchEventY, rippleRadius, ripplePaint)
    }
}

The article concludes that custom Drawable objects allow developers to isolate pure drawing responsibilities, making complex Views more modular and improving readability. Readers are encouraged to explore additional Drawable resources on the author's GitHub repository and to provide feedback or stars.

mobile developmentgraphicsAndroidKotlinDrawableCustom View
Sohu Tech Products
Written by

Sohu Tech Products

A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.