Custom XML Attributes For Your Custom Android Widgets

In the last part of my Custom UI Tutorial, commenter Knut asked:

Is there an easy way to declare the listener in XML (similar to the android:onClick=”onMyButtonClick” in the stock controls)?

This is a great question, and in answering his question, I’ll go ahead and show you how to create other custom attributes you can put in the XML for your custom widgets. The goal will be something like this in your layouts:

<com.kdion.tutorial.MyCustomWidget
	android:id="@+id/theId"
	<!-- ...more stuff here... -->
	custom:myText="Something"
	custom:fancyColors="true"
	custom:onAction="myDoSomething"
	/>

We have a custom View called MyCustomWidget, and along with the standard android: attributes, we have a few of our own: myText="Something", fancyColors="true", and onAction="doSomething". Presumably, these would map to attributes we already have for our custom widget which we could set in code, but we want a shortcut to set them from XML.

Step 1: attrs.xml

The first step to enable us to use our custom xml attributes is to define them for the android system. To do this, you need to create a new xml file under res/values/ and call it attrs.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
	<declare-styleable name="MyCustomWidget">
		<attr name="myText" format="string"/>
		<attr name="fancyColors" format="boolean"/>
		<attr name="onAction" format="string"/>
	</declare-styleable>
</resources>

The first thing you see inside the requisite resources tag is a declare-styleable tag, with name="MyCustomWidget". This lets android know what it is we want to add our attributes to (the Java class name). As far as I know you can only do this with your own widgets – I don’t think you can put, say, declare-styleable name="EditText" (not that I could think of why you would want to, but still). Next, we have out custom attributes in attr tags. The name= value is what we want to use in our xml, and the format= is the type of attribute. There are a number of different types, including string, integer, float, dimension, boolean, float, resource, enum, and color. There may be one or two more, but I’m having a hard time finding where they are defined.

Step 2: Add logic to read in XML attributes to your custom widget

Now that we have a way to pass in attributes from XML to our widget, we need to read them in. This code will go in the constructors that get passed an AttributeSet, since that is what contains the XML attributes. To read the values in the XML, you need to first create a TypedArray from the AttributeSet, then use that to read the values:

TypedArray a = context.obtainStyledAttributes(attrs,
	R.styleable.MyCustomWidget);

final int N = a.getIndexCount();
for (int i = 0; i < N; ++i)
{
	int attr = a.getIndex(i);
	switch (attr)
	{
		case R.styleable.MyCustomWidget_myText:
			String myText = a.getString(attr);
			//...do something with myText...
			break;
		case R.styleable.MyCustomWidget_fancyColors:
			boolean fancyColors = a.getBoolean(attr, false);
			//...do something with fancyColors...
			break;
		case R.styleable.MyCustomWidget_onAction:
			String onAction = a.getString(attr);
			//...we'll setup the callback in a bit...
			break;
	}
}
a.recycle();

The first call to obtainStyledAttributes passes in the AttributeSet we get in the constructor, and we also pass in R.styleable.MyCustomWidget, which tells the system to just return those attributes we defined in our attrs.xml. Next, we simply loop over each of the attributes we get, then perform a switch on the index, which is really just an int we can tie back to the name in attrs.xml. The convention for the attribute ‘name’ or ‘index’ or whatever you want to call it is just:

R.styleable.ClassName_attributeName

Thus R.styleable.MyCustomWidget_myText, R.styleable.MyCustomWidget_fancyColors, etc. An alternative way to read out the values is

TypedArray a = context.obtainStyledAttributes(attrs,
	R.styleable.MyCustomWidget);

String myText = a.getString(R.styleable.MyCustomWidget_myText);
//...do something with myText...

boolean fancyColors = a.getBoolean(R.styleable.MyCustomWidget_fancyColors, false);
//...do something with fancyColors...

