14

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> 

5 Answers 5

18

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> 
Sign up to request clarification or add additional context in comments.

6 Comments

Eclipse tells me that there is no LayoutInflater.Factory
@Ragnar It's been there since API 1 (developer.android.com/reference/android/view/…). Did you add the proper import for it?
found it, i just had to import android.view.LayoutInflater
Interesting answer, but why is it more efficient than using fully qualified name? edit: nevermind, you answered below Marcin's answer. Without this reflection will be used, which would be - albeit tiny - improvement
@wasyl Just avoids using reflection to look up the constructor (since you invoke it directly this way). Not a huge difference in the scheme of things since I believe it caches the constructors for a while anyway. I wouldn't implement this just for the sake of efficiency. :)
|
2

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.

Comments

2

You can also do this:

<view class="com.wehavelongdomainname.android.ui.MyButton" ... /> 

cf. http://developer.android.com/guide/topics/ui/custom-components.html#modifying

3 Comments

Really? This works on Lollipop too? That's a new one :)
@milosmns I don't think it's new in general. (Perhaps you meant new to you.)
Yeah, didn't use it before. Good one!
0

None of the answers here work with Android Studio layout designer preview. I tried to put my custom view classes in the android.widget package and that compiles and run put still the designer preview is not happy with that.

In the end I just put my custom view classes in the view package so that in the XML it looks like:

<view.FancyWidget /> 

That's short enough for me.

Comments

-1

No. You need to to give "full path" to your class, otherwise framework will not be able to inflate your layout.

3 Comments

I was thinking it might be possible to define a resource in xml that would have this class as a parent. Is it not?
@kcoppock answer shows how to help framework with the search, but I am far from seeing any real benefit here. Rather mainteance complication.
@MarcinOrlowski Internally, reflection will be used to inflate a custom view if you don't provide a LayoutInflater, so I would say that this should improve performance (although I doubt it would be a noticeable improvement), but I agree that it does add some overhead.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.