Understanding Android Custom Attributes: An Article

简介: Understanding Android Custom Attributes: An ArticleBeing able to modify behavior of a run ti...

Understanding Android Custom Attributes: An Article

Being able to modify behavior of a run time component through configruation is good architecture. Take a look at a text view declaration in an Android layout file


<TextView  
	android:id="@+id/text1"
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:text="Debut Text Appears here"
    />

In this example the string "android:text" is called an attribute of the class android.widget.TextView. The string "android" in "android:text" defines the XML namespace to separate your own attributes from those as defined in the android SDK. This is an example of how configuration can be used to change the behavior of a component (TextView in this example) at run time.

TextView happens to be a class that comes with the Android SDK. Android SDK has architected this class in such a way that we can change its behavior at tun time. Won't it be nice if I can define my own view class that can allow its own customization based on custom attributes. Here is an example


<com.ai.android.book.apptemplate2.MyTextView
	android:id="@+id/custom_text_id"
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:text="Debut Text Appears here"
    apptemplate:custom_text="Custom Hello"
    />    

The attribute "custom_text" is a new attribute that is understood only by MyTextView, the class that I wrote and inherited from TextView. You may ask what is "apptempate" in "apptemplate:custom_text"? This is again a convention in XML to avoid attribute name conflicts. The name space become clear when you see the parent root XML node definition in the full xml layout file. Here is an example of that layout file presenting a few lines at the top and bottom of that file.


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:apptemplate="http://schemas.android.com/apk/res/com.ai.android.book.apptemplate2"
    ....    
    >
<TextView .... />  
<com.ai.android.book.apptemplate2.MyTextView .... />
</LinearLayout>    

Notice that the two name spaces "android" and "apptemplate" at the begining of the XML root node LinearLayout. The name spaces have to be uniquie. It is a mere convention that they point to an "http" like resource identifier. It is also a convention that we distinguish the new custom name space using a structure similar to android spec and end it with our own root package name where the custom classes reside. In this example the root java package name is


com.ai.android.book.apptemplate2

Even if there are sub packages under this package, it is sufficient to namespace them with the root package (in the layout file) unless you feel there are too many custom classes and you want to avoid name conflicts. Then you are free to have multiple name spaces for your sub packages. All I am trying to say is that you are free to choose what ever pattern that meet your needs and don't have to follow the EXACT class package structure for the namespace. It may be good, but not a rule!!

MyTextView Implementation

Let's see now what MyTextView implementation may look like to read this custom attribute


public class MyTextView extends TextView
{
	public MyTextView(Context context) {
		super(context);
		setMyText(context);
	}
	public MyTextView(Context context, AttributeSet attrs) {
		super(context, attrs);
		setMyText(context,attrs);
	}
	public MyTextView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		setMyText(context,attrs);
	}
	
	//Implementation of setMyText
	private void setMyText(Context context)
	{
		this.setText("Hello there");
	}
	private void setMyText(Context context, AttributeSet attrs)
	{
		this.setText(getFromAttrs(context,attrs));
	}
	//Function to read the custom attribute
	//Stubbed out for now
	private String getFromAttrs(Context ctx, AttributeSet attrs)
	{
	    String myText ="Custom Text";
		return mytext;
	}
}

Notice how this custom class is trying to read from the custom attribtue in the method getFromAttrs(). For now I have stubbed this method.

First problem: Attribute Resource IDs and attrs.xml

So far we have a java class MyTextView. We have a layout file where we indicated a custom attribute called "custom_text".

Now if you place the layout file that has this custom attribute in your project and the compiler will tell you that the following attribute


apptemplate:custom_text="Custom Hello"

is in error. The error says that there is no "resource id" available for 'custom_text'. How do you get a resource id for this? why is it important that I get a resource id?

In android when you declare resources such as strings, images, menus, and layouts in xml files Android stores them away and gives them unique integer constants called resource ids.

So to get a resource id for "custom_text", like other resources such as strings, we have to define this in an XML file. One could argue that "custom_text" is already in the XML file and why not have android generate this id from here.

