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

时间:2019-09-16 17:32:48

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

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

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

这是我的代码。

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 但我认为,他们建议我已经做过的事情。

感谢您的帮助。

2 个答案:

答案 0 :(得分:1)

对基础数据进行更改/更新后,您似乎没有更改ListView的Cursor。

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

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

这里相当于您的App,但使用Java而不是Kotlin(没有运气转换,因为我很少使用Kotlin)。

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

  • 如果选择未选择的行,则包含该单词的所有行都会被选中并显示为灰色,并且所选列的值将设置为1。
  • 如果选择一个选定的(灰色)行,则包含该单词的那些行将被取消选择,并变为白色,而选定的列值又变回0

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

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

enter image description here

如果单击了猫(第二行),则所有其他猫行也将按照以下方式进行切换和变灰:-

enter image description here

以此类推。

代码

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类。
  • 问题的症结在于 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 方法(而不是将行提取到游标中)来驱动更新,而是使用CASE WHEN ELSE END子句来切换值,并且应该更有效。
  • 请注意,由于MATCH是特定于FTS的,因此不是 MATCH (我相信)已使用 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方法中进行了更改。

答案 1 :(得分:0)

如果要使用多种不同的视图类型,则需要覆盖getItemViewType(position)。如果不这样做,那么适配器将无法知道是否以View的正确类型传递了convertView实例,因此您将获得回收不良的视图。

对于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)
}
相关问题