CustomView无法实例化(编辑器预览错误,编译后无显示)

CustomView couldn't be instantiated (error in editor preview and nothing shown after compile)

所以,我创建了一个名为 ButtonWithCaption.xml

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

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_weight="1"
    android:background="?attr/selectableItemBackground"
    android:paddingBottom="@dimen/spacing_normal"
    android:paddingLeft="@dimen/spacing_small"
    android:paddingRight="@dimen/spacing_small"
    android:paddingTop="@dimen/spacing_normal">

<ImageView
    android:id="@+id/bwc_icon"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerHorizontal="true"
    android:layout_marginBottom="2dp"
    android:scaleType="centerCrop" />

<TextView
    android:id="@+id/bwc_caption"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_below="@id/bwc_icon"
    android:textAlignment="center" />

及其控制器ButtonWithCaption.java

public class ButtonWithCaption extends RelativeLayout {
    private ImageView mImageView;
    private TextView mTextView;
    private int mIconSrc;
    private String mCaptionText;
    private boolean mShowIcon;
    private boolean mShowText;

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

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

    public ButtonWithCaption(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs, defStyleAttr);
    }

    private void init(Context context, AttributeSet attrs, int defStyleAttr) {
        TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ButtonWithCaption, 0, 0);

        try {
            mIconSrc = a.getResourceId(R.styleable.ButtonWithCaption_iconSrc, 0);
            mCaptionText = a.getString(R.styleable.ButtonWithCaption_captionText);
            mShowIcon = a.getBoolean(R.styleable.ButtonWithCaption_showIcon, mIconSrc != 0);
            mShowText = a.getBoolean(R.styleable.ButtonWithCaption_showText, !mCaptionText.isEmpty());

            mImageView = (ImageView) findViewById(R.id.bwc_icon);
            mTextView = (TextView) findViewById(R.id.bwc_caption);

            mImageView.setImageResource(mIconSrc);
            mTextView.setText(mCaptionText);
        } finally {
            a.recycle();
        }
    }
}

没什么特别的。

然后,我在 attrs.xml

中声明自定义属性
<resources>
    <declare-styleable name="ButtonWithCaption">
        <attr name="iconSrc" format="reference" />
        <attr name="captionText" format="string" />
        <attr name="showIcon" format="boolean" />
        <attr name="showText" format="boolean" />
    </declare-styleable>
</resources>

这是我的 activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="id.foodmap.foodmapcustomviews.MainActivity">

    <id.foodmap.foodmapcustomviews.ButtonWithCaption
        android:id="@+id/button_with_caption"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:captionText="Test"
        app:iconSrc="@drawable/ic_email_gray_24dp" />
</RelativeLayout>

...但奇怪的是,Android Studio 的预览给了我渲染错误:

Failed to instantiate one or more classes

使用堆栈跟踪:

java.lang.NullPointerException at id.foodmap.foodmapcustomviews.ButtonWithCaption.init(ButtonWithCaption.java:54) at id.foodmap.foodmapcustomviews.ButtonWithCaption.(ButtonWithCaption.java:29) at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:423) at org.jetbrains.android.uipreview.ViewLoader.createNewInstance(ViewLoader.java:475) at org.jetbrains.android.uipreview.ViewLoader.loadClass(ViewLoader.java:262) at org.jetbrains.android.uipreview.ViewLoader.loadView(ViewLoader.java:220) at com.android.tools.idea.rendering.LayoutlibCallbackImpl.loadView(LayoutlibCallbackImpl.java:186) at android.view.BridgeInflater.loadCustomView(BridgeInflater.java:334) at android.view.BridgeInflater.loadCustomView(BridgeInflater.java:345) at android.view.BridgeInflater.createViewFromTag(BridgeInflater.java:245) at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:727) at android.view.LayoutInflater.rInflate_Original(LayoutInflater.java:858) at android.view.LayoutInflater_Delegate.rInflate(LayoutInflater_Delegate.java:70) at android.view.LayoutInflater.rInflate(LayoutInflater.java:834) at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:821) at android.view.LayoutInflater.inflate(LayoutInflater.java:518) at android.view.LayoutInflater.inflate(LayoutInflater.java:397) at com.android.layoutlib.bridge.impl.RenderSessionImpl.inflate(RenderSessionImpl.java:324) at com.android.layoutlib.bridge.Bridge.createSession(Bridge.java:429) at com.android.ide.common.rendering.LayoutLibrary.createSession(LayoutLibrary.java:368) at com.android.tools.idea.rendering.RenderTask.compute(RenderTask.java:567) at com.android.tools.idea.rendering.RenderTask.compute(RenderTask.java:549) at com.intellij.openapi.application.impl.ApplicationImpl.runReadAction(ApplicationImpl.java:863) at com.android.tools.idea.rendering.RenderTask.createRenderSession(RenderTask.java:549) at com.android.tools.idea.rendering.RenderTask.lambda$inflate(RenderTask.java:680) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:745)

我的 findViewById()ButtonWithCaption.java 找到 TextViewImageView 也 returns null.

有人知道这是什么原因造成的吗?因为它甚至没有出现在编辑器预览中 window。

P.S。如果我使用,预览会正确显示元素。 P.S.S.它编译成我的 phone 没有错误,但没有显示 ButtonWithCaption

提前致谢

您必须将标签 <RelativeLayout> 替换为您的 class,如下所示:

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

<com.yourpackage.app.ButtonWithCaption xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    ...
    >
        ...
</com.yourpackage.app.ButtonWithCaption>

com.yourpackage.app 应该是你的 ButtonWithCaptionclass 所在的路径。

编辑:

A您发布的布局应该是您的 activity 布局,ButtonWithCaptionclass 应该是您的 xml 元素,而不是您的 xml 名称,所以更改名称,替换RelativeLayout 与您的自定义 class 并试一试。

您得到 NullPointerException 因为您没有夸大 ButtonWithCaption.xml 的观看次数。解决此问题的一种方法是使用静态 View.inflate() 方法:

 private void init(Context context, AttributeSet attrs, int defStyleAttr) {
    TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ButtonWithCaption, 0, 0);

    try {
        mIconSrc = a.getResourceId(R.styleable.ButtonWithCaption_iconSrc, 0);
        mCaptionText = a.getString(R.styleable.ButtonWithCaption_captionText);
        mShowIcon = a.getBoolean(R.styleable.ButtonWithCaption_showIcon, mIconSrc != 0);
        mShowText = a.getBoolean(R.styleable.ButtonWithCaption_showText, !mCaptionText.isEmpty());

        View v = View.inflate(context, R.layout.ButtonWithCaption, this);   // add this line
        mImageView = (ImageView) findViewById(R.id.bwc_icon);
        mTextView = (TextView) findViewById(R.id.bwc_caption);

        mImageView.setImageResource(mIconSrc);
        mTextView.setText(mCaptionText);
    } finally {
        a.recycle();
    }
}