Drawing lines on the screen of Androiddevices is one of the basic tasks that developers face when creating custom interfaces, graphic editors or game elements. Despite its apparent simplicity, the implementation of this feature can vary from a couple of lines of code to complex, optimized solutions, depending on the chosen approach. In this article we will analyze all the current methods - from classic Canvas to modern Jetpack Compose, and also consider the nuances of performance and adaptation to different versions. Android.

The peculiarity of drawing in Android lies in the variety of tools: you can use standard widgets, custom views or a declarative approach with Compose. Each method has its pros and cons. For example, Canvas gives maximum control, but requires manual optimization, while XML figures is easier to implement, but limited in flexibility. We will help you choose the best option for your task - be it a simple dividing line in the layout or a dynamic graph in real time.

It is important to consider that some methods (for example, working with SurfaceView) may require knowledge of the life cycle of Androidcomponents, while others (like Vector Drawable) may require an understanding of vector graphics. If you are a beginner, start with the section about XMLlines; if you are an experienced developer, pay attention to Compose and Pathanimations.

1. Drawing a line via XML: the simplest way

The fastest way to add a static line to the layout is to use standard Androidwidgets. Suitable for horizontal or vertical dividers <View> with a specified height/width and background. This method is ideal for static interface elements where dynamic changes in parameters are not required.

Example code for a horizontal line with height 1dp and gray color:

<View

android:layout_width="match_parent"

android:layout_height="1dp"

android:background="#FFCCCCCC" />

  • โœ… Pros: minimal code, no need for custom classes, supported by all versions Android.
  • โŒ Cons: you cannot draw an inclined line or change it programmatically without redrawing the layout.
  • ๐Ÿ”ง When to use: for separators in lists (RecyclerView), screen footers or static ones layouts.

For more complex shapes (for example, a dotted line) you can use Vector Drawable. Create a file in the folder res/drawable:

<vector xmlns:android="http://schemas.android.com/apk/res/android"

android:width="24dp"

android:height="1dp"

android:viewportWidth="24"

android:viewportHeight="1">

<path

android:fillColor="#FF000000"

android:pathData="M0,0 L24,0"

android:strokeWidth="1"/>

</vector>

โš ๏ธ Attention: When using vector lines in RecyclerView make sure that the parameter android:tint is not overridden in the theme - this may cause the line color to change unexpectedly on some devices.
๐Ÿ“Š Which drawing method do you use most often?
XML markup
Custom View
Canvas
Jetpack Compose
Other

2. Custom View with onDraw() override

If you need a dynamic line (for example, changing color on hover or animated), you will have to create a custom one View-class. This method requires understanding the life cycle of a view and working with Canvas, but gives full control over the appearance.

Example code for a simple horizontal line with the ability to change color:

class CustomLineView @JvmOverloads constructor(

context: Context,

attrs: AttributeSet? = null,

defStyleAttr: Int = 0

) : View(context, attrs, defStyleAttr) {

private var lineColor = Color.BLACK

private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {

style = Paint.Style.STROKE

strokeWidth = 2f

}

init {

attrs?.let {

val typedArray = context.obtainStyledAttributes(it, R.styleable.CustomLineView)

lineColor = typedArray.getColor(R.styleable.CustomLineView_lineColor, Color.BLACK)

typedArray.recycle()

}

}

override fun onDraw(canvas: Canvas) {

super.onDraw(canvas)

paint.color = lineColor

canvas.drawLine(0f, height / 2f, width.toFloat(), height / 2f, paint)

}

fun setLineColor(color: Int) {

lineColor = color

invalidate()

}

}

  • ๐ŸŽจ Additional features:
    • Changing the thickness of the line through paint.strokeWidth.
    • Adding a shadow using paint.setShadowLayer().
    • Drawing a dotted line via PathEffect.
  • โšก Performance: method onDraw() is called frequently - avoid heavy calculations inside it.