But I like the switch method because then we only try to read the attributes that were present in the XML. It may not be a big deal if you only have one or two attributes, but if you have a lot, it could save processing time to only read available attributes. Plus that is how Google does it in View…so until I have a firmer grasp of best practices in android than the creators, I’ll try to use their conventions.

Also, note this doesn’t really show you how to use an XML attribute as a callback, but we’ll see that later.

Step 3: Add the attributes to the XML

The final step is to add our new custom attributes to the layout XML:

<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:custom="http://schemas.android.com/apk/res/com.kdion.tutorial"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  android:orientation="vertical"
  >
	<com.kdion.tutorial.MyCustomWidget
		android:id="@+id/theId"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		custom:myText="Something"
		custom:fancyColors="true"
		custom:onAction="myDoSomething"
		/>
<!-- Other stuff -->
</LinearLayout>

The important line is the additional xmlns in the root element, right after xmlns:android. The url you pass as the argument is "http://schemas.android.com/apk/res/your.package.namespace". Note also that you can use anything after xmlns:, in this example I used custom, but you could use app, mine, foobar, whatever (well not whatever, but you get the idea). Just make sure you use the same prefix when you use your custom attributes in your widget.

And that’s it! You now have the ability to define and use your own custom XML attributes in your layouts.

Using a custom attribute for a callback

So, the original question is how you can setup a custom XML attribute to use as a method callback, like android:onClick="myMethod" for the stock controls. As we saw above, the first step is to simply define our desired attribute to accept a string (the method name we will call), but once we’ve read it in, what do we do? At first, I had no idea – but fortunately, Android already does this, and it is open source. So, it was simply a matter of tracking down where in the source code android deals with the onClick setting, and we can use that as a guide for how to handle our custom callback.

I finally found the relevant code in android.view.View. It is in the section of the constructor where it is reading the passed in attributes, dealing specifically with R.styleable.View_onClick. As of API 9 (2.3), you can see the code here. I won’t reproduce the whole thing here, since the case logic is pretty complicated. But, I will do my best to strip down the logic to pseudocode, so I can try to explain it, and you can follow along (with the actual source is best).

case R.styleable.View_onClick:
	if (context.isRestricted()) {
		throw IllegalStateException();
	}

	final String handlerName = a.getString(attr);
	if (handlerName != null) {
		setOnClickListener(new OnClickListener() {
			private Method mHandler;

			public void onClick(View v) {
				if (mHandler == null) {
					try {
						mHandler = getContext().getClass().getMethod(handlerName, View.class);
					} catch (NoSuchMethodException e) {
						throw IllegalStateException();
					}
				}

				try {
					mHandler.invoke(getContext(), View.this);
				} catch (IllegalAccessException e) {
					throw IllegalStateException();
				} catch (InvocationTargetException e) {
					throw IllegalStateException();
				}
			}
		});
	}
	break;

The first thing the code does is to check if the context (passed into the constructor) is restricted, and if so throws an exception. Otherwise, setOnClickListener is called, with an anonymous created OnClickListener. You would obviously replace this with your own listener set method and class.

The next important bit of code is when a Method is retrieved from the Context‘s Class (usually our Activity), with the name we passed into the xml attribute. The View.class parameter passed to getMethod is passed because the onClick methos takes a View as an argument. So if your callback method had no arguments, you wouldn’t pass a second argument into getMethod.

Finally, the Method is invoked, once again passing in the View as an argument. The code above is not really valid, and it’s missing a lot of information in the exception methods you wouldn;t want to take out in your own code.

But anyways, that’s how you would use your own custom xml attribute to define a callback you can set in xml!

