Defining Custom Attrs

Defining custom attrs

Currently the best documentation is the source. You can take a look at it here (attrs.xml).

You can define attributes in the top <resources> element or inside of a <declare-styleable> element. If I'm going to use an attr in more than one place I put it in the root element. Note, all attributes share the same global namespace. That means that even if you create a new attribute inside of a <declare-styleable> element it can be used outside of it and you cannot create another attribute with the same name of a different type.

An <attr> element has two xml attributes name and format. name lets you call it something and this is how you end up referring to it in code, e.g., R.attr.my_attribute. The format attribute can have different values depending on the 'type' of attribute you want.

  • reference - if it references another resource id (e.g, "@color/my_color", "@layout/my_layout")
  • color
  • boolean
  • dimension
  • float
  • integer
  • string
  • fraction
  • enum - normally implicitly defined
  • flag - normally implicitly defined

You can set the format to multiple types by using |, e.g., format="reference|color".

enum attributes can be defined as follows:

<attr name="my_enum_attr">
<enum name="value1" value="1" />
<enum name="value2" value="2" />
</attr>

flag attributes are similar except the values need to be defined so they can be bit ored together:

<attr name="my_flag_attr">
<flag name="fuzzy" value="0x01" />
<flag name="cold" value="0x02" />
</attr>

In addition to attributes there is the <declare-styleable> element. This allows you to define attributes a custom view can use. You do this by specifying an <attr> element, if it was previously defined you do not specify the format. If you wish to reuse an android attr, for example, android:gravity, then you can do that in the name, as follows.

An example of a custom view <declare-styleable>:

<declare-styleable name="MyCustomView">
<attr name="my_custom_attribute" />
<attr name="android:gravity" />
</declare-styleable>

When defining your custom attributes in XML on your custom view you need to do a few things. First you must declare a namespace to find your attributes. You do this on the root layout element. Normally there is only xmlns:android="http://schemas.android.com/apk/res/android". You must now also add xmlns:whatever="http://schemas.android.com/apk/res-auto".

Example:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:whatever="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">

<org.example.mypackage.MyCustomView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center"
whatever:my_custom_attribute="Hello, world!" />
</LinearLayout>

Finally, to access that custom attribute you normally do so in the constructor of your custom view as follows.

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

TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyCustomView, defStyle, 0);

String str = a.getString(R.styleable.MyCustomView_my_custom_attribute);

//do something with str

a.recycle();
}

The end. :)

How do you define custom attributes for the LayoutParams class of a custom RecyclerView.LayoutManager implementation?

In the custom LayoutParams constructor, the obtainStyledAttributes() call must include the AttributeSet passed in. Otherwise, it's pulling values from just the Context's theme, and those attribute values specified in the layout XML won't be included in the returned TypedArray.

For example:

TypedArray styledAttrs =
context.obtainStyledAttributes(attrs, R.styleable.ScrollableGridLayoutManager_LayoutParams);

Android - custom UI with custom attributes

Yes. Short guide:

1. Create an attribute XML

Create a new XML file inside /res/values/attrs.xml, with the attribute and it's type

<?xml version="1.0" encoding="UTF-8"?>
<resources>
<declare-styleable name="MyCustomElement">
<attr name="distanceExample" format="dimension"/>
</declare-styleable>
</resources>

Basically you have to set up one <declare-styleable /> for your view that contains all your custom attributes (here just one). I never found a full list of possible types, so you need to look at the source for one I guess. Types that I know are reference (to another resource), color, boolean, dimension, float, integer and string. They are pretty self-explanatory

2. Use the attributes in your layout

That works the same way you did above, with one exception. Your custom attribute needs it's own XML namespace.

<com.example.yourpackage.MyCustomElement
xmlns:customNS="http://schemas.android.com/apk/res/com.example.yourpackage"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Element..."
customNS:distanceExample="12dp"
/>

Pretty straight forward.

3. Make use of the values you get passed

Modify the constructor of your custom view to parse the values.

public MyCustomElement(Context context, AttributeSet attrs) {
super(context, attrs);

TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MyCustomElement, 0, 0);
try {
distanceExample = ta.getDimension(R.styleable.MyCustomElement_distanceExample, 100.0f);
} finally {
ta.recycle();
}
// ...
}

distanceExample is a private member variable in this example. TypedArray got lot's of other things to parse other types of values.

And that's it. Use the parsed value in your View to modify it, e.g. use it in onDraw() to change the look accordingly.

Defining multiple custom attributes on a custom HTML Element

use Object.defineProperty( element , attrName , { getter , setter , options} )

Add something like this to your BaseClass (so this is your Custom Element)

  defineProperty(
attr,
getter = (attr) => this.getAttribute(attr),
setter = (val, attr) => (val == undefined ? this.removeAttribute(attr) : this.setAttribute(attr, val)),
options = {
configurable: true,
}
) {
return Object.defineProperty(this, attr, {
get() {
return getter(attr);
},
set(val) {
return setter(val, attr);
},
...options,
});
}

See:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

Plural:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperties

Can someone explain the attr?

The ?attr/menuIconCamera value means that an icon from menuIconCamera attribute of the current theme will be used.

There must be a drawable assigned to the menuIconCamera attribute somewhere in the themes.xml file. If there're two themes with different values of this attribute then actual icon will depend on a theme which is currently used.

The attrs.xml file is used to define custom attributes. Without this definition compiler will treat unknown attributes as erroneous.

How to set view attribute from custom attr on a custom Preference class?

I solved my problem. As Martin pointed out a styleable is not really intended for strings anyways. As it turns out, using the built-in android:title attribute instead of a custom styleable made things really easy. All I have to do is call this.getTitle() to access its value from my CustomPreference class.

public class CustomPreference extends Preference {

public CustomPreference(Context context, AttributeSet attrs,
int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
setLayoutResource(R.layout.preference_widget_custom);
}

// other required constructor overloads omitted

@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
setPreferenceTitle();
}

private void setPreferenceTitle() {
((SpecialCustomComponent) holder.itemView).setText(this.getTitle());
}
}

Define style and custom attribute in View instead of xml

Yep, you can use the default style attribute in the constructor.

Define a new attribute:

<attr name="customButtonStyle" format="reference" />

Then use the appropriate view constructor:

private val defStyleAttr = R.attr.customButtonStyle

class CustomButton(context: Context, attrs: AttributeSet) : MaterialButton(
ThemeEnforcement.createThemedContext(context, attributeSet, defStyleAttr, 0),
attributeSet,
defStyleAttr
) {

init {
val typedArray = context.obtainStyledAttributes(
attrs,
R.styleable.CustomButton,
defStyleAttr,
0
)
...

}

In your theme, point customButtonStyle to the style resource that you want used by default:

<style name="Theme.Demo" parent="Base.Theme.Demo">
<item name="customButtonStyle">@style/Widget.Demo.Button.Primary</item>
</style>

Note that android:theme should be changed to materialThemeOverlay in that style resource, as it won't be applied when read from a default style. As you're already wrapping the context with the ThemeEnforcement function (newer versions of Material Design Components change this to MaterialThemeOverlay), this custom view supports materialThemeOverlay /p>


You can add your custom attributes to the style too:

<style name="Widget.Demo.Button.Primary" parent="@style/Widget.MaterialComponents.Button">
<item name="abc">primary</item>
<item name="fontFamily">@font/roboto</item>
<item name="android:fontFamily">@font/roboto</item>
<item name="android:minHeight">64dp</item>
<item name="materialThemeOverlay">@style/ThemeOverlay.Demo.GrayPrimary</item>
</style>

Reference (blog + video)

Custom view custom attribute not being set without @{}

Mike's comments answer the confusion I had about the different syntaxes.

What I ended up doing ultimately is to inherit the existing android TextView. TextView has a bunch of stuff I needed like font, font size etc, so it was just simpler to inherit rather than re-implement it myself.



Related Topics



Leave a reply



Submit