简体   繁体   English

从数据库中删除项目时如何使用自定义适配器自动更新 ListView

[英]How to automatically update ListView with custom adapter when item is deleted from database

I created a ListView for which I implemented a custom adapter.我创建了一个 ListView,我为其实现了一个自定义适配器。 Additionally I connected it to a SQLite database, which contains the content for the ListView.此外,我将它连接到 SQLite 数据库,其中包含 ListView 的内容。

I have one ListView with several items.我有一个包含多个项目的 ListView。 Every item consists of a TextView and an ImageView which functions as a button.每个项目都由一个 TextView 和一个用作按钮的 ImageView 组成。 When the user clicks the ImageView, I want to delete the item from the database and automatically update the ListView.当用户单击 ImageView 时,我想从数据库中删除该项目并自动更新 ListView。

Unluckily, I just know how to delete the item from the database.不幸的是,我只知道如何从数据库中删除该项目。 I can't think of a way to automatically update the ListView in my program, after the item got deleted from the database.在项目从数据库中删除后,我想不出一种自动更新程序中的 ListView 的方法。

In the version below, I added an onItemClickListener for the ListView in the MainActivity - but it doesn't function for when the user clicks the ImageView (even though the ImageView is part of the ListView).在下面的版本中,我在 MainActivity 中为 ListView 添加了一个 onItemClickListener - 但它在用户单击 ImageView 时不起作用(即使 ImageView 是 ListView 的一部分)。

MainActivity主要活动

public class MainActivity extends AppCompatActivity {
//References to buttons and other controls on the layout
private Button btn_add;
private EditText et_todo;
private Switch sw;
private static ListView lv;
private static DataAdapter todoAdapter;
private DataBaseHelper dbHelper;

/**
 * Initialization Method
 *
 * @param savedInstanceState
 */
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    dbHelper = new DataBaseHelper(MainActivity.this);

    assignVariables();
    registerClick();
    showAllToDos(dbHelper);
}

private void assignVariables() {
    //assign values to variables
    btn_add = (Button) findViewById(R.id.btn_add);
    et_todo = (EditText) findViewById(R.id.et_todo);
    lv = (ListView) findViewById(R.id.lv);
}

public void showAllToDos(DataBaseHelper dbHelper) {
    todoAdapter = new DataAdapter(MainActivity.this, R.layout.list_item, dbHelper.getAllAsList(), dbHelper);
    lv.setAdapter(todoAdapter);
}

private void registerClick() {
    btn_add.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            String toDoTitle = et_todo.getText().toString();

            if(Pattern.matches("s*", toDoTitle)) {
                Toast.makeText(MainActivity.this, "Title is missing", Toast.LENGTH_SHORT).show();
            } else if(dbHelper.existsInDB(new DataModel(toDoTitle))) {
                Toast.makeText(MainActivity.this, "Already added as ToDo", Toast.LENGTH_SHORT).show();
            } else {
                DataModel dModel = new DataModel(toDoTitle);
                dbHelper.addOne(dModel);

                showAllToDos(dbHelper);
            }
            //empty input field
            et_todo.setText("");
        }
    });

    lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
            DataModel clickedItem = (DataModel) adapterView.getItemAtPosition(position);
            dbHelper.deleteOne(clickedItem);

            showAllToDos(dbHelper);
        }
    });
}

} }

DataModel数据模型

public class DataModel {

//Attributes
private int id;
private String title;

//Constructors
public DataModel(String title) {
    this.title = title;
}

public DataModel() {

}

//toString
@Override
public String toString() {
    return "DataModel{" +
            "id=" + id +
            ", title='" + title + '\'' +
            '}';
}

//Getters and Setters
public int getId() {
    return id;
}

public void setId(int id) {
    this.id = id;
}

public String getTitle() {
    return title;
}

public void setTitle(String title) {
    this.title = title;
}

} }

DataAdapter数据适配器

