Java 从ListView获取子对象会导致在空对象引用上出现android.view.view.view.findViewById(int)

Java 从ListView获取子对象会导致在空对象引用上出现android.view.view.view.findViewById(int),java,android,android-layout,listview,Java,Android,Android Layout,Listview,单击my activity_main.xml上的edit按钮时,它会在my main activity中激发此方法 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();

单击my activity_main.xml上的edit按钮时,它会在my main activity中激发此方法

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();
    }
}
在列表的大小超过屏幕大小之前,所有这些都可以正常工作。一些研究表明,尽管我的自定义适配器包含整个列表视图的所有条目,但列表视图仅存储可见条目。我想这就是我出错的原因

如何使用选中项id填充ArrayList EditID,以便从SQLite db检索该记录或将其删除

我的适配器:

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();
    }
}
My 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_width="match_parent"
    android:layout_height="match_parent"
    android:theme="@style/AppTheme">

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

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            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_width="0dp"
            android:layout_height="wrap_content"
            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_width="0dp"
            android:layout_height="wrap_content"
            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_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_weight="0.1">

        <ListView
            android:id="@+id/listView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </LinearLayout>

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme">

        <Button
            android:id="@+id/edit"
            android:onClick="editActivity"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/edit" />
        <Button
            android:id="@+id/delete"
            android:onClick="deleteActivity"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/delete" />


    </LinearLayout>


</LinearLayout>
编辑

我已将此代码添加到适配器中

 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,对适配器的任何调用都会使应用程序崩溃

这里是我用来检查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();
        }
    }

非常感谢您迄今为止的帮助。

这是我编写的一段代码,用于将所有光标数据放入对象中,然后将这些对象绑定到视图。这样,控制复选框状态就容易多了

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));
}

您正在尝试在适配器创建视图之前获取该视图。您应该保存适配器本身的复选框状态。@devgianlu在列表超出屏幕之前,它都会工作。我不知道如何在适配器中保存复选框状态。如果可以的话,一个小的示例代码将非常好。谢谢。My ide不喜欢此行public void onCheckedChangedCompoundButton buttonView、boolean isChecked{checked.putcursor.getPosition、isChecked;}checked和游标抛出错误通过更改复选框变量名称修复,因为它与SparseBooleanArray共享。光标所在的行中仍有错误。IDE说我应该将变量设为final。?我必须在bindView中将光标设为final,并更改复选框的变量。无论勾选哪个框,始终返回相同的ID。我想我的更改可能是问题的原因。当我遍历所有适配器项时,只有最后一项被标记为选中。我的项不是按数据库ID顺序显示的,而是按时间顺序降序显示的,这有什么区别吗?
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;
    }
}
        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();
        }
    }
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));
}