PIPIONE
工作示例这是一个基于您之前的问题和这个问题的工作示例应用程序。然而,按钮单击处理,在不使用布局的onCLick(如 XML 中编码)的情况下进行处理,但在自定义CursorAdapter ( MyCursorAdapter.java ) 的bindView方法中设置。它演示了 3 种获取与按钮关联的 id 的方法item_id_fromcursor显示了如何从作为列表源的 Cursor 获取值(在本例中为该 id),该列表的位置应适当。item_id_fromtag展示了如何从标签中获取 id(有些人不赞成这种技术)。item_id_fromview展示了如何根据传递给按钮的视图从视图层次结构中获取值。该处理允许删除和更新行(尽管更新非常基本,只是附加一个值而不是使用自定义对话框布局(您似乎掌握了这方面的知识))。与前面的答案不同,还实现了处理添加的行(尽管值有限)。生成的应用程序看起来像:-A表示行已被删除(即 id 为 5-7 的行不存在)B显示已更新的行(即已附加更新(如果更新更新则该行已更新两次))C显示已添加的行。如果单击编辑或删除按钮,则会出现对话框,例如:-单击取消从对话框中返回,什么也不做。单击删除将删除相应的行(如对话框消息中所述)。单击 EDIT 通过附加更新的值来编辑名称(编辑已经编辑的行将添加进一步更新的行)显然这可能不是所需的操作,它只是为了演示。编码activity_main.xml<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" > <TextView android:id="@+id/panelup" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="LIST SQ1" /> <ListView android:id="@+id/contentlist" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_below="@id/panelup" android:layout_above="@id/paneldown"/> <LinearLayout android:id="@+id/paneldown" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true"> <EditText android:id="@+id/name" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1" /> <EditText android:id="@+id/quantity" android:layout_width="match_parent" android:layout_height="wrap_content" android:inputType="number" android:layout_weight="2" /> <Spinner android:id="@+id/mu" android:layout_width="match_parent" android:layout_height="wrap_content" android:entries="@array/mu_values" android:layout_weight="2" /> <Button android:id="@+id/add" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="2" android:text="+" /> </LinearLayout></LinearLayout>行.xml<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="wrap_content"> <LinearLayout android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="@+id/layoutmain" > <TextView android:layout_width="wrap_content" android:layout_height="fill_parent" android:padding="2dip" android:text="M"/> <TextView android:id="@+id/id" android:layout_width="wrap_content" android:layout_height="fill_parent" android:padding="2dip" android:paddingRight="10dip"/> <TextView android:layout_width="wrap_content" android:layout_height="fill_parent" android:padding="2dip" android:paddingRight="10dip" android:text="-" /> <TextView android:id="@+id/name" android:layout_width="fill_parent" android:layout_height="fill_parent" android:padding="2dip"/> </LinearLayout> <TextView android:id="@+id/quantity" android:layout_width="fill_parent" android:layout_height="wrap_content" android:padding="2dip"/> <CheckBox android:id="@+id/checkboxmain2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:visibility="gone"/> <Button android:id="@+id/editordelete" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="EDIT or DELETE"/></LinearLayout>注意 CheckBox 已隐藏SQLiteHelper.javapublic class SQLiteHelper extends SQLiteOpenHelper { public static final String MYDATABASE_NAME = "mydatabase"; public static final int MYDATABASE_VERSION = 1; public static final String MYDATABASE_TABLE = "mytable"; SQLiteDatabase mDB; public SQLiteHelper(Context context, String name, SQLiteDatabase.CursorFactory factory,int version) { super(context, name, factory, version); mDB = this.getWritableDatabase(); } @Override public void onCreate(SQLiteDatabase db) { String crt_tbl_sql = "CREATE TABLE IF NOT EXISTS " + MYDATABASE_TABLE + "(" + SQLiteAdapter._id + " INTEGER PRIMARY KEY, " + SQLiteAdapter.KEY_NAME + " TEXT, " + SQLiteAdapter.KEY_SHOP + " TEXT, " + SQLiteAdapter.KEY_PDATE + " TEXT, " + SQLiteAdapter.KEY_PRICE + " REAL, " + SQLiteAdapter.KEY_QUANTITY + " INTEGER, " + SQLiteAdapter.KEY_MU + " TEXT, " + SQLiteAdapter.KEY_CHECKED + " INTEGER DEFAULT 0" + ")"; db.execSQL(crt_tbl_sql); addSomeTestingData(db,10); } @Override public void onConfigure(SQLiteDatabase db) { super.onConfigure(db); db.setForeignKeyConstraintsEnabled(true); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } public long addRow(String name, String shop, String pdate, double price, int quantity, String mu, SQLiteDatabase db) { ContentValues cv = new ContentValues(); cv.put(SQLiteAdapter.KEY_NAME,name); cv.put(SQLiteAdapter.KEY_SHOP,shop); cv.put(SQLiteAdapter.KEY_PDATE,pdate); cv.put(SQLiteAdapter.KEY_PRICE,price); cv.put(SQLiteAdapter.KEY_QUANTITY,quantity); cv.put(SQLiteAdapter.KEY_MU,mu); return db.insert(MYDATABASE_TABLE,null,cv); } private void addSomeTestingData(SQLiteDatabase db, int number_to_add) { for (int i = 0; i < number_to_add;i++) { String suffix = String.valueOf(i); String day_in_month = suffix; if (i < 10) { day_in_month = "0" + day_in_month; } addRow( "Test" + suffix, "Shop" + suffix, "2019-01-" + day_in_month, 10.5 + new Double(i * 3), i * 4, "mu" + suffix, db ); } }}SQLiteAdapter.javapublic class SQLiteAdapter { SQLiteDatabase sqLiteDatabase; SQLiteHelper sqLiteHelper; Context context; public static final String KEY_CHECKED = "checked"; public static final String _id = BaseColumns._ID; public static final String KEY_NAME = "name"; public static final String KEY_QUANTITY = "quantity"; public static final String KEY_PRICE = "price"; public static final String KEY_MU = "mu"; public static final String KEY_PDATE = "pdate"; public static final String KEY_SHOP = "shop"; public SQLiteAdapter(Context context) { this.context = context; openToWrite(); } public SQLiteAdapter openToWrite() throws android.database.SQLException { sqLiteHelper = new SQLiteHelper(context, MYDATABASE_NAME, null, MYDATABASE_VERSION); sqLiteDatabase = sqLiteHelper.getWritableDatabase(); return this; } public void close() { sqLiteHelper.close(); } public long insertChecked(boolean data1) { ContentValues contentValues = new ContentValues(); contentValues.put(KEY_CHECKED, data1); return sqLiteDatabase.insert(MYDATABASE_TABLE, null, contentValues); } public int updateChecked(long id,int check) { ContentValues cv = new ContentValues(); cv.put(KEY_CHECKED,check); String whereclause = _id + "=?"; String[] whereargs = new String[]{String.valueOf(id)}; return sqLiteDatabase.update(MYDATABASE_TABLE,cv,whereclause,whereargs); } public Cursor queueAll() { String[] columns = new String[]{_id, KEY_NAME, KEY_PRICE, KEY_QUANTITY, KEY_MU, KEY_PDATE, KEY_SHOP, KEY_CHECKED}; Cursor cursor = sqLiteDatabase.query(MYDATABASE_TABLE, columns, null, null, null, null, null); return cursor; } public Cursor queueOneById(long id) { String whereclause = _id + "=?"; String[] whereargs = new String[]{String.valueOf(id)}; String[] columns = new String[]{_id, KEY_NAME, KEY_PRICE, KEY_QUANTITY, KEY_MU, KEY_PDATE, KEY_SHOP, KEY_CHECKED}; return sqLiteDatabase.query(MYDATABASE_TABLE,columns,whereclause,whereargs, null,null,null); } public int delete(long id) { String whereclause = SQLiteAdapter._id + "=?"; String[] wherargs = new String[]{String.valueOf(id)}; return sqLiteDatabase.delete(MYDATABASE_TABLE,whereclause,wherargs); } public long updateById(long id, String column_to_change, String newvalue) { ContentValues cv = new ContentValues(); cv.put(column_to_change,newvalue); String whereclause = SQLiteAdapter._id + "=?"; String[] whereargs = new String[]{String.valueOf(id)}; return sqLiteDatabase.update(MYDATABASE_TABLE,cv,whereclause,whereargs); }}增加了一些方法MyCursorAdapter.javapublic class MyCursorAdapter extends CursorAdapter { SQLiteAdapter sqliteAdapter; MyCursorAdapter thisCursorAdapter; public MyCursorAdapter(Context context, Cursor c) { super(context, c, true); sqliteAdapter = new SQLiteAdapter(context); thisCursorAdapter = this; } @Override public View getView(int position, View convertView, ViewGroup parent) { View v = super.getView(position, convertView, parent); return v; } @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { return LayoutInflater.from(context).inflate(R.layout.row,parent,false); } @Override public void bindView(View view, Context context, final Cursor cursor) { //Note Cursor will be positioned appropriately TextView name = (TextView) view.findViewById(R.id.name); TextView id = (TextView) view.findViewById(R.id.id); TextView quantity = (TextView) view.findViewById(R.id.quantity); CheckBox cb = (CheckBox) view.findViewById(R.id.checkboxmain2); Button eod = (Button) view.findViewById(R.id.editordelete); name.setText(cursor.getString(cursor.getColumnIndex(SQLiteAdapter.KEY_NAME))); id.setText(cursor.getString(cursor.getColumnIndex(SQLiteAdapter._id))); quantity.setText(cursor.getString(cursor.getColumnIndex(SQLiteAdapter.KEY_QUANTITY))); cb.setChecked(cursor.getInt(cursor.getColumnIndex(SQLiteAdapter.KEY_CHECKED)) > 0); cb.setTag(cursor.getString(cursor.getColumnIndex(SQLiteAdapter._id))); //<<<<<<<<<< SET TAG to the ID eod.setTag(cursor.getString(cursor.getColumnIndex(SQLiteAdapter._id))); // dynamically add the listeners as opposed to coding onCLick in layout XML eod.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // get the id from the id TextView, within the view hierarchy rather than from the buttons tag // NOTE assumes the Button's parent is a LinearLayout (for simplicity) // This in theory is the recommended way rather than setting the tag LinearLayout ll = (LinearLayout) v.getParent(); TextView id = ll.findViewById(R.id.id), name = ll.findViewById(R.id.name); final long item_id_fromview = Long.valueOf(id.getText().toString()); final String item_name = name.getText().toString(); // get the id from the tag long item_id_fromtag = Long.valueOf(v.getTag().toString()); // get the if from the cursor that is the source of the Listview, it should be positioned accordingly long item_id_fromcursor = cursor.getLong(cursor.getColumnIndex(SQLiteAdapter._id)); // Show both Toast.makeText(v.getContext(), "The id (from the view hierarchy) is " + String.valueOf(item_id_fromview) + " or (from the tag) is " + String.valueOf(item_id_fromtag) + " or (from the cursor) is" + String.valueOf(item_id_fromcursor) , Toast.LENGTH_SHORT).show(); AlertDialog.Builder mydialog = new AlertDialog.Builder(v.getContext()); mydialog.setMessage("EDIT or DELETE Row:- ID: " + String.valueOf(item_id_fromview) + "Name: " + item_name); mydialog.setPositiveButton("DELETE", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { sqliteAdapter.delete(item_id_fromview); refreshList(); } }); mydialog.setNeutralButton("EDIT", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { sqliteAdapter.updateById(item_id_fromview,SQLiteAdapter.KEY_NAME,item_name + " Updated"); refreshList(); } }); mydialog.setNegativeButton("CANCEL", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { } }); mydialog.show(); } }); } private void refreshList() { thisCursorAdapter.swapCursor(sqliteAdapter.queueAll()); thisCursorAdapter.notifyDataSetChanged(); }}查看绑定视图时如何设置 onCLickListener查看获取id的 3 种不同方法请参阅类似于 AndroidSQLite 活动中使用的 ManageListView 方法的 refreshList 方法。AndroidSQlite.java(活动)public class AndroidSQLite extends AppCompatActivity { ListView listContent; Button buttonAdd; Cursor cursor; SQLiteAdapter mySQLiteAdapter; EditText name, quantity; Spinner mu; //SimpleCursorAdapter cursorAdapter; //<<<<<<<<<< NOT USED ANYMORE MyCursorAdapter myadapter; //<<<<<<<<<< Use a custom adapter that sets the tag of the checkbox to the respective id @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); listContent = (ListView) findViewById(R.id.contentlist); name = (EditText) findViewById(R.id.name); quantity = (EditText) findViewById(R.id.quantity); buttonAdd = (Button) findViewById(R.id.add); mu = (Spinner) findViewById(R.id.mu); handleAddButton(); mySQLiteAdapter = new SQLiteAdapter(this); mySQLiteAdapter.openToWrite(); manageListView(); //<<<<<<<<<< ADDED } //<<<<<<<<<< ADDED >>>>>>>>>> @Override protected void onResume() { super.onResume(); manageListView(); //Refresh the List when resuming e.g. returning from another activity } @Override protected void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); cursor.close(); //<<<<<<<<<< SHOULD ALWAYS CLOSE CURSOR mySQLiteAdapter.close(); } private void handleAddButton() { buttonAdd.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if ((name.getText().toString()).length() < 1) { Toast.makeText(v.getContext(),"Name cannot be empty",Toast.LENGTH_SHORT).show(); name.requestFocus(); return; } if ((quantity.getText().toString()).length() < 1) { Toast.makeText(v.getContext(),"Quantity cannot be empty",Toast.LENGTH_SHORT).show(); quantity.requestFocus(); return; } mySQLiteAdapter.sqLiteHelper.addRow( name.getText().toString(), // Arbritary values for testing "2019-01-01", "The Shop", 100.33, Integer.valueOf(quantity.getText().toString()), mu.getSelectedItem().toString(), mySQLiteAdapter.sqLiteDatabase ); manageListView(); } }); } private void manageListView() { cursor = mySQLiteAdapter.queueAll(); // get the source data (cursor) for the listview if (myadapter == null) { myadapter = new MyCursorAdapter(this,cursor); listContent.setAdapter(myadapter); } else { myadapter.swapCursor(cursor); } }}
慕工程0101907
Cursor cursor = (Cursor) parent.getItemAtPosition(id); 将根据它在列表中的位置获取项目,但您使用的是 id(从按钮的标签中提取)作为它的位置。这将很少是正确的。列表中的第一个位置是 0,第一个 id 将是 1(假设没有删除任何行并且您没有手动设置 id),因此您报告的症状。如果行已被删除,那么每个缺失的 id 都会增加 id 和 position 之间的差异。如果行按 id 以外的任何其他顺序排序,则位置 v id 可能会出路。如果 where 子句不包括行,那么 id 和位置也会有所不同。简单的事实是您不能依赖 id 和列表中的位置之间的任何关系。您可以做的是使用标签中的 id 来查询数据库以提取特定行。该查询将与用于获取列表光标的查询相同,只是它将使用第三个 ( SQLiteAdapter._id + "=?") 和第四个参数 ( new String[]{String.valueOf(id)}) (如果使用查询便利方法)。注释已更正(缺少S添加到 QLiteAdapter),因为评论。已修改我尝试添加我在上一条评论中写的代码,但这是错误的。我不知道如何实施您的更改。–您的代码可能是:-public void ListViewButtonHandler(View v){ Button bt = v.findViewById(R.id.rowalertdialog); Toast.makeText(this, "You clicked the Button for ID " + (String)bt.getTag(), Toast.LENGTH_SHORT).show(); //TODO not needed int id = Integer.valueOf((String) bt.getTag()); ListView parent = (ListView)findViewById(R.id.contentlist); //<<<<<<<<<< NOT NEEDED (I think) String whereclause = SQLiteAdapter._id _ "=?"; //<<<<<<<<<< ADDED String[] whereargs = new String[]{bt.getTag()}; //<<<<<<<<<< ADDED Cursor cursor = mySQLiteAdapter.getWitableDatabase().query(SQLiteAdapter.MYDATABASE_TABLE,null,whereclause,whereargs,null,null,null); //<<<<<<<<<< ADDED //Cursor cursor = (Cursor) parent.getItemAtPosition(id); //<<<<<<<<<< REMOVED/COMMENTED OUT //<<<<<<<<<<< ADDED to move to the extracted row, will toast if no such row if (!cursor.moveToFirst) { Toast.makeText(this, "Unable to retrieve row for ID " + (String)bt.getTag(), Toast.LENGTH_SHORT).show(); return; } final int item_id = cursor.getInt(cursor.getColumnIndex(SQLiteAdapter._id)); String item_name = cursor.getString(cursor.getColumnIndex(SQLiteAdapter.KEY_NAME)); String item_quantity = cursor.getString(cursor.getColumnIndex(SQLiteAdapter.KEY_QUANTITY)) cursor.close(); //<<<<<<<<<< ADDED should always close a cursor when done with it .......... the rest of the code请注意评论请注意,您可能需要将SQLiteAdapter.MYDATABASE_TABLE更改为适当的值。注意上面的代码是in-principal code,它没有经过测试或运行,因此可能有一些错误选择如果您将以下方法(基于上一个问题)添加到 SQLiteAdapter.java :-public Cursor queueOneById(long id) { String whereclause = _id + "=?"; String[] whereargs = new String[]{String.valueOf(id)}; String[] columns = new String[]{_id, KEY_NAME, KEY_PRICE, KEY_QUANTITY, KEY_MU, KEY_PDATE, KEY_SHOP, KEY_CHECKED}; return sqLiteDatabase.query(MYDATABASE_TABLE,columns,whereclause,whereargs, null,null,null);}然后你可以改变 Cursor cursor = mySQLiteAdapter.getWitableDatabase().query(SQLiteAdapter.MYDATABASE_TABLE,null,whereclause,whereargs,null,null,null);至 Cursor cursor = mySQLiteAdapter.queueOneById(id);请注意,这仍然需要上述其他更改。那就是你需要:-if (!cursor.moveToFirst) { Toast.makeText(this, "无法检索 ID 行" + (String)bt.getTag(), Toast.LENGTH_SHORT).show(); 返回; }没有上面的代码。光标将位于位置-1(在第一行之前)。所以cursor.moveToFirst需要定位到提取的行(应该只有一行)。如果没有行,则 moveToFirst 将返回 false(无法完成移动)并且将发出 Toast,然后 onClick 方法将返回。