8 Comments

  1. Knut
    January 30, 2011 at 6:09 pm |

    Perfect, thank you! My only suggestion for an improvement would be in Step 3, where I spent some time figuring out that “…/your.package.namespace” means the same as “…/path.to.the.R.in.your.gen.folder” but that’s probably just me being a newbie.

    Again, thank you, I owe you a beer!

  2. April 8, 2011 at 7:35 pm |

    Thanks Kevin, this was helpful.

    Also Thanks Knut! I was stuck on that for a long time :)

    I owe you a tall glass of cold milk. Mmmmm.

  3. shenglong
    September 12, 2011 at 8:02 pm |

    is it possible to create customfonts using this method ? this person http://stackoverflow.com/questions/2376250/custom-fonts-and-xml-layouts-android seems to have done it but i cant get the code to work >:(

  4. naveen
    March 6, 2012 at 2:09 am |

    hi,
    i tried it in myapplication but i getting null pointer exception
    following is my xml code:

    following is mycustom view:
    public class CustomTextView extends View {

    private int m_nColor;
    private Typeface m_tTypeface;
    private int m_nSize;
    private int m_nRotationAngle, m_nRotationW, m_nRotationH;
    private String m_szText;

    public CustomTextView(Context context) {
    super(context);
    // set default parameters
    Log.d(“dghgfhf”, “dfghgfh”);
    m_nColor = Color.WHITE;
    m_nSize = 14;
    m_nRotationAngle = 0;
    m_nRotationW = 0;
    m_nRotationH = 0;
    m_tTypeface = Typeface.create(“arial”, Typeface.NORMAL);
    }

    public CustomTextView(Context context, AttributeSet attrs) {
    super(context, attrs);
    }

    public CustomTextView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    }

    public void SetColor(int newcolor) {
    m_nColor = newcolor;
    this.invalidate();
    }

    public void SetTextSize(int newsize) {
    m_nSize = newsize;
    this.invalidate();
    }

    // style: normal-0,bold-1,italic-2,bold-italic-3,
    public void SetFont(String newfontface, int style) {
    m_tTypeface = Typeface.create(newfontface, style);
    this.invalidate();
    }

    public void SetRotation(int newangle, int neww, int newh) {
    m_nRotationAngle = newangle;
    m_nRotationW = neww;
    m_nRotationH = newh;
    this.invalidate();
    }

    public void SetText(String newtext) {
    m_szText = newtext;
    this.invalidate();
    }

    @Override
    protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    Paint paint = new Paint();
    paint.setTypeface(m_tTypeface);
    paint.setStyle(Paint.Style.FILL);
    paint.setColor(m_nColor);
    // paint.setShadowLayer(1, 0, 1, Color.parseColor(“#000000″));
    paint.setTextSize(m_nSize);
    canvas.rotate(m_nRotationAngle, m_nRotationW, m_nRotationH);
    canvas.drawText(m_szText, 0, 0, paint);

    }
    }
    Here i am getting null pointer exception please help me

  5. Joe
    March 20, 2012 at 10:53 am |

    Yes, for #3 you missed an important step. Why do most tutorials always miss something important?

  6. Sridhar
    April 16, 2012 at 2:39 am |

    Awesome post! Very helpful!

  7. Eric
    June 15, 2012 at 2:24 am |

    This tutorial was simply awesome! Much appreciated. Eric

  8. Vaishak
    September 5, 2012 at 8:43 am |

    Thank you very much. Should place a pointer to Java Reflection API for those who want to know how a method can be invoked just using its name and its parameters.

6 Trackbacks

  1. […] Dion’s article about custom XML attribute tags in […]

  2. […] couple of things you could do to make this component more useful as a standalone widget are to create your own XML attributes that can be used by your constructor, and making a custom listener that is called when the value is […]

  3. By Custom xml in android . « Android World on April 20, 2012 at 6:32 am
  4. […] else has written a fantastic tutorial on implementing custom XML attributes, so I refer you to their post. I would add a couple of prefatory comments to that […]

  5. […] You could allow more customisation of your widget using custom attributes. Check out that other blog. […]

  6. […] Maybe you can create custom attributes to use instead (like here: http://kevindion.com/2011/01/custom-xml-attributes-for-android-widgets/) […]


© 2010 Kevin Dion