preface

For Android programmers, custom View is around the topic, as an Android terminal, in addition to some background applications, most of the applications directly face the user or our interface, interface beauty and fluency to some extent determines the retention of users.

At the same time, custom View is also in line with the idea of encapsulation, the general function of the control to make up for the use of the official control inconvenience, which will improve our development efficiency.

In addition to the convenience, of course, the pursuit of customization of various complex views is also a reflection of our comprehensive quality as Android programmers (not showcasing skills).

Custom View classification

Android officially divides custom views into three categories:

  • Inheriting existing controls
  • The combination control
  • Fully custom controls

Inheriting existing controls

If the need to achieve the function of the custom View and the function of the existing control is similar, you can directly expand the control of the way to customize the control.

Implementing this kind of custom View requires some knowledge of the properties and apis of the existing controls that need to be inherited. Of course, you can do this by completely customizing the View, but it is much more efficient to start with the encapsulated controls.

The combination control

When there are some more complex custom View needs to be implemented, the combination of controls is a good choice, not too complex and relatively simple, generally inherit a layout to combine the existing controls.

This implementation of custom View is the most commonly used, in the daily development process, there will often be the need to encapsulate some components of the demand, this time the combination of controls is a good choice.

Fully custom controls

When you want to do something that is irregular and extremely complex, you need to implement the View in a completely custom way, by implementing the various methods provided.

In general, fully custom controls are used when existing controls cannot be implemented. They are commonly used for UI with complex shapes or layout patterns that need to be redefined.

Basic steps for customizing a View (this article covers the first two steps)

Different ways to achieve custom View their basic implementation routines are similar, generally through the following steps:

  • Inherit an existing View or ViewGroup to implement the constructor
  • Custom attributes
  • measurement
  • draw
  • Handling interaction events
  • Some other functional logic processing

This article focuses on the first two steps, which can be called the creation of a custom View

Inherit an existing View or ViewGroup to implement the constructor

The constructor is the entry point of every class, and a View is no exception. In order to visualize the View constructor more intuitively, here is a constructor derived from FrameLayout (the custom View instance used in this article is based on FrameLayout) :

class CustomCreateView : FrameLayout {

    // The first constructor
    constructor(context: Context) : super(context) {

    }

    // The second constructor
    constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {

    }

    // The third constructor
    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr
    ) {

    }

    // The fourth constructor
    // Use API 21 or above
    constructor(
        context: Context,
        attrs: AttributeSet,
        defStyleAttr: Int,
        defStyleRes: Int
    ) : super(context, attrs, defStyleAttr, defStyleRes) {

    }
}

Copy the code

Normally, instantiating a View object in code calls the first constructor, and defining a View object in XML calls the second constructor. The system doesn’t call the third and fourth constructors directly, so what’s the use of them?

Before we expand the constructor, we need to look at the concept of custom attributes. What do we mean by the extra parameters in the third and fourth constructors?

Custom attributes

View attributes help us to quickly achieve the functions we need, in addition to the use of the system provided to us those View attributes, we can also customize the relevant attributes, to achieve the original can not achieve the function.

Implementation steps

Define custom properties

Create attrs. XML in res/values and create a new custom attribute under the tag as follows:

<resources>
   <declare-styleable name="CustomCreateView">
       <attr name="showText" format="boolean" />
       <attr name="labelPosition" format="enum">
           <enum name="left" value="0"/>
           <enum name="right" value="1"/>
       </attr>
   </declare-styleable>
</resources>
Copy the code

In the code, each attribute represents a custom attribute, and you can see that each contains two attributes:

  • Name – The name of the property set in the XML reference control
  • Format – Type of attribute value
Setting of the format attribute type

Android presets a lot of attribute types in format to meet the needs of daily development of attribute types. Let’s take a look at the specific attribute types in detail

Attribute types Attribute Type Description
color The color value is usually the hexadecimal value of the color, for example, #000000
dimension Size value, used to set the control size or font size, for example: 16DP, 18SP
integer Plastic numerical
string String type
boolean Boolean value used when a property needs to be evaluated as true or false
enum Enumeration type, property value can select only one value, child value is set to integer, see the above code
flags The value of the sub-item should be set as an integer, usually a multiple of 2 to distinguish it (the specific reason will be given in the section of obtaining the attribute value). The specific setting method is the same as enum. Change the inner label to
float Floating point value
fraction The percentage of
reference Resource ID

