Android Custom View (2. Deep parsing of custom attributes)

Posted by mallen on Sun, 19 May 2019 13:32:07 +0200

Catalog:

In the last blog Android Custom View (First Experience) We have experienced the basic process of custom control:

  1. Inheriting View, Overlay Construction Method
  2. Custom Properties
  3. Rewriting onMeasure Method for Measuring Width and Height
  4. Rewrite the onDraw method to draw controls


_Next several blogs study each step of knowledge points in depth, the first step need not be talked about, this blog let's see how custom attributes is a process, why to do so.

1. Why do you want to customize attributes

In order to use attributes, the attributes should exist first, so if we want to use our own attributes, we must define them before we can use them. But we don't seem to define our own attributes when we write layout files, but we can still use many attributes. Why? I think you all know: we can use the system defined attributes, but do you know what attributes the system defines? Which attributes can be used directly by our custom controls and which cannot be used? What attributes can we use? I don't think all of these questions need to be cleared up. Let's go and solve them one by one.  
We can find attrs.xml in sdk platforms android-xx data res values directory. This is all the attributes of the system. Open up and see some familiar ones:

<declare-styleable name="View">
    <attr name="id" format="reference" />
    <attr name="background" format="reference|color" />
    <attr name="padding" format="dimension" />
     ...
    <attr name="focusable" format="boolean" />
     ...
</declare-styleable>

<declare-styleable name="TextView">
    <attr name="text" format="string" localization="suggested" />
    <attr name="hint" format="string" />
    <attr name="textColor" />
    <attr name="textColorHighlight" />
    <attr name="textColorHint" />
     ...
</declare-styleable>

<declare-styleable name="ViewGroup_Layout">
    <attr name="layout_width" format="dimension">
        <enum name="fill_parent" value="-1" />
        <enum name="match_parent" value="-1" />
        <enum name="wrap_content" value="-2" />
    </attr>
    <attr name="layout_height" format="dimension">
        <enum name="fill_parent" value="-1" />
        <enum name="match_parent" value="-1" />
        <enum name="wrap_content" value="-2" />
    </attr>
</declare-styleable>

<declare-styleable name="LinearLayout_Layout">
    <attr name="layout_width" />
    <attr name="layout_height" />
    <attr name="layout_weight" format="float" />
    <attr name="layout_gravity" />
</declare-styleable>

<declare-styleable name="RelativeLayout_Layout">
    <attr name="layout_centerInParent" format="boolean" />
    <attr name="layout_centerHorizontal" format="boolean" />
    <attr name="layout_centerVertical" format="boolean" />
     ...
</declare-styleable>

_Look at the attrs.xml file above and find that they are organized in the form of regular grouping. With declare-style leable as a combination, followed by a name attribute, the value of the attribute is View, TextView and so on, have you ever thought of anything? Yes, the group whose attribute value is View is the attribute defined for View, and the attribute value is TextView is the attribute defined for TextView....

Because all controls are subclasses of View, all controls defined for View can be used, which is why our custom controls can use some system attributes without defining attributes.

However, not every control can use all attributes, such as TextView is a subclass of View, so it can use all attributes defined for View, but subclasses must have their own unique attributes and have to extend some attributes for it alone. These attributes can only be extended by itself, and View can not be used, for example, android:text="" can not be used in View. . For example, the layout_weight attribute can be used in LinearLayout, but Relative Layout can't, because layout_weight is defined for LinearLayout's Layout Params.

To sum up, if a custom control does not customize its attributes, it can only use the attributes of VIew, but in order to extend some attributes to our control, we must define them ourselves.


2. How to customize attributes

_Look through the system's property files, you will find that some of these forms, some; the difference between the attr tag with no format attribute, if the format is in the definition of attributes, if not with format is in the use of existing attributes, the value of name is the name of attributes, format is to limit the current definition of attributes can accept what value.

For example, the system has defined android:text attribute, and our custom control also needs a text attribute. There are two ways to do this:

First, we don't know that the system defines the attribute of this name. We define an attribute named text or mText by ourselves (the attribute name can be arbitrary)

<resources>
    <declare-styleable name="MyTextView">
        <attr name="text" format="string" />
    </declare-styleable>
</resources>

Second: We know that the system has defined an attribute named text. We don't need to define it ourselves. We just need to state in the custom attribute that I want to use the text attribute.
(Note that the android namespace is added so that you know that the text attribute of the system is used)

<resources>
    <declare-styleable name="MyTextView">
        <attr name="android:text"/>
    </declare-styleable>
</resources>

Why does the system define this property and declare it when we use it? Because the text attribute defined by the system is for TextView, we can't use the text attribute if we don't declare it.

3. Type format of attribute values

There are 11 types of format support:
(1). reference: Refer to a resource ID

  • Property definitions:
<declare-styleable name = "Name">
     <attr name = "background" format = "reference" />
</declare-styleable>

  • Property use:
<ImageView android:background = "@drawable/pictureID"/>

(2). color: color value

  • Property definitions:
<attr name = "textColor" format = "color" />

  • Property use:
<TextView android:textColor = "#00FF00" />

(3). boolean: Boolean value

  • Property definitions:
<attr name = "focusable" format = "boolean" />

  • Property use:
<Button android:focusable = "true"/>

(4). dimension: dimension value

  • Property definitions:
<attr name = "layout_width" format = "dimension" />

  • Property use:
<Button android:layout_width = "42dip"/>

(5). float: floating point value

  • Property definitions:
<attr name = "fromAlpha" format = "float" />

  • Property use:
<alpha android:fromAlpha = "1.0"/>

(6). integer: Integer values

  • Property definitions:
<attr name = "framesCount" format="integer" />

  • Property use:
<animated-rotate android:framesCount = "12"/>

(7). string: string

  • Property definitions:
<attr name = "text" format = "string" />

  • Property use:
<TextView android:text = "I am the text."/>

(8). fraction: percentage

  • Property definitions:
<attr name = "pivotX" format = "fraction" />

  • Property use:
<rotate android:pivotX = "200%"/>

(9). enum: enumerated values

  • Property definitions:
<declare-styleable name="Name">
    <attr name="orientation">
        <enum name="horizontal" value="0" />
        <enum name="vertical" value="1" />
    </attr>
</declare-styleable>

  • Property use:
<LinearLayout  
    android:orientation = "vertical">
</LinearLayout>

Note: Attributes of enumerated types can only be used with one of them in the process of use, not android:orientation = horizontal vertical“

(10). flag: bits or operations

  • Property definitions:
<declare-styleable name="Name">
    <attr name="gravity">
            <flag name="top" value="0x30" />
            <flag name="bottom" value="0x50" />
            <flag name="left" value="0x03" />
            <flag name="right" value="0x05" />
            <flag name="center_vertical" value="0x10" />
            ...
    </attr>
</declare-styleable>

  • Property use:
<TextView android:gravity="bottom|left"/>

Note: Attributes of bitwise operation types can use multiple values in their use

Mixed Types: Multiple Type Values can be specified in attribute definitions

  • Property definitions:
<declare-styleable name = "Name">
     <attr name = "background" format = "reference|color" />
</declare-styleable>

  • Property use:
<ImageView
android:background = "@drawable/pictureID" />
//Or:
<ImageView
android:background = "#00FF00" />

_Through the above learning, we already know how to define various types of attributes and how to use them, but after we have written the layout file, we need to parse them out to use them in the control.

4. Getting attribute values in classes

Before that, by the way, when we use attributes in the layout file (android:layout_width="match_parent"), we find that there is an android: the Android is the namespace xmlns:android="http://schemas.android.com/apk/res/android", which means to find the source of the attribute in the Android system. Only by introducing a namespace can an XML file know where to look for the attributes used below (where defined, not empty, based).

