俺的碎碎念:写了个简单的记事本APP 复习了下SQLite的使用以及各种边角知识,希望能对各位带来一定的启发。
功能简介:本项目实现了记事本的基本功能,支持创建笔记,编辑笔记,长按删除笔记等功能;后续新增功能也会同步更新到博客和公众号。
主要功能演示:
创建新笔记:
编辑笔记:
删除笔记:
本项目的笔记APP核心的存储逻辑是基于SQLite数据库开发的,说白了就是增删改查的简单封装和使用。
思路也比较简单,RecycleView负责展示对应的笔记列表,而实际的存储是依靠SQLite。
废话不多说来看下关键代码。
SQLite数据库代码讲解
DBUtils工具类代码如下:
//工具类的存在主要是为了创建Helper文件时 引用的数据库参数更加便捷,使逻辑更清楚。
public class DBUtils {
public static final String SQL_NAME = "Notepad.db";//数据库名
public static final String SQL_TABLE = "notemane";//表名
public static final int DATABASE_VERSION = 1;//数据库版本
//数据库表中的列名
public static final String NOTEPAD_ID = "id";
public static final String NOTEPAD_CONTENT = "content";//item内容
public static final String NOTEPAD_TIME = "time";//item的修改时间
public static final String NOTEPAD_TITLE = "title";//标题
//获取时间
public static final String getTime() {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
Date date = new Date(System.currentTimeMillis());
return simpleDateFormat.format(date);
}
}
Android 提供了抽象类SQLiteOpenHelper来帮助我们使用SQLite数据库,如果我们想要使用SQLite数据库,那么就必须新建一个Helper类来继承SQLiteOpenHelper这个抽象类。
SQLiteHelper代码如下:
public class SQLiteHelper extends SQLiteOpenHelper {
SQLiteDatabase mSQLiteDatabase;
public SQLiteHelper(Context context) {
super(context, DBUtils.SQL_NAME, null, DBUtils.DATABASE_VERSION);
mSQLiteDatabase = this.getWritableDatabase();
//在数据库创建时即允许写入
}
/**
* 构造函数
*
* @param context
* @param name
* @param factory
* @param version
* @param errorHandler
*/
public SQLiteHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version, DatabaseErrorHandler errorHandler) {
super(context, name, factory, version, errorHandler);
}
public SQLiteHelper(Context context, String name, int version, @NonNull SQLiteDatabase.OpenParams openParams) {
super(context, name, version, openParams);
}
@Override
public void onCreate(SQLiteDatabase db) {
//创建表
String sql = "create table notemane(id integer primary key autoincrement,content varchar(20),time varchar(20),title varchar(20))";
db.execSQL(sql);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
//增
public boolean insertDate(String userContent, String userTime, String userTitle) {
mSQLiteDatabase = this.getWritableDatabase();//允许写入
ContentValues values = new ContentValues();
values.put(DBUtils.NOTEPAD_CONTENT, userContent);
values.put(DBUtils.NOTEPAD_TIME, userTime);
values.put(DBUtils.NOTEPAD_TITLE, userTitle);
return mSQLiteDatabase.insert(DBUtils.SQL_TABLE, null, values) > 0;
}
//删
public boolean deleteData(String id) {
mSQLiteDatabase = this.getWritableDatabase();//允许写入
String sql = DBUtils.NOTEPAD_TIME + "=?";
//删除是根据时间删除的,也可以根据id删除,但是根据id删除要考虑到recycleView的position会变化
String[] contentValues = new String[]{String.valueOf(id)};
return mSQLiteDatabase.delete(DBUtils.SQL_TABLE, sql, contentValues) > 0;
}
//改
public boolean updateData(String id, String userTitle, String userContent, String userTime) {
mSQLiteDatabase = this.getWritableDatabase();//允许写入
ContentValues values = new ContentValues();
values.put(DBUtils.NOTEPAD_TITLE, userTitle);
values.put(DBUtils.NOTEPAD_CONTENT, userContent);
values.put(DBUtils.NOTEPAD_TIME, userTime);
String sql = DBUtils.NOTEPAD_ID + "=?";
String[] strings = new String[]{id};
return mSQLiteDatabase.update(DBUtils.SQL_TABLE, values, sql, strings) > 0;
}
}
主界面代码讲解
主界面的主要功能是展示笔记列表,列表这里我选用的是RecycleView。
RecycleView的使用流程: 在主布局中引用RecycleView控件 -> 搭建RecycleView中item的布局 -> 搭建RecycleView的适配器 -> 在MainActivity中使用。
RecycleViewAdapter代码如下:
public class RecycleViewAdapter extends RecyclerView.Adapter<RecycleViewAdapter.ViewHolder> {
private static final String TAG = "RecycleViewAdapter";
Context mContext;
private ArrayList<NoteMessageBean> mList;
NoteMessageBean mNoteMessageBean = new NoteMessageBean();//笔记的bean类
onRecycleItemClickListener monRecycleItemClickListener;
onRecycleItemCheckChangeListener mRecycleItemCheckChangeListener;
//构造函数,一个参数
public RecycleViewAdapter(Context mContext) {
this.mContext = mContext;
}
//构造函数,两个参数
public RecycleViewAdapter(Context mContext, ArrayList<NoteMessageBean> mList) {
this.mContext = mContext;
this.mList = mList;
}
@Override
public RecycleViewAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycle_item, parent, false);
return new ViewHolder(itemView);
}
@Override
public void onBindViewHolder(RecycleViewAdapter.ViewHolder holder, int position) {
mNoteMessageBean = (mList.get(position));
holder.text.setText(mNoteMessageBean.getContent());//每个item中的简单介绍
holder.title.setText(mNoteMessageBean.getTitle());//每个item中的标题
holder.time.setText(mNoteMessageBean.getmTime());//每个item中的时间
holder.itemView.setOnClickListener(v -> {
if (monRecycleItemClickListener != null) {
NoteMessageBean messageBean = mList.get(position);
Log.d(TAG, "onBindViewHolder-> itemView -> onClick: " + position + messageBean.getTitle() + messageBean.getContent() + messageBean.getmTime());
monRecycleItemClickListener.onItemClick(messageBean.getId(), messageBean.getTitle(), messageBean.getContent(), messageBean.getmTime());
}
});
holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if (mRecycleItemCheckChangeListener != null) {
holder.mCheckBox.setVisibility(View.VISIBLE);
}
return true;
}
});
holder.mCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
Log.d(TAG, "onCheckedChanged: !isChecked" + isChecked);
if (!isChecked) {
buttonView.setChecked(false);
//从选中 -> 未选中状态
} else {
buttonView.setChecked(true);
//从未选中 -> 选中状态
//当选中CheckBox时 弹出弹窗提示 是否删除
Dialog mDialog = new Dialog(mContext, R.style.ThemeOverlay_AppCompat_Dialog);
View view = View.inflate(mContext, R.layout.delete_dialog, null);
ImageView mImageView = (ImageView) view.findViewById(R.id.delete_item_image);
mDialog.setContentView(view);
mDialog.setCanceledOnTouchOutside(false);
mDialog.show();
Window window = mDialog.getWindow();
window.setContentView(view);
window.setGravity(Gravity.BOTTOM);
WindowManager.LayoutParams lp = mDialog.getWindow().getAttributes();
lp.width = WindowManager.LayoutParams.MATCH_PARENT; //设置宽度
lp.height = WindowManager.LayoutParams.WRAP_CONTENT; //设置宽度
mDialog.getWindow().setAttributes(lp);
mDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
//如果手势操作返回键 取消了弹窗显示
//则 弹窗取消显示时 对 CheckBox进行隐藏
holder.mCheckBox.setChecked(false);
holder.mCheckBox.setVisibility(View.GONE);
}
});
//点击删除图标
mImageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
NoteMessageBean messageBean = mList.get(position);
mRecycleItemCheckChangeListener.onImageViewOnClick((messageBean.getmTime()), position);
mDialog.dismiss();
holder.mCheckBox.setVisibility(View.GONE);
holder.mCheckBox.setChecked(false);
notifyDataSetChanged();
}
});
}
}
});
//这里是给不同的item设置不同的颜色
//用position的个位数进行约束
int mNumber = position % 10;
Log.d(TAG, "onBindViewHolder: " + mNumber);
switch (mNumber) {
case 0:
case 6:
holder.itemView.setBackgroundResource(R.drawable.shape_rounded_corners);
break;
case 1:
case 7:
holder.itemView.setBackgroundResource(R.drawable.itemstyle0);
break;
case 2:
case 8:
holder.itemView.setBackgroundResource(R.drawable.itemstyle1);
break;
case 3:
case 9:
holder.itemView.setBackgroundResource(R.drawable.itemstyle2);
break;
case 4:
holder.itemView.setBackgroundResource(R.drawable.itemstyle3);
break;
case 5:
holder.itemView.setBackgroundResource(R.drawable.itemstyle4);
break;
}
}
@Override
public int getItemCount() {
return mList.size();//item数量
}
/**
* 为列表设置数据源
*
* @param data
*/
public void setList(ArrayList<NoteMessageBean> data) {
this.mList = data;
notifyDataSetChanged();
}
public class ViewHolder extends RecyclerView.ViewHolder {
private TextView title, text, time;
private ConstraintLayout mLinearLayout;
private CheckBox mCheckBox;
public ViewHolder(View itemView) {
super(itemView);
text = itemView.findViewById(R.id.text_note);
title = itemView.findViewById(R.id.title_note);
time = itemView.findViewById(R.id.time_note);
mLinearLayout = itemView.findViewById(R.id.item_layout);
mCheckBox = itemView.findViewById(R.id.radioButton);
}
}
/**
* RecycleView的点击回调
*/
public interface onRecycleItemClickListener {
void onItemClick(String id, String Title, String content, String time);
}
public void setOnItemClickListener(onRecycleItemClickListener listener) {
monRecycleItemClickListener = listener;
}
/**
* 删除动作的回调
*/
public interface onRecycleItemCheckChangeListener {
void onImageViewOnClick(String time, int position);
}
public void setOnItemLongClickListener(onRecycleItemCheckChangeListener listener) {
mRecycleItemCheckChangeListener = listener;
}
}
RecycleView在MainActivity中使用流程如下:
public class MainActivity extends AppCompatActivity implements View.OnClickListener, RecycleViewAdapter.onRecycleItemClickListener, RecycleViewAdapter.onRecycleItemCheckChangeListener {
private RecycleViewAdapter mAdapter;
private LinearLayoutManager manager;
private RecyclerView mRecyclerView;
@Override
protected void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "onCreate: ");
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
mRecyclerView = findViewById(R.id.recycler_view);
mAdapter = new RecycleViewAdapter(this, mList);
mAdapter.setOnItemClickListener(this);
mAdapter.setOnItemLongClickListener(this);
manager = new LinearLayoutManager(getApplicationContext());
mRecyclerView.setLayoutManager(manager);
mRecyclerView.setAdapter(mAdapter);
}
//代码篇幅略长,仅展示RecycleView在MainActivity如何使用
}
编辑界面代码讲解
编辑界面的主要功能:1.新建笔记时写入内容标题等。2.打开笔记时对原有笔记进行修改。
其主要的布局为EditText。
编辑界面主要代码如下:
public class EditActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "EditActivity";
private ImageView mBack, mCommit;
private EditText mTitle, mContent;
private TextView mTime;
private SQLiteHelper mSQLiteHelper;
private Context mContext;
private boolean flag;
private String id;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.edit_activity_layout);
initView();
initData();
}
private void initView() {
mBack = findViewById(R.id.bt_back);
mBack.setOnClickListener(this);
mCommit = findViewById(R.id.bt_commit);
mCommit.setOnClickListener(this);
mTitle = findViewById(R.id.edit_title);
mContent = findViewById(R.id.edit_text);
mTime = findViewById(R.id.time_date);
}
private void initData() {
Log.d(TAG, "initData: ");
mSQLiteHelper = new SQLiteHelper(getApplicationContext());
mTime.setText(DBUtils.getTime());
Intent intent = getIntent();
flag = intent.getBooleanExtra("flag", false);
//以flag为基准,有且仅有点击item之后才会调用 intent.putExtra("flag", true);
if (flag) {
id = intent.getStringExtra("id");
mTitle.setText(intent.getStringExtra("title"));
mContent.setText(intent.getStringExtra("content"));
mTime.setText(intent.getStringExtra("time"));
}
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.bt_back:
Intent intent = new Intent(EditActivity.this, MainActivity.class);
startActivity(intent);
break;
case R.id.bt_commit:
if (mContent.length() == 0) {
//当内容为空时,提醒一下,不创建新的item
Toast.makeText(this.getApplicationContext(), "请输入文本内容后再进行提交操作!", Toast.LENGTH_SHORT).show();
} else {
if (flag) {
//为true表示现在是打开了一个item进行修改编辑 并不是新建笔记
mSQLiteHelper.updateData(id, mTitle.getText().toString(), mContent.getText().toString(), mTime.getText().toString());
//更新数据后 跳转到MainActivity页面刷新数据
Intent mIntent = new Intent(EditActivity.this, MainActivity.class);
startActivity(mIntent);
} else {
//内容不为空则调用对应的数据库插入操作
mSQLiteHelper.insertDate(mContent.getText().toString(), DBUtils.getTime(), mTitle.getText().toString());
//插入数据之后 跳转到main页面 更新列表视图
Intent mIntent = new Intent(EditActivity.this, MainActivity.class);
startActivity(mIntent);
}
}
break;
}
}
}
至此,笔记APP的基本功能完成,扩展功能目前的计划加入 手写输入功能,图片插入功能,语音输入功能,搜索功能。
扩展功能以及源码我会逐渐更新在公众号中(二两仙气儿),毕竟我也不能一直摸鱼(狗头)。