在我的 ListView 中选择项目后,滚动 Kotlin 时随机选择其他项目

After items selection in my ListView other items get randomly selected while scrolling Kotlin

我是新手。滚动我的 ListView 后,"same position" 中的项目作为我在自动选择之前选择的项目。 (相同的位置我指的是屏幕中的位置而不是数据库中的位置。)我以前遇到过这个问题,因为我是通过 OnItemClickListener 中 ListView 的索引来选择项目的。但是我又遇到了这个问题,尽管我认为我的做法是正确的。

当我单击 ListView 中的项目时,我获得了它的唯一 ID,并基于此将此项目(数据库中此行的)的 SELECTED 值更改为 0 或 1(取决于它是否被单击或不是)。之后,我将背景颜色切换为灰色(或返回白色)。这是在区分 SELECTED 属性.

的 CursorAdapter 中处理的

这是我的代码。

OnCreate 在 MainActivity.kt

    val dbHelper = DBHelper(this)

    val db = dbHelper.writableDatabase

    val myCursor = db.rawQuery("SELECT * FROM ${ContractClass.FeedReaderContract.TABLE_NAME}", null)
    val myAdapter = CursorAdapter(this, myCursor)
        myListView.adapter = myAdapter

    myListView.setOnItemClickListener { _, view, _, _ ->
            val text = view.txtName.text
            val select = "${ContractClass.FeedReaderContract.COLUMN_NAME_ENWORD} MATCH ?"
            val selectCursor = db.query(
                ContractClass.FeedReaderContract.TABLE_NAME,   // The table to query
                null,             // The array of columns to return (pass null to get all)
                select,              // The columns for the WHERE clause
                arrayOf("$text"),          // The values for the WHERE clause
                null,                   // don't group the rows
                null,                   // don't filter by row groups
                null               // The sort order
            )
            with(selectCursor) {
                while (moveToNext()) {
                    val itemSel = getInt(getColumnIndexOrThrow(ContractClass.FeedReaderContract.COLUMN_NAME_SELECTED))
                    if (itemSel == 1){
                        val values = ContentValues().apply {
                            put(ContractClass.FeedReaderContract.COLUMN_NAME_SELECTED, 0)
                        }
                        val count = db.update(
                            ContractClass.FeedReaderContract.TABLE_NAME, values, select, arrayOf("$text"))

                    }else{
                        val values = ContentValues().apply {
                            put(ContractClass.FeedReaderContract.COLUMN_NAME_SELECTED, 1)
                        }
                        val count = db.update(
                            ContractClass.FeedReaderContract.TABLE_NAME, values, select, arrayOf("$text"))
                    }
                }
            }
        }

CursorAdapter.kt

class CursorAdapter(context: Context, cursor: Cursor) : CursorAdapter(context, cursor, 0) {

    // The newView method is used to inflate a new view and return it,
    // you don't bind any data to the view at this point.
    override fun newView(context: Context, cursor: Cursor, parent: ViewGroup): View {
        if (cursor.getInt(cursor.getColumnIndexOrThrow(ContractClass.FeedReaderContract.COLUMN_NAME_SELECTED)) == 0){
            return LayoutInflater.from(context).inflate(R.layout.row_list_row, parent, false)
        }else{
            return LayoutInflater.from(context).inflate(R.layout.user_list_row_selected, parent, false)
        }
    }

    // The bindView method is used to bind all data to a given view
    // such as setting the text on a TextView.
    override fun bindView(view: View, context: Context, cursor: Cursor) {
        // Find fields to populate in inflated template
        val tvBody = view.findViewById<View>(R.id.txtName) as TextView
        val tvPriority = view.findViewById<View>(R.id.txtComment) as TextView
        val tvPriority2 = view.findViewById<View>(R.id.txtThird) as TextView
        val tvPriority3 = view.findViewById<View>(R.id.txtThi) as TextView
        // Extract properties from cursor
        val body = cursor.getString(cursor.getColumnIndexOrThrow(ContractClass.FeedReaderContract.COLUMN_NAME_ENWORD))
        val priority = cursor.getString(cursor.getColumnIndexOrThrow(ContractClass.FeedReaderContract.COLUMN_NAME_DEFN))
        val priority2 = cursor.getInt(cursor.getColumnIndexOrThrow(ContractClass.FeedReaderContract._id))
        val priority3 = cursor.getInt(cursor.getColumnIndexOrThrow(ContractClass.FeedReaderContract.COLUMN_NAME_SELECTED))
        // Populate fields with extracted properties
        tvBody.text = body
        tvPriority.text = priority.toString()
        tvPriority2.text = priority2.toString()
        tvPriority3.text = priority3.toString()
    }
}

数据库Table创建

