前几天写了一个Android记事本小程序,现在记录一下。
项目源码地址在文章最后。
考虑到是记事本小程序,记录的内容只有文字,而且内容不会太长,所以选择使用SQLite数据库,数据存放在用户的手机上。
牵涉到数据库,那自然是一个实体。先设计实体数据表:DBHelper.java
package com.ikok.notepad.DBUtil;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
/**
* Created by Anonymous on 2016/3/24.
*/
public class DBHelper extends SQLiteOpenHelper {
/**
* 创建笔记表
*/
private static final String CREATE_NOTE = "create table Note(" +
"id integer primary key autoincrement," +
"content text," +
"time text)";
private Context mContext;
public DBHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
mContext = context;
}
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
sqLiteDatabase.execSQL(CREATE_NOTE);
// Toast.makeText(mContext,"Created",Toast.LENGTH_SHORT).show();
}
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
}
}
package com.ikok.notepad.DBUtil;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import com.ikok.notepad.Entity.Note;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Anonymous on 2016/3/24.
*/
public class NoteDB {
public static final String DB_NAME = "notepad";
public static final int VERSION = 1;
private static NoteDB mNoteDB;
private SQLiteDatabase db;
public NoteDB(Context context) {
DBHelper dbHelper = new DBHelper(context,DB_NAME,null,VERSION);
db = dbHelper.getWritableDatabase();
}
/**
* 获取 NoteDB 的实例
* @param context
* @return
*/
public synchronized static NoteDB getInstance(Context context){
if (mNoteDB == null){
mNoteDB = new NoteDB(context);
}
return mNoteDB;
}
public void saveNote(Note note){
if (note != null) {
ContentValues values = new ContentValues();
values.put("content", note.getContent());
values.put("time", note.getTime());
db.insert("Note", null, values);
}
}
public List loadNotes(){
List noteList = new ArrayList();
/**
* 先按时间降序排列,再按id降序排列
*/
Cursor cursor = db.query("Note",null,null,null,null,null,"time desc,id desc");
if (cursor.moveToNext()){
do {
Note note = new Note();
note.setId(cursor.getInt(cursor.getColumnIndex("id")));
note.setContent(cursor.getString(cursor.getColumnIndex("content")));
note.setTime(cursor.getString(cursor.getColumnIndex("time")));
noteList.add(note);
} while (cursor.moveToNext());
}
return noteList;
}
public Note loadById(int id){
Note note = null;
Cursor cursor = db.query("Note",null,"id = " + id,null,null,null,null);
if (cursor.moveToNext()){
note = new Note();
note.setContent(cursor.getString(cursor.getColumnIndex("content")));
note.setTime(cursor.getString(cursor.getColumnIndex("time")));
}
return note;
}
public void deleteById(Integer id){
db.delete("Note","id = " + id,null);
}
public void deleteAllNote(){
db.delete("Note", null, null);
}
public void updateById(String noteTime, String noteContent, int noteId){
ContentValues values = new ContentValues();
values.put("content",noteContent);
values.put("time",noteTime);
db.update("Note",values,"id = " + noteId,null);
}
}
package com.ikok.notepad.Entity;
import java.io.Serializable;
/**
* Created by Anonymous on 2016/3/24.
*/
public class Note implements Serializable {
private int id;
private String content;
private String time;
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 String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
}
左边的是一个关于App的按钮,右边的新建记事本的按钮。
因为主页需要显示已经记录的内容,所以我选择用ListView去显示。用到ListView,则与之对应的是要一个数据源,一个适配器。所以我为每一条子项设计了一个样式,去让它左边显示创建或更新的时间,右边显示内容。如下:list_item.xml
创建好了ListView,接下来为它准备适配器:MyAdapter.java
package com.ikok.notepad.Util;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;
import com.ikok.notepad.Entity.Note;
import com.ikok.notepad.R;
import java.util.List;
/**
* Created by Anonymous on 2016/3/24.
*/
public class MyAdapter extends BaseAdapter {
private List noteList;
private LayoutInflater mInflater;
private Context mContext;
private int index;
public MyAdapter(Context context,List noteList,ListView listView) {
this.mInflater = LayoutInflater.from(context);
this.noteList = noteList;
this.mContext = context;
}
@Override
public int getCount() {
return noteList.size();
}
@Override
public Object getItem(int i) {
return noteList.get(i);
}
@Override
public long getItemId(int i) {
return i;
}
@Override
public View getView(int i, View convertView, ViewGroup viewGroup) {
ViewHolder viewHolder = null;
if (convertView == null){
viewHolder = new ViewHolder();
convertView = mInflater.inflate(R.layout.list_item, null);
viewHolder.mTime = (TextView) convertView.findViewById(R.id.show_time);
viewHolder.mContent = (TextView) convertView.findViewById(R.id.show_content);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.mTime.setText(noteList.get(i).getTime());
viewHolder.mContent.setText(noteList.get(i).getContent());
index = i;
// convertView.setOnClickListener(new View.OnClickListener() {
// @Override
// public void onClick(View view) {
// Intent intent = new Intent(mContext,UpdateOrReadActivity.class);
//// Bundle bundle = new Bundle();
//// bundle.putSerializable("note_item",noteList.get(index));
//// intent.putExtras(bundle);
// intent.putExtra("note_id",noteList.get(index).getId());
// Log.d("Anonymous","备忘录ID:"+noteList.get(index).getId());
// mContext.startActivity(intent);
// Log.d("Anonymous","执行了适配器里的点击事件");
// }
// });
return convertView;
}
class ViewHolder{
public TextView mTime;
public TextView mContent;
}
}
创建好了ListView,准备好了适配器,接下来要为ListView准备数据源,而这数据源是要从数据库读出来的。但是数据库操作和网络访问等都是属于耗时操作,如果用主UI线程去执行响应操作的话,很可能会出现ANR现象,所以这里我用AsyncTask去执行数据库操作。主Activity代码如下:MainActivity.java
package com.ikok.notepad.Activity;
import android.app.Activity;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.AlertDialog;
import android.view.View;
import android.view.Window;
import android.widget.AdapterView;
import android.widget.ImageButton;
import android.widget.ListView;
import android.widget.TextView;
import com.ikok.notepad.DBUtil.NoteDB;
import com.ikok.notepad.Entity.Note;
import com.ikok.notepad.R;
import com.ikok.notepad.Util.DeleteAsyncTask;
import com.ikok.notepad.Util.MyAdapter;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Anonymous on 2016/3/24.
*/
public class MainActivity extends Activity {
/**
* 布局控件
*/
private TextView mTitle;
private TextView mNoteNum;
private ImageButton mWrite;
private ListView mNoteListView;
private ImageButton mAbout;
/**
* 数据库实例,数据源
*/
private List mNoteList = new ArrayList() ;
private NoteDB mNoteDB;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.main_activity);
initView();
new NewAsyncTask().execute();
initEvent();
}
private void initEvent() {
/**
* 新写一条备忘录
*/
mWrite.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(MainActivity.this, AddNoteActivity.class);
startActivity(intent);
}
});
/**
* 修改或查看一条已有的备忘录
*/
mNoteListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView> adapterView, View view, int i, long l) {
Note note = (Note) adapterView.getItemAtPosition(i);
// Log.d("Anonymous", "点击ListView获取的note id: " + note.getId());
Intent intent = new Intent(MainActivity.this, UpdateOrReadActivity.class);
intent.putExtra("note_id", note.getId());
startActivity(intent);
}
});
/**
* listview长按删除
*/
mNoteListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView> parent, View view, int position, long id) {
final Note note = (Note) parent.getItemAtPosition(position);
// Log.d("Anonymous", "长按ListView获取的note id: " + note.getId());
/**
* 长按提示是否删除
*/
new AlertDialog.Builder(MainActivity.this)
.setTitle("提示")
.setMessage("真的要删除这条记录吗?")
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
new DeleteAsyncTask(mNoteDB).execute(note.getId());
new NewAsyncTask().execute();
}
})
.setNegativeButton("取消", null)
.show();
return true;
}
});
/**
* 关于自己
*/
mAbout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this,AboutActivity.class);
startActivity(intent);
}
});
}
public void initView() {
/**
* 布局控件初始化
*/
mTitle = (TextView) findViewById(R.id.app_title);
// 画TextView文字下的下划线
// mTitle.getPaint().setFlags(Paint.UNDERLINE_TEXT_FLAG);
mNoteNum = (TextView) findViewById(R.id.note_num);
mWrite = (ImageButton) findViewById(R.id.write_btn);
mNoteListView = (ListView) findViewById(R.id.listview);
mAbout = (ImageButton) findViewById(R.id.about_btn);
/**
* 获取数据库实例
*/
mNoteDB = NoteDB.getInstance(this);
}
/**
* 异步加载备忘录
*/
class NewAsyncTask extends AsyncTask>{
@Override
protected List doInBackground(Void... voids) {
mNoteList = mNoteDB.loadNotes();
return mNoteList;
}
@Override
protected void onPostExecute(List notes) {
super.onPostExecute(notes);
/**
* 设置适配器,绑定适配器
*/
MyAdapter myAdapter = new MyAdapter(MainActivity.this,notes,mNoteListView);
mNoteListView.setAdapter(myAdapter);
/**
* 更新备忘录记录数
*/
int temp = mNoteList.size();
mNoteNum.setText("共 " + temp + " 条备忘录");
}
}
/**
* 当活动恢复时,刷新listview和备忘录记录数
*/
@Override
protected void onResume() {
super.onResume();
new NewAsyncTask().execute();
}
}
接下来是新建记事本的Activity,布局如下:write_note.xml
新建记事本的Activity如下:AddNoteActivity.java
package com.ikok.notepad.Activity;
import android.app.Activity;
import android.content.DialogInterface;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.AlertDialog;
import android.view.View;
import android.view.Window;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
import com.ikok.notepad.DBUtil.NoteDB;
import com.ikok.notepad.Entity.Note;
import com.ikok.notepad.R;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* Created by Anonymous on 2016/3/24.
*/
public class AddNoteActivity extends Activity {
/**
* 布局控件
*/
private TextView mComplete;
private ImageButton mBackBtn;
private EditText mContent;
/**
* 备忘录数据
*/
private String noteTime;
private String noteContent;
/**
* 数据库
*/
private NoteDB mNoteDB;
private Note note;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.write_note);
initView();
initEvent();
}
private void initView() {
/**
* 布局控件初始化
*/
mComplete = (TextView) findViewById(R.id.complete_btn);
mBackBtn = (ImageButton) findViewById(R.id.back_btn);
mContent = (EditText) findViewById(R.id.note_content);
/**
* 获取数据库实例
*/
mNoteDB = NoteDB.getInstance(this);
}
/**
* 事件处理
*/
private void initEvent() {
/**
* 返回上一级菜单,如果有内容,提示是否保存
* 是、保存,销毁活动;否,直接销毁活动
*/
mBackBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
saveDataOrNot();
}
});
/**
* 完成按钮,保存备忘录到数据库
*/
mComplete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (!mContent.getText().toString().equals("")){
new AddAsyncTask().execute();
finish();
} else {
finish();
}
}
});
}
/**
* 根据是否有内容,提示保存
*/
private void saveDataOrNot() {
if (!mContent.getText().toString().trim().equals("")) {
new AlertDialog.Builder(AddNoteActivity.this)
.setTitle("提示")
.setMessage("需要保存您编辑的内容吗?")
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
new AddAsyncTask().execute();
finish();
}
})
.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
finish();
}
})
.show();
} else {
finish();
}
}
/**
* 添加数据到数据库
*/
class AddAsyncTask extends AsyncTask{
@Override
protected Void doInBackground(Void... voids) {
mNoteDB.saveNote(note);
return null;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
/**
* 记录数据
*/
SimpleDateFormat sdf = new SimpleDateFormat("MM-dd HH:mm");
Date date = new Date(System.currentTimeMillis());
noteTime = sdf.format(date);
noteContent = mContent.getText().toString();
note = new Note();
note.setTime(noteTime);
note.setContent(noteContent);
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
Toast.makeText(AddNoteActivity.this, "保存成功!", Toast.LENGTH_SHORT).show();
}
}
/**
* 按返回键,有内容时,提示保存
*/
@Override
public void onBackPressed() {
saveDataOrNot();
}
}
接下来是查看或修改一条记事本了,布局我是直接复用新建记事本的布局。因为没有区别 - -
接下来是查看或修改一条记事本的Activity了,之前,我想的是点击一条记事本,则进入这条记事本,把这条记事本直接显示在页面上,用户直接在内容最后进行编辑。所以这里需要一个子项点击事件。我在MainActivity里已经写了,先获取当前点击的这一项的对象,这里我费了好多时间,我不知道点击这一项的时候,怎么把该项的对象读取出来。最后自己查看源码,查API,看到参数中AdapterView是个泛型,我试着从它着手,把它强转成Note对象,然后试试获取id,没想到就成了。 - -
所以,我获取了当前点击的item中的Note对象的id,把它放在Intent中,带着这个参数去开启活动。
这里,查看或修改一条记事本的Activity正式开始了,如下:UpdateOrReadActivity.java
package com.ikok.notepad.Activity;
import android.app.Activity;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.AlertDialog;
import android.util.Log;
import android.view.View;
import android.view.Window;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.ikok.notepad.DBUtil.NoteDB;
import com.ikok.notepad.Entity.Note;
import com.ikok.notepad.R;
import com.ikok.notepad.Util.DeleteAsyncTask;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* Created by Anonymous on 2016/3/24.
*/
public class UpdateOrReadActivity extends Activity {
/**
* 布局控件
*/
private TextView mComplete;
private ImageButton mBackBtn;
private EditText mContent;
private LinearLayout mScreen;
/**
* 备忘录数据
*/
private int noteId;
private String noteTime;
private String noteContent;
private String originData;
/**
* 数据库
*/
private NoteDB mNoteDB;
private static Note note;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.write_note);
/**
* 获取传递过来的note对象
*/
Intent intent = getIntent();
// 传递Note对象,必须要Note实体实现Serializable
// note = (Note) intent.getSerializableExtra("note_item");
noteId = intent.getIntExtra("note_id",0);
Log.d("Anonymous", "传递后的备忘录ID:" + noteId);
initView();
/**
* 加载显示数据
*/
new LoadAsyncTask().execute();
initEvent();
}
private void initView() {
/**
* 布局控件初始化
*/
mComplete = (TextView) findViewById(R.id.complete_btn);
mBackBtn = (ImageButton) findViewById(R.id.back_btn);
mContent = (EditText) findViewById(R.id.note_content);
mScreen = (LinearLayout) findViewById(R.id.screen_view);
/**
* 获取数据库实例
*/
mNoteDB = NoteDB.getInstance(this);
}
private void initEvent() {
/**
* 返回上一级菜单,直接销毁当前活动
*/
mBackBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
updateDataOrNot();
}
});
/**
* 完成按钮,修改备忘录到数据库
*/
mComplete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (mContent.getText().toString().trim().equals("")){
// Log.d("Anonymous","进入判断为空函数");
new DeleteAsyncTask(mNoteDB).execute(noteId);
finish();
} else if (mContent.getText().toString().equals(originData)) {
finish();
} else {
// Log.d("Anonymous","进入判断不为空函数");
new UpdateAsyncTask().execute();
// Toast.makeText(UpdateOrReadActivity.this, "修改成功!", Toast.LENGTH_SHORT).show();
finish();
}
}
});
/**
* 点击屏幕空白区域,EditText选中
*/
}
/**
* 根据id从数据库读数据的异步任务
*/
class LoadAsyncTask extends AsyncTask{
@Override
protected Note doInBackground(Void... voids) {
note = mNoteDB.loadById(noteId);
return note;
}
@Override
protected void onPostExecute(Note note) {
super.onPostExecute(note);
/**
* 根据传递进来的Note显示备忘录内容,并把光标移动到最后
* 记录最初的文本内容
*/
originData = note.getContent();
mContent.setText(note.getContent());
mContent.setSelection(mContent.getText().toString().length());
}
}
/**
* 更新数据库的异步任务
*/
class UpdateAsyncTask extends AsyncTask{
@Override
protected void onPreExecute() {
super.onPreExecute();
/**
* 记录数据
*/
SimpleDateFormat sdf = new SimpleDateFormat("MM-dd HH:mm");
Date date = new Date(System.currentTimeMillis());
noteTime = sdf.format(date);
noteContent = mContent.getText().toString();
note.setTime(noteTime);
note.setContent(noteContent);
}
@Override
protected Void doInBackground(Void... voids) {
mNoteDB.updateById(noteTime, noteContent, noteId);
return null;
}
}
/**
* 根据是否有内容,提示保存
*/
private void updateDataOrNot() {
if (!mContent.getText().toString().equals(originData)) {
new AlertDialog.Builder(UpdateOrReadActivity.this)
.setTitle("提示")
.setMessage("需要保存您编辑的内容吗?")
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
new UpdateAsyncTask().execute();
finish();
}
})
.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
finish();
}
})
.show();
} else {
finish();
}
}
/**
* 返回键事件
* 根据内容是否有变化,提示是否保存
*/
@Override
public void onBackPressed() {
updateDataOrNot();
}
}
package com.ikok.notepad.Util;
import android.os.AsyncTask;
import com.ikok.notepad.DBUtil.NoteDB;
/**
* Created by Anonymous on 2016/3/25.
*/
public class DeleteAsyncTask extends AsyncTask {
private NoteDB noteDB;
public DeleteAsyncTask(NoteDB noteDB) {
this.noteDB = noteDB;
}
@Override
protected Void doInBackground(Integer... params) {
noteDB.deleteById(params[0]);
return null;
}
}
接下来是CRUD的最后一项,删除数据了,在主页的时候,我设计的是单击进入该条记事本,去查看或修改这一条记事本,然后我考虑的是长按删除。长按,弹出对话框,提示是否删除,是则删除,否则不做任何事。所以在MainActivity中可以看到长按事件的监听器。但是因为Android的事件分发机制,长按事件必定会触发点击事件。所以需要在ListView中设置这样一个属性,才能点击事件和长按事件同时监听。
android:descendantFocusability="blocksDescendants"
主要功能都差不多完成了。接下来就是优化App了。我设计了过渡动画,引导页,以及是否第一次启动App。是则过渡动画过渡完到引导页,引导页完才到主页。否则过渡动画过渡完则直接进入主页。还设计了引导页的切换动画,使用了nineoldandroid,保证动画在低版本手机上可显示。
优化App部分可见我另外一篇博客,传送门: http://blog.csdn.net/someone_ikok/article/details/51027357
项目地址在:https://github.com/someonexiaole/Android
Notepad 即是。
欢迎大家 pr , star , fork , follow 我的 Github。