1

I want to write a custom view that requires some button with a speicific id to be present (to be able to be found by id), as in ListActivity android.R.id.list must be present, how do i do the same with a custom id of my own?

I plan on reusing this view in an Android lib for several use cases, but all of them must declare some specific view with specific id so that i can find it by id in the lib code and manipulate it for later use in using Applications...

Ofek Ron
  • 7,601
  • 12
  • 47
  • 91
  • 1
    I suspect you'll have to [write a custom lint rule for this](http://tools.android.com/tips/lint-custom-rules). The example on the site seems to do exactly what you want. – Ali Dec 18 '13 at 18:17

2 Answers2

2

Just do what the ListActivity does.

Check for the ID in your custom view and throw an exception if it does not exist in the layout.

Snippet from ListActivity Source:

 @Override
public void onContentChanged() {
    super.onContentChanged();
    View emptyView = findViewById(com.android.internal.R.id.empty);
    mList = (ListView)findViewById(com.android.internal.R.id.list);
    if (mList == null) {
        throw new RuntimeException(
                "Your content must have a ListView whose id attribute is " +
                "'android.R.id.list'");
    }
Kuffs
  • 34,562
  • 10
  • 74
  • 91
  • but how? like findViewById(R.id.button)? but what if i dont have that button attribute? will it still work if i make one up in some useless layout file? – Ofek Ron Dec 18 '13 at 18:30
  • In your custom view, use findviewbyid to check if the view with the ID you expect has been included in the layout. Throw an exception if you do not find it. (If findviewbyid returns null) – Kuffs Dec 18 '13 at 18:33
  • but what would you give findViewById as a parameter? note that i dont know the exact integer value of the id, just the suffix of R.id. – Ofek Ron Dec 18 '13 at 18:38
  • I added an example to the answer – Kuffs Dec 18 '13 at 18:39
  • this is not answering my question – Ofek Ron Dec 18 '13 at 18:39
  • You said in your question that you are expecting a **specific** ID. Therefore I assume you already know the specific ID that you will be expecting otherwise it would not be specific. If you want to use a user definable ID then you would need to use a Styleable resource. http://stackoverflow.com/questions/2695646/declaring-a-custom-android-ui-element-using-xml – Kuffs Dec 18 '13 at 18:43
  • If you do want a specific ID, use one of the built in systems ones such as android.R.id.button1 – Kuffs Dec 18 '13 at 18:46
2

The best way to do this is in a flexible way is to use a custom attribute. It allows for any id to be the required id, but it also uses the dev tools to enforce that a valid id is used.

Declare that your custom view is style-able with a custom attribute in an attrs.xml file like this:

<resources>
    <declare-styleable name="MyView">
        <attr name="required_view_id" format="integer" />
    </declare-styleable>
</resources>

You can then refer to the attribute from a layout file like below. Pay special attention to the header where the "app" namespace is defined. You can use any name you want for your custom attribute namespace, but you have to declare it to use any of your custom attributes when defining the views later. Note the custom attribute on MyView.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <View
        android:id="@+id/the_id_of_the_required_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <com.full.package.to.MyView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:required_view_id="@id/the_id_of_the_required_view" />

</LinearLayout>

Now you need to make sure this view is present in your custom view class. You can required that your custom attribute is set by overriding certain constructors. You'll also need to actually verify the presence of the required view at some point. Here's a rough idea:

public class MyView extends View {

    private int mRequiredId;

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

    public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        checkForRequiredViewAttr(context, attrs);
    }

    // Verify that the required id attribute was set
    private void checkForRequiredViewAttr(Context context, AttributeSet attrs) {
        TypedArray a = context.obtainStyledAttributes(attrs,
                R.styleable.MyView, 0, 0);
        mRequiredId = a.getResourceId(R.styleable.MyView_required_view_id, -1);
        a.recycle();
        if (mRequiredId == -1) {
            throw new RuntimeException("No required view id attribute was set");
        }
    }

    // This allows the custom view to be programmatically instantiated, so long as
    // the required id is manually set before adding it to a layout
    public void setRequiredId(int id) {
        mRequiredId = id;
    }

    // Check for the existence of a view with the required id
    @Override
    protected void onAttachedToWindow() {
        View root = getRootView();
        View requiredView = root.findViewById(mRequiredId);
        if (requiredView == null) {
            throw new RuntimeException(
                    String.format("Cannot find view in layout with id of %s", mRequiredId));
        }
        super.onAttachedToWindow();
    }
}

Using onAttachedToWindow to check for the required view may not be good enough for your purposes. It won't, for example, prevent the required view from being removed. Finding a view in a layout isn't a cheap operation, especially for complex layouts, so you shouldn't constantly check for it.

Krylez
  • 15,934
  • 4
  • 29
  • 41