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:
<Viewandroid: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 inRecyclerViewmake sure that the parameterandroid:tintis not overridden in the theme - this may cause the line color to change unexpectedly on some devices.
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.
- Changing the thickness of the line through
- โก 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 inRecyclerVieworListViewbe sure to implement the methodonMeasure()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.
- More complex implementation compared to conventional
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:
@Composablefun 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 coordinatesOffsetare measured from the upper left corner, and the Y axis is directed down. This differs from the standardCanvasvView, 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
PathMeasureto 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.colorviaArgbEvaluator. - Wave movement: use
sin/costo 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
Choreographerto 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 (useinvalidate(Rect)). - B Compose use
Modifier.drawBehindfor local changes.
- Call
- ๐ Optimize objects
Paint:- Create and configure
Paintonce (for example, ininitblocks). - Avoid changes parameters
PaintinonDraw().
- Create and configure
- ๐ For complex scenes:
- Use
SurfaceVieworTextureViewinstead of the usual oneView. - Consider rendering via OpenGL ES (library Rajawali or Filament).
- Use
| 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 usingHardware Accelerationsome operations withCanvasmay 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+Canvasor library MPAndroidChart. - Nuance: For large data sets, use decartation (draw only the visible area).
- Solution:
- โ๏ธ Drawing applications:
- Solution:
SurfaceViewwith touch processing (onTouchEvent). - Nuance: Save line history for the "cancel" function (
undo).
- Solution:
- ๐ฎ 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:
ItemDecorationforRecyclerView. - Nuance: Take into account the indents (
padding) of list elements.
- Solution:
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
Modifierwith 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(usesize.width/size.heightfor 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)
)