-
Notifications
You must be signed in to change notification settings - Fork 2
Drawing
Information about custom drawing.
There are various ways you can do custom drawing within your application. Before you do so, however, you should always make sure that it is absolutely necessary. While custom components can give you the exact look you are looking for, they will rarely auto-adjust to newly released versions of Android or updated theme guidelines.
While the Android documentation still shows the Activity Lifecycle and the Fragment Lifecycle, it no longer seems to host an image for the View Lifecycle. There are some available in Google Images - but perhaps it is better if we just try it and see what happens?
Let's consider a real world example. How about we customize your reading list?
Android allows us to specify drawable resources in XML.
First, let's define a couple colors. Open your colors.xml. Add these
<color name="cornflower">#6495ed</color>
<color name="lightGray">#d3d3d3</color>
<color name="hanBlue">#446ccf</color>Now, right-click on your res/drawable folder and choose New | Drawable resource file.
Name it bg_title.xml and click OK.
It may start you in Design mode. Switch to Text mode. We're going to replace the XML.
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:startColor="@color/cornflower"
android:centerColor="@color/lightGray"
android:endColor="@color/hanBlue"
android:angle="90"/>
<corners
android:radius="0dp"/>
</shape>Documentation for this format can be found on the Android Developer site.
Open your item_title.xml and add a new background element to the TextView:
android:background="@drawable/bg_title"Remember that auto-complete it your friend.
Deploy and see the change.
Let's try some custom drawing.
Create a new class in your ui package called CustomDivider.
We'll extend the RecyclerView.ItemDecoration class. We'll also pass a Context in as a parameter.
package com.aboutobjects.curriculum.readinglist.ui
import android.content.Context
import androidx.recyclerview.widget.RecyclerView
class CustomDivider(context: Context): RecyclerView.ItemDecoration() {
}If you attempt to override any methods at that point, you will notice that we have a few onDraw methods available to us. Unfortunately, it does NOT tell you which ones are deprecated.
Select all of the ItemDecoration methods, make sure Copy Javadoc is selected and click OK.
Remove any method that has a strikethrough on it. You should be left with only 3 methods.
override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State)
override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State)
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State)Before we go too far off in the weeds, let's understand when these are called.
Add a custom log message to each function.
Then. in your BookListActivity replace this line:
addItemDecoration(DividerItemDecoration(this@BookListActivity, DividerItemDecoration.VERTICAL))with
addItemDecoration(CustomDivider(context = this@BookListActivity))Enable Logcat and watch for your ReadingListApp tag.
Deploy your application and scroll through the reading list.
What did you notice?
These methods get called so frequently, we want to make sure that they execute very fast. There is more to it than that. Any memory leak in this code can also skyrocket quickly. Some of the blogs will tell you that onDraw is only called once - but Logcat has just shown you otherwise.
Open your CustomDivider.
One of the ways we can reduce memory is by avoiding re-allocation of variables that don't change. For example, instead of specifying a Paint object every single time the onDraw method is called, we can set it up once and re-use it on each call.
Let's setup a couple variables to use.
Since we have one method that will draw "under" and one that will draw "over" (you did read those Javadocs, right?), let's setup two sets of variables so that we can clearly see what is happening.
private val overPaint = Paint()
private val underPaint = Paint()
private val thickness = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
5.5f,
context.resources.displayMetrics
)
private val center = thickness / 2
init {
overPaint.color = Color.WHITE
overPaint.strokeWidth = thickness / 3
underPaint.color = Color.BLUE
underPaint.strokeWidth = thickness
}Here we setup two Paint objects and set the "under" one to BLUE and the "over" one to WHITE. We are setting the thickness of the BLUE one to 5.5 dp and the WHITE one to 1/3rd of that. Since both will be centered, the end result will be a WHITE line centered within a thicker BLUE line.
Let's start by updating our getItemOffsets. We'll simply make enough room for our line.
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
outRect.set(0, 0, 0, thickness.toInt())
}The IDE will show you that this is outRect.set(left, top, right, bottom).`
Unless you are debugging, you probably don't want to keep active logging in these calls since they will slow down the rendering.
Since both onDraw and onDrawOver are going to draw a centered line, and the only difference is the paint color... let's simplify rather than duplicate.
First, we'll create a generic method that also takes a Paint variable.
private fun drawLines(c: Canvas, parent: RecyclerView, paint: Paint) {
for (i in 0 until parent.childCount) {
val view = parent.getChildAt(i)
c.drawLine(view.left.toFloat(),
view.bottom.toFloat() + center,
view.right.toFloat(),
view.bottom.toFloat() + center,
paint)
}
}The c.drawLine(startX, startY, stopX, stopY, paint) wants us to specify the center. If startY and stopY are different, you will get a diagonal line. We are specifying that the startY/stopY should be below the previous view, at the center of our thickness.
Now we can implement our onDraw and onDrawOver
override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
drawLines(c, parent, underPaint)
}
override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
drawLines(c, parent, overPaint)
}Deploy your application.
This just scratches the surface of custom drawing. If you are interested in more advanced graphic development, here are some additional resources to look into.
Sceneform makes it straightforward to render realistic 3D scenes in AR and non-AR apps, without having to learn OpenGL.
OpenGL is a cross-platform graphics API that specifies a standard software interface for 3D graphics processing hardware.
Vulkan is a low-overhead, cross-platform API for high-performance, 3D graphics.
LWJGL is a Java library that enables cross-platform access to popular native APIs useful in the development of graphics (OpenGL, Vulkan), audio (OpenAL) and parallel computing (OpenCL) applications.