public class DataAdapter extends ArrayAdapter<DataModel> {

/**
 * Attributes
 */
private Context mContext;
private int mResource;
private ArrayList<DataModel> mList;
private DataBaseHelper mDbHelper;


public DataAdapter(Context context, int resource, ArrayList<DataModel> list, DataBaseHelper dbHelper) {
    super(context, resource, list);
    mContext = context;
    mResource = resource;
    mList = list;
    mDbHelper = dbHelper;
}

public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
    //get the objects information
    String title = getItem(position).getTitle();

    //create object with the information
    DataModel model = new DataModel(title);

    LayoutInflater inflater = LayoutInflater.from(mContext);
    convertView = inflater.inflate(mResource, parent, false);

    //get TextViews
    TextView tvTitle = (TextView) convertView.findViewById(R.id.task);

    //set information to TextViews
    tvTitle.setText(title);

    //delete function
    ImageView delView = (ImageView) convertView.findViewById(R.id.delView);
    delView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            mDbHelper.deleteOne(model);
        }
    });
    return convertView;
}

} }

DataBaseHelper数据库助手

public class DataBaseHelper extends SQLiteOpenHelper {

public static final String TODO_TABLE = "TODO_TABLE";
public static final String COLUMN_ID = "ID";
public static final String COLUMN_TODO_TITLE = "TODO_TITLE";
private Context mContext;

public DataBaseHelper(@Nullable Context context) {
    super(context, "todo.db", null, 1);
    mContext = context;
}

/**
 * Is called when the app requests or inputs new data.
 *
 * @param db
 */
@Override
public void onCreate(SQLiteDatabase db) {

    String createTableStatement = "CREATE TABLE " + TODO_TABLE
            + " (" + COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
            + COLUMN_TODO_TITLE + " TEXT)";

    db.execSQL(createTableStatement);
    //create new Table
}

/**
 * Called whenever the database version number changes.
 * Prevents the previous apps from crashing.
 *
 * @param sqLiteDatabase
 * @param i
 * @param i1
 */
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {

}

/**
 * method to add new database entry
 *
 * @param dModel
 * @return
 */
public boolean addOne(DataModel dModel) {
    SQLiteDatabase db = this.getWritableDatabase(); //for insert actions
    ContentValues cv = new ContentValues(); //Content values stores data in pairs

    cv.put(COLUMN_TODO_TITLE, dModel.getTitle());

    long insert = db.insert(TODO_TABLE, null, cv);

    //clean up, close connection to database and cursor
    db.close();
    if(insert == -1) { //if insert is negative number than insert went wrong
        return false;
    } else { //if insert is positive number than insert succeeded
        return true;
    }
}

public boolean deleteOne(DataModel dModel) {
    //if DataModel is found in the database, delete it and return true
    //if it is not found, return false
    SQLiteDatabase db = this.getWritableDatabase();

    String queryString = "DELETE FROM " + TODO_TABLE
            + " WHERE " + COLUMN_TODO_TITLE + " = " + "\"" + dModel.getTitle() + "\"";;

    Cursor cursor = db.rawQuery(queryString, null);

    if(cursor.moveToFirst()) {
        return true;
    } else {
        return false;
    }
}

public ArrayList<DataModel> getAllAsList() {
    //create empty list
    ArrayList<DataModel> returnList = new ArrayList<>();
    //get data from the database
    String queryString = "SELECT * FROM " + TODO_TABLE;
    SQLiteDatabase db = this.getReadableDatabase(); //get data from database
    Cursor cursor = db.rawQuery(queryString, null);

    if(cursor.moveToFirst()) { //returns a true if there were items selected
        //loop through results, create new todo objects, put them into return list
        do {
            String todoTitle = cursor.getString(1);

            DataModel newTodo = new DataModel(todoTitle);
            returnList.add(newTodo);

        } while(cursor.moveToNext());
    } else { //returns a false if no items were selected
        //failure, to not add anything to the list
    }

    //clean up, close connection to database and cursor
    cursor.close();
    db.close();

    return returnList;
}

public boolean existsInDB(DataModel dModel) {
    SQLiteDatabase db = this.getWritableDatabase(); //for insert actions
    String queryString = "SELECT * FROM " + TODO_TABLE
            + " WHERE " + COLUMN_TODO_TITLE + " = " + "\"" + dModel.getTitle() + "\"";

    Cursor cursor = db.rawQuery(queryString, null);

    if(cursor.moveToFirst()) {
        return true;
    } else {
        return false;
    }
}

} }

