Custom View in Android Apps

There are two approach for making new custom view in Android:
  1. Extending View class
  2. Extending already existing views like TextView, EditText, Button and so on.
While the second approach shows shorter way, the former one gives much control and flexibility. In this page we will discuss an example from the first approach in details.
One of the most important issues is to be well familiar with View life cycle. When we create an instance of a View or one extension of it, the following functions are called. However, there are some other functions (not listed here) called in some situations. Refer to View in android for more information. Also as a good article see here.
  1. Constructor(s): There is a form of the constructor that are called when the view is created from code and a form that is called when the view is inflated from a layout file. The second form should parse and apply any attributes defined in the layout file.
  2. onFinishInflate: Finalize inflating a view from XML. This is called as the last phase of inflation, after all child views have been added. Even if the subclass overrides onFinishInflate, they should always be sure to call the super method, so that we get called.
  3. onAttachedToWindow: This is called when the view is attached to a window. At this point it has a Surface and will start drawing. Note that this function is guaranteed to be called before onDraw(android.graphics.Canvas), however it may be called any time before the first onDraw -- including before or after onMeasure(int, int). If you override this method you must call through to the superclass implementation.
  4. onMeasure: Measure the view and its content to determine the measured width and the measured height. This method is invoked by measure(int, int) and should be overridden by subclasses to provide accurate and efficient measurement of their contents.
    When overriding this method, you must call setMeasuredDimension(int, int) to store the measured width and height of this view. Failure to do so will trigger an IllegalStateException, thrown by measure(int, int). Calling the superclass' onMeasure(int, int) is a valid use.
    The base class implementation of measure defaults to the background size, unless a larger size is allowed by the MeasureSpec. Subclasses should override onMeasure(int, int) to provide better measurements of their content.
    If this method is overridden, it is the subclass's responsibility to make sure the measured height and width are at least the view's minimum height and width (getSuggestedMinimumHeight() and getSuggestedMinimumWidth()).
  5. onSizeChanged: This is called during layout when the size of this view has changed. This function has two arguments related to old values. If you were just added to the view hierarchy, you're called with the old values of 0.
  6. onLayout: Called from layout when this view should assign a size and position to all of its children. Derived classes with children should override this method and call layout on each of their children. This method is called after onMeasure.
  7. onDraw: This is called to implement our drawing.
  8. onDetachedFromWindow: This is called when the view is detached from a window. At this point it no longer has a surface for drawing. If you override this method you must call through to the superclass implementation.
The best time to set proper dimensions for the view occurs inside onMeasure function. Also, it is good idea to get and use the dimensions in onSizeChanged. The below example shows this. The onLayout function usually is suitable for extending a ViewGroup class or one of its subclases. Inside the onDraw function we access to two functions getWidth and getMeasuredWidth (Likely, getHeight and getMeasuredHeight). Whats the difference? Really they probably return the same. But it may not be true always. The getWidth returns the actual width of a view while getMeasuredwidth returns the width set inside onMeasure function. The width/height may be changed or set in onLayout. It is better to do (and save) width/height or other possible calculations outside onDraw and make it as light as possible. This will help for example in making animations in which onDraw is called multiple times using invalidate or requestLayout functions which have important differences.
  • invalidate: The invalidate function is called if we want to redraw the view. Then the onDraw will be called eventually (soon, but not immediately). For example when we want to change a text or background color property. The view will be redrawn but the size will not change. The invalidate must be called from a UI thread. To call from a non-UI thread, call postInvalidate().
  • requestLayout: If something about your view changes that will affect the size, then you should call requestLayout(). This will trigger onMeasure and onLayout not only for this view but all the way up the line for the parent views.
    This should not be called while the view hierarchy is currently in a layout pass (to check we must use isInLayout()). If layout is happening, the request may be honored at the end of the current layout pass (and then layout will run again) or after the current frame is drawn and the next layout occurs.
    Subclasses which override this method should call the superclass method to handle possible request-during-layout errors correctly.
    Calling requestLayout() is not guaranteed to result in an onDraw so it is usually combined with invalidate().
For a good article about overriding onMeasure see mastering onMeasure.

Example: Assume that we want to create a new and custom view namely PieView which we will extend it from View class. Please follow the following steps.

Step 1- Create a new Android Studio project and select an Empty Activity template.

Step 2- Create a new Java class named PieView and let it extend from View class.

Step 3- We can define new attributes for the custom view. Please read this page to see how we can define new attributes in detail

Go to the path res->values->attrs.xml file if exists (you can create it here if it is not available) and write the following syntax. Here three new attribute have been declared for our new view.


Step 4- At this point we will see red lines indicating an error report, so we must write suitable constructors for the new class. We should override 4 different constructors. One of them is for Lollipop and above. Why 4 constructors and for what reasons!


Step 5- We may need to catch the attributes from each created view and apply them in the next steps to draw the view. So, we use init() function to do it.

Step 6- We can override two functions onMeasure()and onSizeChanged(). Let discuss these two functions.

1-onMeasure(int widthMeasureSpec, int heightMeasureSpec)

Measure the view and its content to determine the measured width and the measured height. This method is invoked by measure(int, int) and should be overridden by subclasses to provide accurate and efficient measurement of their contents.

The base class implementation of measure defaults to the background size, unless a larger size is allowed by the MeasureSpec. Subclasses should override onMeasure(int, int) to provide better measurements of their content.

If this method is overridden, it is the subclass's responsibility to make sure the measured height and width are at least the view's minimum height and width.

When overriding this method, you must call setMeasuredDimension(int, int) to store the measured width and height of this view. Failure to do so will trigger an IllegalStateException, thrown by measure(int, int). Calling the superclass' onMeasure(int, int) is a valid use. If we call setMeasuredDimension(int, int) and send our calculated width and height, we then be able to catch them in onSizeChanged() function.

2-onSizeChanged(int w, int h, int oldW, int oldH)

This is called during layout when the size of this view has changed. If you were just added to the view hierarchy, you're called with the old values of 0. This function is called after onMeasure() so our saved values will be sent as the first and second parameters.


Step 7- Now it is the time to override onDraw(Canvas canvas) function to provide our custom appearance to the view. All attributes and sizes that have been provided in previous steps are applied to draw the custom shape appropriately.


After this 7 steps, we have a class called PieView like this


Let continue another step.
Step 8- Here we add two instances of our new defined view in xml editor of Android Studio. In the ctivity_main.xml file add the following widgets.


Now just run the app and see result!. Tanks for your attention.

Comments