_If we customize the attribute, it should be found in our application package, so we need to introduce the namespace xmlns:openxu="http://schemas.android.com/apk/res-auto". res-auto means automatic lookup. There is also a way to write xmlns: openxu="http://schemas.android.com/apk/com.example.openxu.view", "com.myxu.view". Application package name.

According to the above knowledge, we first define some attributes and write the layout file.  
First create attrs.xml in the res values directory and define your own attributes:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="MyTextView">
        <! - Declare that MyTextView needs to use the system-defined text attribute, and note that the android name is added before it - >
        <attr name="android:text" />
        <attr name="mTextColor" format="color" />
        <attr name="mTextSize" format="dimension" />
    </declare-styleable>
</resources>

In the layout file, use attributes (note the introduction of our application's namespace so that attrs in our package can be found):

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:openxu="http://schemas.android.com/apk/res-auto"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
        <com.example.openxu.myview.MyTextView
            android:layout_width="200dip"
            android:layout_height="100dip"
            openxu:mTextSize="25sp"
            android:text="I am character."
            openxu:mTextColor ="#0000ff"
            android:background="#ff0000"/>
</LinearLayout>


Get the attribute value in the construction method:

public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MyTextView);
    String text = ta.getString(R.styleable.MyTextView_android_text);
    int mTextColor = ta.getColor(R.styleable.MyTextView_mTextColor, Color.BLACK);
    int mTextSize = ta.getDimensionPixelSize(R.styleable.MyTextView_mTextSize, 100);
    ta.recycle();  //Attention recovery
    Log.v("openxu", "text Attribute value:"+mText);
    Log.v("openxu", "mTextColorAttribute value:"+mTextColor);
    Log.v("openxu", "mTextSizeAttribute value:"+mTextSize);
}

log output:

05-21 00:14:07.192: V/openxu(25652): mText attribute value: I am a text
 05-21 00:14:07.192: V/openxu(25652): mTextColor attribute value: -16776961
 05-21 00:14:07.192: V/openxu(25652): mTextSize attribute value: 75

So far, has it been found that custom attributes are so simple?

We should learn about the definition of attributes, but have we found that there are two unfamiliar classes AttributeSet and TypedArray when we get the attribute value in the construction method? How do these two classes parse the attribute value from the layout file?

5. Attributeset and TypedArray and declare-styleable

Attributeset is a collection of attributes by name. In fact, it is an XML parser inside. It helps us parse all attributes of the control in the layout file and maintains them in the form of key-value part-time pairs. In fact, we can just use the following code to get our attributes.

public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    int count = attrs.getAttributeCount();
    for (int i = 0; i < count; i++) {
        String attrName = attrs.getAttributeName(i);
        String attrVal = attrs.getAttributeValue(i);
        Log.e("openxu", "attrName = " + attrName + " , attrVal = " + attrVal);
    }
}

log output:

05-21 02:18:09.052: E/openxu(14704): attrName = background , attrVal = @2131427347
05-21 02:18:09.052: E/openxu(14704): attrName = layout_width , attrVal = 200.0dip
05-21 02:18:09.052: E/openxu(14704): attrName = layout_height , attrVal = 100.0dip
05-21 02:18:09.052: E/openxu(14704): attrName = text , attrVal = I am character.
05-21 02:18:09.052: E/openxu(14704): attrName = mTextSize , attrVal = 25sp
05-21 02:18:09.052: E/openxu(14704): attrName = mTextColor , attrVal = #0000ff

_Find that when we get the value of attributes through Attributeset, it will get the original value of our layout file, such as the width of 200.0 dip. In fact, this is not what we want. If we want to use the width value next, we need to remove the dip, and then convert it into shaping, which is troublesome. In fact, it's nothing. What's more disgusting is that I applied a color resource ID to backgroud, which gave me the ID value directly, and added an @ before it. Next, I need to get the resources myself and get the real color through the ID value.  

Let's try another Typed Array.  
_Here, intersperse a knowledge point, when defining attributes, there is a declare-style leable. What is it for? If not, why not? The answer is yes. Our custom attributes can be written in the following form:

<?xml version="1.0" encoding="utf-8"?>
<resources>
        <attr name="mTextColor" format="color" />
        <attr name="mTextSize" format="dimension" />
</resources>

Previous forms were as follows:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="MyTextView">
        <attr name="android:text" />
        <attr name="android:layout_width" />
        <attr name="android:layout_height" />
        <attr name="android:background" />
        <attr name="mTextColor" format="color" />
        <attr name="mTextSize" format="dimension" />
    </declare-styleable>
</resources>

//Or:
<?xml version="1.0" encoding="utf-8"?>
<resources>
          <!--Defining attributes-->
       <attr name="mTextColor" format="color" />
        <attr name="mTextSize" format="dimension" />
    <declare-styleable name="MyTextView">
        <!--Generate index-->
        <attr name="android:text" />
        <attr name="android:layout_width" />
        <attr name="android:layout_height" />
        <attr name="android:background" />
        <attr name="mTextColor" />
        <attr name="mTextSize"  />
    </declare-styleable>
</resources>


We all know that all resource files correspond to an integer constant brightness in R. We can find the resource files through this ID value.  
The corresponding class of attribute in R is public static final class attr. If we write declare-styleable, we will generate styleable class in R file. This class is actually grouping the attributes of each control and recording the index value of the attribute. TypedArray just needs to get the attributes through this index value.

public static final class styleable

          public static final int[] MyTextView = {
            0x0101014f, 0x7f010038, 0x7f010039
        };
        public static final int MyTextView_android_text = 0;
        public static final int MyTextView_mTextColor = 1;
        public static final int MyTextView_mTextSize = 2;
}


Use TypedArray to get attribute values:

public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MyTextView);
    String mText = ta.getString(R.styleable.MyTextView_android_text);
    int mTextColor = ta.getColor(R.styleable.MyTextView_mTextColor, Color.BLACK);
    int mTextSize = ta.getDimensionPixelSize(R.styleable.MyTextView_mTextSize, 100);
    float width = ta.getDimension(R.styleable.MyTextView_android_layout_width, 0.0f);
    float hight = ta.getDimension(R.styleable.MyTextView_android_layout_height,0.0f);
    int backgroud = ta.getColor(R.styleable.MyTextView_android_background, Color.BLACK);
    ta.recycle();  //Attention recovery
    Log.v("openxu", "width:"+width);
    Log.v("openxu", "hight:"+hight);
    Log.v("openxu", "backgroud:"+backgroud);
    Log.v("openxu", "mText:"+mText);
    Log.v("openxu", "mTextColor:"+mTextColor);
    Log.v("openxu", "mTextSize:"+mTextSize);ext, 0, mText.length(), mBound);
}

log output:

05-21 02:56:49.162: V/openxu(22630): width:600.0
05-21 02:56:49.162: V/openxu(22630): hight:300.0
05-21 02:56:49.162: V/openxu(22630): backgroud:-12627531
05-21 02:56:49.162: V/openxu(22630): mText:I am character.
05-21 02:56:49.162: V/openxu(22630): mTextColor:-16777216
05-21 02:56:49.162: V/openxu(22630): mTextSize:100


Seeing how comfortable the results are, we get the desired width (float type), background color (decimal color) and so on. TypedArray provides a series of methods to get different types of attributes, so that we can get the data types we want directly, instead of processing them one by one after Attributeset gets the attributes. In fact, Typed Array provides a series of methods to get the specific data. Array is a convenient way for us to get attribute values. Note that when TypedArray is used, remember to call ta.recycle(); reclaim.


Well, today's task is finished, we have fully mastered the custom attributes, summarize the key content of this blog:

  1. Why do you want to customize attributes
  2. Method of customizing attributes
  3. Declare the use of system-owned properties
  4. Type format for attribute values
  5. Getting attribute values in construction methods
  6. Use of Attributeset, TypedArray, and declare-style leable

Topics: Android Attribute xml encoding