May be, I speculate, because "custom_text" is an attribute that is going to be used again and again (like an instance). So, perhaps, we need to define it somewhere else first and possibly tell android more about this "custom_text" attribute such as, is it a string or an integer, or a boolean etc.

This definition of a custom attribute happens in an XML file sitting under the


/res/values

sub directory. Typically this file is caleld


attrs.xml

Here is an example


<resources>
<attr name="custom_text" format="string"></attr>
</resources>

The custom attribute definitions don't have to be in attrs.xml. You can put them in strings.xml if you want, as long as this file is under the values sub directory. However, the convention is this file is generally called attrs.xml. Important thing is the XML node "attr".

The custom attribute definition in the attrs.xml will create a resource constant like the following


R.attr.custom_text

Here is an implication of this. Because every native view/class in the android SDK may also have attributes that are defined this way there must be an android.R.attr.* namespace that will tell you all possible attributes defined in the Android SDK!!

It is not wildly useful but if you want to know what is the "sum total" of the attributes defined by all the controls of Android you can look at the API reference for


android.R.attr

The http link for this is at


http://developer.android.com/reference/android/R.attr.html

Turning attention to the "format" property of the custom attribute, in my example above I have defined the custom_text as a string. This is indicated by the "format" attribute. Here are all the possible formats possible for XML defined object attribtues


reference
string
color
dimension
boolean
integer
float
fraction
enum
flag

To know how these format types are used you can see the Android API examples project which comes with the android SDK. Look at the /res/values/attrs.xml file.

Also, if you are able to get access to the source code of Core Android jar you can take a look at the "attrs.xml" in the android core project to see most of the formats used.

Here are a handful of examples of custom attributes taken from these android attrs.xml files


<attr name="text" format="string" />
<attr name="textColor" format="color" />
<attr name="textSize" format="dimension" />
<attr name="textAppearance" format="reference" />
<attr name="textColorPrimary" format="reference|color" />
<attr name="anr">
    <enum name="none" value="0" />
    <enum name="thumbnail" value="1" />
    <enum name="drop" value="2" />
</attr>
<attr name="windowSoftInputMode">
    <flag name="stateUnspecified" value="0" />
    <flag name="stateUnchanged" value="1" />
</attr>
<!-- absolute dimension or fraction of the screen size -->
<attr name="windowMinWidthMajor" format="dimension|fraction" />

Most attribute formats listed above are easy to interpret. Not so obvious in this listing is where formats can be combined as in "dimension|fraction" indicating that the value for the attribute can be either an absolute dimension or a percentage fraction.

Exploring the AttributeSet: Can I read my attribute now??

So far you have done the following

1. Define a custom class

2. Define a custom attribute for your class/application in attrs.xml

3. Provide a namespace for your attribute

4. Provide a value for your custom attribute in the file

With all the 4 in place, you ask, should I not be able to read that custom attribute?

Well, let's give it a try. Let's locate the method where we are expecting to read the custom attribute


//Function to read the custom attribute
private String getFromAttrs(Context ctx, AttributeSet attrs)
{
    String myText ="Custom Text";
    return mytext;
}

In this method, we got in our hand the object "AttributeSet". Let's see what kind of methods this object offers by looking at its API docs


http://developer.android.com/reference/android/util/AttributeSet.html

As per the documentation, an AttributeSet object corresponds to the set of attributes that are specified at that specific XML view object tag. For example in the following attribute specification for MyTextView


<com.ai.android.book.apptemplate2.MyTextView
	android:id="@+id/custom_text_id"
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:text="Debut Text Appears here"
    apptemplate:custom_text="Custom Hello"
    />

The passed in AttributeSet object is a set of attributes that is a collection of id, layout_width, layout_height, text, and custom_text and NO MORE.

The AttributeSet class has methods like


getAttributeCount();
getAttributeValue(String namespace, String name);
getAttributeName(int index);
getAttributeValue(int index);

