在自定义复合视图中使用 <include> 会导致 onFinishInflate() 中的 NullPointerException
Using <include> in custom compound view results in NullPointerException in onFinishInflate()
我正在尝试实现一个简单的自定义拨号盘视图。一切正常,直到我尝试使用 <include>
为我的按钮包含一个 "template",而不是在 XML 文件中明确定义每个按钮。因为我将有 12 个几乎相同的按钮,所以我想通过这样做可以让我的 XML 更简洁一些。
问题是在 onFinishInflate()
我的自定义视图中,当我调用 findViewById(R.id.dialpad_view_btn1)
时它 returns 为空。我当然将 ID dialpad_view_btn1
分配给我的 <include>
之一。如果我只是明确定义按钮而不是从我的 "template" 中包含它,它就可以正常工作。对此有任何解决方法,还是我应该接受 findViewById()
和 <include>
不能一起玩?
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>
ID 问题:
您遇到的问题是由于混用了<include>
和<merge>
标签造成的。
请注意 <merge>
有不同的用途,在这种情况下不应使用它,因此请仅使用 <incluce>
并使 <Button>
成为 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() 方法。您可以在那里找到处理 <merge>
inflation 的代码。可以看到,<include>
的子级传递 layout 和 id 属性的代码没有为 <merge>
调用,因为 <merge>
应该用于不同的场景。那里:
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
中错误地膨胀了 <android.support.v7.widget.GridLayout>
,因此您最终会得到这样的层次结构:
<DialPadView>
<android.support.v7.widget.GridLayout>
<Button/>
<Button/>
</android.support.v7.widget.GridLayout>
</DialPadView>
但是 <DialPadView>
已经是 android.support.v7.widget.GridLayout
的一个实例,所以基本上你最终会在 GridLayout
中得到 GridLayout
。除了不是最优的之外,它可能导致布局看起来与您想象的外观和行为不同。
我正在尝试实现一个简单的自定义拨号盘视图。一切正常,直到我尝试使用 <include>
为我的按钮包含一个 "template",而不是在 XML 文件中明确定义每个按钮。因为我将有 12 个几乎相同的按钮,所以我想通过这样做可以让我的 XML 更简洁一些。
问题是在 onFinishInflate()
我的自定义视图中,当我调用 findViewById(R.id.dialpad_view_btn1)
时它 returns 为空。我当然将 ID dialpad_view_btn1
分配给我的 <include>
之一。如果我只是明确定义按钮而不是从我的 "template" 中包含它,它就可以正常工作。对此有任何解决方法,还是我应该接受 findViewById()
和 <include>
不能一起玩?
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>
ID 问题:
您遇到的问题是由于混用了<include>
和<merge>
标签造成的。
请注意 <merge>
有不同的用途,在这种情况下不应使用它,因此请仅使用 <incluce>
并使 <Button>
成为 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() 方法。您可以在那里找到处理 <merge>
inflation 的代码。可以看到,<include>
的子级传递 layout 和 id 属性的代码没有为 <merge>
调用,因为 <merge>
应该用于不同的场景。那里:
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
中错误地膨胀了 <android.support.v7.widget.GridLayout>
,因此您最终会得到这样的层次结构:
<DialPadView>
<android.support.v7.widget.GridLayout>
<Button/>
<Button/>
</android.support.v7.widget.GridLayout>
</DialPadView>
但是 <DialPadView>
已经是 android.support.v7.widget.GridLayout
的一个实例,所以基本上你最终会在 GridLayout
中得到 GridLayout
。除了不是最优的之外,它可能导致布局看起来与您想象的外观和行为不同。