Android Custom Views: Creating an Animated Pie Chart from Scratch (Part 1 of 4)
Introduction to Part 1
This post is the first in a four-part series on creating Android custom views, and covers a few introductory topics, including: how to decide if a custom view is the best solution to your problem, the three basic methods for creating a custom view, and the required constructors you’ll need to implement when subclassing the View class.
Do you need a custom view?
If you’re reading this blog post, you’re probably considering writing a custom view to solve a particular problem. Maybe you have a vision for your product that the built-in Android libraries don’t address, or you were tasked with building an unusual UI — or maybe you just genuinely enjoy creating things from scratch.
Before you proceed, though, you should ask yourself a question that could save you a lot of time (and your employer a lot of money): Do I need to create a custom view?
This question can feel a bit loaded, so let’s break it down:
- Have you really ruled out the Android libraries? Android’s built-in libraries are designed to support efficient development of most conventional UIs. Some of the libraries are very flexible, and allow extensive customization of look and feel through adapters (see RecyclerView). Before you start writing a custom view, you might want to challenge yourself to find a creative way of utilizing the built-in options.
- Has this problem already been solved? If the built-in libraries don’t address your problem, do some research to determine whether another developer has already created what you need. More often than not, you’ll find something that’s at least similar to the feature or interface you’re trying to build, and (assuming the licensing allows), you’ll be able to extend that solution to fit your needs. A great resource for existing solutions is Android Arsenal.
- Is your custom view a good user experience? Innovation is great — but if your interface is so unconventional that it requires a custom view, it’s also more likely to be confusing for your users. Make sure you’ve considered user expectations — is your design intuitive? Would a non tech-savvy friend be able to use your view in the way you’ve designed it?
If you’re still convinced that a custom view is the best solution (or if you’d just like to learn more about them), let’s dive in.
The Three Types of Custom Views
In this section, I’ll briefly introduce the three most common methods of creating a custom view. The remainder of this post will focus on one method (subclassing the View class).
Subclassing Existing Views
This method takes an existing View class and subclasses it to extend its functionality:
creating Compound Custom Views
This method creates a new view by grouping multiple existing View classes. Compound views can be useful when two views are heavily reliant on one another. For example, to create a search bar with an adjacent “submit” button, we’d start by creating an xml layout to inflate into the LinearLayout custom ViewGroup we will be creating:
Then we’d subclass the LinearLayout ViewGroup and find the views from the xml:
Subclassing the View Class
If none of the View classes from the built-in Android library have similar behaviors to what you’re looking for in your custom view, you can extend the base View class and build the view yourself.
This method is typically more labor intensive; you’ll be required to override functions like onMeasure()
and onDraw()
, and you’ll need to program most of the view behaviors on your own.
In the remainder of this guide, I’ll walk through the process for creating a custom Pie Chart view by subclassing the View class. We’ll start by extending the base View class:
That’s the easy part. Now that we have our Pie Chart class, let’s dive into the individual components.
Subclassing the View Class: The Basics
Required Constructors
We’ll need to implement three required constructors before we can use our custom view; without these constructors, creating our view using their respective methods will raise an exception and crash the app.
The first is the constructor is called when creating our view programmatically (from code):
This second constructor is used if we’re defining the custom view in an XML layout; specifically, it’s used by the LayoutInflator to apply the attributes defined in the XML. This is what we’ll be doing in our Pie Chart example later on.
The third and final constructor is used to apply default styles to all of the views in an app, including the custom view.
If you’re using Kotlin (as we are in this tutorial) you can also use the defaults for all three constructors by adding the @JvmOverloads
annotation at the beginning of you class.
The init { }
Block
Use this block for initializing variables when the view first gets created. I generally use this to set Paint()
properties or any other static values I may need later. You will see this in action when we build our Pie Chart in the upcoming posts in this series.
Overriding fun onDraw()
This is where all the logic for drawing to the screen goes. We’re given a canvas object that includes all the necessary methods for drawing graphics.
A few pointers about overriding onDraw()
:
- Never instantiate objects here.
onDraw()
is invoked every time the screen refreshes (which is does very quickly and very frequently). If you use this function to instantiate objects, you will quickly run out of memory, and the view will not perform well. - Try to keep the logic here minimal. Since this function is invoked often and is required to run quickly, large amounts of code or heavy computation will make your view much less performant.
- Calling
invalidate()
anywhere in the view causes it to forcefully re-draw itself. This is very useful when it comes to property animations (we’ll talk about these in a later post) and when updating data.
Overriding fun onMeasure()
and fun onLayout()
You don’t necessarily have to override these functions, but doing so is highly encouraged by the Android Developer Docs.
Miscellaneous Tips
- Custom views, though custom, should also be reusable. Try to avoid creating custom views that only accept a data type specific to your project. Instead, consider writing an adapter class that helps translate foreign data types into something your custom view should understand.
- Avoid doing heavy computation or data cleaning in the custom view. Do this computation/cleaning before you send the information to the view.
- As mentioned above, you should never create objects in
onDraw()
. - Avoid adding unrelated logic into lifecycle methods. For example, don’t set the size of text in your view in
onLayout()
, or set paint colors inonMeasure()
.
That’s all for now — in Parts 2 through 4 of this series, I’ll address building out the PieChart class, adding touch events, and adding transitions. Stay tuned!