从 ListView 获取孩子会导致 android.view.View android.view.View.findViewById(int) 在空对象引用上
Posted
技术标签:
【中文标题】从 ListView 获取孩子会导致 android.view.View android.view.View.findViewById(int) 在空对象引用上【英文标题】:Getting child from ListView causes android.view.View android.view.View.findViewById(int) on a null object reference 【发布时间】:2019-07-21 13:33:00 【问题描述】:当在我的 activity_main.xml 上单击编辑按钮时,它会在我的主要活动中触发此方法。
public void editActivity(View view)
editIds.clear();
//Check to see if any activities are ticked (checked) and grab the id if they are.
for (int i = 0; i < adapter.getCount(); i++)
CheckBox c = listView.getChildAt(i).findViewById(R.id.checkBox);
if (c.isChecked())
editIds.add(adapter.getItemId(i));
//If only one item is checked start the Edit activity
if (editIds.size() == 1)
Intent intent = new Intent(this, EditActivity.class);
intent.putExtra("ID", String.valueOf(editIds.get(0)));
startActivityForResult(intent, 2);
//If more than 1 or none are checked inform the user
else
String output = "VIEW / EDIT: Please select one activity.";
Toast.makeText(this, output, Toast.LENGTH_SHORT).show();
这是检查 ListView 中选中的所有复选框。一切正常,直到 ListView 的大小超出屏幕的大小。我的意思是,一旦必须滚动列表,单击编辑就会导致应用程序崩溃并出现此错误:
Caused by: java.lang.reflect.InvocationTargetException
at java.lang.reflect.Method.invoke(Native Method)
at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:385)
at android.view.View.performClick(View.java:5610)
at android.view.View$PerformClick.run(View.java:22265)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6077)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'android.view.View android.view.View.findViewById(int)' on a null object reference
at com.example.activitytracker.MainActivity.editActivity(MainActivity.java:108)
在列表大小超过屏幕大小之前一切正常,并且一些研究表明,尽管我的自定义适配器包含整个列表视图的所有条目,但列表视图仅存储可见条目。我认为这就是我收到错误的原因。
如何使用选中的项目 ID 填充 ArrayList editIds,以便我可以从 SQLite 数据库中检索该记录或将其删除。
我的适配器:
package com.example.activitytracker;
import android.content.Context;
import android.database.Cursor;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import android.widget.TextView;
public class ActivityAdapter extends CursorAdapter
public ActivityAdapter(Context context, Cursor cursor)
super(context, cursor, 0);
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent)
return LayoutInflater.from(context).inflate(R.layout.activity, parent, false);
@Override
public void bindView(View view, Context context, Cursor cursor)
// Find fields to populate in inflated template
TextView activityTitle = (TextView) view.findViewById(R.id.activityTitle);
TextView activityDescription = (TextView) view.findViewById(R.id.activityDescription);
TextView activityDate = (TextView) view.findViewById(R.id.activityDate);
// Extract properties from cursor
String activity = cursor.getString(cursor.getColumnIndexOrThrow("activity"));
String description = cursor.getString(cursor.getColumnIndexOrThrow("description"));
String date = cursor.getString(cursor.getColumnIndexOrThrow("date"));
// Shorten description for cleaner screen display
String displayValueOfDescription = description;
if (description.length() > 20)
displayValueOfDescription = description.substring(0, 19) + "...";
// Create UK date format for screen display
String yearDB = date.substring(0, 4);
String monthDB = date.substring(5, 7);
String dayDB = date.substring(8, 10);
String dateDB = dayDB + "-" + monthDB + "-" + yearDB;
// Populate fields with extracted properties
activityTitle.setText(activity);
activityDescription.setText(String.valueOf(displayValueOfDescription));
activityDate.setText(String.valueOf(dateDB));
public void update(Cursor cursor)
this.swapCursor(cursor);
this.notifyDataSetChanged();
我的 layout_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_
android:layout_
android:theme="@style/AppTheme">
<include
android:id="@+id/toolbar"
layout="@layout/toolbar" />
<LinearLayout
android:orientation="horizontal"
android:layout_
android:layout_>
<TextView
android:layout_
android:layout_
android:text="@string/activity"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="18sp"
android:textStyle="italic"
android:padding="5dp"
android:layout_weight="4"/>
<TextView
android:layout_
android:layout_
android:text="@string/description"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="18sp"
android:textStyle="italic"
android:padding="5dp"
android:layout_weight="6"/>
<TextView
android:layout_
android:layout_
android:text="@string/date"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="18sp"
android:textStyle="italic"
android:padding="5dp"
android:layout_weight="3"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_
android:layout_
android:layout_weight="0.1">
<ListView
android:id="@+id/listView"
android:layout_
android:layout_ />
</LinearLayout>
<LinearLayout
android:orientation="vertical"
android:layout_
android:layout_
android:theme="@style/AppTheme">
<Button
android:id="@+id/edit"
android:onClick="editActivity"
android:layout_
android:layout_
android:text="@string/edit" />
<Button
android:id="@+id/delete"
android:onClick="deleteActivity"
android:layout_
android:layout_
android:text="@string/delete" />
</LinearLayout>
</LinearLayout>
我的列表视图:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal" android:layout_
android:layout_>
<CheckBox
android:id="@+id/checkBox"
android:layout_
android:layout_
android:text="@null"
android:layout_weight="1"/>
<TextView
android:id="@+id/activityTitle"
android:layout_
android:layout_
android:text="@string/activity"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="15sp"
android:paddingTop="5dp"
android:paddingBottom="5dp"
android:paddingLeft="5dp"
android:layout_weight="3"/>
<TextView
android:id="@+id/activityDescription"
android:layout_
android:layout_
android:text="@string/description"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="15sp"
android:paddingTop="5dp"
android:paddingBottom="5dp"
android:paddingLeft="5dp"
android:layout_weight="6"/>
<TextView
android:id="@+id/activityDate"
android:layout_
android:layout_
android:text="@string/date"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="15sp"
android:paddingTop="5dp"
android:paddingBottom="5dp"
android:paddingLeft="5dp"
android:layout_weight="3"/>
</LinearLayout>
感谢任何帮助,因为我是 Android 和 Java 的初学者。
这是我的 onCreate()
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
dbManager = new DBManager(this);
dbManager.open();
adapter = new ActivityAdapter(this, dbManager.fetch());
listView = (ListView) findViewById(R.id.listView);
listView.setAdapter(adapter);
编辑
我已将此代码添加到我的适配器中
checked.setOnClickListener(new View.OnClickListener()
@Override
public void onClick(View v)
// Do the stuff you want for the case when the row TextView is clicked
// you may want to set as the tag for the TextView the position paremeter of the `getView` method and then retrieve it here
Integer realPosition = (Integer) v.getTag();
// using realPosition , now you know the row where this TextView was clicked
Log.d("CLICKEDCHECK", "POS=" + v.getId());
);
我正在记录 getId,但无论我检查哪个复选框,这都是一样的。如何获取该特定复选框的记录 ID?然后我可以在数组列表中添加或删除它们。
编辑 使用 devgianlu 的代码更新。
无论您选中要编辑或删除的框,代码始终返回相同的 id。
这是更新后的 ActivityAdapter
package com.example.activitytracker;
import android.content.Context;
import android.database.Cursor;
import android.util.SparseBooleanArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CursorAdapter;
import android.widget.TextView;
public class ActivityAdapter extends CursorAdapter
private final SparseBooleanArray checked = new SparseBooleanArray();
public ActivityAdapter(Context context, Cursor cursor)
super(context, cursor, 0);
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent)
return LayoutInflater.from(context).inflate(R.layout.activity, parent, false);
@Override
public void bindView(View view, Context context, final Cursor cursor)
// Find fields to populate in inflated template
TextView activityTitle = (TextView) view.findViewById(R.id.activityTitle);
TextView activityDescription = (TextView) view.findViewById(R.id.activityDescription);
TextView activityDate = (TextView) view.findViewById(R.id.activityDate);
CheckBox check = (CheckBox) view.findViewById(R.id.checkBox);
check.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener()
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked)
checked.put(cursor.getPosition(), isChecked);
);
// Extract properties from cursor
String activity = cursor.getString(cursor.getColumnIndexOrThrow("activity"));
String description = cursor.getString(cursor.getColumnIndexOrThrow("description"));
String date = cursor.getString(cursor.getColumnIndexOrThrow("date"));
// Shorten description for cleaner screen display
String displayValueOfDescription = description;
if (description.length() > 20)
displayValueOfDescription = description.substring(0, 19) + "...";
// Create UK date format for screen display
String yearDB = date.substring(0, 4);
String monthDB = date.substring(5, 7);
String dayDB = date.substring(8, 10);
String dateDB = dayDB + "-" + monthDB + "-" + yearDB;
// Populate fields with extracted properties
activityTitle.setText(activity);
activityDescription.setText(String.valueOf(displayValueOfDescription));
activityDate.setText(String.valueOf(dateDB));
public boolean isChecked(int pos)
return checked.get(pos, false);
public void update(Cursor cursor)
this.swapCursor(cursor);
this.notifyDataSetChanged();
以及我对 MainActivity 中 isChecked 方法的调用
public void editActivity(View view)
editIds.clear();
//Check to see if any activities are ticked (checked) and grab the id if they are.
// Add checked items to deleteIds
for (int i = 0; i < adapter.getCount(); i++)
if (adapter.isChecked(i))
editIds.add(adapter.getItemId(i));
Log.d("EDITACT", "ID="+adapter.getItemId(i) + "size="+editIds.size());
//If only one item is checked start the Edit activity
if (editIds.size() == 1)
Intent intent = new Intent(this, EditActivity.class);
intent.putExtra("ID", String.valueOf(editIds.get(0)));
startActivityForResult(intent, 2);
//If more than 1 or none are checked inform the user
else
String output = "VIEW / EDIT: Please select one activity.";
Toast.makeText(this, output, Toast.LENGTH_SHORT).show();
进一步更新
我的 ActivityAdapter 现在看起来像这样。
package com.example.activitytracker;
import android.content.Context;
import android.database.Cursor;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Adapter;
import android.widget.BaseAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import java.util.ArrayList;
import java.util.List;
class Activities
private boolean checked = false;
private DBManager dbManager;
Cursor cursor = dbManager.fetch();
public Activities(Cursor cursor)
List<Activities> items = new ArrayList<>();
while (cursor.moveToNext())
items.add(new Activities(cursor));
public class ActivityAdapter extends BaseAdapter
private final List<Activities> items;
private final LayoutInflater inflater;
public ActivityAdapter(Context context, List<Activities> items)
this.items = items;
this.inflater = LayoutInflater.from(context);
@Override
public View getView(int position, View convertView, ViewGroup parent)
if (convertView == null)
convertView = inflater.inflate(R.layout.activity, parent, false);
final Activities item = items.get(position);
CheckBox checkBox = convertView.findViewById(R.id.checkBox);
checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener()
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked)
// item.isChecked() = isChecked;
);
// *** Bind the data to the view ***
return convertView;
public boolean isChecked(int pos)
// return items.get(pos).checked;
return true;
@Override
public int getCount()
return items.size();
@Override
public Object getItem(int position)
return items.get(position);
@Override
public long getItemId(int position)
return position;
此外,现在我的适配器在启动时没有填充我的 listView,并且对适配器的任何调用都会导致应用程序崩溃。
这里是我用来检查列表视图的复选框然后刷新适配器的地方。不知道现在在这里做什么。
deleteIds.clear();
// Add checked items to deleteIds
for (int i = 0; i < adapter.getCount(); i++)
CheckBox c = listView.getChildAt(i).findViewById(R.id.checkBox);
if (c.isChecked())
deleteIds.add(adapter.getItemId(i));
//Check to see if any activities are ticked (checked)
if (deleteIds.size() > 0)
//If there are checked activities a dialog is displayed for user to confirm
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage("Are you sure you want to delete?")
.setPositiveButton("Yes", new DialogInterface.OnClickListener()
@Override
public void onClick(DialogInterface dialog, int which)
for (int i = 0; i < deleteIds.size(); i++)
dbManager.delete(deleteIds.get(i));
adapter.update(dbManager.fetch());
)
.setNegativeButton("No", new DialogInterface.OnClickListener()
@Override
public void onClick(DialogInterface dialog, int which)
deleteIds.clear();
dialog.cancel();
).show();
非常感谢您到目前为止的帮助。
【问题讨论】:
您正试图在适配器创建视图之前获取视图。您应该从适配器本身保存复选框状态。 @devgianlu 一切正常,直到列表超出屏幕。我不知道如何在适配器中保存复选框状态。如果可以的话,一个小示例代码会很棒。 【参考方案1】:这是我编写的一段代码,用于将所有 Cursor 数据放入对象中,然后将这些对象绑定到视图。这种方式更容易控制复选框的状态。
public class CustomItem
public boolean checked = false;
public CustomItem(Cursor cursor)
// Get yuor data and put it in the object
public class CustomAdapter extends BaseAdapter
private final List<CustomItem> items;
private final LayoutInflater inflater;
public CustomAdapter(Context context, List<CustomItem> items)
this.items = items;
this.inflater = LayoutInflater.from(context);
@Override
public View getView(int position, View convertView, ViewGroup parent)
if (convertView == null)
convertView = inflater.inflate(/* Your layout ID */, parent, false);
final CustomItem item = items.get(position);
CheckBox checkBox = convertView.findViewById(R.id.checkBox);
checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener()
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked)
item.checked = isChecked;
);
// *** Bind the data to the view ***
return convertView;
public boolean isChecked(int pos)
return items.get(pos).checked;
@Override
public int getCount()
return items.size();
@Override
public Object getItem(int position)
return items.get(position);
@Override
public long getItemId(int position)
return position;
Cursor cursor = ....;
List<CustomItem> items = new ArrayList<>();
while (cursor.moveToNext())
items.add(new CustomItem(cursor));
Adapter adapter = new BaseAdapter(this /* Your activity */, items);
listView.setAdapter(adapter);
for (int i = 0; i < adapter.getCount(); i++)
System.out.println(i + " IS " + adapter.isChecked(i));
【讨论】:
谢谢。我的 ide 不喜欢这一行public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) checked.put(cursor.getPosition(), isChecked);
检查和光标抛出错误
一个通过更改复选框变量名称来修复,因为它与 SparseBooleanArray 共享。光标所在行仍有错误。 IDE 说我应该将变量设为 final。??
我必须在 bindView 中将 Cursor 光标设为 final 并更改复选框的变量。无论选中哪个框,都始终返回相同的 ID。
认为我的改动可能是问题的原因。当我遍历所有适配器项时,只有最后一项被标记为已选中。
我的项目不是按数据库 ID 顺序显示而是按时间降序显示的事实有什么不同吗?以上是关于从 ListView 获取孩子会导致 android.view.View android.view.View.findViewById(int) 在空对象引用上的主要内容,如果未能解决你的问题,请参考以下文章