Parameter Description Default value
Paint.Style Drawing style (fill, stroke) Style.FILL
Paint.ANTI_ALIAS_FLAG Smoothing line edges Disabled
strokeWidth Line thickness in pixels 0f (invisible)
color Line color in format ARGB Color.BLACK
โš ๏ธ Attention: When using custom views in RecyclerView or ListView be sure to implement the method onMeasure() for correct size calculation. Otherwise, artifacts may occur when scrolling.

Define attributes in values/attrs.xml

Override onDraw()

Add methods for dynamically changing parameters

Optimize invalidate() for animations

-->

3. Drawing on Canvas in SurfaceView

For complex graphics tasks (for example, real-time drawing or animations), the standard View may not be performant enough. In such cases, it is used SurfaceView โ€”it provides a separate thread for drawing, which prevents the interface from slowing down.

An example of implementing a simple drawing canvas:

class DrawingSurfaceView(context: Context) : SurfaceView(context), SurfaceHolder.Callback {

private lateinit var drawThread: DrawThread

private val paint = Paint().apply {

color = Color.RED

strokeWidth = 5f

style = Paint.Style.STROKE

}

private val path = Path()

init {

holder.addCallback(this)

}

override fun surfaceCreated(holder: SurfaceHolder) {

drawThread = DrawThread(holder)

drawThread.start()

}

override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {}

override fun surfaceDestroyed(holder: SurfaceHolder) {

drawThread.interrupt()

}

fun addLine(startX: Float, startY: Float, endX: Float, endY: Float) {

path.moveTo(startX, startY)

path.lineTo(endX, endY)

}

inner class DrawThread(private val holder: SurfaceHolder) : Thread() {

override fun run() {

while (!isInterrupted) {

val canvas = holder.lockCanvas()

try {

canvas.drawColor(Color.WHITE)

canvas.drawPath(path, paint)

} finally {

holder.unlockCanvasAndPost(canvas)

}

sleep(16) // ~60 FPS

}

}

}

}

  • ๐Ÿ”„ When to use SurfaceView:
    • For drawing at high frame rates (games, graphics).
    • When you need to avoid artifacts during fast updates.
    • For working with video or camera.
  • ๐Ÿšซ Limitations:
    • More complex implementation compared to conventional View.
    • Manual control of the drawing flow is required.

Critical nuance: when using SurfaceView in RecyclerView or ViewPager be sure to stop the drawing flow in onDetachedFromWindow(), otherwise memory leaks are possible.

๐Ÿ’ก

To debug drawing performance, use the tool Android Studio Profiler (tab GPU Rendering). It will show which operations take more than 16 ms and may cause lags.

4. Jetpack Compose: a declarative approach

With the advent Jetpack Compose drawing lines has become easier and more intuitive. The library provides a component Canvasthat allows you to draw directly in composition functions without creating custom views. This method is ideal for modern applications written in Kotlin.

Example code for drawing a diagonal line:

@Composable

fun DiagonalLine() {

Canvas(modifier = Modifier.fillMaxSize()) {

drawLine(

color = Color.Blue,

start = Offset(0f, 0f),

end = Offset(size.width, size.height),

strokeWidth = 3f

)

}

}

  • โœจ Advantages Compose:
    • Minimal boilerplate code.
    • Automatic optimization of redraws.
    • Easy integration with animations via animate*AsState.
  • ๐Ÿ“‰ Disadvantages:
    • Requires a minimum version API 21.
    • Less control over low-level settings Paint.

To create a dotted line in Compose use PathEffect:

drawLine(

color = Color.Green,

start = Offset(0f, center.y),

end = Offset(size.width, center.y),

strokeWidth = 4f,

pathEffect = PathEffect.dashPathEffect(floatArrayOf(10f, 10f), 0f)

)

โš ๏ธ Attention: The Compose coordinates Offset are measured from the upper left corner, and the Y axis is directed down. This differs from the standard Canvas v View, where the Y axis is directed upward.
How to animate a line in Compose?