For our example the first method, getAttributeCount() returns 5. Then using the index we can get the attribute value for our index for which our custom attribute name matches. I have also wondered what goes into the "namespace" argument of the method getAttributeValue() from the following namespace spec


xmlns:apptemplate="http://schemas.android.com/apk/res/com.ai.android.book.apptemplate2"

I have tried "apptemplate" but that doesn't work, what works is the following code


String namespace=
"http://schemas.android.com/apk/res/com.ai.android.book.apptemplate2";

String mytext = 
attrs.getAttributeValue(namespace, "custom_text");

However there are drawbacks to reading custom attributes this way. First it is not recommended. And you will know the reasons by the end of this article.

To start with if value of the custom attribute has a reference to another resource such as "@string/mystring" as opposed to "mystring stuff", then we need to untangle that referecne ourselves. The recommended approach is to use a structure called "TypedArray". However we need another detour to understand "TypedArray" and the APIs available to get a TypedArray and work with it.

An arbitrary grouping: The Styleable Wrinkle

According to the recommendation we need to use the following appoach


//Make an array of integers
//where integers are the resource ids of the attributes
int attrsResourceIdArray[] = {R.attr.custom_text};

//At what offset is the custom attribute's resource id
int custom_text_offset = 0;

//Pass the array of attribute resource ids and the
//attribute set to get an ARRAY of values
TypedArray t = context.obtainStyledAttributes(attributeSet,attrsResourceIdArray);

//Use the offset to get the fully typed value
mytext = t.getString(custom_text_offset);

Notice how the method obtainStyledAttributes() takes an array of integers to return a typed array. The object TypedArray will have methods to extract the right values. The API docs can be found at


http://developer.android.com/reference/android/content/res/TypedArray.html

As you see we have manually constructed an integer array (attribute resource ids) to get values from the AttributeSet. This can get tedious if you have a number of custom attributes. Android ADK provides a quicker way to do this by packaging a number of attributes together in the attrs.xml itself through an XML tag called styleable.

Say we are interested in reading the following custom attributes


a1
a2
custom_text

Then we can define these as follows in our res/values/attrs.xml


<resources>
<attr name="a1" format="integer"></attr>
<declare-styleable name="SomeArbitraryGroupName">
    <attr name="a1"/>
    <attr name="a2" format="string" />
    <attr name="custom_text" format="string"/>
</declare-styleable>
</resources>

Notice how we have grouped the definitions for a1, a2, and custom_text. This will create an integer array constant as follows


public static final class R {
...
public static final class styleable {
...
public static final int[] SomeArbitraryGroupName = {
    0x7f010000, 0x7f010001, 0x7f010002
};
...
};//end of class styleable
...
}//end of class R

Usually every resource in Android generates a single integer constant in the yourpackage.R namespace. Here is an exampel where Android actually generates an entire ARRAY of resource ids.

So this approach gives us the constant


R.styleable.SomeArbitraryGroupName

allowing us to rewrite


TypedArray t = context.obtainStyledAttributes(attributeSet,attrsResourceIdArray);

as


TypedArray t = context.obtainStyledAttributes(attrs
    ,R.styleable.SomeArbitraryGroupName);

So this saves us from constructing our own attribute resource Id arrays.

The styleable tag is merely an aggregation of attributes. This means the attribute names will conflict if they are inside or outside of the styleable grouping. This means you cannot take an attribute like "a1" and define it once inside a styleable and once outside a styleable. That would be considered duplicate. So the resource ids generated for attributes are independent of what styleable underwhich the attributes are defined. The styleable merely gathers the attribute resource ids into an integer array that can be readily referenced in your java code.

So again, the attributes can be defined either outside or inside the declare-styleable tag. The declare-styleable is merely a convenient grouping of attributes.

You can choose to take an attribute and include it in many declare-styleable groupings. In such a case just don't specify the "format" tag of the attribute. Such an attribute then is considered reused. In our example the attribute "a1" is defined outside first and then for convenience included in our grouping the second time, but omitting the foramt.