private val SQL_CREATE_ENTRIES =
        "CREATE VIRTUAL TABLE ${ContractClass.FeedReaderContract.TABLE_NAME} USING fts4(" +
                "${ContractClass.FeedReaderContract._id}," +
                "${ContractClass.FeedReaderContract.COLUMN_NAME_ENWORD} TEXT," +
                "${ContractClass.FeedReaderContract.COLUMN_NAME_DEFN} TEXT," +
                "${ContractClass.FeedReaderContract.COLUMN_NAME_SELECTED} INTEGER)"

我在这里找到了类似的 post:List view with simple cursor adapter items checked are un-checked during scroll 但我认为,他们建议我已经做过的事情。

感谢您的帮助。

如果您想使用多种不同的视图类型,您需要覆盖 getItemViewType(position)。如果你不这样做,那么适配器就无法知道它作为 convertView 传递的 View 实例是否是正确的类型,你最终会得到严重回收的视图。

这对于 CursorAdapter 来说似乎不是微不足道的(我没有任何经验)。我认为正确的做法应该是这样的:

override fun getItemViewType(position: Int): Int {
    cursor.moveToPosition(position)
    val columnIndex = cursor.getColumnIndexOrThrow(ContractClass.FeedReaderContract.COLUMN_NAME_SELECTED)
    return when (cursor.getInt(columnIndex)) {
        0 -> 0
        else -> 1
    }
}

我还认为您应该更改 newView() 以利用这些类型。保留现有的 应该 工作,但它会是重复的代码。

override fun newView(context: Context, cursor: Cursor, parent: ViewGroup): View {
    val layoutId = when (val type = getItemViewType(cursor.position)) {
        0 -> R.layout.row_list_row
        1 -> R.layout.user_list_row_selected
        else -> throw IllegalStateException("unexpected viewType: $type")
    }
    return LayoutInflater.from(context).inflate(layoutId, parent, false)
}

在更改 to/updated 基础数据后,您似乎没有更改 ListView 的 Cursor。

尝试在对基础数据进行更改后,通过

更新 ListView 的 Cursor
  1. 重新查询数据然后
  2. 使用适配器的 swapCursor(myCursor)(或 notiftDatasetChanged() 方法)

这是您的应用程序的等价物,但在 Java 而不是 Kotlin 中(没有任何运气转换,因为我几乎从不使用 Kotlin)。

我相信这可以满足您的要求。也就是说,

  • 如果您 select 未被 select 编辑的行,那么所有包含 enword 的行都会被 select 编辑并变灰,并且 select ed 列值设置为 1。

  • 如果您 select 一个 selected(灰色)行,那么那些包含 enword 的行将被 de-selected 并变为白色selected 列值被改回 0

  • 请注意,我没有创建 FTS table,而是模仿了 FTS 并使用 LIKE 而不是 MATCH。

如果 selected 背景为灰色,则切换为白色。例如最初是:-

如果单击猫(第 2 行),则所有其他猫行也会根据 :-

切换并变灰

等等。

代码

MainActivity(我在转换时遇到问题的文件)

public class MainActivity extends AppCompatActivity {

    DBHelper dbhelper;
    ListView myListView;
    MyCursorAdapter myAdapter;
    Cursor mCursor;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        myListView = this.findViewById(R.id.myListView);
        dbhelper = new DBHelper(this);
        addSomeTestingData();
        manageListView();
    }

    private void manageListView() {
        mCursor = dbhelper.getAllRows();
        if (myAdapter == null) {
            myAdapter = new MyCursorAdapter(this,mCursor);
            myListView.setAdapter(myAdapter);
            myListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                @Override
                public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                    dbhelper.updateSelected(mCursor.getString(mCursor.getColumnIndex(ContractClass.FeedReaderContract.COLUMN_NAME_ENWORD)));
                    manageListView();
                }
            });
        } else {
            myAdapter.swapCursor(mCursor);
        }
    }

    private void addSomeTestingData() {

        if (DatabaseUtils.queryNumEntries(dbhelper.getWritableDatabase(),ContractClass.FeedReaderContract.TABLENAME) > 1) return;
        for (int i=0; i < 10; i++) {
            dbhelper.addRow("Apple", "Thing that falls from trees");
            dbhelper.addRow("Cat", "Something that is furry and sits on mats");
            dbhelper.addRow("Bear", "Something that is furry that eats honey but doesn't ssit on a mat");
            dbhelper.addRow("Dog", "Something is furry and friendly");
            dbhelper.addRow("Echida", "An upside down hedgehog");
            dbhelper.addRow("Ferret", "Something that is furry and found up trouser legs");
            dbhelper.addRow("Giraffe", "Something that has 5 legs one pointing up");
            dbhelper.addRow("Hippo", "An animal that loves mud and water but not humans");
            dbhelper.addRow("Ibis", "A white feathered flying thing");
            dbhelper.addRow("Jaguar", "A car or a large black cat");
            dbhelper.addRow("Kangaroo", "A marsupial that boxes, skips and has a puch for shopping trips");
            dbhelper.addRow("Lizard", "A rock dweller");
            dbhelper.addRow("Mammoth", "A big hairy elephant now extinct");
            dbhelper.addRow("Nuthatch", "A small bird that does not customise nuts so they have hatches.");
            dbhelper.addRow("Ostrich", "A l argefast running bird that does not fly");
            dbhelper.addRow("Panther", "A skinny pink cat that walks on only two of it's four lehs");
            dbhelper.addRow("Queen", "A female rule of a country");
            dbhelper.addRow("Rhinocerous", "A Hippo like animal that has a name that is hard to spell");
            dbhelper.addRow("Tiger", "A live verion of Winnie the Pooh's friend Tigger");
            dbhelper.addRow("Stork", "A skinny ostrich that flies and delivers children through Dream World.");
        }
    }
}
  • 显然 addSomeTestingData 方法就是为了这个。

  • 请注意,几乎没有任何其他代码。数据库访问已全部移至 DBHelper class.

  • 关键是manageListView方法

    • 首先填充适配器使用的游标。
    • 适配器尚未实例化,因此实例化为 null,然后绑定到 ListView。
    • 添加了 OnItemClickListener 注意它会在数据库更新后调用 manageListView 方法。
    • 如果适配器已被实例化,则调用 swapCursor 方法,告诉适配器底层游标已更改。