Use animateFloatAsState to smoothly change the coordinates or parameters of the line. An example of length animation:

var lineLength by remember { mutableStateOf(0f) }

val animatedLength by animateFloatAsState(

targetValue = lineLength,

animationSpec = tween(durationMillis = 1000)

)

Canvas(Modifier.fillMaxSize()) {

drawLine(

color = Color.Red,

start = Offset(0f, center.y),

end = Offset(animatedLength * size.width, center.y),

strokeWidth = 5f

)

}

5. Drawing lines with Path

Class Path allows you to create complex shapes, including polylines, Bezier curves, and closed paths. This is a powerful tool for working with vector graphics, which is often used in custom interface elements or animations.

An example of creating a zigzag line:

val path = Path().apply {

moveTo(0f, 0f)

lineTo(100f, 100f)

lineTo(200f, 0f)

lineTo(300f, 100f)

// We continue to draw a zigzag

}

canvas.drawPath(path, paint)

  • ๐Ÿ›  Basic methods Path:
    • moveTo(x, y) โ€” move the โ€œpenโ€ to a point.
    • lineTo(x, y) โ€”draw a line to a point.
    • quadTo(x1, y1, x2, y2) โ€”a quadratic Bezier curve.
    • cubicTo(x1, y1, x2, y2, x3, y3) โ€”a cubic curve.
    • close() โ€”close a contour.
  • ๐Ÿ”„ Optimization: for complex paths with a large number of points use PathMeasure to divide into segments.

To create a dotted line through Path set Paint as follows:

paint.pathEffect = DashPathEffect(floatArrayOf(10f, 5f), 0f)

canvas.drawPath(path, paint)

๐Ÿ’ก

Use Path for complex shapes, but remember: the more points in the path, the longer it will take to render. For dynamic lines (for example, in graphs), optimize the number of segments.

6. Line animation: from simple to complex

Animated lines are often used to visualize processes (for example, loading), create interactive elements or game effects. There are several ways to implement animation - from simple Android There are several ways to implement animation - from simple ValueAnimator to complex physics simulations.

An example of line growth animation using ValueAnimator:

val animator = ValueAnimator.ofFloat(0f, 1f).apply {

duration = 1000

addUpdateListener { animation ->

val progress = animation.animatedValue as Float

lineEndX = progress * screenWidth

invalidate() // Redraw View

}

}

animator.start()

  • ๐ŸŽฌ Types animations for lines:
    • Linear change in length: as in the example above.
    • Color change: animate the parameter paint.color via ArgbEvaluator.
    • Wave movement: use sin/cos to calculate coordinates.
    • Drawing by points: sequential connection of points with a delay ("hand drawing" effect).
  • โšก Performance: for smooth animation, try to keep FPS at 60. Use Choreographer to synchronize with frames systems.

To create the effect of "drawing a line by points" (as in games like "connect the dots") you can use the following approach:

val points = listOf(/ coordinates of points /)

val path = Path()

var currentIndex = 0

val animator = ValueAnimator.ofInt(0, points.size - 1).apply {

duration = 2000

addUpdateListener {

currentIndex = it.animatedValue as Int

path.reset()

for (i in 0..currentIndex) {

if (i == 0) path.moveTo(points[i].x, points[i].y)

else path.lineTo(points[i].x, points[i].y)

}

invalidate()

}

}

7. Optimizations and best practices

Regardless of the drawing method you choose, it's important to monitor performance, especially if the lines are part of a complex interface or animation. Here are the key recommendations:

  • ๐Ÿ” Avoid unnecessary redraws:
    • Call invalidate() only for changed areas (use invalidate(Rect)).
    • B Compose use Modifier.drawBehind for local changes.
  • ๐Ÿ–Œ Optimize objects Paint:
    • Create and configure Paint once (for example, in init blocks).
    • Avoid changes parameters Paint in onDraw().
  • ๐Ÿ“Š For complex scenes:
    • Use SurfaceView or TextureView instead of the usual one View.
    • Consider rendering via OpenGL ES (library Rajawali or Filament).
