Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using custom Views in XML without using fully-qualified class name

I have my own style for buttons defined as themes but I also use my own class to handle buttons (because of own fonts). Is it possible to call my button with a pretty name such as

<MyButton>

instead of

<com.wehavelongdomainname.android.ui.MyButton>
like image 604
Ragnar Avatar asked Jul 04 '13 20:07

Ragnar


2 Answers

So the answer, surprisingly, is "yes". I learned about this recently, and it's actually something you can do to make your custom view inflation more efficient. IntelliJ still warns you that its invalid (although it will compile and run successfully) -- I'm not sure whether Eclipse warns you or not.

Anyway, so what you'll need to do is define your own subclass of LayoutInflater.Factory:

public class CustomViewFactory implements LayoutInflater.Factory {
    private static CustomViewFactory mInstance;

    public static CustomViewFactory getInstance () {
        if (mInstance == null) {
            mInstance = new CustomViewFactory();
        }

        return mInstance;
    }

    private CustomViewFactory () {}

    @Override
    public View onCreateView (String name, Context context, AttributeSet attrs) {
        //Check if it's one of our custom classes, if so, return one using
        //the Context/AttributeSet constructor
        if (MyCustomView.class.getSimpleName().equals(name)) {
            return new MyCustomView(context, attrs);
        }

        //Not one of ours; let the system handle it
        return null;
    }
}

Then, in whatever activity or context in which you're inflating a layout that contains these custom views, you'll need to assign your factory to the LayoutInflater for that context:

public class CustomViewActivity extends Activity {
    public void onCreate (Bundle savedInstanceState) {
        //Get the LayoutInflater for this Activity context
        //and set the Factory to be our custom view factory
        LayoutInflater.from(this).setFactory(CustomViewFactory.getInstance());

        super.onCreate(savedInstanceState);
        setContentView(R.layout.layout_with_custom_view);
    }
}

You can then use the simple class name in your XML:

<?xml version="1.0" encoding="utf-8"?>

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
             android:orientation="vertical"
             android:layout_width="match_parent"
             android:layout_height="match_parent">

    <MyCustomView
        android:id="@+id/my_view"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:layout_gravity="center_vertical" />

</FrameLayout>
like image 188
Kevin Coppock Avatar answered Sep 19 '22 14:09

Kevin Coppock


Defining your own subclass of LayoutInflater.Factory seems a lot of work me. Simply override the Activity's onCreateView() with some generic code:

@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {

    View view;

    // No need wasting microseconds getting the inflater every time.
    // This method gets called a great many times.
    // Better still define these instance variables in onCreate()
    if (mInflator == null){

        mInflator = LayoutInflater.from(context);

        mPrefix = ((Activity) context).getComponentName().getClassName();

        // Take off the package name including the last period
        // and look for custom views in the same directory.
        mPrefix = mPrefix.substring(0, mPrefix.lastIndexOf(".")+1);
    }

    // Don't bother if 'a path' is already specified.
    if (name.indexOf('.') > -1) return null;

    try{

        view = mInflator.createView(name, mPrefix, attrs);

    } catch (ClassNotFoundException e) {

        view = null;

    } catch (InflateException e) {

        view = null;
    }

    // Returning null is no big deal. The super class will continue the inflation.
    return view;
}

Note the custom views must reside in the same package (i.e. in the same directory) as this activity, but then it's just a generic piece of code you can slap in any activity (or even better, inherit from a custom parent activity class). You're not worried about looking out for a particular class as specified in the solution offered by kcoppock:

if (MyCustomView.class.getSimpleName().equals(name)) {....

You're certainly not creating a whole new class.

The real magic is in the core library class, LayoutInflator.java. See the call, mPrivateFactory.onCreateView(), below?:

    if (view == null && mPrivateFactory != null) {
        view = mPrivateFactory.onCreateView(parent, name, mContext, attrs);
    }

    if (view == null) {
        if (-1 == name.indexOf('.')) {
            view = onCreateView(parent, name, attrs);
        } else {
            view = createView(name, null, attrs);
        }
    }

You see, if the so called, mPrivateFactory, returns null (mPrivateFactory happens to be your activity class by the way), the LayoutInflator just carries on with it's other alternative approach and continues the inflation:

if (view == null) {
   if (-1 == name.indexOf('.')) {
      view = onCreateView(parent, name, attrs);
   } else {
      view = createView(name, null, attrs);
   }
}

It's a good idea to 'walk through' the library classes with your IDE debugger and really see how Android works. :)

Notice the code,if (-1 == name.indexOf('.')) {, is for you guys who still insist on putting in the full path with your custom views, <com.wehavelongdomainname.android.ui.MyButton> If there is a 'dot' in the name, then the creatview() is called with the prefix (the second parameter) as null: view = createView(name, null, attrs);

Why I use this approach is because I have found there were times when the package name is moved (i.e. changed) during initial development. However, unlike package name changes performed within the java code itself, the compiler does not catch such changes and discrepancies now present in any XML files. Using this approach, now it doesn't have to.

Cheers.

like image 35
Drawn Avatar answered Sep 19 '22 14:09

Drawn