【问题标题】:Using <include> in custom compound view results in NullPointerException in onFinishInflate()在自定义复合视图中使用 <include> 会导致 onFinishInflate() 中出现 NullPointerException
【发布时间】:2016-04-04 12:24:20
【问题描述】:

我正在尝试实现一个简单的自定义拨号盘视图。在我尝试使用 &lt;include&gt; 为我的按钮包含一个“模板”而不是在 XML 文件中显式定义每个按钮之前,一切正常。因为我将拥有 12 个几乎相同的按钮,所以我想我可以通过这样做让我的 XML 更整洁、更短。

问题是在我的自定义视图中的onFinishInflate() 中,当我调用findViewById(R.id.dialpad_view_btn1) 时它返回null。我当然将 ID dialpad_view_btn1 分配给我的 &lt;include&gt; 之一。如果我只是明确定义按钮而不是从我的“模板”中包含它,它就可以正常工作。有什么解决方法吗,还是我应该接受findViewById()&lt;include&gt; 不能很好地配合?

DialPadView.java

public class DialPadView extends android.support.v7.widget.GridLayout {


    public DialPadView(Context context) {
        super(context);
        initializeViews(context);
    }

    public DialPadView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initializeViews(context);
    }

    public DialPadView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initializeViews(context);
    }

    private void initializeViews(Context context) {
        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        inflater.inflate(R.layout.dialpad_view, this);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();

        // This call to findViewById() results in a NullPointerException
        ((ImageButton) findViewById(R.id.dialpad_view_btn1)).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                // I'm just a dummy
            }
        });
    }

}

dialpad_view.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.GridLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    tools:context="com.me.myproject.DialPadView"
    xmlns:grid="http://schemas.android.com/apk/res-auto"
    android:id="@+id/dialpad_grid_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_centerHorizontal="true"
    grid:alignmentMode="alignMargins"
    grid:columnCount="3"
    grid:rowCount="4">

    <include
        android:id="@+id/dialpad_view_btn1"
        layout="@layout/dialpad_button" />

    <include
        android:id="@+id/dialpad_view_btn2"
        layout="@layout/dialpad_button" />


</android.support.v7.widget.GridLayout>

dialpad_button.xml

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
       xmlns:grid="http://schemas.android.com/apk/res-auto">

    <Button
        android:layout_width="0dp"
        android:layout_height="0dp"
        grid:layout_columnWeight="1"
        grid:layout_rowWeight="1"
        grid:layout_gravity="fill"
        android:layout_margin="5dp"
        android:scaleType="centerInside"
        android:text="Test" />

</merge>

【问题讨论】:

  • 尝试从dialpad_button.xml中删除&lt;merge&gt;标签,使&lt;Button&gt;成为该文件的根目录。
  • 在您的 xml 中使用 ,而不是
  • @pskink 那是 XML for DialPadView,而不是使用自定义视图的布局。
  • 我不明白你,那你在哪里创建 DialPadView?
  • @pskink 当然,在我的Activity 的布局中,我认为我不需要包含它,因为它与问题无关?你认为我也应该包括AndroidManifest.xmlbuild.gradle等?

标签: java android android-layout android-custom-view android-xml


【解决方案1】:

ID 问题:

你遇到的问题是&lt;include&gt;&lt;merge&gt;标签混合造成的。 请注意&lt;merge&gt; 有不同的用途,在这种情况下不应使用它,因此请仅使用&lt;incluce&gt; 并将&lt;Button&gt; 设置为您的 dialpad_button.xml 文件中的根。然后应该正确分配所有 id。

<?xml version="1.0" encoding="utf-8"?>
<Button xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:grid="http://schemas.android.com/apk/res-auto">
    android:layout_width="0dp"
    android:layout_height="0dp"
    grid:layout_columnWeight="1"
    grid:layout_rowWeight="1"
    grid:layout_gravity="fill"
    android:layout_margin="5dp"
    android:scaleType="centerInside"
    android:text="Test" />

详情:

如果您对此问题感到好奇,可以查看 LayoutInflater 源文件中的 parseInclude() 方法。您可以在那里找到处理&lt;merge&gt; 通货膨胀的代码。可以看到,&lt;include&gt; 的子代传递 layout 和 id 属性的代码没有被 &lt;merge&gt; 调用,因为 &lt;merge&gt; 应该用于不同的场景。那里:

if (TAG_MERGE.equals(childName)) {
    // Inflate all children.
    rInflate(childParser, parent, childAttrs, false);
} else {
    [...]

    // We try to load the layout params set in the <include /> tag. If
    // they don't exist, we will rely on the layout params set in the
    // included XML file.

    [...]

    // Attempt to override the included layout's android:id with the
    // one set on the <include /> tag itself.
    TypedArray a = mContext.obtainStyledAttributes(attrs,
        com.android.internal.R.styleable.View, 0, 0);
    int id = a.getResourceId(com.android.internal.R.styleable.View_id, View.NO_ID);
    // While we're at it, let's try to override android:visibility.

    [...]
}

其他问题:

这与您缺少 id 的问题并不严格相关,但正如 pskink 提到的,您在自定义 DialPadView 中错误地膨胀了 &lt;android.support.v7.widget.GridLayout&gt;,因此您最终会得到这样的层次结构:

<DialPadView>
    <android.support.v7.widget.GridLayout>
        <Button/>
        <Button/>
    </android.support.v7.widget.GridLayout>
</DialPadView>

但是&lt;DialPadView&gt; 已经是android.support.v7.widget.GridLayout 的一个实例,所以基本上你最终会在GridLayout 中拥有GridLayout。除了不是最佳之外,它还可能导致布局看起来与您想象的外观和行为方式不同。

【讨论】:

  • 这是我问题的答案。然而,事实证明这个问题是错误的。为了简化代码,我从使用ImageButton 而不是Button 进行了更改。当使用具有透明背景的ImageButtons 时,如果不以root 身份使用&lt;merge&gt;,按钮似乎不会显示。在进一步调查中,按钮 do 出现了,但它们没有缩放到GridLayout,并且图像也没有出现。我将开始一个新问题,因为现在问题不再是NullPointerException,现在是布局问题。谢谢。
  • 我已经更新了我的答案,专注于 pskink 提到的事情。即使这不是您在此问题中提到的NullPointerException 的原因,这仍然是错误的做法,您应该调查并修复它。
  • 我尝试使用 &lt;merge&gt; 作为我的 DialPadView 的根,但这破坏了 GridLayout 的功能 - 它的所有子项最终都在同一行,而不是在行和列中。我是否应该从GridLayout 以外的东西继承我的视图类,以避免有两个嵌套的 GridLayouts?当根元素和基类都是 GridLayout... 时,事情实际上会按预期工作
  • 两种解决方案: 1. 如果你想在你的 DialPadView 文件中膨胀孩子,你需要使用&lt;merge&gt; 标签而不是&lt;android.support.v7.widget.GridLayout&gt; 但是你会失去它的所有属性(合并不能通过属性),因此您需要将它们应用到活动 XML 文件中。 2. 只需将 &lt;android.support.v7.widget.GridLayout&gt; 替换为 XML 文件中的 DialPadView 类,并将其用作活动的一部分。然后不要在您的自定义类中膨胀子视图。它们已经在那里膨胀了。您可以创建一个新问题,因为此处的字符限制太小 :(
  • 我不能在我的 DialPadView 类中扩展 LinearLayout 并将 GridLayout 作为 XML 根吗?
猜你喜欢
  • 2017-05-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多