Problem Cause Solution
Lags when scrolling Frequent calls onDraw() Optimize invalidate(), use SurfaceView
Ragged edges of the line No anti-aliasing Add flag Paint.ANTI_ALIAS_FLAG
Line not visible Incorrect Paint.Style or color Check style = Paint.Style.STROKE and color alpha channel
High memory consumption Too many objects Path Reuse objects, clear path.reset()
โš ๏ธ Attention: On devices with Android 10+ when using Hardware Acceleration some operations with Canvas may work differently. Test drawing on real devices, and not just on the emulator.

8. Practical application: examples from real projects

Let's consider several scenarios where drawing lines is needed in practice, and suitable solutions for them:

  • ๐Ÿ“Š Graphs and diagrams:
    • Solution: Path + Canvas or library MPAndroidChart.
    • Nuance: For large data sets, use decartation (draw only the visible area).
  • โœ๏ธ Drawing applications:
    • Solution: SurfaceView with touch processing (onTouchEvent).
    • Nuance: Save line history for the "cancel" function (undo).
  • ๐ŸŽฎ Game interfaces:
    • Solution: Jetpack Compose for UI or LibGDX for games.
    • Nuance: For physical simulations (for example, ropes) use the library Box2D.
  • ๐Ÿ“ฑ Custom separators in lists:
    • Solution: ItemDecoration for RecyclerView.
    • Nuance: Take into account the indents (padding) of list elements.

Example of custom implementation ItemDecoration to draw dividers in RecyclerView:

class LineDividerDecoration(private val color: Int, private val height: Int) : RecyclerView.ItemDecoration() {

private val paint = Paint().apply {

this.color = color

style = Paint.Style.STROKE

strokeWidth = height.toFloat()

}

override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {

val left = parent.paddingLeft.toFloat()

val right = parent.width - parent.paddingRight.toFloat()

for (i in 0 until parent.childCount - 1) {

val child = parent.getChildAt(i)

val params = child.layoutParams as RecyclerView.LayoutParams

val top = child.bottom + params.bottomMargin.toFloat()

c.drawLine(left, top, right, top, paint)

}

}

}

To add decoration to RecyclerView:

recyclerView.addItemDecoration(

LineDividerDecoration(Color.GRAY, 1)

)

FAQ: Frequently asked questions about drawing lines in Android

How to draw a dotted line in XML without code?

B You cannot create a pure XML dashed line - you will need either Vector Drawable with a manual dash, or a custom View. Alternative: use the library Material Designwhere there is a component MaterialDivider with support for dotted styles.

Why is my line not displayed in Compose?

Common reasons:

  • Not specified Modifier with non-zero dimensions (for example, fillMaxWidth()).
  • The color of the line matches the background (check the alpha channel).
  • The coordinates of the line go beyond Canvas (use size.width/size.height for borders).

How to animate the color change of a line?

In classic View use ValueAnimator s ArgbEvaluator:

ValueAnimator.ofObject(ArgbEvaluator(), Color.RED, Color.BLUE).apply {

addUpdateListener { animator ->

paint.color = animator.animatedValue as Int

invalidate()

}

duration = 1000

start()

}

In Compose use animateColorAsState.

Is it possible to draw lines in the Background stream?

No, all operations with Canvas must be performed in main thread (UI thread). For heavy calculations (for example, trajectory generation), use a background thread, but the call itself must be in drawLine() must be in onDraw() or Composefunctions.

How to draw a line along GPS coordinates on the map?

To draw routes on the map (for example, in Google Maps or Mapbox) use the built-in APIs:

  • In Google Maps: PolylineOptions.
  • In Mapbox: LineString + GeoJsonSource.

Example for Google Maps:

val polyline = googleMap.addPolyline(

PolylineOptions()

.add(LatLng(51.5, -0.1), LatLng(40.7, -74.0))

.width(5f)

.color(Color.RED)

)