Creating a Custom View in Android App

tudip-logo

Tudip

27 May 2019

Android SDK offers a powerful componentized model for building your UI, based on the fundamental layout classes: View and ViewGroup. The view is one of the most general classes in Android which holds references to a single piece of UI. Some examples of default views present in the Android Framework are EditText, TextView, Button, CheckBox, RadioButton, etc. ViewGroup is a special view that can contain other views (called children). We can create custom views and use them in our Application. However, there are cases in which we have to do more than just use one of them we have to create a custom one. You can create a custom view in Android app very easily by extending one of the existing views. It is possible to create custom views in 2 ways:

  • By extending an Existing View.
  • By extending a View class.

View Lifecycle

view-lifecycle

  1. onMeasure

To modify the width and height we override the onMeasure() method. onMeasure method is used to tell Android how big we want our custom view to be dependent on the layout constraints provided by the parent. These constraints are sent into the MeasureSpec values that are passed into the onMeasure() method. If you don’t need to change something in onMeasure there’s absolutely no need for you to override it.There are basic 3 modes:

  • MeasureSpec.EXACTLY: It means our view should be exactly with some specified size. This is possible only when we use a fixed size (like android:layout_width = “100dp”) or even match_parent .
  • MeasureSpec.AT_MOST: It means that our view can be as big as it wants up to specified size. This is possible only when we use wrap_content or also match_parent.
  • MeasureSpec.UNSPECIFIED: It means that our view can take as much space as it wants. Sometimes this is used when the parent is trying to determine how big every child wants to be before calling onMeasure again.
protected void onMeasure (int widthSpecificMeasure, int heightSpecificMeasure)

{

      super.onMeasure(widthSpecificMeasure, heightSpecificMeasure); 

}

The 2 parameters passed in onMeasure() represents both mode and size of width and height. Let’s suppose if you passed layout_width parameter as match_parent in View’s XML, and the width of the view’s parent is 100, then the mode and size would be:

int mode = MeasureSpec.getMode(widthSpecificMeasure) // mode = View.MeasureSpec.EXACTLY

int size = MeasureSpec.getSize(widthSpecificMeasure) // size = 100

Calculating your view size

Every view has its own width and height. We can get the width of the Layout by using getMeasuredWidth() and height by using getMeasuredHeight(). By using these methods we can know the values of width and height given in our Xml.

  1. onLayout

In onLayout() method you can decide the position of child. In onMeasure() method we can get the width and height of all children and with the help of it we can position them in onLayout() method. You need to call onLayout() method from each child.

Protected void onLayout (boolean changedStatus, int left, int top, int right, int bottom) {

 }

You need to be careful about right and bottom parameters. If they are specified wrong then Layout may cut or enlarge. Before specifying the parameters get the width and height of Layout. The left and top parameters specify the left and right from the Parent. Initially this is zero unless you have set margins on custom view. If you have defined padding on custom view, you must include it in layout call, otherwise the padding will be in front of the view. For example if we need to position child at (0, 0) then in onLayout() method you should pass child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight());

  1. onDraw

Finally, Android provides you with a 2D drawing surface i.e the canvas on which you can draw using a paint object. Try to avoid the calculation part of this method. Keep it light and state forward.

How to expose custom attributes for your view?

Declaring XML attributes is simple.To set resources in any layout where they are included, so we need to add custom attributes in attrs.xml file. You can define resources in either <resources> or inside <declare-styleable> element. <declare-styleable> specifies the attributes the custom view use.All attributes present inside the element share the same global namespace. So you cannot create another attribute with the same name of different type.

For instance, if you are creating a simple view which displays a rectangle. Your attributes may look like this.

<declare-styleable name="ChipView"> 
<attr name="chipWidth" format="dimension" /> 
<attr name="chipBackgroundeColor" format="color"/> 
<attr name="chipDrawable" format="reference"/ </declare-styleable>

The same is referenced while creating a view in the following manner.

app:chipWidth="5dp" 
app:chipDrawable="@drawable/ic_rect" 
app:chipBackgroundeColor="@color/black"

Now, we have to parse these attributes in your java class.

  • Create your view class which extends the android. view class
  • Obtain a reference to the attributes declared in XML. While attrs is passed in the constructor, the second parameter is a reference to the styleable we just declared. The latter two are used for getting default style attributes in theme or supplying a default style attributes
    val a = context.theme.obtainStyledAttributes(attrs, R.styleable.ChipView, 0, 0)
  • Parsing the attribute arguments
    width= a.getDimension(R.styleable.ChipView_chipeWidth,fallback) 
    
    color= a.getColor(R.styleable.ChipView_chipBackgroundeColor,fallback 
    
    drawable= a.getDrawable(R.styleable.ChipView_chipDrawable, 0)
  • Finally, recycle the typed array to be used by the later caller.
    a.recycle()

Making a Custom View will follow the following steps

  • Create a new class, name it as related to your custom view CustomChipView, and extend it by View class.At this point, Android Studio will prompt you to an error to create constructor(s) matching super. On clicking the prompt, you should select all the options for the constructor.
    public CustomChipView(Context context) { 
    super(context); 
    } 
    public CustomChipView(Context context, @Nullable AttributeSet attrs) { 
    super(context, attrs); 
    } 
    public CustomChipView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 
    super(context, attrs, defStyleAttr); 
    }
  • Next, you create a new function to get the values of custom attributes. ex. (void init(@NonNull Context context, @Nullable AttributeSet set))
    public init(@NonNullContext context, @Nullable AttributeSet attrs) { 
    TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.PieChart,0, 0); 
    try { width= a.getDimension(R.styleable.CustomView_rectangeWidth,fallback) 
    color= a.getColor(R.styleable.CustomView_rectangleColor,fallback) 
    drawable= a.getDrawable(R.styleable.CustomView_rectangleDrawable, 0) } 
    finally { a.recycle(); } }
  • Override the onDraw(Canvas canvas) in this class. In this function you have to: Create a new Paint object and assign a color to it, Create a Rect object and assign left, right, top, bottom coordinates to it ( Please note that shape in canvas has positive coordinates from top to bottom and left to right), then call canvas.drawRect( your rect object, your paint object).
  • Last step: Add your custom view in the layout where you want to add this custom view.(activity_main.xml)
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools" 
    android:layout_width="match_parent" 
    android:layout_height="match_parent" 
    android:gravity="center"> 
    <com.test.CustomChipView android:layout_width="300sp" android:layout_height="300sp" app:rectangleWidth="50dp" app:rectangleColor="red"/>
     </LinearLayout>

The Basic Approach for Custom View

  • Extend an existing View class or subclass with your own class.
  • Override some of the methods from the superclass. The superclass methods to override start with ‘on’, for example, onDraw(), onMeasure(), and onKeyDown(). This is similar to the events in Activity or ListActivity that you override for lifecycle and other functionality hooks.
  • Use your new extension class. Once completed, your new extension class can be used in place of the view upon which it was based.

Request a quote