Well there is a bit more to styleable. The compiler also generates a number of constants for the attribute offsets such as (for our example)


R.styleable.SomeArbitraryGroupName_a1 (value 0)
R.styleable.SomeArbitraryGroupName_a2 (value 1)
R.styleable.SomeArbitraryGroupName_custom_text (value 2)

Using these constants we can now rewrite the following code int attrsResourceIdArray[] = {R.attr.custom_text}; int custom_text_offset = 0; TypedArray t = context.obtainStyledAttributes(attrs,attrsResourceIdArray); mytext = t.getString(custom_text_offset);

as


//You don't need these any more. Generated by R
//int attrsResourceIdArray[] = {R.attr.custom_text};
//int custom_text_offset = 0;

TypedArray t = context.obtainStyledAttributes(attrs,R.styleable.SomeArbitraryGroupName);
mytext = t.getString(R.styleable.SomeArbitraryGroupName_custom_text);

//you have to recycle the typed array
t.recycle();

You should be able to answer the following questions about custom attributes?

  1. What are custom attributes?
  2. Why use custom attributes?
  3. How do you code a custom class?
  4. How do you specify a custom attribute?
  5. Do you have to define a custom attribute to Android?
  6. Where do you have to define a custom attribute to Android?
  7. What is AttributeSet?
  8. What is TypedArray?
  9. How do you read a custom attribute in your java code?
  10. What is the styleable tag?
  11. What is attrs.xml?
  12. Does the name of a styleable tag need to match your java custom view name?
  13. How do you declare a namespace for your custom attribute?
  14. Do you need to declare your namespace differently if you have a sub package?
  15. Do you need to specify package/sub-package name for your custom view class in a styleable?
  16. Does an attribute name has to be unique in your entire package?
  17. Can a styleable grouping avoid attribute name uniqueness constraint?

References

AttributeSet API You rarely use this API directly. But it is there if you are curious as this is the object that gets passed into class initialization.

TypedArray API This is the object you read your typed custom attributes from.

R.attr If you want to see the total universe Android SDK attributes.

R.styleable If you want to see the grouping of attributes as seen by various views in Android SDK.

R.style To contrast the difference between attributes, styles, and styleables.

Resources.Theme API to read/work with the current theme that is in play. A theme is a named style that is applied to a whole application.

Custom Components Presentation by Chiu-Ki Chan Custom components use custom attributes. This is a good presentation into the basics of creating custom views.

Resource related references My research notes on Android resources in general, of which "attr" is just another value resource.

How to get the Android Core Source code If you want to get access to the root attrs.xml.

Understand Styles and Themes My research on styles and themes. This is a good read once you understand what custom attributes are.

My Research notes on Custom attributes. This entire article is based on the research I have done on this page.

A number of Android core SDK files: attrs.xml, styles.xml etc. If you can't get the source code you can use this link to look at these files. I have copied them to my site to support this article

Custom Attribute Predicates

  1. Objects have attributes (ex: TextView)
  2. You can specify attributes (values) declaratively for an object in xml layout files (ex: layouts)
  3. Attributes need to be declared first through "attr" tag in the /res/values sub directory.
  4. Attributes are usually declared in /res/values/attrs.xml
  5. An attribute definition has a name and a format (like boolean, string etc)
  6. Attribute formats can be combined (Ex: reference|color)
  7. One uses TypedArray object to read custom attributes
  8. An XML tag called "styleable" enables TypedArray usage
  9. Styleable tags are also found in attrs.xml
  10. Styleables are mere groupings of, otherwise independent, attributes
  11. To repeat an attribute in a styleable group, avoid the format tag and use the same name
  12. The value of an attribute can use a "?" to de-reference another value's attribute (ex: attrib1="?attrib2" Use the value of attrib2 as the value of attrib1)
  13. Styleables don't play a big role in XML layout definitions
  14. Styleables are useful in Java code and used by objects to read a set of attributes that those objects care about
  15. Context.obtainStyledAttribues() is used in conjunction with TypedArray and styleables
  16. Attributes are represented by R.attr.*
  17. Styles and themes are represented by R.style.*
  18. Styleable grouping are represented by R.styleable.* and R.styleable.group_attribute1, R.styleable.group_attribute2 etc.