DBHelper.java

public class DBHelper extends SQLiteOpenHelper {

    public DBHelper(@Nullable Context context) {
        super(context, ContractClass.DBNAME,null,ContractClass.DBVERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(ContractClass.FeedReaderContract.CRTSQL);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }

    public Cursor getAllRows() {
        SQLiteDatabase db = this.getWritableDatabase();
         return db.query(ContractClass.FeedReaderContract.TABLENAME,null,null,null,null,null,null);
    }

    public void updateSelected(String selected) {
        SQLiteDatabase db = this.getWritableDatabase();
        db.execSQL("UPDATE "
                        + ContractClass.FeedReaderContract.TABLENAME
                        + " SET " + ContractClass.FeedReaderContract.COLUMN_NAME_SELECTED
                        + "= CASE " + ContractClass.FeedReaderContract.COLUMN_NAME_SELECTED +
                        " WHEN 1 THEN 0 ELSE 1 END " +
                        " WHERE " + ContractClass.FeedReaderContract.COLUMN_NAME_ENWORD + " LIKE ?",
                new String[]{selected}
        );
    }

    public long addRow(String enWord, String definition) {
        SQLiteDatabase db = this.getWritableDatabase();
        ContentValues cv = new ContentValues();
        cv.put(ContractClass.FeedReaderContract.COLUMN_NAME_ENWORD,enWord);
        cv.put(ContractClass.FeedReaderContract.COLUMN_NAME_DEFN,definition);
        return db.insert(ContractClass.FeedReaderContract.TABLENAME,null,cv);
    }
}
  • 选择所有行已移至此处作为 getAllRows 方法
  • addRow只是允许添加一些测试数据。
  • updateSelected 方法不是将行提取到 Cursor 中,而是使用 CASE WHEN ELSE END 子句切换值来驱动更新,应该更有效。
  • 请注意,不是 MATCH,因为 MATCH 是 FTS 特定的(我相信)LIKE 已被用作基础 table 不是 FTS table。 您将使用 MATCH

MyCursorAdapter.java

public class MyCursorAdapter extends CursorAdapter {

    public MyCursorAdapter(Context context, Cursor c) {
        super(context, c, false);
    }

    @Override
    public View newView(Context context, Cursor cursor, ViewGroup parent) {
        return LayoutInflater.from(context).inflate(R.layout.row_list_row,parent,false);
    }


    @Override
    public void bindView(View view, Context context, Cursor cursor) {
        if (cursor.getInt(cursor.getColumnIndex(ContractClass.FeedReaderContract.COLUMN_NAME_SELECTED)) < 1) {
            view.setBackgroundColor(0xFFF9F9F9);
        } else {
            view.setBackgroundColor(0xFFD9D9D9);
        }
        TextView tvBody = view.findViewById(R.id.txtName);
        TextView tvPriority = view.findViewById(R.id.txtComment);
        TextView tvPriority2 = view.findViewById(R.id.txtThird);
        TextView tvPriority3 = view.findViewById(R.id.txtThi);
        tvBody.setText(cursor.getString(cursor.getColumnIndex(ContractClass.FeedReaderContract.COLUMN_NAME_ENWORD)));
        tvPriority.setText(cursor.getString(cursor.getColumnIndex(ContractClass.FeedReaderContract.COLUMN_NAME_DEFN)));
        tvPriority2.setText(cursor.getString(cursor.getColumnIndex(ContractClass.FeedReaderContract._id)));
        tvPriority3.setText(cursor.getString(cursor.getColumnIndex(ContractClass.FeedReaderContract.COLUMN_NAME_SELECTED)));
    }
}
  • 主要区别在于视图的背景是在 bindView 方法中更改的,而不是在 newView 方法中更改布局。