Flags and enum attribute values are the same as enum attribute values.

  • Enum — Attribute value Only one value can be selected, such as orientation attribute of LinearLayout
  • Flags — Attribute values Can be selected from multiple values, such as the gravity attribute

In addition it is important to note the format can set up multiple attribute value at the same time, to specify more than one attribute types, are common application reference resource ID and other mixed attribute types, multiple attribute types through “|” connection.

Specify attribute values in the XML layout

After we set the custom properties, we can use them in the corresponding control under the corresponding XML file:


      
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:custom="http://schemas.android.com/apk/res/com.example.customviews"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.redrain.viewdemo.custom_create.CustomCreateView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        custom:showText="true"
        custom:labelPosition="left"/>

</FrameLayout>
Copy the code

Of course, this does not implement the properties function, because we have not set the specific properties of the logic, so next we will see how to receive the set custom properties and set the function.

Receive custom attributes

Remember the constructor of a custom View? One of the attributes is an AttributeSet, which is used to get the value, but getting the parameter directly from the AttributeSet can cause problems, so the obtainStyledAttributes() method is used to get the attribute value.

constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
    // Get the TypedArray object through the obtainStyledAttributes method
    val a = context.theme.obtainStyledAttributes(
        attrs,
        R.styleable.CustomCreateView,
        0.0
    )

    try {
        // Get the corresponding attribute value
        isShowText = a.getBoolean(R.styleable.CustomCreateView_showText, false)
        textPos = a.getInteger(R.styleable.CustomCreateView_labelPosition, 0)}finally {
        // TypedArray is a shared resource that must be recycled after use
        a.recycle()
    }
}
Copy the code

After receiving the attribute values, you can perform the logical operations related to the requirements.

How to obtain the values of various attributes

Each attribute type has a corresponding method for obtaining the attribute value:

// Get the color
// Parameter description: index indicates the styleable ID. DefValue indicates the default color
int getColor(int index, int defValue)       

// Get dimension
// As for the dimension, the obtained value is px value, which needs to be transformed accordingly
// Parameter description: index indicates the styleable ID. DefValue indicates the default size
float getDimension(int index, float defValue)           // Get the pixel value
int getDimensionPixelOffset(int index, int defValue)    // Get the pixel value and convert it to an integer (round)
int getDimensionPixelSize(int index, int defValue)      // Get the pixel value and convert it to an integer (rounded)

// Get integer
// Parameter description: index indicates the styleable ID. DefValue indicates the default value
// Note that getInt attempts to force an int if it gets an attribute value that is not an integer, while getInteger throws an exception
int getInt(int index, int defValue)
int getInteger(int index, int defValue)

// Get the string
// Parameter description: index indicates the styleable ID
String getString(int index)

// Get Boolean
// Parameter description: index indicates the styleable ID. DefValue indicates the default value
boolean getBoolean(int index, boolean defValue)

// The value is obtained
GetInt or getInteger is used to obtain the corresponding attribute value. The corresponding value is obtained to obtain the enumeration value
int getInt(int index, int defValue)
int getInteger(int index, int defValue)

// Get flags
// Use getInt or getInteger to get the corresponding attribute value
// Note: Since the flags attribute can set multiple attribute values, the obtained value is the sum of the corresponding attribute values to determine which attribute values are selected
int getInt(int index, int defValue)
int getInteger(int index, int defValue)

// Get float
// Parameter description: index indicates the styleable ID. DefValue indicates the default value
float getFloat(int index, float defValue)

// A fraction is obtained
// Parameter description: index indicates the styleable ID. Base indicates the value to be multiplied by the attribute value; Pbase represents the value to be divided by the attribute value; DefValue indicates the default value
float getFraction(int index, int base, int pbase, float defValue)

// Get reference
// Parameter description: index indicates the styleable ID. DefValue indicates the default value
int getResourceId(int index, int defValue)
Copy the code

Some properties can be set in a variety of ways. For example, the text property can be set directly by a string or by a string reference to an ID. In this case, you just need to set it directly by using the getString method. The same applies to other types of attribute values.