I appreciate any kind of help or suggestion.我感谢任何帮助或建议。 Let me know, if you need further explanation.如果您需要进一步解释,请告诉我。

Nicole妮可

Using SQLite directly can be challenging, also ListView is outdated not recommended to use anymore.直接使用 SQLite 可能具有挑战性,而且 ListView 已过时,不建议再使用。 The best practise is to use RecycleView最佳实践是使用 RecycleView

Here is a good tutorial how to use it all :这是一个很好的教程如何使用它

Room - wrapper library for SQLite from Google Room - 来自 Google 的 SQLite 包装库

LiveData - for subscribing for live update LiveData - 用于订阅实时更新

RecyclerView - UI widget for building efficient lists RecyclerView - 用于构建高效列表的 UI 小部件

In the version below, I added an onItemClickListener for the ListView in the MainActivity - but it doesn't function for when the user clicks the ImageView (even though the ImageView is part of the ListView).在下面的版本中,我在 MainActivity 中为 ListView 添加了一个 onItemClickListener - 但它在用户单击 ImageView 时不起作用(即使 ImageView 是 ListView 的一部分)。

Consider the following based upon your code:-根据您的代码考虑以下内容:-

Working Example工作示例

The following code is based upon yours BUT with various changes that does delete and refresh the list when the image (purple box is clicked).以下代码基于您的代码,但进行了各种更改,这些更改会在图像(单击紫色框)时删除并刷新列表。

The list_item.xml layout (altered to highlight the items as no image):- list_item.xml布局(更改为将项目突出显示为无图像):-

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:background="@color/teal_200">
    <TextView
        android:id="@+id/task"
        android:layout_width="400dp"
        android:layout_height="match_parent"
        >
    </TextView>
    <ImageView
        android:id="@+id/delView"
        android:layout_width="100dp"
        android:layout_height="20dp"
        android:layout_gravity="center"
        android:background="@color/purple_200"
        >
    </ImageView>
</LinearLayout>

The DataModel class is unchanged. DataModel类保持不变。

The DatabaseHelper class has been changed to a) not close the database (this is inefficient as it will apply the WAL changes and then have to subseuqently open the databaset which is relatively resource costly) and b) to close all cursors when done with the and c) utilise the convenience methods :- DatabaseHelper类已更改为 a) 不关闭数据库(这是低效的,因为它将应用 WAL 更改,然后必须随后打开相对资源消耗较大的数据库)和 b) 完成后关闭所有游标和c) 利用方便的方法:-

public class DataBaseHelper extends SQLiteOpenHelper {

    public static final String TODO_TABLE = "TODO_TABLE";
    public static final String COLUMN_ID = "ID";
    public static final String COLUMN_TODO_TITLE = "TODO_TITLE";
    private Context mContext;

    public DataBaseHelper(@Nullable Context context) {
        super(context, "todo.db", null, 1);
        mContext = context;
    }
    
    @Override
    public void onCreate(SQLiteDatabase db) {
        String createTableStatement = "CREATE TABLE " + TODO_TABLE
                + " (" + COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
                + COLUMN_TODO_TITLE + " TEXT)";

        db.execSQL(createTableStatement);
    }
    
    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {

    }
    
