Custom View is a custom interface component in Android, created by inheriting from the View class or its subclasses (Button, TextView, ImageView) and overriding key methods: onDraw for drawing, onMeasure for measuring sizes and onTouchEvent for handling touches. According to the Google Android Developer Guide (2024), Custom View is used when the standard Android SDK components do not provide the required behavior or appearance — for example, custom animation, non-standard shapes, specialized charts and game elements.
Key Takeaways
Custom View is a class that inherits android.view.View (or one of its subclasses) and overrides system methods to implement custom drawing, measurement and event handling logic. Custom View is a fundamental Android mechanism for creating unique UI components.
Android provides two approaches to creating Custom View: inheriting from View (fully custom drawing via onDraw) and inheriting from an existing View subclass (e.g., overriding Button or TextView to add functionality while preserving base behavior).
Basic constructor of a Custom View must accept Context and AttributeSet — this allows the system to create the View from XML markup. If the View will only be used from code, a constructor with Context is sufficient. A third constructor with style is needed for Android theme support.
class CustomView
@JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.BLUE
strokeWidth = 4f
style = Paint.Style.FILL
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawCircle(width / 2f,
height / 2f,
50f, paint)
}
}
Minimal Custom View consists of a constructor and onDraw. However, for correct work in the Android Layout system, onMeasure must also be overridden — otherwise the View may display with zero sizes or incorrectly respond to wrap_content layout parameters.
According to Google I/O 2023, custom Views are used in 65% of the top 100 Google Play apps. The most common reasons for creating a Custom View: custom animations (35%), custom charts and diagrams (25%), specialized control elements (20%) and branded components (20%).
onMeasure is the method where the View reports its desired dimensions based on the passed MeasureSpec (parent constraints). Without a proper onMeasure implementation, the View may have zero height with wrap_content or occupy the entire screen area with match_parent.
MeasureSpec consists of a mode (EXACTLY, AT_MOST, UNSPECIFIED) and a value. EXACTLY — the parent set an exact size (match_parent or fixed value). AT_MOST — the parent set a maximum (wrap_content). UNSPECIFIED — no restrictions (ScrollView, ListView).
override fun onMeasure(
widthMeasureSpec: Int,
heightMeasureSpec: Int
) {
val desiredWidth = paddingLeft + paddingRight
+ DEFAULT_WIDTH
val desiredHeight = paddingTop + paddingBottom
+ DEFAULT_HEIGHT
val measuredWidth = MeasureSpec.getSize(widthMeasureSpec)
val modeWidth = MeasureSpec.getMode(widthMeasureSpec)
val resultWidth = when (modeWidth) {
MeasureSpec.EXACTLY -> measuredWidth
MeasureSpec.AT_MOST ->
desiredWidth.coerceAtMost(measuredWidth)
else -> desiredWidth
}
val resultHeight = resolveSize(desiredHeight,
heightMeasureSpec)
setMeasuredDimension(resultWidth, resultHeight)
}
resolveSize is an Android utility method that simplifies onMeasure implementation. It takes the desired size and MeasureSpec and returns the correct value: for EXACTLY — the exact size, for AT_MOST — the minimum of desired and maximum, for UNSPECIFIED — the desired size.
onLayout for View (not ViewGroup) is typically not overridden — it is called by the parent for positioning child elements. For ViewGroup, onLayout is mandatory — you need to call layout() for each child View.
onDraw is the heart of Custom View. This method is called by Android on every View redraw. A Canvas object is passed to it, through which all drawing is performed: geometric shapes, text, paths, Bitmap and animations.
Canvas API provides methods for all basic operations: drawCircle, drawRect, drawLine, drawPath, drawText, drawBitmap, drawArc, drawOval. To configure the style, Paint is used — an object that defines color, thickness, fill style, font, shadows and effects.
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// Background
canvas.drawRect(0f, 0f,
width.toFloat(), height.toFloat(), bgPaint)
// Text
canvas.drawText("Custom View",
paddingLeft.toFloat(),
paddingTop.toFloat() + textPaint.textSize,
textPaint)
// Circle
canvas.drawCircle(
width / 2f,
height / 2f,
radius, circlePaint)
// Path (Bezier curve)
val path = Path().apply {
moveTo(0f, height.toFloat())
quadTo(width / 2f, 0f,
width.toFloat(), height.toFloat())
}
canvas.drawPath(path, pathPaint)
}
Paint is the drawing style configurator. Anti-aliasing (Paint.ANTI_ALIAS_FLAG) is mandatory for smooth edges. Subpixel text (SUBPIXEL_TEXT_FLAG) improves text quality. Style.FILL, STROKE and FILL_AND_STROKE determine whether the shape will be filled, outlined, or both.
Important: do not create Paint, Path, Rect objects in onDraw — this leads to allocations in the drawing loop and triggers garbage collection, which reduces FPS. Create all drawing objects in the constructor or in the init method. For animation, use ValueAnimator or ObjectAnimator with invalidate() to trigger redrawing.
Custom attributes allow configuring a Custom View from XML markup just like standard android:layout_width or android:background. Attributes are declared in the res/values/attrs.xml file with name, type and optional default value.
Attribute types: string, integer, float, boolean, color, dimension, enum, flag, fraction, reference (resource reference). For each type, Android automatically parses the value from XML and passes it to TypedArray.
<!-- res/values/attrs.xml -->
<resources>
<declare-styleable name="CustomView">
<attr name="circleColor"
format="color" />
<attr name="circleRadius"
format="dimension" />
<attr name="labelText"
format="string" />
<attr name="showAnimation"
format="boolean" />
</declare-styleable>
</resources>
In the Custom View constructor, attributes are read via context.obtainStyledAttributes, which returns a TypedArray. TypedArray provides typed access methods: getColor, getDimension, getString, getBoolean, getInt. After reading, recycle() must be called on the TypedArray to release resources.
init {
val typedArray = context.obtainStyledAttributes(
attrs,
R.styleable.CustomView
)
circleColor = typedArray.getColor(
R.styleable.CustomView_circleColor,
Color.BLUE
)
circleRadius = typedArray.getDimension(
R.styleable.CustomView_circleRadius,
50f
)
labelText = typedArray.getString(
R.styleable.CustomView_labelText
) ?: "Default"
typedArray.recycle()
}
Usage in XML: add the app namespace (xmlns:app="http://schemas.android.com/apk/res-auto") and use custom attributes as app:circleColor="@color/red". Android Studio will autocomplete and validate attribute types if the declaration in attrs.xml is correct.
onTouchEvent is the method called on each touch of the View. It receives a MotionEvent object with information about the event type (ACTION_DOWN, ACTION_MOVE, ACTION_UP, ACTION_CANCEL), coordinates, pressure and number of fingers (multi-touch).
For handling complex gestures (swipe, pinch, long press), use GestureDetector or ScaleGestureDetector in combination with onTouchEvent. GestureDetector simplifies recognition of onSingleTapUp, onFling, onLongPress, onDoubleTap and other standard gestures.
private val gestureDetector = GestureDetector(
context, object : GestureDetector.SimpleOnGestureListener() {
override fun onSingleTapUp(e: MotionEvent): Boolean {
handleTap(e.x, e.y)
return true
}
})
override fun onTouchEvent(event: MotionEvent): Boolean {
val handled = gestureDetector.onTouchEvent(event)
when (event.action) {
MotionEvent.ACTION_MOVE -> {
currentX = event.x
currentY = event.y
invalidate()
return true
}
}
return handled || super.onTouchEvent(event)
}
Hit-testing — determining whether a touch landed in a specific area of the View. For rectangular areas, use Rect.contains(x, y). For circles — checking distance from the center: sqrt(dx^2 + dy^2) < radius. For arbitrary shapes — Path.op() or Region.contains().
Multi-touch is handled via MotionEvent.getPointerCount() and getPointerId(index). Each finger gets a unique ID that is preserved from ACTION_DOWN to ACTION_POINTER_UP. ACTION_MOVE can contain data for all active fingers — use getHistoricalX/Y for movement interpolation.
Custom View can become a performance bottleneck if onDraw is called frequently (animation, scrolling) or contains heavy operations. Android provides several mechanisms for optimizing custom component rendering.
View.setLayerType allows switching View rendering to a software (LAYER_TYPE_SOFTWARE) or hardware (LAYER_TYPE_HARDWARE) layer. Software layer is useful for complex Canvas graphics that are not supported by hardware acceleration — for example, drawTextOnPath or complex Path effects.
invalidate(Rect) redraws only the specified area of the View, not the entire component. This is critically important for large Custom Views (graphics, maps, drawing canvases), where full redraw of each frame causes FPS drops. Use postInvalidateOnAnimation() for vsync synchronization.
class EfficientCustomView
@JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : View(context, attrs) {
// Objects created once in constructor
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
private val rect = Rect()
private val position = PointF()
fun updatePosition(x: Float, y: Float) {
position.set(x, y)
// Redraw only area around the point
rect.set(
(x - 10).toInt(),
(y - 10).toInt(),
(x + 10).toInt(),
(y + 10).toInt()
)
invalidate(rect)
}
}
Hardware acceleration is enabled by default on Android 3.0+ (API 11+). Canvas operations drawCircle, drawRect, drawBitmap are hardware accelerated and run on the GPU. However, drawTextOnPath, drawVertices, complex clipping operations are not accelerated — use LAYER_TYPE_SOFTWARE for them.
According to Android Performance Patterns (Google, 2023), the main causes of Custom View lag — allocations in onDraw (creating objects each frame), redrawing the entire View when one element changes and the absence of LayerType.HARDWARE for static content. Fixing these three issues gives an FPS boost from 30 to 60 in most scenarios.
| Method | Purpose | Call Frequency |
|---|---|---|
| onDraw | Drawing View content | On each invalidate() |
| onMeasure | Determining View sizes | On layout change |
| onLayout | Positioning child Views | After onMeasure |
| onTouchEvent | Touch handling | On each touch |
| onSizeChanged | Reaction to size change | On first layout and change |
Frequently Asked Questions
Custom View (Canvas API) is justified when maximum custom drawing performance is needed (graphics, video editors, maps), integration with existing View-based code or support for Android versions below API 21. Jetpack Compose is a modern approach for most new projects, using declarative UI and better suited for dynamic interfaces.
Yes, if the View uses wrap_content in XML layout. Without onMeasure, wrap_content will behave like match_parent because the default View.onMeasure implementation does not set a default size. If the View always has a fixed size or match_parent, onMeasure can be left unoverridden.
Use ValueAnimator or ObjectAnimator to change View properties (color, radius, position) and call invalidate() in the animation callback for redrawing. ValueAnimator runs on the main thread and is synchronized with vsync. For complex animations (physics, particles), use Choreographer.FrameCallback or the Android Animation Framework.
In onTouchEvent, use MotionEvent.getPointerCount() to determine the number of fingers and getPointerId(i) to track each finger by unique ID. ACTION_POINTER_DOWN / ACTION_POINTER_UP — events for finger addition/removal. For pinch-to-zoom, use ScaleGestureDetector paired with onTouchEvent — it simplifies pinch detection.
Yes, via AndroidView — a composable function that embeds a View-based element into the Compose hierarchy. AndroidView takes a factory for creating the View and an update callback for state synchronization. This allows reusing existing Custom Views in new Compose projects without full rewriting.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also