【问题标题】:After items selection in my ListView other items get randomly selected while scrolling Kotlin在我的 ListView 中选择项目后,在滚动 Kotlin 时随机选择其他项目
【发布时间】:2020-01-17 13:59:12
【问题描述】:

我是新手。滚动我的 ListView 后,与我在自动选择之前选择的项目处于“相同位置”的项目。 (相同的位置,我的意思是屏幕中的位置而不是数据库中的位置。)我之前有过这个问题,因为我在 OnItemClickListener 中通过它们的 ListView 索引来选择项目。然而,我再次面临这个问题,尽管我以我认为的正确方式做这件事。

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

这是我的代码。

MainActivity.kt 中的 OnCreate

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

数据库表创建

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)"

我在这里找到了类似的帖子:List view with simple cursor adapter items checked are un-checked during scroll 但我认为,他们建议了我已经完成的工作。

感谢您的帮助。

【问题讨论】:

    标签: database sqlite android-studio kotlin android-listview


    【解决方案1】:

    在更改/更新基础数据后,您似乎没有更改 ListView 的光标。

    尝试,在对基础数据进行更改后,更新 ListView 的光标

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

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

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

    • 如果您选择了未选择的行,则所有包含 enword 的行都将被选中并显示为灰色,并且所选列的值设置为 1。

    • 如果您选择了一个选定的(灰色)行,那么那些包含该 enword 的行将被取消选择并变为白色,而选定的列值将变回 0

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

    如果它们被选中则切换背景为灰色,否则为白色。例如最初是:-

    如果点击了猫(第 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 类。

    • 问题的关键在于ma​​nageListView方法。

      • 首先填充适配器使用的光标。
      • Adapter 尚未实例化,因此为 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 方法不是将行提取到游标中来驱动更新,而是使用 CASE WHEN ELSE END 子句来切换值,应该更有效。
    • 请注意,不是 MATCH 因为 MATCH 是 FTS 特定的(我相信)LIKE 已被用作基础表不是 FTS 表。 你会使用 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 方法中更改。

    【讨论】:

    • 嗯...你救了我的命。非常感谢! :) 也许,我还有一个问题。 ListView(或数据库)中的某个项目是否有可能完全破坏所有选择(随机选择和未选择)?我正面临这个问题,所以我删除了这个项目,但我不希望它以后再次发生。
    • 我发现这些物品里面有破折号。我会尽力管理它。
    【解决方案2】:

    如果您想使用多种不同的视图类型,您需要覆盖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)
    }
    

    【讨论】:

    • 您不需要光标适配器,因为光标可用并且也定位到适当的行。此外,第 4 个参数 id 是唯一标识行 rowid(如果按预期使用,则所需的 _id 列在实际上是 rowid 列的别名)。使用 getItemAtPosition 返回 Cursor 本身。
    猜你喜欢
    • 2013-08-13
    • 1970-01-01
    • 1970-01-01
    • 2017-12-15
    • 1970-01-01
    • 1970-01-01
    • 2021-05-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多