Android framework provides a set of base classes and XML tags to create a custom view. For example, say we need to set typeface to our text views. All of the view classes defined in the Android framework extend View. A custom view can also extend View directly, or can extending one of the existing view subclasses.
A custom view must provide a constructor that takes a Context and an AttributeSet object as parameters. The constructor allows the layout editor to create and edit an instance of custom view. AttributeSet is a collection of attributes. Attributes are elements defined in XML through which we can set properties of the custom view and control its appearance.
Define Custom Attributes
To define custom attribute to a view, you must:
- Define attributes for the custom view in a resource element (
<resources>
) inside of a<declare-styleable>
element. - Specify values for the attributes in XML layout
- Retrieve attribute values at runtime, and apply it to custom view
It’s customary to define custom attributes in res/values/attrs.xml
file. Here’s an example of an attrs.xml file.
<?xml version="1.0" encoding="utf-8"?> <resources> <!-- Custom parameters for IndicatorLayout --> <declare-styleable name="IndicatorLayout"> <attr name="indicatorCount" format="integer"/> </declare-styleable> </resources>
This code declares custom attributes, indicatorCount
, that belong to a styleable entity named IndicatorLayout
. By convention, name of the styleable entity is same name as the name of the class that defines the custom view. An element has two xml attributes name
and format
. name is used for referring in code, e.g. R.attr.indicatorCount
. format
can have different values depending on the ‘type’ of attribute you want.
Now custom attributes, can be uses in layout XML files just like built-in attributes. For example, here’s how to use the attributes defined.
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <!-- Custom view --> <com.aphalaprepsunaa.mahabharat.layout.IndicatorLayout android:id="@+id/indicatorLayoutId" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:layout_marginBottom="@dimen/spacing_normal" android:layout_alignParentBottom="true" android:gravity="center_horizontal" app:indicatorCount="0"/> </RelativeLayout>
Format Types
Format can have different values depending on the ‘type’ of attribute . Some of the possible values are
- reference : References another resource id (e.g, “@color/white”)
- color
- boolean
- dimension
- float
- integer
- string
- fraction
- enum
- flag
enum attributes can be defined as follows:
<attr name="enum_attr"> <enum name="val1" value="1" /> <enum name="val2" value="2" /> </attr>
Similarly flag attributes can also be defined except the values need to be defined so they can be bit ored together. If attribute is reviously defined you do not specify the format. For example,
<declare-styleable name="CustomView"> <attr name="android:gravity" /> </declare-styleable>
Create Custom View
All of the attributes in the XML tag are read from the resource bundle and passed into the view’s constructor as an AttributeSet. Use obtainStyledAttributes() to retrieve attribute from AttributeSet. This method passes back a TypedArray array of values that have already been dereferenced and styled. Below example show the custom view class.
package com.aphalaprepsunaa.mahabharat.layout import android.content.Context import android.util.AttributeSet import android.view.View import android.widget.LinearLayout import com.aphalaprepsunaa.mahabharat.R class IndicatorLayout : LinearLayout { private var mIndicatorCount: Int = 0 private var mSelectedPosition: Int = 0 // Override Constructors constructor(context: Context, attrs: AttributeSet): super(context, attrs) { initIndicators(context, attrs, 0) } private fun px(dpValue: Float): Int { return (dpValue * context.resources.displayMetrics.density).toInt() } /** * attrs are attributes used to build the layout parameters */ private fun initIndicators(context: Context, attrs: AttributeSet, defStyleAttr: Int) { /** * Get TypedArray holding the attribute values in set that are listed in attrs. * Default style specified by defStyleAttr and defStyleRes * defStyleAttr contains a reference to a style resource that supplies defaults values for attributes * defStyleRes is resource identifier of a style resource that supplies default values for the attributes, * used only if defStyleAttr is 0 or can not be found in the theme. Can be 0 to not look for defaults. */ val typedArray = context.obtainStyledAttributes(attrs, R.styleable.IndicatorLayout, defStyleAttr, 0) try { mIndicatorCount = typedArray.getInt(R.styleable.IndicatorLayout_indicatorCount, 0) } finally { typedArray.recycle() } updateIndicators() } fun setIndicatorCount(count: Int) { mIndicatorCount = count updateIndicators() } private fun updateIndicators() { // Remove all child views from the ViewGroup. removeAllViews() for (i in 0 until mIndicatorCount) { // Get view val indicator = View(context) // Setting indicator layout margin val layoutParams = LayoutParams(px(10f), px(10f)) layoutParams.setMargins(px(3f), px(3f), px(3f), px(3f)); indicator.layoutParams = layoutParams indicator.setBackgroundResource(R.drawable.indicator_unselected) // Add the view to indicator layout addView(indicator) } } fun selectCurrentPosition(position: Int) { if (position in 0..mIndicatorCount) { for (index in 0 until mIndicatorCount) { // Child view at the specified position in the group. val childView = getChildAt(index) if (index == position) { mSelectedPosition = position childView.setBackgroundResource(R.drawable.indicator_selected) } else { childView.setBackgroundResource(R.drawable.indicator_unselected) } } } } }
In this example custom view IndicatorLayout
extend existing view LinearLayout
. It constructor takes AttributeSet as one of the parameter. It update the custom attribute indicatorCount
as defined above.