Animated Pie Chart from Scratch- Part I: Custom View Crash Course

by

Before we start

Identify if creating a custom view is really the solution to your problem. If you’ve began reading this blog post, chances are you’ve thought about writing a custom view at some point in your Android career. Maybe you had a vision for your product that the built-in Android libraries didn’t offer a solution for, maybe you were tasked with a UI design you haven’t done/seen before, or maybe you’re like me and genuinely enjoy creating things from scratch.

The first step when it comes to creating custom views is asking yourself a very important question that could potentially save you a lot of time (and your employer a lot of money): Do I need to make this custom view? This can have somewhat of a loaded answer, so let’s dig a bit deeper by inquiring a bit further:

  • Have you truly ruled out the Android libraries? The built-in libraries were designed to help you create conventional UIs both simply and efficiently. Some of these are very flexible and allow you to customize how they look through adapters (see RecyclerView)

  • Is what you’re attempting to create so unconventional that it would cause a negative user experience? Innovation is great – but stay alert to how your users expect to use your app, and by extension, your view. Is your design intuitive? A good exercise is to think of a non tech-savvy friend and imagine if they would be able to use your view in the way you’ve designed it.

  • Is this already a solved problem? Do some research to discover if someone else hasn’t already created what you need – if the feature is even remotely common, chances are someone has. More often than not, though, you’ll find something close to what you’re looking for and extend their implementation to fit your needs (given the licensing allows). A great resource for this is Android Arsenal

If you’re convinced a custom view is the solution to your issue or you just want to learn more about them, let’s dive in.

The Three Types of Custom Views

I’m going to briefly introduce the three general ways of custom views are created, though this blog post will be focusing on the last to be introduced (creating custom views by subclassing the View class)

Subclassing Existing Views

This involves taking an already existing View and subclassing it to extend it’s functionality to fit your needs. It looks something like this:

Compound Custom Views

Compound views are created by grouping multiple views into one. This can be useful when two views are heavily reliant on one another. An example of this type of view would be a search bar that has a “submit” button next to it.

First we would create an xml layout to inflate into the LinearLayout custom ViewGroup we will be creating:

Then we subclass the LinearLayout ViewGroup and find the views from the xml:

Subclassing the View Class

When none of the View classes from the built-in Android library have similar behaviors to what you’re looking for in your custom view and it doesn’t make sense to either extend an existing View class or create a Compound Custom View, you have the option to extend the base View class and build the view yourself.

This method is typically more labor intensive since you will be required to override functions like onMeasure() and onDraw(), then program most of the view behaviors on your own. In this post, we’ll be creating a Pie Chart view and getting started looks a lot like the first method I introduced – we just need to change the class being extending to View:

Easy enough, yeah? Well there’s a few more things we’ll need to consider before we can see our Pie Chart! Let’s dive into the individual components we need to consider before we start adding to our custom view next.

The Basics

Required Constructors

There are three required constructors that need to be implemented in order to use our custom view through any part of the project. If we didn’t implement these, the creating our view using their respective methods will raise an exception and crash the app. Let’s take a closer look into each one:

The first is the constructor is the one that gets called when creating our view programmatically (from code)

This second constructor is utilized if you’ll be defining your custom view in an XML layout. It’s used by the LayoutInflator to apply the attributes defended in XML. This is what we’ll be doing in our Pie Chart example later on.

The third is used if you’d like to apply some default style to all of the views in your app, including your 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 latter half of this post.

Overriding fun onDraw()

This is where all of the logic for drawing to the screen goes. We’re given a canvas object that includes all of 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). This means you will quickly run out of memory and the view will not perform well.

  • Try and keep the logic here minimal. Since this is invoked very often and is required to run quickly, large amounts of code or heavy computation will make your view a lot 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 Part 4) and when updating data.

Overriding fun onMeasure() and fun onLayout()

https://gist.github.com/sidgrio/d998fb1f2550b56d5085de2c3404d16a

This one isn’t a necessity to override, but 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 types of data to something your custom view should understand

  • Avoid doing heavy computation or data cleansing in the custom view. Do this computation/cleaning before you send the information to the view

  • Never create objects in onDraw()

  • Avoid adding logic into lifecycle methods that doesn’t make sense. For instance, setting the size of text in your view in onLayout() or setting paint colors in onMeasure()