    public boolean addOne(DataModel dModel) {
        SQLiteDatabase db = this.getWritableDatabase(); //for insert actions
        ContentValues cv = new ContentValues(); //Content values stores data in pairs
        cv.put(COLUMN_TODO_TITLE, dModel.getTitle());
        return db.insert(TODO_TABLE, null, cv) > -1;
    }

    public boolean deleteOne(DataModel dModel) {
        SQLiteDatabase db = this.getWritableDatabase();
        return db.delete(TODO_TABLE,COLUMN_TODO_TITLE +"=?",new String[]{dModel.getTitle()}) > 0;
    }

    @SuppressLint("Range")
    public ArrayList<DataModel> getAllAsList() {
        //create empty list
        ArrayList<DataModel> returnList = new ArrayList<>();
        SQLiteDatabase db = this.getReadableDatabase();
        Cursor cursor = db.query(TODO_TABLE,null,null,null,null,null,null);
        while (cursor.moveToNext()) {
            returnList.add(new DataModel(cursor.getString(cursor.getColumnIndex(COLUMN_TODO_TITLE))));
        }
        cursor.close();
        return returnList;
    }

    public boolean existsInDB(DataModel dModel) {
        boolean rv = false;
        SQLiteDatabase db = this.getWritableDatabase(); //for insert actions
        Cursor cursor = db.query(TODO_TABLE,null,COLUMN_TODO_TITLE+"=?",new String[]{dModel.getTitle()},null,null,null);
        if(cursor.moveToFirst()) {
            rv = true;
        }
        cursor.close();
        return rv;
    }
}

DataAdapter数据适配器

public class DataAdapter extends ArrayAdapter<DataModel> {

   /**
    * Attributes
    */
   private Context mContext;
   private int mResource;
   private ArrayList<DataModel> mList;
   private DataBaseHelper mDbHelper;


   public DataAdapter(Context context, int resource, ArrayList<DataModel> list, DataBaseHelper dbHelper) {
      super(context, resource, list);
      mContext = context;
      mResource = resource;
      mList = list;
      mDbHelper = dbHelper;
   }

   public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
      //get the objects information
      String title = getItem(position).getTitle();

      //create object with the information
      DataModel model = new DataModel(title);

      LayoutInflater inflater = LayoutInflater.from(mContext);
      convertView = inflater.inflate(mResource, parent, false);

      //get TextViews
      TextView tvTitle = (TextView) convertView.findViewById(R.id.task);

      //set information to TextViews
      tvTitle.setText(title);

      //delete function
      ImageView delView = (ImageView) convertView.findViewById(R.id.delView);
      delView.setTag(position); //<<<<< ADDDED Sets the tag with the position in the list
      delView.setOnClickListener(new View.OnClickListener() {
         @Override
         public void onClick(View view) {
            Integer position = new Integer(view.getTag().toString()); // gets the position from the tag
            DataModel model = mList.get(position); // gets the respective DataModel from the ArrayList
            Toast.makeText(view.getContext(),"You clicked the Image for Title = " + model.getTitle() + " position " + view.getTag(),Toast.LENGTH_SHORT).show();
            Log.d("CLICKACTION","Image clicked for title = " + model.getTitle() + " position " + view.getTag());
            /* probably faster but the potential for issues if for some reason deletes other than expected
            if (mDbHelper.deleteOne(model)) {
               mList.remove(model);
               notifyDataSetChanged();
            }
             */
            /* Alternative approach - more intensive but the resultant list IS accurate */
            mDbHelper.deleteOne(model);
            ArrayList<DataModel> newlist = mDbHelper.getAllAsList();
            mList.clear();
            for (DataModel dm: newlist) {
               mList.add(dm);
               notifyDataSetChanged();
            }
         }
      });
      return convertView;
   }
}
  • NOTE an important aspect is utilising the tag to tie the clicked view to the Todo/DataModel.注意一个重要的方面是利用标签将点击的视图绑定到 Todo/DataModel。
  • NOTE another important factor is the manipulation of the DataAdapter's instance of the ArrayList and the subsequent notifyDatasetChanged that tells the adapter to rebuild.注意另一个重要因素是对 DataAdapter 的 ArrayList 实例的操作以及随后通知适配器重建的notifyDatasetChanged