Adding dynamic Properties

The above introduction is about the static property setting of the control, sometimes need to dynamically adjust the property, so the need to provide a setter&getter method for each property value to achieve dynamic property setting:

var isShowText: Boolean = false
    set(value) {
        field = value
        tvText.visibility = if (value) VISIBLE else INVISIBLE
    }
Copy the code

Of course, the setting method of dynamic properties is not limited to the above, in the definition of dynamic properties need to flexibly adjust the implementation mode according to the needs.

Meaning of constructor arguments

With the concept of custom attributes in mind, going back to the constructors we talked about earlier, what are the parameters of different constructors?

class CustomCreateView : View {

    // The first constructor
    constructor(context: Context?) : super(context) {

    }

    // The second constructor
    constructor(context: Context? , attrs: AttributeSet?) :super(context, attrs) {

    }

    // The third constructor
    constructor(context: Context? , attrs: AttributeSet? , defStyleAttr:Int) : super(
        context,
        attrs,
        defStyleAttr
    ) {

    }

    // The fourth constructor
    // Use API 21 or above
    constructor( context: Context? , attrs: AttributeSet? , defStyleAttr:Int,
        defStyleRes: Int
    ) : super(context, attrs, defStyleAttr, defStyleRes) {

    }
}

Copy the code
  • You know what the context is
  • Attrs represents a collection of attribute values, as explained above
  • defStyleAttr
  • defStyleRes

If you have done the code experiment, you will find that the method to obtain the attribute contains two parameters of the prequel 0:

val a = context.theme.obtainStyledAttributes(
    attrs,
    R.styleable.CustomCreateView,
    0.0
)
Copy the code

The last two parameters represent defStyleAttr and defStyleRes, respectively.

defStyleAttr

This is equivalent to setting up a theme-style attribute configuration for the View. If no related attribute is defined in the XML, but the related attribute is defined in the theme, the corresponding attribute will be found in the style pointed to by defStyleAttr.

It depends on the theme, and different themes define different defStyleAttr’s to achieve different effects.

defStyleRes

Resource ID that points to Style, but only if defStyleAttr is 0 or if defStyleAttr is not 0 but the defStyleAttr attribute is not assigned in Theme.

In short, this approach is theme-independent and is the default property style of the bottom.

The specific usage of parameter transmission

defStyleAttr

First, define specific attribute names in values/attrs:

<attr name="CustomCreateViewDefStyleAttr" format="reference"/>
Copy the code

Then define the attribute style you want to set in values/styles:

<style name="CustomCreateViewLeftStyleAttr">
    <item name="showText">true</item>
    <item name="labelPosition">left</item>
</style>
Copy the code

Next, define the corresponding properties in the style you want to define (I’m going to use the AppTheme directly here) :

<! -- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    <! -- Customize your theme here. -->
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
    <item name="colorAccent">@color/colorAccent</item>
    <item name="CustomCreateViewDefStyleAttr">@style/CustomCreateViewLeftStyleAttr</item>
</style>
Copy the code

Finally, define the default attribute style in the constructor:

constructor(context: Context, attrs: AttributeSet) : this(context, attrs, R.attr.CustomCreateViewDefStyleAttr) {
    Log.d("customCreate"."Second constructor")}constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(
    context,
    attrs,
    defStyleAttr
) {
    Log.d("customCreate"."Third construction method")

    val view = LayoutInflater.from(context).inflate(R.layout.view_custom_create, this)
    tvText = view.findViewById(R.id.tv_text)
    tvText.text = "CustomCreateView"

    // Get the TypedArray object through the obtainStyledAttributes method
    val a = context.obtainStyledAttributes(
        attrs,
        R.styleable.CustomCreateView,
        defStyleAttr, 0
    )

    try {
        // Get the corresponding attribute value
        isShowText = a.getBoolean(R.styleable.CustomCreateView_showText, false)
        labelPosition = a.getInteger(R.styleable.CustomCreateView_labelPosition, 0)}finally {
        // TypedArray is a shared resource that must be recycled after use
        a.recycle()
    }

    tvText.visibility = if (isShowText) VISIBLE else INVISIBLE
    tvText.gravity = if (labelPosition == 0) Gravity.LEFT else Gravity.RIGHT
}
Copy the code

