- Android SDK: Creating Custom Views
- Step 1: Create an Android Project
- Step 2: Create a View Class
- Step 3: Create Attribute Resources
- Step 4: Add the View to the Layout
- Step 5: Retrieve the Attributes
- Step 6: Draw the View
- Step 7: Provide Get and Set Methods
- Step 8: Manipulate the View from the Activity
- Conclusion
- How to create custom views in android?
Android SDK: Creating Custom Views
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 Eclipse. 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 to 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 Eclipse 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:
Add the following import statements above this:
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:
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:
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:
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:
Again, alter the package name to suit your own, and 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:
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:
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 setup the View. First instantiate the Paint object:
Now let’s retrieve the attribute values we set in XML:
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:
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 for 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:
Since we’re going to draw a circle, let’s get some information about the available space, inside the onDraw method:
Now we can calculate the circle radius:
Now let’s set some properties for painting with:
Now we will use the selected circle color as stored in our instance variable:
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:
Now let’s add the text. First set the color using the value retrieved from the layout XML:
Now set some more properties:
Finally we can draw the text, using the text string retrieved:
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:
Each method simply returns the value requested. Now add the set methods for the color attributes:
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:
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 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:
Before the onCreate method, inside the class declaration, add an instance variable representing the instance of the custom View displayed:
Inside the onCreate method, after the existing code, retrieve this using its ID as included in the XML layout file:
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:
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:
Now go back to the Activity class and add the method listed for clicks on the button:
Let’s use the set methods we defined to update the custom View appearance:
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.
Источник
How to create custom views in android?
Before diving into the process of creating a custom view, It would be worth stating why we may need to create custom views.
- Uniqueness: Create something that cannot be done by ordinary views.
- Optimisation: A lot of times we tend to add multiple views or constraints to create the desired view that can be optimized drastically in terms of draw, measure or layout time.
The best way to start would be to understand how android manages view groups and lays out views on the screen. Let us take a look at the diagram below.
onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int)
Every parent view passes a height and width constraint to its child view based on which the child view decides how big it wants to be. The child view then calls setMeasuredDimension() to store its measured width and height.
How are these constraints passed?
Android uses a 32-bit int called the measure spec to pack a dimension and its mode. The mode is a constraint and can be of 3 types:
- MeasureSpec.EXACTLY: A view should be absolutely the same size as dimension passed along with spec. Eg. layout_width= “100dp”, layout_width=”match_parent”,layout_weight=”1″.
- MeasureSpec.AT_MOST: A view can have maximum height/width of dimension passed. However, it can be also smaller if it wishes to be. Eg android:layout_width=”wrap_content”
- MeasureSpec.UNSPECIFIED: A view can be of any size. This is passed when we are using a ScrollView or ListView as our parent.
onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int)
Android applies any offsets or margins and calls this method to inform your view about where exactly it would be placed on the screen. Unlike onMeasure, it is called only once during the traversal. So it is recommended to perform any complex calculations in this method.
onDraw(canvas: Canvas)
Finally, Android provides you with a 2D drawing surface i.e the canvas on which you can draw using a paint object.
The UI thread then passes display lists to render thread which does a lot of optimizations and finally GPU process the data passed to it by render thread.
How to define attributes for your view?
Declaring XML attributes is simple. You just need to add a declarable-style in your attrs.xml and declare a format for every attribute.
For instance, if you are creating a simple view which displays a circle with its label. Your attributes may look like this.
The same is referenced while creating a view in the following manner.
Now, we have to parse these attributes in your java or kotlin 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.
- Parsing the attribute arguments
Android automatically handles the process of converting dp or sp to the right amount of pixels according to screen size when parsing a dimension attribute. But, You need to ensure that the fallback value is converted to appropriate pixel value since android returns fallback value without any conversions if an attribute value is not defined in XML.
While parsing all other attributes is quite straightforward. I will brief you about how to parse flags. Declaring flags attributes can be really useful sometimes since we can check for multiple properties using a single attribute. This is the same way android handles the visibility flag.
colorType here is an integer which represents a flagSet. Now, since every bit in an integer can be used to represent an indication. We can check if a flag exists and perform our operations accordingly. To check if a flag type stroke exits, we can simply perform an or operation on flagSet with the stroke value. If the result stays the same that means the flag actually exists in the flagSet.
- Finally, recycle the typed array to be used by the later caller.
Initialising your objects
It is always better to initialize your paint and path objects or other variables in the constructor itself. Since declaring it any other traversal method may result in the meaningless creation of objects again and again.
Calculating your view size
Calculating view size can be really challenging sometimes. You have to make sure that your view does not take any extra pixel or request any less pixel as it may end up showing extra white space or not showing complete view respectively. These are the basic steps that you need to follow to calculate the size of your view.
- Calculate how much width and height your view requires. For instance, if you are drawing a simple circle with its label below the circle. The suggested width would be :
(circle diameter+ any extra width if occupied by the label). - Calculate the desired width by adding the suggested width with paddingStart and paddingEnd. Similarly, desiredHeight would be suggested height plus paddingTop & paddingBottom.
- Calculate actual size respecting the constraints. To calculate this, you simply need to pass measure spec passed to you in onMeasure() and your desired dimension in this method called resolveSize(). This method would tell you closest possible dimension to your desired width or height while still respecting its parent’s constraints.
- Most importantly, you need to set the final width and height in onMeasure method by calling setMeasuredDimension(measuredWidth,measuredHeight) to store the measured width and height of this view otherwise, you might see your view crashing with an IllegalStateException.
Positioning your views
We can position our child views by using the onLayoutMethod. The code simply may involve iterating over any child views and assigning them a left, top, right and a bottom bound depending on measured widths and heights.
Drawing your view
Before using the canvas there are few things that we need to understand:
- Paint: The Paint class holds the style and color information about how to draw geometries, text, and bitmaps. Here is how we create a paint object.
You can read about more about the properties here.
- Drawing Shapes: You can directly draw shapes like a line, arc, circle etc on the canvas. Let us take a look at the diagram below to gain a better understanding.
Using Paths: Drawing complex shapes with the above methods may get a bit complex so android offers a Path class. With the Path class, you can imagine that you are holding a pen and you can draw a shape, then maybe move to a different position and draw another shape. Finally, when you are done creating a path. You can simply draw the path on the canvas like this. Also, when using paths you can also use different path effects (discussed below in detail). Below, is an example of the shape created using paths.
- Path Effects: If you also apply a Corner path effect to your paint object with a certain radius the polygon will look like this. You can also use other path effects like DashPathEffect, DiscretePath etc. To combine two different path effects you can use the ComposePathEffect.
bitmap: Bitmap that you want to draw on canvas
src: It takes a rect object which specifies the portion of the bitmap you want to draw. This can be null if you want to draw the complete bitmap.
dest: A rect object which tells how much area do you want to cover on the canvas with the bitmap
paint: The paint object with which you want to draw the bitmap
Android automatically does all the necessary scaling or translation to fit the source on destination area.
You can also draw drawables on canvas.
Before drawing a drawable, you would need to set bounds to your drawable. The left, top, right and bottom describe the drawable’s size and its position on the canvas. You can find the preferred size for Drawables using getIntrinsicHeight() and getIntrinsicWidth() methods and decide bounds accordingly.
Drawing Texts: Drawing texts can be a bit of pain. Not the drawing itself, but the alignment or measurement of text. This occurs because different characters have different heights and to make it more worse there can be different typefaces too. So to measure a text’s height you would need to calculate specific text bounds for your text like this.
Then, the rect object passed in the end would then contain the text bounds of actual text to be drawn. This way you can calculate the actual height of text to be drawn and set a correct baseline y for your text. To calculate the width of your text you should use textPaint.measureText() as it is more accurate than the width given by paint text bounds (because of the way these methods are implemented in skia library). Alternatively, for ensuring the text is centered horizontally on the canvas you can just set your paint’s alignment to TextAlign.CENTER and pass center point of your canvas in the x coordinate.
Drawing multiline text: If you want to handle line breaks (\n) or If you have a fixed width to draw a text you can use Static Layout or Dynamic Layout. This would automatically handle all the word breaks or line breaks and also tell you how much height would be needed to draw a text in given width.
- Saving & Restoring Canvas: As you might have noticed, we need to save the canvas and translate it before drawing on it and finally we have to restore the canvas. A lot of times we need to draw something with a different setting such as rotating the canvas, translating it, or clipping a certain part of canvas while drawing a shape. In this case, we can call canvas.save() which would save our current canvas settings on a stack. After this, we change canvas settings ( translation etc) and then draw whatever we want to with these settings. Finally, when we are done drawing we can call canvas.restore() which would restore canvas to the previous configuration that we had saved.
- Handling User Inputs: Finally, you have created your own custom view using XML attributes, BUT what if you want to change any property at runtime such as the radius of the circle, text color etc. You would need to inform Android API’s to reflect the changes. Now, if any change in property affects the size of your view you will set the variable and call requestLayout() which would recalculate your view’s size and redraw it. However, if a property like a text color is changed you would only need to redraw it with new text paint color and in this case, it would be wise to just call invalidate().
Additional Note: Now if your view has a lot of attributes, there may be a lot of times you would have to write invalidate()/requestLayout after every setter. This problem can be solved by using kotlin’s delegates. Let us take a look a the example below to be more clear.
Now, If I know that a property if changed should only redraw the view, I would initialize it using OnValidateProp but if it can affect the size of the view I would initialize by creating a new OnLayoutProp delegate.
Finally! You can start by creating your own custom views. If you are interested to see what an actual custom view code looks like. You can check out the library that I just published. It displays steps along with the descriptions and covers most of the things that I have discussed in this article.
Источник