Elegant way to use custom/standard attributes on Android

Joseph Cheng
KKday Tech Blog
Published in
2 min readMay 6, 2018

Original way:

You need to get/set attribute one by one.

// standard attributes
val textSet = intArrayOf(android.R.attr.text)
val ta = context.obtainStyledAttributes(attrs, textSet)
val text = ta.getString(0)
setText(text)
ta.recycle()
val backgroundSet = intArrayOf(android.R.attr.background)
val ba = context.obtainStyledAttributes(attrs, backgroundSet)
val background = ba.getDrawable(0)
setBackground(background)
ba.recycle()

// custom attributes
val ca = context.obtainStyledAttributes(attrs, R.styleable.MyCustomView)

val title = ca.getString(R.styleable.MyCustomView_title)
setTitle(title)

val titleColor = ca.getColor(R.styleable.MyCustomView_titleColor, Color.BLACK)
setTitleColor(titleColor)

ca.recycle()

Elegant way:

Use an array to include attributes you want to get and set

val set = intArrayOf(
android.R.attr.background,
android.R.attr.text,
R.styleable.MyCustomView[R.styleable.MyCustomView_title],
R.styleable.MyCustomView[R.styleable.MyCustomView_titleColor]
)
// for android limitation, before finding attrs, you need to sort all attrs you want to find in ascending order, otherwise some attrs cannot be found
// https://developer.android.com/reference/android/content/res/Resources.Theme.html#obtainStyledAttributes(android.util.AttributeSet, int[], int, int)
val sorted = set.apply { sort() }
val sa = context.obtainStyledAttributes(attrs, sorted)
val background = sa.getDrawable(sorted.indexOf(android.R.attr.background))
setBackground(background)

val text = sa.getText(sorted.indexOf(android.R.attr.text))
setText(text)

val title = sa.getText(sorted.indexOf(R.styleable.MyCustomView[R.styleable.MyCustomView_title]))
setTitle(title)

val titleColor = sa.getColor(sorted.indexOf(R.styleable.MyCustomView[R.styleable.MyCustomView_titleColor]))
setTitleColor(titleColor)

sa.recycle()

More elegant way:

Define a map, whose key is attribute you want to find, value is a function that you want to do after finding attribute.

val map = mapOf(
android.R.attr.background to
fun (a: TypedArray, i: Int) { background = a.getDrawable(i) },

android.R.attr.text to
fun (a: TypedArray, i: Int) = setText(a.getString(i)),

R.styleable.MyCustomView[R.styleable.MyCustomView_title] to
fun (a: TypedArray, i: Int) = setTitle(a.getString(i)),

R.styleable.MyCustomView[R.styleable.MyCustomView_titleColor] to
fun (a: TypedArray, i: Int) = setTitleColor(a.getColor(i, Color.BLACK))
)
setupAttrs(context, attrs, map)
/**
*
@param context application/activity context
*
@param attrs The attributes of the XML tag that is inflating the view.
*
@param map Map<Int, (TypedArray, Int) -> Unit>
* <Int> the attr resource id that you want to find in attrs,
* <(TypedArray, Int) -> Unit>, A callback function after finding specific attr resource id
* <TypedArray> contains real attribute values, <Int> is index in <TypedArray>
*/
fun setupAttrs(
context: Context, attrs: AttributeSet, map: Map<Int, (TypedArray, Int) -> Unit>
) {
// for android limitation, you need to sort all attrs you want to find in ascending order
// then find attrs, otherwise some attrs cannot be found
// https://developer.android.com/reference/android/content/res/Resources.Theme.html#obtainStyledAttributes(android.util.AttributeSet, int[], int, int)
val sorted = map.toSortedMap()
val set = sorted.keys.toIntArray().apply { sort() }

// extract out attribute from your layout xml
val a = context.obtainStyledAttributes(attrs, set)

(0 until a.indexCount)
.map(a::getIndex)
.forEach {
// map to what you want to do
sorted[set[it]]?.invoke(a, it)
}

a.recycle()
}

--

--