Note that the difference between this approach and the generic attribute definition is that:

  • The three-parameter constructor is implemented through the two-parameter constructor, and the third parameter is defined as the resource ID we defined
  • obtainStyledAttributesThe third parameter in the method is passed indefStyleAttr

This implements attribute definition in the defStyleAttr way, which actually looks for the defined specific attribute name in the theme and, if so, uses the defined attribute name if priority allows, and if not, doesn’t get the attribute.

defStyleRes

The defStyleRes implementation only requires a minor change to the defStyleAttr implementation.

Remove the definition from theme

<! -- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    <! -- Customize your theme here. -->
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
    <item name="colorAccent">@color/colorAccent</item>
</style>
Copy the code

Let the three-parameter constructor implement the four-parameter constructor:

constructor(context: Context, attrs: AttributeSet) : this(context, attrs, R.attr.CustomCreateViewDefStyleAttr) {
    Log.d("customCreate"."Second constructor")}constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : this(
    context,
    attrs,
    defStyleAttr,
    R.style.CustomCreateViewLeftStyleAttr
) {
    Log.d("customCreate"."Third construction method")}constructor(
    context: Context,
    attrs: AttributeSet,
    defStyleAttr: Int,
    defStyleRes: Int
) : super(context, attrs, defStyleAttr, defStyleRes) {
    Log.d("customCreate"."Fourth construction method")

    val view = LayoutInflater.from(context).inflate(R.layout.view_custom_create, this)
    tvText = view.findViewById(R.id.tv_text)
    tvText.text = "CustomCreateView"

    // Get the TypedArray object through the obtainStyledAttributes method
    val a = context.obtainStyledAttributes(
        attrs,
        R.styleable.CustomCreateView,
        defStyleAttr, defStyleRes
    )

    try {
        // Get the corresponding attribute value
        isShowText = a.getBoolean(R.styleable.CustomCreateView_showText, false)
        labelPosition = a.getInteger(R.styleable.CustomCreateView_labelPosition, 0)}finally {
        // TypedArray is a shared resource that must be recycled after use
        a.recycle()
    }

    tvText.visibility = if (isShowText) VISIBLE else INVISIBLE
    tvText.gravity = if (labelPosition == 0) Gravity.LEFT else Gravity.RIGHT
}
Copy the code

Note that the difference between this approach and the generic attribute definition is that:

  • The four-parameter constructor is implemented through the three-parameter constructor, and the fourth parameter is defined as the resource ID we defined
  • obtainStyledAttributesThe fourth parameter in the method is passed indefStyleRes

Since the attribute resources defined by defStyleAttr have been removed from the theme, the attribute resources defined by defStyleRes will eventually be called:

<style name="CustomCreateViewLeftStyleAttr">
    <item name="showText">true</item>
    <item name="labelPosition">right</item>
</style>
Copy the code

Property Settings difference

The above has explained all kinds of attribute assignment, so what are their differences and application scenarios?

Property Settings:

  1. Defined directly in the layout XML
  2. Defined by style in the layout XML
  3. Specify a style reference in the Activity Theme of the custom View
  4. The default value specified in the constructor defStyleRes

Properties can be set in one of the following ways, from high priority to low priority (if a high priority is defined, then the lower priority is not used), using numbers in the following:

  1. Understandably, the XML attribute definition is a programmer’s first setup item and reflects programmer expectations.
  2. The style definition can be understood as a specification of a design to enhance reusability, but it can be replaced by the attributes defined in 1 to improve the possibility of customization while standardizing.
  3. More like a theme nature definition, unified theme definition, convenient for programmers to customize the overall style.
  4. The basic default values are provided to ensure the consistency of the overall style.

Android distinguishes attribute definitions through different granularity definitions, the most important is to help developers improve the efficiency of development.

conclusion

In this paper, we explain the creation of a custom View from the constructor this entry as the breakthrough point, on custom properties of the defined process and different ways to View the properties of the style, the paper did not give a complete source code, the code is coherent, but use suggest that students can write a write code yourself to try to do a try, Especially the concept of defStyleAttr and defStyleRes must be well understood, the actual development will greatly improve our development efficiency.