The Android platform provides an extensive range of user interface items that are sufficient for the needs of most apps. However, there may be occasions on which you feel the need to implement a custom user interface for a project you are working on. In this tutorial, we will work through the process of creating a custom View.
To create and use our custom View, we will extend the View class, define and specify some custom attributes, add the View to our layout XML, override the onDraw
method to tailor the View appearance, and manipulate it from our app's main Activity.
Step 1: Create an Android Project
Create a new Android project in Android Studio. You can choose whatever settings you like as long as your app has a main Activity class and a layout file for it. We do not need any amendments to the Manifest file. In the source code download file, the main Activity is named LovelyActivity and the layout file is activity_lovely.xml—alter the code to suit your own names if necessary. We will be creating and adding a few additional files as we go along.
Step 2: Create a View Class
Our custom View can extend any of the existing Android View classes such as Button or TextView. However, we will create a direct subclass of View. Extending an existing class allows you to use the existing functionality and styling associated with that class, while providing processing to suit your own additional needs.
Create a new class in your application by selecting the app's main package in Android Studio and choosing File > New > Class. Enter a name of your choice and click Finish. The tutorial code uses the class name LovelyView
—you will need to alter it in all of the below code if you choose a different name. Make your new class extend View
by adding to its opening declaration line:
public class LovelyView extends View {
Add the following import statements above this:
import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Paint.Style; import android.util.AttributeSet; import android.view.View;
Step 3: Create Attribute Resources
In order to use our custom View as we would use a standard View (i.e. set its attributes in layout XML and refer to them in our Java code), we will declare attribute resources. In Eclipse, create a new file in your project res/values folder by selecting it and choosing File> New> File. Enter attrs.xml as the file name and click Finish.
In the attributes file, we first need to indicate that we are listing resources, so add the following parent element:
<resources></resources>
Inside this element, we are going to declare three attributes for the View that will allow us to style it. Let's keep things relatively simple—the View is going to display a circle with some text in the middle. The three attributes will be the circle color, the text string, and the text color. Add the following inside your resources
element:
<declare-styleable name="LovelyView"><attr name="circleColor" format="color" /><attr name="circleLabel" format="string"></attr><attr name="labelColor" format="color"></attr></declare-styleable>
The declare-styleable
element specifies the View name. Each attribute has a name and format. We will be able to specify these attributes in the layout XML when we add the custom View and also retrieve them in the View class. We will also be able to retrieve and set the attributes from our Java Activity class. The values provided for each attribute will need to be of the type listed here as format
.
Step 4: Add the View to the Layout
Let's add an instance of the custom View to our app's main layout file. In order to specify the custom View and its attributes, we need to add an attribute to the parent layout element. In the source download, it is a RelativeLayout
, but you can use whichever type you prefer. Add the following attribute to your layout's parent element:
xmlns:custom="https://schemas.android.com/apk/res/your.package.name"
Alter your.package.name
to reflect the package your app is in. This specifies the namespace for our app, allowing us to use the attributes we defined within it. Now we can add an instance of the new View. Inside the layout, add it as follows:
<your.package.name.LovelyView android:id="@+id/custView" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_margin="5dp" custom:circleColor="#ff0099" custom:circleLabel="Hello" custom:labelColor="#ffff66" />
Again, alter the package name to suit your own, and change the class name if necessary. We will use the ID to refer to the View in our Activity code. Notice that the element lists standard View attributes alongside custom attributes. The custom attributes are preceded by custom:
and use the names we specified in our attributes XML file. Note also that we have specified values of the types we indicated using the format attributes in the "attrs.xml" file. We will retrieve and interpret these values in our View class.
Step 5: Retrieve the Attributes
Now let's turn back to the View class we created. Inside the class declaration, add some instance variables as follows:
//circle and text colors private int circleCol, labelCol; //label text private String circleText; //paint for drawing custom view private Paint circlePaint;
We will use the first three of these to keep track of the current settings for color and text. The Paint
object is for when we draw the View. After these variables, add a constructor method for your class:
public LovelyView(Context context, AttributeSet attrs){ super(context, attrs); }
As we are extending the View class, the first thing we do is call the superclass method. After the super call, let's extend the method to set up the View. First, instantiate the Paint
object:
//paint object for drawing in onDraw circlePaint = new Paint();
Now, let's retrieve the attribute values we set in XML:
//get the attributes specified in attrs.xml using the name we included TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.LovelyView, 0, 0);
This typed array will provide access to the attribute values. Notice that we use the resource name we specified in the attrs.xml file. Let's now attempt to retrieve the attribute values, using a try block in case anything goes wrong:
try { //get the text and colors specified using the names in attrs.xml circleText = a.getString(R.styleable.LovelyView_circleLabel); circleCol = a.getInteger(R.styleable.LovelyView_circleColor, 0);//0 is default labelCol = a.getInteger(R.styleable.LovelyView_labelColor, 0); } finally { a.recycle(); }
We read the attributes into our instance variables. Notice that we use the names we listed for each in "attrs.xml" again. The colors are retrieved as integer values and the text label as a String.
That's the constructor method complete—by the time it has executed, the class should have retrieved the selected View attributes we defined in the attribute resources file and set values in the layout XML.
Step 6: Draw the View
Now we have our View attributes in the class, so we can go ahead and draw it. To do this, we need to override the onDraw
method. Add its outline after your constructor method as follows:
@Override protected void onDraw(Canvas canvas) { //draw the View }
Since we're going to draw a circle, let's get some information about the available space, inside the onDraw
method:
//get half of the width and height as we are working with a circle int viewWidthHalf = this.getMeasuredWidth()/2; int viewHeightHalf = this.getMeasuredHeight()/2;
Now we can calculate the circle radius:
//get the radius as half of the width or height, whichever is smaller //subtract ten so that it has some space around it int radius = 0; if(viewWidthHalf>viewHeightHalf) radius=viewHeightHalf-10; else radius=viewWidthHalf-10;
Now let's set some properties for painting with:
circlePaint.setStyle(Style.FILL); circlePaint.setAntiAlias(true);
Now we will use the selected circle color as stored in our instance variable:
//set the paint color using the circle color specified circlePaint.setColor(circleCol);
This means that the circle will be drawn with whatever color we listed in the layout XML. Let's draw it now using these details:
canvas.drawCircle(viewWidthHalf, viewHeightHalf, radius, circlePaint);
Now let's add the text. First set the color using the value retrieved from the layout XML:
//set the text color using the color specified circlePaint.setColor(labelCol);
Now set some more properties:
//set text properties circlePaint.setTextAlign(Paint.Align.CENTER); circlePaint.setTextSize(50);
Finally, we can draw the text, using the text string retrieved:
//draw the text using the string attribute and chosen properties canvas.drawText(circleText, viewWidthHalf, viewHeightHalf, circlePaint);
That's onDraw
complete.
Step 7: Provide Get and Set Methods
When you create a custom View with your own attributes, it is recommended that you also provide get
and set
methods for them in your View class. After the onDraw
method, first add the get
methods for the three customizable attributes:
public int getCircleColor(){ return circleCol; } public int getLabelColor(){ return labelCol; } public String getLabelText(){ return circleText; }
Each method simply returns the value requested. Now add the set
methods for the color attributes:
public void setCircleColor(int newColor){ //update the instance variable circleCol=newColor; //redraw the view invalidate(); requestLayout(); } public void setLabelColor(int newColor){ //update the instance variable labelCol=newColor; //redraw the view invalidate(); requestLayout(); }
These methods accept int
parameters representing the color to set. In both cases, we update the instance variable in question, then prompt the View to be redrawn. This will make the onDraw method execute again, so that the new values affect the View displayed to the user. Now add the set
method for the text:
public void setLabelText(String newLabel){ //update the instance variable circleText=newLabel; //redraw the view invalidate(); requestLayout(); }
This is the same as the other two set
methods except for the String parameter. We will call on these methods in our Activity class next.
Step 8: Manipulate the View From the Activity
Now that we have the basics of our custom View in place, let's demonstrate using the methods within our Activity class. In the app's main Activity class, add the following import statements:
import android.graphics.Color; import android.view.View;
Before the onCreate
method and inside the class declaration, add an instance variable representing the instance of the custom View displayed:
private LovelyView myView;
Inside the onCreate
method, after the existing code, retrieve this using its ID as included in the XML layout file:
myView = (LovelyView)findViewById(R.id.custView);
To demonstrate setting the View attribute values from the Activity, we will add a simple button. Open your layout file and add it after the custom View element:
<Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:onClick="btnPressed" android:text="@string/btn_label" />
We specify a method to execute on user clicks—we will add this to the Activity class. First, add the string to your res/values/strings XML file:
<string name="btn_label">Press Me</string>
Now go back to the Activity class and add the method listed for clicks on the button:
public void btnPressed(View view){ //update the View }
Let's use the set
methods we defined to update the custom View appearance:
myView.setCircleColor(Color.GREEN); myView.setLabelColor(Color.MAGENTA); myView.setLabelText("Help");
This is of course just to demonstrate how you can interact with a custom View within your Activity code. When the user clicks the button, the appearance of the custom View will change.
Conclusion
In general, it's advisable to use existing Android View classes where possible. However, if you do feel that you need a level of customization beyond the default settings, creating your own custom Views is typically straightforward. What we have covered in this tutorial is really just the beginning when it comes to creating tailored Android user interfaces. See the official guide for information on adding interactivity and optimization to your customizations.