and MainActivity :-MainActivity :-

public class MainActivity extends AppCompatActivity {
    //References to buttons and other controls on the layout
    private Button btn_add;
    private EditText et_todo;
    private Switch sw;
    private static ListView lv;
    private static DataAdapter todoAdapter;
    private DataBaseHelper dbHelper;

    /**
     * Initialization Method
     *
     * @param savedInstanceState
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        dbHelper = new DataBaseHelper(MainActivity.this);

        assignVariables();
        registerClick();
        showAllToDos(dbHelper); // creates a new instance of the adapter each time it is called
    }


    private void assignVariables() {
        //assign values to variables
        btn_add = (Button) findViewById(R.id.btn_add);
        et_todo = (EditText) findViewById(R.id.et_todo);
        lv = (ListView) findViewById(R.id.lv);
    }

    public void showAllToDos(DataBaseHelper dbHelper) {
        todoAdapter = new DataAdapter(MainActivity.this, R.layout.list_item, dbHelper.getAllAsList(), dbHelper);
        lv.setAdapter(todoAdapter);
    }
    
    private void registerClick() {
        btn_add.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String toDoTitle = et_todo.getText().toString();

                if(Pattern.matches("s*", toDoTitle)) {
                    Toast.makeText(MainActivity.this, "Title is missing", Toast.LENGTH_SHORT).show();
                } else if(dbHelper.existsInDB(new DataModel(toDoTitle))) {
                    Toast.makeText(MainActivity.this, "Already added as ToDo", Toast.LENGTH_SHORT).show();
                } else {
                    DataModel dModel = new DataModel(toDoTitle);
                    dbHelper.addOne(dModel);

                    showAllToDos(dbHelper);
                }
                //empty input field
                et_todo.setText("");
            }
        });


        lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
                Toast.makeText(view.getContext(),"Item was clicked for Title = " + ((DataModel) adapterView.getItemAtPosition(position)).getTitle(),Toast.LENGTH_SHORT).show();
                Log.d("CLICKACTION","Item was clicked for Title" + ((DataModel) adapterView.getItemAtPosition(position)).getTitle());
                //DataModel clickedItem = (DataModel) adapterView.getItemAtPosition(position);
                //dbHelper.deleteOne(clickedItem);
                //showAllToDos(dbHelper);
            }
        });
    }
}

* Results *结果

From a new install (empty Database) then the following actions were taken:-从新安装(空数据库)开始,采取了以下措施:-

  1. Add Test001 - Test006 so :-添加 Test001 - Test006 所以: -

    1. 在此处输入图像描述
  2. Click on Item Test003 (NOT the purple box (image))单击项目 Test003(不是紫色框(图像))

    1. Toast displays as expected. Toast 按预期显示。
    2. Log includes D/CLICKACTION: Item was clicked for TitleTest003日志包括D/CLICKACTION: Item was clicked for TitleTest003
  3. Click on the Delete image (purple box) for Test003单击 Test003 的删除图像(紫色框)

    1. Toast displays as expected Test003 position 2 Toast 按预期显示 Test003 位置 2
    2. Log includes D/CLICKACTION: Image clicked for title = Test003 position 2日志包括D/CLICKACTION: Image clicked for title = Test003 position 2
    3. 在此处输入图像描述
      1. ie Test003 removed from the display ie Test003 从显示中移除
    4. Database via App Inspection shows:-通过 App Inspection 的数据库显示:-
    • 在此处输入图像描述
      • ie no Test003 row in the database即数据库中没有Test003行

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM