Acquire a custom view's xml id

Viewed 572

I have a custom view ZoneSwitchItem (extends LinearLayout) which I use in a fragment layout xml.

From inside the custom view, I need to get the id that was assigned to it in the fragment xml. So I use attrs.getIdAttribute(); but it returns null instead of the expected id zone1.

I could add a custom attribute ZoneSwitchItemId but would like to avoid that if I could use the default id attribute.

The usage in the fragment xml:

<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/white">

    <com.android.common.ZoneSwitchItem
        android:id="@+id/zone1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/cardPadding4x"
        android:gravity="center_horizontal"
        app:zoneItemStyle="ArmourFont_HeadlineBig" />

    ...

The custom view:

public class ZoneSwitchItem extends LinearLayout {

    private TextView itemValue;
    private TextView itemTitle;

    public ZoneSwitchItem(Context context) {
        super(context);
        init(context, null);
    }

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

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

    public void onStart() {
        EventBus.getDefault().register(this);
    }

    public void onStop() {
        EventBus.getDefault().unregister(this);
    }

    private void init(final Context context, AttributeSet attrs) {
        inflate(getContext(), R.layout.zone_switch_item, this);

        itemValue = findViewById(R.id.itemValue);
        itemTitle = findViewById(R.id.itemTitle);

        if (attrs != null) {
            TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ZoneSwitchItem, 0, 0);
            try {
                if (typedArray.getString(R.styleable.ZoneSwitchItem_zoneItemStyle) != null &&
                        typedArray.getString(R.styleable.ZoneSwitchType_zoneItemImage).equals("ArmourFont_HeadlineBig")) {
                    itemValue.setTextAppearance(context, R.style.ArmourFont_HeadlineBig);
                }
            } finally {
                typedArray.recycle();
            }
        }

        final String id = attrs.getIdAttribute(); // <<== returns null
        itemValue.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                EventBus.getDefault().post(new OnZoneSwitchItemClickedEvent(id));
            }
        });
    }

    public void setSelected(boolean selected) {
        float alpha;

        if (selected) {
            alpha = 1.0f;
        } else {
            alpha = 0.5f;
        }

        itemValue.setAlpha(alpha);
        itemTitle.setAlpha(alpha);
    }
}
1 Answers

The documentation for the AttributeSet#getIdAttribute() method states:

Return the value of the "id" attribute or null if there is not one. Equivalent to getAttributeValue(null, "id").

The first parameter in the getAttributeValue() method is the namespace for the attribute. This is where the problem is, as null signifies no namespace, but the android:id attribute is in the namespace that the android prefix represents – http://schemas.android.com/apk/res/androi‌​d.

This means that the getIdAttribute() method will only return the value for an id attribute with no prefix. That is:

<com.android.common.ZoneSwitchItem
    id="@+id/zone1"
    ... />

I'm not sure how useful this method is, since, with the example above, it will actually return the complete string there (@+id/zone1), you'd still have to have a separate android:id attribute to get the View properly assigned an ID, and Android Studio will certainly complain that the attribute lacks a namespace prefix.

The first solution that might come to mind is to simply call getAttributeValue() directly, and pass the correct namespace.

String id = attrs.getAttributeValue("http://schemas.android.com/apk/res/android", "id");

However, this will return the integer ID value in string form, prepended with @, as it seems that aapt directly substitutes that value in the XML during its processing.

The actual, simple resource name can be obtained from Resources, with its getResourceEntryName() method, and the View's getId() method, since the ID will be set in View's constructor during the super call. For example:

String id = getResources().getResourceEntryName(getId());
Related