Elegant way to use custom/standard attributes on Android
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()
}