I. 引言
点击查看该项目GitHub地址
本文将主要实现笔记的可删除功能:
用户能够长按笔记进行删除操作。
II. 前情回顾
轻松上手:<Android Studio笔记应用开发>(一)入门与笔记应用浅开发
轻松上手:<Android Studio笔记应用开发>(二)笔记可显示Part1:实现逻辑与textView
轻松上手:<Android Studio笔记应用开发>(二)笔记可显示Part2:定义笔记的数据结构类型
轻松上手:<Android Studio笔记应用开发>(二)笔记可显示Part3:适配器
轻松上手:<Android Studio笔记应用开发>(二)大功告成!添加新笔记!
轻松上手:<Android Studio笔记应用开发>(三)笔记可再编辑
为了创建一个简单的Android笔记应用,前文已经成功实现了以下主要功能:
-
笔记的展示: 主活动(MainActivity)中通过一个列表视图(ListView)展示了所有笔记的内容和创建时间。
-
笔记的添加: 用户通过悬浮按钮(FloatingActionButton)可以添加新的笔记,进入编辑页面(EditActivity),并在该页面输入笔记内容后保存。
-
笔记的保存和显示: 新添加的笔记会保存在 SQLite 数据库中,主活动在每次启动时从数据库读取笔记列表,并通过适配器(NoteAdapter)将笔记显示在列表视图中。
-
笔记的点击编辑: 用户可以点击笔记列表中的项,进入编辑页面,编辑该笔记的内容。
III. 适配器的改进
1. 引入笔记项点击的回调接口 OnNoteItemLongClickListener
用于处理笔记项的长按事件。
2. 添加 OnNoteItemLongClickListener 接口
在 NoteAdapter 类中添加一个接口 OnNoteItemLongClickListener,用于定义笔记项长按的回调方法。
public interface OnNoteItemLongClickListener { void onNoteItemLongClick(long noteId); }
3. 注册点击事件监听器
在 NoteAdapter 类中添加一个 OnNoteItemLongClickListener 成员变量,并在构造函数中接收它。
private OnNoteItemLongClickListener onNoteItemLongClickListener; public NoteAdapter(Context context, ListnoteList, OnNoteItemClickListener listener, OnNoteItemLongClickListener longClickListener) { this.context = context; this.noteList = noteList; this.onNoteItemClickListener = listener; this.onNoteItemLongClickListener = longClickListener; }
4. 在 getView 中触发回调
在 getView 方法中,当笔记项被长按时,触发回调接口的方法,将长按事件传递给主活动类处理。
view.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View view) { // 触发笔记项长按事件 if (onNoteItemLongClickListener != null) { onNoteItemLongClickListener.onNoteItemLongClick(noteList.get(position).getId()); } return true; // 消耗长按事件 } });
5. 适配器更新后的代码
package com.example.my_notes_record; import android.content.Context; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.TextView; import java.util.List; public class NoteAdapter extends BaseAdapter { public interface OnNoteItemClickListener { void onNoteItemClick(long noteId); } public interface OnNoteItemLongClickListener { void onNoteItemLongClick(long noteId); } private Context context; private ListnoteList; private OnNoteItemClickListener onNoteItemClickListener; private OnNoteItemLongClickListener onNoteItemLongClickListener; // 默认构造函数 public NoteAdapter(){ } // 带参数的构造函数,接受上下文和笔记列表 public NoteAdapter(Context Context,List noteList){ this.context=Context; this.noteList=noteList; } public NoteAdapter(Context context, List noteList, OnNoteItemClickListener onNoteItemClickListener) { this.context = context; this.noteList = noteList; this.onNoteItemClickListener = onNoteItemClickListener; } public NoteAdapter(Context context, List noteList, OnNoteItemClickListener listener, OnNoteItemLongClickListener longClickListener) { this.context = context; this.noteList = noteList; this.onNoteItemClickListener = listener; this.onNoteItemLongClickListener = longClickListener; } // 获取列表项数量 @Override public int getCount() { return noteList.size(); } // 获取指定位置的笔记对象 @Override public Object getItem(int position){ return noteList.get(position); } // 获取指定位置的笔记ID @Override public long getItemId(int position){ return position; } // 创建并返回每个列表项的视图 @Override public View getView(int position, View convertView, ViewGroup parent) { // 从XML布局文件实例化视图 View view = View.inflate(context, R.layout.note_list_item, null); // 获取布局中的TextView控件 TextView tv_content = (TextView) view.findViewById(R.id.tv_content); TextView tv_time = (TextView) view.findViewById(R.id.tv_time); // 从笔记对象中获取内容和时间信息 String allText = noteList.get(position).getContent(); // 设置TextView的文本内容 tv_content.setText(allText.split("\n")[0]); tv_time.setText(noteList.get(position).getTime()); // 将笔记ID作为视图的标签 view.setTag(noteList.get(position).getId()); view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // 触发笔记项点击事件 if (onNoteItemClickListener != null) { onNoteItemClickListener.onNoteItemClick(noteList.get(position).getId()); } } }); view.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View view) { // 触发笔记项长按事件 if (onNoteItemLongClickListener != null) { onNoteItemLongClickListener.onNoteItemLongClick(noteList.get(position).getId()); } return true; // 消耗长按事件 } }); return view; } }
IV. 主活动的更新
1. 实现接口 NoteAdapter.OnNoteItemLongClickListener
public class MainActivity extends AppCompatActivity implements NoteAdapter.OnNoteItemClickListener , NoteAdapter.OnNoteItemLongClickListener{ // ... }
2. 适配器初始化
在主活动中初始化适配器时,同时传递实现了 OnNoteItemLongClickListener 接口的当前活动实例。
adapter = new NoteAdapter(getApplicationContext(), noteList , this , this);
3. 实现接口方法处理笔记项长按事件
在这一部分中,我们将实现 OnNoteItemLongClickListener 接口的方法,以处理笔记项的长按事件。这个过程主要包括弹出确认删除对话框和在用户确认后删除笔记。
3.1 实现 onNoteItemLongClick 方法
@Override public void onNoteItemLongClick(long noteId) { // 当笔记项长按时触发,显示删除确认对话框 showDeleteConfirmationDialog(noteId); }
3.2 显示删除确认对话框
private void showDeleteConfirmationDialog(final long noteId) { // 创建一个AlertDialog.Builder实例,用于构建对话框 AlertDialog.Builder builder = new AlertDialog.Builder(this); // 设置对话框消息和按钮 builder.setMessage("确定要删除此笔记吗?") .setPositiveButton("删除", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { // 在用户确认后删除笔记 deleteNoteById(noteId); } }) .setNegativeButton("取消", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { // 用户取消对话框,不执行任何操作 } }); // 创建并显示对话框 builder.create().show(); }
在这里,AlertDialog.Builder 被用于构建一个确认删除的对话框。其中,setMessage 方法设置对话框的消息,setPositiveButton 和 setNegativeButton 方法分别设置确认和取消按钮,并通过 OnClickListener 处理点击事件。
3.3 删除笔记的实现
// 通过 ID 删除笔记的方法 private void deleteNoteById(long noteId) { // 创建一个 CRUD 实例 CRUD op = new CRUD(this); op.open(); // 调用 CRUD 类中的 deleteNoteById 方法执行删除操作 op.deleteNoteById(noteId); // 关闭数据库连接 op.close(); // 删除后刷新笔记列表 refreshListView(); }
4. 主活动更新后的代码
package com.example.my_notes_record; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.ListView; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import com.google.android.material.floatingactionbutton.FloatingActionButton; import java.util.ArrayList; import java.util.List; // 创建名为 "MainActivity" 的主活动类 public class MainActivity extends AppCompatActivity implements NoteAdapter.OnNoteItemClickListener , NoteAdapter.OnNoteItemLongClickListener{ private Context context = this; // 上下文对象,用于数据库操作 private NoteDatabase dbHelper; // 数据库帮助类 private NoteAdapter adapter; // 笔记适配器 private ListnoteList = new ArrayList<>(); // 笔记列表 private FloatingActionButton btn; // 悬浮按钮 private ListView lv; // 列表视图 // 定义一个 ActivityResultLauncher,用于处理其他活动的结果 private ActivityResultLauncher someActivityResultLauncher; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //调用父类的 onCreate 方法,用于执行一些初始化操作 //savedInstanceState 参数用于恢复之前的状态 setContentView(R.layout.activity_main);//设置当前 Activity 的布局 btn = findViewById(R.id.floatingActionButton); // 悬浮按钮 lv = findViewById(R.id.lv); // 列表视图,用于显示数据列表 adapter = new NoteAdapter(getApplicationContext(), noteList , this , this);//初始化一个笔记适配器,并将应用的上下文对象和笔记列表传递给适配器 refreshListView(); // 刷新笔记列表 lv.setAdapter(adapter); // 将适配器与列表视图关联,从而显示笔记列表中的数据在界面上 // 初始化 ActivityResultLauncher,用于处理启动其他活动的结果 someActivityResultLauncher = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), result -> { if (result.getResultCode() == RESULT_OK) { Intent data = result.getData(); if (data != null) { // 从 EditActivity 返回的内容和时间 String content = data.getStringExtra("content"); String time = data.getStringExtra("time"); long noteId = data.getLongExtra("note_id", -1); // 检查是否是新笔记还是更新现有笔记 if (noteId == -1L) { // 如果是新笔记,调用添加新笔记的方法 if(!content.isEmpty()) addNewNote(content, time); } else { // 如果是现有笔记,调用更新现有笔记的方法 updateExistingNote(noteId, content, time); } refreshListView(); // 刷新笔记列表 } } } ); // 设置悬浮按钮的点击事件监听器 btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // 启动 EditActivity 并等待结果 Intent intent = new Intent(MainActivity.this, EditActivity.class); someActivityResultLauncher.launch(intent); } }); } // 刷新笔记列表 public void refreshListView() { // 创建数据库操作对象,打开数据库连接 CRUD op = new CRUD(context); op.open(); if (noteList.size() > 0) noteList.clear(); // 清空笔记列表 noteList.addAll(op.getAllNotes()); // 获取数据库中所有笔记 op.close(); // 关闭数据库连接 adapter.notifyDataSetChanged(); // 通知适配器数据已更改,刷新列表视图 } @Override public void onNoteItemClick(long noteId) { // 处理项点击,启动 EditActivity 并传递选定笔记以进行编辑 Intent intent = new Intent(MainActivity.this, EditActivity.class); intent.putExtra("note_id", noteId); someActivityResultLauncher.launch(intent); } // 添加新笔记 private void addNewNote(String content, String time) { CRUD op = new CRUD(this); op.open(); Note newNote = new Note(content, time); op.addNote(newNote); op.close(); } // 更新现有笔记 private void updateExistingNote(long noteId, String content, String time) { CRUD op = new CRUD(this); op.open(); Note updatedNote = new Note(content, time); updatedNote.setId(noteId); op.updateNote(updatedNote); op.close(); } @Override public void onNoteItemLongClick(long noteId) { // 当笔记项长按时触发,显示删除确认对话框 showDeleteConfirmationDialog(noteId); } private void showDeleteConfirmationDialog(final long noteId) { // 创建一个AlertDialog.Builder实例,用于构建对话框 AlertDialog.Builder builder = new AlertDialog.Builder(this); // 设置对话框消息和按钮 builder.setMessage("确定要删除此笔记吗?") .setPositiveButton("删除", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { // 在用户确认后删除笔记 deleteNoteById(noteId); } }) .setNegativeButton("取消", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { // 用户取消对话框,不执行任何操作 } }); // 创建并显示对话框 builder.create().show(); } // 通过 ID 删除笔记的方法 private void deleteNoteById(long noteId) { // 创建一个 CRUD 实例 CRUD op = new CRUD(this); op.open(); // 调用 CRUD 类中的 deleteNoteById 方法执行删除操作 op.deleteNoteById(noteId); // 关闭数据库连接 op.close(); // 删除后刷新笔记列表 refreshListView(); } }
V. 数据库操作的扩展
1. 实现根据 ID 删除笔记的方法
public void deleteNoteById(long noteId) { // 执行删除操作,根据 ID 删除指定笔记 db.delete( NoteDatabase.TABLE_NAME, NoteDatabase.ID + "=?", new String[]{String.valueOf(noteId)} ); }
2. 数据库操作扩展后的代码
package com.example.my_notes_record; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import java.util.ArrayList; import java.util.List; public class CRUD { SQLiteOpenHelper dbHandler; // SQLiteOpenHelper 实例用于处理数据库连接 SQLiteDatabase db; // SQLiteDatabase 实例用于执行数据库操作 // 定义数据库表的列名 private static final String[] columns = { NoteDatabase.ID, NoteDatabase.CONTENT, NoteDatabase.TIME }; // 构造方法,接受上下文参数 public CRUD(Context context) { dbHandler = new NoteDatabase(context); // 初始化数据库处理器 } // 打开数据库连接 public void open() { db = dbHandler.getWritableDatabase(); // 获取可写的数据库连接 } // 关闭数据库连接 public void close() { dbHandler.close(); // 关闭数据库处理器 } // 添加一条笔记记录 public Note addNote(Note note) { ContentValues contentValues = new ContentValues(); // 创建一个用于存储数据的 ContentValues 对象 contentValues.put(NoteDatabase.CONTENT, note.getContent()); // 添加内容 contentValues.put(NoteDatabase.TIME, note.getTime()); // 添加时间 long insertId = db.insert(NoteDatabase.TABLE_NAME, null, contentValues); // 将数据插入数据库 note.setId(insertId); // 将插入后的 ID 设置到笔记对象中 return note; // 返回包含新数据的笔记对象 } public ListgetAllNotes() { Cursor cursor = db.query( NoteDatabase.TABLE_NAME, // 表名 columns, // 要查询的列(在这里是ID、内容、时间) null, // 查询条件(null表示无特殊条件) null, // 查询条件参数(null表示无特殊条件) null, // 分组方式(null表示不分组) null, // 过滤方式(null表示不过滤) null // 排序方式(null表示不排序) ); List notes = new ArrayList<>(); // 创建一个笔记列表用于存储查询结果 if (cursor.getCount() > 0) { while (cursor.moveToNext()) { Note note = new Note(); // 创建笔记对象 note.setId(cursor.getLong(cursor.getColumnIndex(NoteDatabase.ID))); // 设置 ID note.setContent(cursor.getString(cursor.getColumnIndex(NoteDatabase.CONTENT))); // 设置内容 note.setTime(cursor.getString(cursor.getColumnIndex(NoteDatabase.TIME))); // 设置时间 notes.add(note); // 将笔记对象添加到列表中 } } cursor.close(); // 关闭游标 return notes; // 返回包含所有笔记记录的列表 } // 根据 ID 获取笔记 public Note getNoteById(long noteId) { // 查询数据库,获取指定 ID 的笔记记录 Cursor cursor = db.query( NoteDatabase.TABLE_NAME, // 表名 columns, // 要查询的列(在这里是ID、内容、时间) NoteDatabase.ID + "=?", // 查询条件(通过 ID 进行查询) new String[]{String.valueOf(noteId)}, // 查询条件参数(指定要查询的 ID 值) null, // 分组方式(null表示不分组) null, // 过滤方式(null表示不过滤) null // 排序方式(null表示不排序) ); Note note = null; if (cursor.moveToFirst()) { // 如果查询到结果,则创建新的笔记对象并设置其属性 note = new Note(); note.setId(cursor.getLong(cursor.getColumnIndex(NoteDatabase.ID))); // 设置 ID note.setContent(cursor.getString(cursor.getColumnIndex(NoteDatabase.CONTENT))); // 设置内容 note.setTime(cursor.getString(cursor.getColumnIndex(NoteDatabase.TIME))); // 设置时间 } cursor.close(); // 关闭游标,释放资源 return note; // 返回获取到的笔记对象,如果未找到则返回 null } // 更新笔记 public void updateNote(Note note) { // 创建一个 ContentValues 对象,用于存储要更新的数据 ContentValues values = new ContentValues(); values.put(NoteDatabase.CONTENT, note.getContent()); values.put(NoteDatabase.TIME, note.getTime()); // 执行数据库更新操作 db.update( NoteDatabase.TABLE_NAME, // 表名 values, // 更新的内容值 NoteDatabase.ID + "=?", // 更新条件(通过 ID 进行更新) //`"=?"` 是一个占位符,它表示在 SQL 查询中使用参数。这是一种防止 SQL 注入攻击的方式。在这里,它表示将在这个位置上填入具体的数值。 new String[]{String.valueOf(note.getId())} // 更新条件参数(指定要更新的 ID 值) //创建一个字符串数组,数组中包含了要替代占位符 `"=?"` 的具体数值。在这里,它包含了笔记对象的 ID。 ); } // 根据 ID 删除笔记 public void deleteNoteById(long noteId) { // 执行删除操作,根据 ID 删除指定笔记 db.delete( NoteDatabase.TABLE_NAME, NoteDatabase.ID + "=?", new String[]{String.valueOf(noteId)} ); } }
VI. 一些不太容易理解的点
1. 为什么要引入接口OnNoteItemLongClickListener,以及它的作用是什么?
此处接口的引入主要是为了实现解耦。
将长按事件的处理逻辑从适配器中抽离,使得适配器可以更灵活地应对不同的事件处理需求。
通过接口,我们将长按事件的处理权力交给了主活动类。
这样一来,可以保障代码的结构良好和可维护性。
2. 为什么在 NoteAdapter 构造函数中要传递一个 OnNoteItemLongClickListener 实例?
为适配器提供了一个接口,允许主活动类定义长按事件的处理逻辑。
3. 为什么要在 getView 方法中设置长按事件监听器,并且传递 noteId ?
通过在 getView 方法中设置长按事件监听器, 可以为每个笔记项设置独立的长按事件监听器。
传递 noteId,我们把笔记项的标识符传递给主活动类,让主活动类负责实际的业务逻辑。
4. 对AlertDialog.Builder不熟悉怎么办?
AlertDialog.Builder 是 Android 提供的一个用于构建对话框的类,它允许我们以链式调用的方式设置对话框的各种属性,包括标题、消息、按钮等。主要步骤包括创建构建器实例、设置属性、创建对话框。
在文章中,我们使用了 AlertDialog.Builder 来构建确认删除的对话框。以下是相关代码:
private void showDeleteConfirmationDialog(final long noteId) { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setMessage("确定要删除此笔记吗?") .setPositiveButton("删除", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { // 在确认后删除笔记 deleteNoteById(noteId); } }) .setNegativeButton("取消", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { // 用户取消对话框,不执行任何操作 } }); builder.create().show(); }
以下作简单解释
-
AlertDialog.Builder builder = new AlertDialog.Builder(this);:创建一个对话框构建器实例,传入当前活动的上下文。
-
builder.setMessage("确定要删除此笔记吗?"):设置对话框的消息,即要显示的文本内容。
-
.setPositiveButton("删除", new DialogInterface.OnClickListener() {...}):设置对话框的确认按钮,以及确认按钮的点击事件处理逻辑。
-
.setNegativeButton("取消", new DialogInterface.OnClickListener() {...}):设置对话框的取消按钮,以及取消按钮的点击事件处理逻辑。
-
builder.create().show();:通过构建器创建对话框实例并显示。
如果读者想更多了解AlertDialog.Builder,不妨查阅官方文档并自行实践。
VII. 拓展
- 有没有更好的方法来优化和扩展现有的代码?
- 是否可以引入其他设计模式或框架来改进应用的结构和性能?
- 除了删除确认对话框外,是否还有其他方式来提高用户交互的友好性和便利性?
VI. 结语
搞定啦!现在你学会了如何用适配器模式改进 Android 应用,更灵活处理长按事件。别忘了思考文章结尾的提问哦,欢迎交流!🚀
持续爆肝更新中…🫡🫡🫡
求点赞👍求关注🩷求转发💕