Creating custom Android views – Part 2: How padding works and how to draw your own content

New views

Sometimes, extending a standard Android View is not enough. For many purposes, you will need to actually draw the view yourself to accomplish a task. In this tutorial, we’ll make a view that draws a line chart using the Canvas class and also learn a bit about how the view dimensions and padding works.

By the way, if you haven’t already, you might want to read the previous part as well.

Drawing the first pixels

If you plan to draw your own content in a custom view the best thing is to just extend the base class: View. This is the most basic building block of the UI and is also a fully functional class, though somewhat lacking in features (but that’s for us to implement).

So, we start with creating a new class that extends View. To draw our first pixels, we just need to override the onDraw() method. In the onDraw() method we will get a canvas that we can fill with our pixels. There is no need to call the superclass implementation of onDraw() in this case since for View, onDraw() is actually just an empty method.

This code will draw a line with a blue color that starts in (0, 0) and ends at (getWidth(), getHeight()) with a width of 4 pixels. The Paint class is used to control how things are drawn. Although the paint object can do quite a lot of fancy effects, it’s only used here to control what color and style we draw with.

Note that when drawing the content of the View the top left part of the view is at (0, 0) regardless of where the view is on the screen. The View class has methods called getLeft() and getTop() but these return the view’s position relative to its parent view so these should not be used when drawing.

So, now we created our own view that draws a line. Not that useful maybe but it’s a start at least.

Adding padding

When you’ve done XML layouts you have probably used padding on the views. However, if you try using padding for the view we just created you’ll notice that it doesn’t have an effect. That’s because we haven’t taken padding into account yet in our view.

Remember that the way padding works is that the width of the view includes the padding. If the view is 100 pixels wide and the padding on both sides is 10 pixels, then the width available for the actual content is 80 pixels (the same is of course true for the height). So, when we use getWidth() it returns the width including the padding. We can get the padding by using the methods getPaddingTop(), getPaddingBottom, getPaddingLeft() and getPaddingRight().

In order to support padding when drawing our line, the line should start at (getPaddingLeft(), getPaddingTop()) instead and end, not at the lower right corner but the lower right corner minus the padding.

Our refactored onDraw method now looks like this:

Now, changing the padding in the XML file will have the intended effect. The app now looks like this:


Here, I’ve set the background of the view to a dark gray color. As you can see, the background will be drawn outside of the padding but this is normal behavior.

Depending on how you plan to use the custom view, you might not need to support padding. However, I would definitely recommend to make sure your view supports it from the start, even though you think you won’t need it. Adding padding support to a more complex view that for example has many sub-items and includes touch handling can be a hassle. Making sure to support padding from the start is an easy way to future-proof your view.

Drawing the line chart

Now, I said in the beginning that this view was going to draw a line chart. So let’s start working with that. The first step is to have some data to draw.

This lets a user of this view set the data that we should display. For each data point in the graph we really need two values, the x and the y values. But, if we assume that the x values of the data points are equally spaced (which is likely) then we can just use the index as x value. What we get in this method are the y values of the graph.

Now we also need to draw the data points. But we also need to scale them so they’ll fit in the view. To scale the values, it’s convenient to make a helper method like this.

This method takes a float, a y data point, and the current max value of all points and returns another float, a y position in the view. The first step is to scale the value so that the graph will fill the entire view. Note that to compute the height of the view, we subtract the padding both at the top and at the bottom. Then we need to invert the value. The reason for this is that in the view coordinates, higher y values are further down, but in the graph, higher y values should be higher in the view. Then, finally, we need to offset the y value in order to take padding into account.

Now we’re ready to draw the actual line chart. We could draw the chart by drawing lines between the data points, but we’re going to do it a slightly different way by using a path. Right now the difference will not be noticeable but it will enable us to make it a bit nicer later on. Now the onDraw() method looks like this.

The first part constructs the path using the getYPos() method we created earlier. It also uses a similar getXPos() function that works the same way except it doesn’t need to invert values. The path construction starts with initializing the path at the start position. Then, for each data point, we extend the path with a line from where we are, to the next data point.

The second part is almost the same as before, the only difference being that instead of drawLine() we call drawPath().

The result, with some fake data, looks like this.

It’s starting to look like a line chart. Now it’s time to look at some details as well.

Adding details

The first thing that we will do is to draw the chart with antialiasing turned on. That will reduce the jagged edge look from the chart and make it a lot smoother. Anti aliasing is controlled by the paint object and can be turned on like this

Another thing that will make the line chart to look a bit better is a drop-shadow for the line. Drop shadows are also easy to add, just being another single function call.

Now, everything we draw with the paint will generate a drop shadow as well. The first argument is the blur radius that determines how blurry the shadow will be. The higher number, the more blurrier the shadow. If you set the blur radius to 0, the shadow layer will be removed. The next two arguments is the drop shadow offset. In this case the shadow will be 2 pixels to the right and 2 pixels lower than what we draw. The last argument is the color of the shadow and here we’ve set it to half transparent black.

Here I’ve also added horizontal lines in the background to make it a bit more appealing and to make it look a bit more like chart. There are some logic that determines where to draw the lines and how many but it’s not really important for this tutorial.

From here on we can add quite a lot of things to our line chart view depending on what we want it to look like. With the canvas we can draw lines, paths, rectangles, ovals, bitmaps and so on. With the paint object we can change fill-style, color, stroke-width, effects and lots of other things. I would recommend to take a look at both Canvas and Paint in the Android reference and then playing around a bit with the different settings and shapes.

In the next part we will go through how to animate our new LineChartView.

You can find the sources for this part here.

This Post Has 13 Comments

  1. Hello,
    Very impressive work ! But, can you share your sources?
    Thank you

  2. Are these canvas prints or actual oil paintings on canvas?

  3. I am just learning drawing custom view and it is an awesome tutorial.I am having a bit of difficulty in understanding the logic implemented in getXPos and getYPos. I couldn’t grasp what is going on those logic. I do understand how Path works in Android and I know the logic of plotting path with X,Y values. But, I still can’t understand the X,Y value generation.Is there any source I can dive deep to get better grasp of path X,Y value generation.

    Lastly great tutorial

  4. Hi…

    First, this article is GREAT!!!
    I saw the example above about padding line.
    I have a question: if start draw on canvas a pie chart.
    I have notice that drawing is clipped a little bit on the top and left sides.

    my question is how can I append padding to my canvas so it won’t clip?

  5. Article is very informative, Thanks for sharing this article, But I have one feedback where the article could be perfect, If the objects creation like new Paint() and new Path() moved out of onDraw() method these custom views will be great

  6. Hi Andy!

    Great tutorial and thanks for sharing. However, how to extend the ‘datapoints’ in the setChartData method to hold negative values as well? Currently, it’s limited to positive values only.

    Cheers!

  7. Great tut,
    one concern, i think that calling getMax method in getYPos is redundand and could cause performance issues with a lot of data points and in case view would be animated and onDraw would be called frequently…

    I guess that getMax could be called once in setChartData and cached

    Cheers

  8. In case when your smallest number in the float[] containing your values is not 0 you need to make the following change to scale them correctly:

    value = (value / maxValue) * height; // scale to view height

    to

    value = (value – minValue / maxValue – minValue) * height; // scale to view height

  9. Great article. Touched lots of aspects of drawing Views. I found it really useful. Thank you.

Leave a Reply

Close Menu