相关文章
|
前端开发 Android开发
Android custom View AirConditionerView hacking
package com.example.arc.view; import android.content.Context; import android.graphics.Canvas; import android.
666 0
|
Android开发 数据格式 XML
《Expert Android》关键点摘录之一:Exploring Custom Views
一、In Android you can customize views in three ways: 1、Custom views (by extending the View class...
827 0
|
21天前
|
缓存 搜索推荐 Android开发
安卓开发中的自定义控件实践
【10月更文挑战第4天】在安卓开发的海洋中,自定义控件是那片璀璨的星辰。它不仅让应用界面设计变得丰富多彩,还提升了用户体验。本文将带你探索自定义控件的核心概念、实现过程以及优化技巧,让你的应用在众多竞争者中脱颖而出。
|
22天前
|
Java Android开发 Swift
安卓与iOS开发对比:平台选择对项目成功的影响
【10月更文挑战第4天】在移动应用开发的世界中,选择合适的平台是至关重要的。本文将深入探讨安卓和iOS两大主流平台的开发环境、用户基础、市场份额和开发成本等方面的差异,并分析这些差异如何影响项目的最终成果。通过比较这两个平台的优势与挑战,开发者可以更好地决定哪个平台更适合他们的项目需求。
82 1
|
24天前
|
Android开发
Android开发表情emoji功能开发
本文介绍了一种在Android应用中实现emoji表情功能的方法,通过将图片与表情字符对应,实现在`TextView`中的正常显示。示例代码展示了如何使用自定义适配器加载emoji表情,并在编辑框中输入或删除表情。项目包含完整的源码结构,可作为开发参考。视频演示和源码详情见文章内链接。
52 4
Android开发表情emoji功能开发
|
22天前
|
Web App开发 安全 程序员
FFmpeg开发笔记(五十五)寒冬里的安卓程序员可进阶修炼的几种姿势
多年的互联网寒冬在今年尤为凛冽,坚守安卓开发愈发不易。面对是否转行或学习新技术的迷茫,安卓程序员可从三个方向进阶:1)钻研谷歌新技术,如Kotlin、Flutter、Jetpack等;2)拓展新功能应用,掌握Socket、OpenGL、WebRTC等专业领域技能;3)结合其他行业,如汽车、游戏、安全等,拓宽职业道路。这三个方向各有学习难度和保饭碗指数,助你在安卓开发领域持续成长。
53 1
FFmpeg开发笔记(五十五)寒冬里的安卓程序员可进阶修炼的几种姿势
|
5天前
|
Java API Android开发
安卓应用程序开发的新手指南:从零开始构建你的第一个应用
【10月更文挑战第20天】在这个数字技术不断进步的时代,掌握移动应用开发技能无疑打开了一扇通往创新世界的大门。对于初学者来说,了解并学习如何从无到有构建一个安卓应用是至关重要的第一步。本文将为你提供一份详尽的入门指南,帮助你理解安卓开发的基础知识,并通过实际示例引导你完成第一个简单的应用项目。无论你是编程新手还是希望扩展你的技能集,这份指南都将是你宝贵的资源。
21 5
|
4天前
|
设计模式 IDE Java
探索安卓开发:从新手到专家的旅程
【10月更文挑战第22天】 在数字时代的浪潮中,移动应用开发如同一座金矿,吸引着无数探险者。本文将作为你的指南针,指引你进入安卓开发的广阔天地。我们将一起揭开安卓平台的神秘面纱,从搭建开发环境到掌握核心概念,再到深入理解安卓架构。无论你是初涉编程的新手,还是渴望进阶的开发者,这段旅程都将为你带来宝贵的知识和经验的财富。让我们开始吧!