Reputation: 198238
I want to define a custom widget, which contains some other controls and has own logic. I want to use XML file to define the UI, and the java code to define the logic. But I don't know how to do it.
I followed this article Building mix-up custom Android component/widget using Java class and XML layout, but don't a working one. Following is my code.
XML:
<com.example.MyView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<Button android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="custom button 1"/>
<Button android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="custom button 2"/>
</com.example.MyView>
Java code:
public class MyView extends LinearLayout {
public MyView(Context context) {
super(context);
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
LayoutInflater.from(getContext()).inflate(R.layout.test_view, this); // **line 32**
}
}
And in the main layout, I invoke it like this:
<com.example.MyView
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</com.example.MyView>
Unfortunately, it throws an exception when running:
05-26 14:11:25.199: ERROR/AndroidRuntime(15799): FATAL EXCEPTION: main
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example/com.example.MyActivity}: android.view.InflateException: Binary XML file line #7: Error inflating class <unknown>
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1651)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1667)
at android.app.ActivityThread.access$1500(ActivityThread.java:117)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:935)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:130)
at android.app.ActivityThread.main(ActivityThread.java:3687)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:507)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:867)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:625)
at dalvik.system.NativeStart.main(Native Method)
Caused by: android.view.InflateException: Binary XML file line #7: Error inflating class <unknown>
at android.view.LayoutInflater.createView(LayoutInflater.java:518)
at com.android.internal.policy.impl.PhoneLayoutInflater.onCreateView(PhoneLayoutInflater.java:56)
at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:568)
at android.view.LayoutInflater.rInflate(LayoutInflater.java:623)
at android.view.LayoutInflater.inflate(LayoutInflater.java:408)
at android.view.LayoutInflater.inflate(LayoutInflater.java:320)
at android.view.LayoutInflater.inflate(LayoutInflater.java:276)
at com.example.MyView.onFinishInflate(MyView.java:32)
at android.view.LayoutInflater.rInflate(LayoutInflater.java:631)
at android.view.LayoutInflater.inflate(LayoutInflater.java:408)
at android.view.LayoutInflater.inflate(LayoutInflater.java:320)
at android.view.LayoutInflater.inflate(LayoutInflater.java:276)
at com.example.MyView.onFinishInflate(MyView.java:32)
at android.view.LayoutInflater.rInflate(LayoutInflater.java:631)
at android.view.LayoutInflater.inflate(LayoutInflater.java:408)
at android.view.LayoutInflater.inflate(LayoutInflater.java:320)
at android.view.LayoutInflater.inflate(LayoutInflater.java:276)
at com.example.MyView.onFinishInflate(MyView.java:32)
at android.view.LayoutInflater.rInflate(LayoutInflater.java:631)
at android.view.LayoutInflater.inflate(LayoutInflater.java:408)
at android.view.LayoutInflater.inflate(LayoutInflater.java:320)
at android.view.LayoutInflater.inflate(LayoutInflater.java:276)
at com.example.MyView.onFinishInflate(MyView.java:32)
at android.view.LayoutInflater.rInflate(LayoutInflater.java:631)
at android.view.LayoutInflater.inflate(LayoutInflater.java:408)
at android.view.LayoutInflater.inflate(LayoutInflater.java:320)
at android.view.LayoutInflater.inflate(LayoutInflater.java:276)
....
Upvotes: 4
Views: 2361
Reputation: 87064
You can't declare the custom view like you did. If you use your custom view in the main layout the onFinishInflate
method will be called to construct the custom view and in that method the layout above will be called. The problem is that you already have a custom view reference in that layout(which will be inflated again when the LayoutInflater
encounters the custom view tag) so you'll get in a circular inflating situation.
You probably want something like this:
<merge xmlns:android="http://schemas.android.com/apk/res/android>
<Button android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="custom button 1"/>
<Button android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="custom button 2"/>
</merge>
so when your custom view will be inflated from a layout file it will append the content of the xml layout above. The onFinishInflate
method will remain the same:
@Override
protected void onFinishInflate() {
super.onFinishInflate();
LayoutInflater.from(getContext()).inflate(R.layout.test_view, this); // **line 32**
}
Edit: It appears that inflating a content layout in the onFinishInflate
method triggers the method again(at least on older versions). You could either modify the layout to use a LinearLayout
like this:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="custom button 1"/>
<Button android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="custom button 2"/>
</LinearLayout>
and then in the onFinishInflate
method:
@Override
protected void onFinishInflate() {
super.onFinishInflate();
View v = LayoutInflater.from(getContext()).inflate(R.layout.aaaaaaaaaaa, this, false);
addView(v);
}
This will introduce another useless element in the layout hierarchy so you might simply inflate the layout with merge
tag in the constructors of the custom view:
public MyView(Context context) {
super(context);
LayoutInflater.from(getContext()).inflate(R.layout.test_view, this);
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(getContext()).inflate(R.layout.test_view, this);
}
public MyView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
LayoutInflater.from(getContext()).inflate(R.layout.test_view, this);
}
Upvotes: 4