// Room数据库
val roomVersion = "2.6.1"
//noinspection UseTomlInstead
implementation("androidx.room:room-runtime:$roomVersion")
//noinspection UseTomlInstead
annotationProcessor("androidx.room:room-compiler:$roomVersion")
// RecyclerView列表
//noinspection UseTomlInstead
implementation("androidx.recyclerview:recyclerview:1.3.2")
import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;
import android.content.Context;
@Database(entities = Note.class, version = 3, exportSchema = false)
public abstract class NoteDatabase extends RoomDatabase {
private static NoteDatabase instance;
public abstract NoteDao noteDao();
// 单例模式,避免重复创建数据库
public static synchronized NoteDatabase getInstance(Context context) {
if (instance == null) {
instance = Room.databaseBuilder(context.getApplicationContext(),
NoteDatabase.class, "note_database")
.fallbackToDestructiveMigration()
.build();
}
return instance;
}
}
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;
import java.util.List;
@Dao
public interface NoteDao {
// 增
@Insert
void insert(Note note);
// 删
@Delete
void delete(Note note);
// 改
@Update
void update(Note note);
// 查所有便签(按时间倒序)
@Query("SELECT * FROM note_table ORDER BY timestamp DESC")
List<Note> getAllNotes();
// 根据id查询单条便签
@Query("SELECT * FROM note_table WHERE id = :id")
Note getNoteById(int id);
}
import androidx.room.Entity;
import androidx.room.PrimaryKey;
@Entity(tableName = "note_table") // 数据库表名
public class Note {
// 自增主键
@PrimaryKey(autoGenerate = true)
private int id;
private String content; // 便签内容
private long timestamp; // 创建/修改时间戳
// 构造函数
public Note(String content, long timestamp) {
this.content = content;
this.timestamp = timestamp;
}
// Getter & Setter
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public long getTimestamp() {
return timestamp;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/page_background"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:paddingVertical="60dp" />
<ImageButton
android:id="@+id/btn_add"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_gravity="end"
android:layout_margin="16dp"
android:background="@drawable/btn_fab_background"
android:contentDescription="@string/str_add"
android:elevation="10dp"
android:src="@android:drawable/ic_menu_add"
android:stateListAnimator="@null"
android:text="@null"
android:tint="@android:color/white"
android:translationZ="10dp" />
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="12dp"
android:layout_marginTop="4dp"
android:layout_marginRight="12dp"
android:layout_marginBottom="8dp"
android:background="@drawable/item_bg_rounded"
android:orientation="vertical"
android:padding="16dp">
<!-- 内容文本 -->
<TextView
android:id="@+id/tv_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:ellipsize="end"
android:lineSpacingExtra="2dp"
android:maxLines="2"
android:textColor="@color/black"
android:textSize="14sp" />
<!-- 时间文本 -->
<TextView
android:id="@+id/tv_time"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:textColor="#666666"
android:textSize="12sp" />
<!-- 删除按钮 -->
<Button
android:id="@+id/btn_delete"
android:layout_width="80dp"
android:layout_height="32dp"
android:layout_gravity="end"
android:layout_marginTop="12dp"
android:background="@drawable/btn_delete_bg"
android:text="@string/str_delete"
android:textColor="@android:color/white"
android:textSize="12sp"
android:visibility="gone" />
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 背景色 -->
<solid android:color="@android:color/white" />
<!-- 圆角 -->
<corners android:radius="12dp" />
<!-- 描边(可选) -->
<stroke
android:width="0.5dp"
android:color="#EEEEEE" />
</shape>
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 按钮背景色 -->
<solid android:color="#FF4444" />
<!-- 圆角 -->
<corners android:radius="16dp" />
<!-- 点击水波纹效果(可选),可以在布局中用 android:foreground="?attr/selectableItemBackground" 替代 -->
</shape>
- activity_add_edit_note.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/page_background"
android:fitsSystemWindows="true"
android:orientation="vertical"
android:padding="16dp">
<EditText
android:id="@+id/et_content"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="16dp"
android:layout_weight="1"
android:gravity="top"
android:hint="@string/str_input_content"
android:textSize="16sp" />
<Button
android:id="@+id/btn_save"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/str_save" />
</LinearLayout>
<color name="page_background">#2b2d30</color>
<resources>
<string name="app_name">我的便签</string>
<string name="str_add">添加</string>
<string name="str_input_content">请输入内容</string>
<string name="str_save">保存</string>
<string name="str_delete">删除</string>
</resources>
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="AppTheme" parent="android:Theme.DeviceDefault.NoActionBar.TranslucentDecor">
<!-- Customize your light theme here. -->
<!-- <item name="colorPrimary">@color/my_light_primary</item> -->
</style>
<style name="Theme.Template" parent="AppTheme" />
</resources>
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Template">
<activity
android:name=".ui.MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".ui.AddEditNoteActivity"
android:exported="false"
android:windowSoftInputMode="adjustResize|stateAlwaysVisible" />
</application>
</manifest>
import android.app.Activity;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.widget.ImageButton;
import android.widget.Toast;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.example.note.R;
import com.example.note.db.Note;
import com.example.note.db.NoteDatabase;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public class MainActivity extends Activity {
public static final int ADD_NOTE_REQUEST = 1;
public static final int EDIT_NOTE_REQUEST = 2;
private NoteDatabase noteDatabase;
private NoteAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 初始化数据库
noteDatabase = NoteDatabase.getInstance(this);
// 列表配置
RecyclerView recyclerView = findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
adapter = new NoteAdapter();
recyclerView.setAdapter(adapter);
// 加载便签
loadNotes();
// 添加便签
ImageButton btnAdd = findViewById(R.id.btn_add);
btnAdd.setOnClickListener(v -> {
Intent intent = new Intent(MainActivity.this, AddEditNoteActivity.class);
startActivityForResult(intent, ADD_NOTE_REQUEST);
});
// 点击编辑
adapter.setOnItemClickListener(note -> {
Intent intent = new Intent(MainActivity.this, AddEditNoteActivity.class);
intent.putExtra(AddEditNoteActivity.EXTRA_ID, note.getId());
intent.putExtra(AddEditNoteActivity.EXTRA_CONTENT, note.getContent());
startActivityForResult(intent, EDIT_NOTE_REQUEST);
});
// 删除便签
adapter.setOnDeleteClickListener(note -> {
AsyncTask.execute(() -> {
noteDatabase.noteDao().delete(note);
runOnUiThread(() -> {
Toast.makeText(MainActivity.this, "已删除", Toast.LENGTH_SHORT).show();
loadNotes();
});
});
});
}
// 加载所有便签(子线程)
private void loadNotes() {
AsyncTask.execute(() -> {
List<Note> notes = noteDatabase.noteDao().getAllNotes();
runOnUiThread(() -> adapter.setNotes(notes));
});
}
// 处理添加/编辑结果
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK && data != null) {
String content = data.getStringExtra(AddEditNoteActivity.EXTRA_CONTENT);
long time = data.getLongExtra(AddEditNoteActivity.EXTRA_TIME, 0);
Note note = new Note(content, time);
// 添加
if (requestCode == ADD_NOTE_REQUEST) {
AsyncTask.execute(() -> noteDatabase.noteDao().insert(note));
Toast.makeText(this, "便签已保存", Toast.LENGTH_SHORT).show();
}
// 编辑
else if (requestCode == EDIT_NOTE_REQUEST) {
int id = data.getIntExtra(AddEditNoteActivity.EXTRA_ID, -1);
if (id != -1) {
note.setId(id);
AsyncTask.execute(() -> noteDatabase.noteDao().update(note));
Toast.makeText(this, "便签已更新", Toast.LENGTH_SHORT).show();
}
}
loadNotes();
}
}
}
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import com.example.note.R;
import com.example.note.db.Note;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public class NoteAdapter extends RecyclerView.Adapter<NoteAdapter.NoteHolder> {
private List<Note> notes = new ArrayList<>();
private OnItemClickListener listener;
private OnDeleteClickListener deleteListener;
private final SimpleDateFormat SDF = new SimpleDateFormat("MM-dd HH:mm:SS", Locale.getDefault());
@Override
public NoteHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.note_item, parent, false);
return new NoteHolder(view);
}
@Override
public void onBindViewHolder(NoteHolder holder, int position) {
Note currentNote = notes.get(position);
holder.tvContent.setText(currentNote.getContent());
holder.tvTime.setText(SDF.format(currentNote.getTimestamp()));
}
@Override
public int getItemCount() { return notes.size(); }
public void setNotes(List<Note> notes) {
this.notes = notes;
notifyDataSetChanged();
}
public Note getNoteAt(int position) {
return notes.get(position);
}
class NoteHolder extends RecyclerView.ViewHolder {
private final TextView tvContent, tvTime;
private final Button btnDelete;
public NoteHolder(View itemView) {
super(itemView);
tvContent = itemView.findViewById(R.id.tv_content);
tvTime = itemView.findViewById(R.id.tv_time);
btnDelete = itemView.findViewById(R.id.btn_delete);
// 点击事件
itemView.setOnClickListener(v -> {
int position = getAbsoluteAdapterPosition();
if (listener != null && position != RecyclerView.NO_POSITION) {
listener.onItemClick(notes.get(position));
}
});
itemView.setOnLongClickListener(v -> {
btnDelete.setVisibility(View.VISIBLE);
return true;
});
// 点击删除
btnDelete.setOnClickListener(v -> {
int position = getAbsoluteAdapterPosition();
if (deleteListener != null && position != RecyclerView.NO_POSITION) {
deleteListener.onDeleteClick(notes.get(position));
}
});
}
}
public interface OnItemClickListener {
void onItemClick(Note note);
}
public void setOnItemClickListener(OnItemClickListener listener) {
this.listener = listener;
}
// 删除点击
public interface OnDeleteClickListener {
void onDeleteClick(Note note);
}
public void setOnDeleteClickListener(OnDeleteClickListener listener) {
this.deleteListener = listener;
}
}
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import com.example.note.R;
public class AddEditNoteActivity extends Activity {
public static final String EXTRA_ID = "extra_id";
public static final String EXTRA_CONTENT = "extra_content";
public static final String EXTRA_TIME = "extra_time";
private EditText etContent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add_edit_note);
etContent = findViewById(R.id.et_content);
Button btnSave = findViewById(R.id.btn_save);
// 编辑模式:填充原有数据
Intent intent = getIntent();
if (intent.hasExtra(EXTRA_ID)) {
setTitle("编辑便签");
etContent.setText(intent.getStringExtra(EXTRA_CONTENT));
} else {
setTitle("添加便签");
}
// 保存按钮
btnSave.setOnClickListener(v -> saveNote());
etContent.requestFocus();
// // 监听软键盘高度变化(仅 Android 11+ 生效)
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// getWindow().getDecorView().setOnApplyWindowInsetsListener((view, insets) -> {
// // 获取软键盘高度(底部内边距)
// int keyboardHeight = insets.getInsets(WindowInsets.Type.ime()).bottom;
//
// // 调整按钮底部边距,使其显示在键盘上方
// LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) btnSave.getLayoutParams();
// params.bottomMargin = keyboardHeight; // 可自定义间距,如 keyboardHeight + 10
// btnSave.setLayoutParams(params);
//
// // 返回处理后的Insets
// return view.onApplyWindowInsets(insets);
// });
// // 强制触发Insets计算
// getWindow().getDecorView().requestApplyInsets();
// }
}
private void saveNote() {
String content = etContent.getText().toString().trim();
if (content.isEmpty()) {
Toast.makeText(this, "内容不能为空", Toast.LENGTH_SHORT).show();
return;
}
// 保存数据
Intent data = new Intent();
data.putExtra(EXTRA_CONTENT, content);
data.putExtra(EXTRA_TIME, System.currentTimeMillis());
int id = getIntent().getIntExtra(EXTRA_ID, -1);
if (id != -1) {
data.putExtra(EXTRA_ID, id);
}
setResult(RESULT_OK, data);
finish();
}
}