簡述:
記事本的第一個版本,需要完善的還有很多,完成了基本的增刪改查的功能,以及在正文中插入圖片,對內(nèi)容進行分類,根據(jù)內(nèi)容查找等基本功能,對于刪除添加了回收站機制,可在回收站中對已刪除的內(nèi)容進行恢復或者永久刪除
暫時只支持Android5.0以上的設備運行,后續(xù)版本將會對4.4版本進行兼容,對4.4以下的設備未做兼容打算
主界面
編輯界面
功能點:
基礎功能
- 對記事的增刪改
- 添加時間戳
- 查詢內(nèi)容
拓展功能
- 對記事進行分類
- 在記事中添加圖片
- 一些界面美化以及人性化細節(jié)設置
基礎功能
對記事的增刪改
使用了數(shù)據(jù)庫輔助類SQLiteOpenHelper來創(chuàng)建數(shù)據(jù)庫
數(shù)據(jù)的插入
使用dbHelper封裝insert方法
插入數(shù)據(jù)
ContentValues values = new ContentValues();
values.put(COLUMN_NAME_NOTE_TITLE ,title);
values.put(COLUMN_NAME_NOTE_CONTENT ,content);
values.put(COLUMN_NAME_NOTE_DATE ,dateNum);
dbread.insert(TABLE_NAME_NOTES ,null,values);
原先使用的是execSQL()方法來插入數(shù)據(jù),但是有一個問題就是輸入 ' 這個符號使,執(zhí)行語句就會出錯,
原先的插入方法
sql = "insert into " +NotesDB.TABLE_NAME_NOTES +"("
+COLUMN_NAME_ID + " ,"
+COLUMN_NAME_NOTE_TITLE +","
+COLUMN_NAME_NOTE_CONTENT + " ,"
+COLUMN_NAME_NOTE_DATE + ")"
+" values("+count+","+"'"+ title +"'"+","+"'"+ content +"'"+","+"'"+ dateNum + "')";
Log.d("LOG",sql);
dbread.execSQL(sql);
數(shù)據(jù)的修改
使用dbHelper封裝update方法
ContentValues values = new ContentValues();;
values.put(COLUMN_NAME_NOTE_TITLE ,title);
values.put(COLUMN_NAME_NOTE_CONTENT ,content);
values.put(COLUMN_NAME_NOTE_DATE,dateNum);
String where = "_id="+id;
dbread.update(TABLE_NAME_NOTES ,values ,where, null);
同上,使用數(shù)據(jù)庫語句的execSQL()方法會因為輸入 ' 而出錯,此處不再列出
數(shù)據(jù)的刪除
本應用的刪除分兩步進行,第一步只是先把記事的屬性改為已刪除,并在回收站顯示,第二部才是進行在數(shù)據(jù)庫的刪除
第一步
更改屬性為刪除
Cursor content = c1;
String id = c1.getString(content.getColumnIndex("_id"));
String setCategory = "update note set category ='" + CATEGORY_DELETED + "' where _id=" + id;
Log.d("DELETE",setCategory);
dbread.execSQL(setCategory);
第二步
在數(shù)據(jù)庫中刪除
Cursor content = (Cursor) deletedview.getItemAtPosition(n);
String id =content.getString(content.getColumnIndex("_id"));
String recovery = "delete from note where _id=" + id;
dbread.execSQL(recovery);
至此,本應用的核心功能增刪改已經(jīng)完成
下面是徹底刪除一條記事的流程:
主界面長按選擇刪除
在彈出的提示中確認刪除,此時,主界面列表已經(jīng)不顯示這條記錄
而在回收站中可以看到先前被刪除的記錄,長按則可以選擇回復或者徹底刪除
選擇刪除則彈出提示,這條記錄"徹底刪除"已在數(shù)據(jù)庫中刪除
邏輯的判斷
進入編輯界面有兩個途徑,第一是點擊新建按鈕,第二是從記事列表進入,所以我們加入了一個屬性,若是新建按鈕則為0(調用插入函數(shù)),若是從記事列表則為1(調用修改函數(shù)).
if(ENTER_STATE == 0){
noteInsert();
else{
noteUpdate();
}
}
添加時間戳
首先要在顯示的listview中加入一個TextView組件來顯示這個時間,
使用SimpleDateFormat 可以把當前時間格式化成指定格式
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm");
String dateNum = sdf.format(date);
然后在數(shù)據(jù)庫相對應的列中加入這個字符串即可
查詢內(nèi)容
查詢的方法在工具欄點擊查詢按鈕即可調出searchview,然后輸入所需查詢的文字然后點擊鍵盤的搜索即可查詢內(nèi)容包含關鍵詞的記錄
查詢"在"的結果
實現(xiàn)
具體搜索的實現(xiàn)代碼不難,執(zhí)行一下代碼就可以將查詢到的內(nèi)容放到Cursor里,然后用適配器進行適配就可以在列表中顯示了
String sql = "select * from note where category !='"+CATEGORY_DELETED+"' and content like ?";
Cursor cursor = dbread.rawQuery(sql, new String[]{"%"+word+"%"});
但是為了實現(xiàn)搜索欄,我們需要用到一個新的組件 SearchView
首先要在AndroidManifest的顯示搜索欄的活動中加入
<meta-data
android:name="android.app.searchable"
android:resource="@xml/searchable" />
<meta-data
android:name="android.app.default_searchable"
android:value="edu.fjnu.birdie.notemd.MainActivity"/>
并在顯示搜索結果的活動中加入
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
在 onCreateOptionsMenu(Menu menu)函數(shù)中加入
SearchManager searchManager =
(SearchManager) getSystemService(Context.SEARCH_SERVICE);
SearchView searchView =
(SearchView) menu.findItem(R.id.action_search).getActionView();
searchView.setSearchableInfo(
searchManager.getSearchableInfo(getComponentName()));
String SearchContent = getIntent().getStringExtra(SearchManager.QUERY);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
Intent intent = new Intent(MainActivity.this,SearchActivity.class);
intent.putExtra("word",query);
startActivity(intent);
return true;
}
@Override
public boolean onQueryTextChange(String newText) {
return true;
}
});
即可讓搜索欄中輸入的文字傳到顯示搜索結果的活動中,并在顯示結果的搜索欄中進行dbread.rawQuery()并裝配到listview就可以得到搜索結果
拓展功能
對記事進行分類
此處的分類有"默認", "重要", "備忘", "筆記", "日程" ,用戶不可自定義
此處一是作為分類,二也是為后續(xù)添加的功能留下接口(備忘接口添加鬧鐘提醒等,但這個版本只單純的作為分類功能)
同時還有一個隱藏分類 刪除
刪除也是通過分類到刪除分類并在select的時候去掉這個分類的記錄
修改分類可以從主界面長按或者編輯界面的右上角分類按鈕進行
實現(xiàn)
public void addCategory(){
//Toast.makeText(this,"add_catagory",Toast.LENGTH_SHORT).show();
//{ "默認", "重要", "備忘", "筆記", "日程" };
if(ENTER_STATE == 1) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("設置分組");
builder.setSingleChoiceItems(category, 0, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
int choose = which;
switch (which) {
case 0: {
setCategory = "update note set category ='" + CATEGORY_NORMAL + "' where _id=" + id;
Log.d("EXE", setCategory);
break;
}
....
builder.setPositiveButton("確定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dbread.execSQL(setCategory);
}
});
builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
builder.create();
builder.show();
使用一個AlertDialog彈出單選框,并根據(jù)選項的不同來加載不同的sql語句,并在確定后執(zhí)行sql語句.
在記事中添加圖片
在編輯界面 點擊右下角的按鈕,就會打開相冊,選擇相冊中的圖片即可將圖片加入到文本中
實現(xiàn):
1.首先使用intent.getData得到uri
2.然后調用BitmapFactory的解碼函數(shù)decodeStream且要求的參數(shù)為流(Stream),所以要用ContentResolver解析uri為流。
3.接著通過一個resizeImage函數(shù)重新調整bitmap大小
4.然后就是要把所得到的圖片放到EditText里了
首先要在AndroidManifest中加入權限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
首先
對點擊按鈕的事件創(chuàng)建監(jiān)聽
Intent getImage = new Intent("android.intent.action.GET_CONTENT"); getImage.addCategory(Intent.CATEGORY_OPENABLE);
getImage.setType("image/*");
startActivityForResult(getImage, 1);
ACTION_GET_CONTENT是標準的Activity Action的一種漾橙,那什么是Activity Action呢稚配,簡單來說就是讓用戶選擇一種特殊的數(shù)據(jù)并得到它。
ACTION_GET_CONTENT可以讓用戶在運行的程序中取得數(shù)據(jù)柏副,例如取照片勾邦,當然這里的運行的程序指的是手機上的文件管理器之類的。
addCategory是要增加一個分類割择,增加一個什么分類呢眷篇?就是增加CATEGORY_OPENABLE,從字面意思值是增加一個可以打開的分類荔泳,也即是取得的uri要可以被ContentResolver解析蕉饼,注意這里的分類即是執(zhí)行的附加條件虐杯。
setType就是設置取得的數(shù)據(jù)類型為image,也即是取照片昧港。
protected void onActivityResult(int requestCode, int resultCode,
Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
ContentResolver resolver = getContentResolver();
if (resultCode == RESULT_OK) {
if (requestCode == 1) {
Uri originalUri = intent.getData();
String Imgpath = getPath(this,originalUri);
Uri realUri = Uri.parse("file://"+Imgpath);//真實路徑轉化為Uri
String realPath = "content://media/"+Imgpath;
try {
Bitmap originalBitmap = BitmapFactory.decodeFile(Imgpath);
Log.d("imageUri",originalUri.toString() );
if(originalBitmap != null) {
bitmap = resizeImage(originalBitmap);
}else{
Log.d("ob","null");
}
} catch (Exception e) {
e.printStackTrace();
}
if (bitmap != null) {
insertIntoEditText(getBitmapMime(bitmap, realUri));
} else {
Toast.makeText(noteEdit.this, "獲取圖片失敗",
Toast.LENGTH_SHORT).show();
}
}
}
if (bitmap != null) {
}
}
將得到的圖片轉化成真實地址
下面是轉化成圖片的真實地址 參考文章:Android4.4中獲取資源路徑問題
public static String getPath(final Context context, final Uri uri) {
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
// DocumentProvider
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
// ExternalStorageProvider
if (isExternalStorageDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
if ("primary".equalsIgnoreCase(type)) {
return Environment.getExternalStorageDirectory() + "/" + split[1];
}
// TODO handle non-primary volumes
}
// DownloadsProvider
else if (isDownloadsDocument(uri)) {
final String id = DocumentsContract.getDocumentId(uri);
final Uri contentUri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
return getDataColumn(context, contentUri, null, null);
}
// MediaProvider
else if (isMediaDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
Uri contentUri = null;
if ("image".equals(type)) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
} else if ("video".equals(type)) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else if ("audio".equals(type)) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}
final String selection = "_id=?";
final String[] selectionArgs = new String[] {
split[1]
};
return getDataColumn(context, contentUri, selection, selectionArgs);
}
}
// MediaStore (and general)
else if ("content".equalsIgnoreCase(uri.getScheme())) {
return getDataColumn(context, uri, null, null);
}
// File
else if ("file".equalsIgnoreCase(uri.getScheme())) {
return uri.getPath();
}
return null;
}
public static String getDataColumn(Context context, Uri uri, String selection,
String[] selectionArgs) {
Cursor cursor = null;
final String column = "_data";
final String[] projection = {
column
};
try {
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
null);
if (cursor != null && cursor.moveToFirst()) {
final int column_index = cursor.getColumnIndexOrThrow(column);
return cursor.getString(column_index);
}
} finally {
if (cursor != null)
cursor.close();
}
return null;
}
public static boolean isExternalStorageDocument(Uri uri) {
return "com.android.externalstorage.documents".equals(uri.getAuthority());
}
public static boolean isDownloadsDocument(Uri uri) {
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}
public static boolean isMediaDocument(Uri uri) {
return "com.android.providers.media.documents".equals(uri.getAuthority());
}
在TextView中顯示圖片
這里用到了Spannable和ImageSpan來在EditText中顯示圖片
這邊使用了< img >標簽標注圖片的路徑
//設置Spannable String
private SpannableString getBitmapMime(Bitmap pic, Uri uri) {
String path = "<img>"+uri.getPath()+"<img>";
SpannableString ss = new SpannableString(path);
ImageSpan span = new ImageSpan(this, pic);
ss.setSpan(span, 0, path.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return ss;
}
//將SS插入EditText
private void insertIntoEditText(SpannableString ss) {
insertEnter();//圖片添加到新的一行
Editable et = et_content.getText();// 先獲取Edittext中的內(nèi)容
int start = et_content.getSelectionStart();
et.insert(start, ss);// 設置ss要添加的位置
et_content.setText(et);// 把et添加到Edittext中
et_content.setSelection(start + ss.length());// 設置Edittext中光標在最后
Log.d("Text",et_content.getText().toString());
insertEnter();//添加完圖片后換行
}
插入圖片時在圖片上下各插入一個空行優(yōu)化排版
//排版問題,插入EditText時在圖片的上下一行加入空格;
private void insertEnter(){
Editable et = et_content.getText();
int start = et_content.getSelectionStart();
String enter = "\n";
et.insert(start,enter);
et_content.setText(et);
et_content.setSelection(start + enter.length());
}
在文本中設置圖片的顯示大小
//壓縮圖片
public Bitmap resizeImage(Bitmap bitmap)
{
if(bitmap != null) {
Bitmap BitmapOrg = bitmap;
int width = BitmapOrg.getWidth();
int height = BitmapOrg.getHeight();
int newWidth = 480;
int newHeight = 800;
float scale = ((float) newWidth) / width;
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
Matrix matrix = new Matrix();
matrix.postScale(scale, scale);//比例不變
Bitmap resizedBitmap = Bitmap.createBitmap(BitmapOrg, 0, 0, width,
height, matrix, true);
return resizedBitmap;
}else{
return null;
}
}
? 在EditText中加載圖片
? 使用了正則表達式來識別< img >標簽,并使用 ImageSpan來顯示圖片
/將內(nèi)容中的圖片加載出來
SpannableString ss = new SpannableString(last_content);
Pattern p= Pattern.compile("<img>.*<img>");
Matcher m=p.matcher(last_content);
while(m.find()){
String image=m.group();
String path=image.substring(5,image.length()-5);
Bitmap bm = BitmapFactory.decodeFile(path);
Log.d("path",path);
if(bm==null)
{
Log.d("bm","null");
}
Bitmap rbm = resizeImage(bm);
ImageSpan span = new ImageSpan(this, rbm);
ss.setSpan(span, m.start(), m.end(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
一些界面美化以及人性化細節(jié)設置
本應用參照了Material Design,雖然并未完全規(guī)范,不過在界面上還是達到了相對應的簡潔,在操作邏輯上也符合用戶的使用
界面美化
界面的演變
4.12 --雛形
內(nèi)容:
- 通過ListView,Button,EditText等控件做出初始的界面
- 并通過內(nèi)嵌數(shù)據(jù)庫SQLite 完成對內(nèi)容的增刪改
4.13 --重新設計界面
內(nèi)容:
- 對界面進行重新設計
為了使輸入界面更加簡潔,可以通過 在<EditText>中,加入
android:background="@null"
去掉輸入框下的橫線
4.13 --Material Design
內(nèi)容:
- 基于對界面的重新設計,加入了Material Design
雖然并不是很規(guī)范,但會在后續(xù)慢慢完善 - 完成了搜索的基本邏輯,記事本的增刪改查功能基本完善
- 同時對設置菜單,關于界面進行了初步的設計,但大部分功能都未實現(xiàn)
后續(xù)的版本都是在第三次界面修改后基本沒有太大的變化,主要實在功能上的變化
當前版本
人性化細節(jié)設置
- 虛擬鍵盤設置
- 自動補充標題
- 提醒設置
- 空界面提示
虛擬鍵盤設置
新建記事會自動彈出虛擬鍵盤,而二次編輯不彈出虛擬鍵盤,需要點擊才會彈出鍵盤,因為作為記事類軟件后續(xù)修改的頻率遠低于查看的頻率,自動彈出鍵盤反而會降低用戶體驗
此外,光標自動聚焦在內(nèi)容編輯處,標題在記事類軟件中的存在性并不重要,若要編輯標題則需要點擊標題欄
實現(xiàn)
et_title.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
et_title.setFocusableInTouchMode(true);
return false;
}
});
//新建文本時調用軟鍵盤,如果是打開原來存在的文本默認不打開軟鍵盤
//在Manifest中添加android:windowSoftInputMode="stateHidden"使得虛擬鍵盤不會自動彈出
if(ENTER_STATE == 0){
Log.d("KeyBoard","VISIBLE");
Log.d("ENTER_STATE",ENTER_STATE+""); getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
}
自動補充標題
若用戶覺得標題不重要大可不填,將會自動生成標題
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy/MM/dd-HH-mm");
String dateNum1 = sdf.format(date);
//title = "新建記事"+ dateNum1;//自動添加為時間
title = "新建記事";//添加為新建記事
}
提醒設置
在刪除,編輯了內(nèi)容未保存的情況下,空內(nèi)容保存記錄,都會彈出提示框來提示用戶確保不會產(chǎn)生誤操作
(在回收站中的刪除沒有提示,邏輯上如果你已經(jīng)進到回收站并確認要刪除那條被刪除過的記錄,應該不會是誤操作)
未保存提示
刪除提示
空界面提示
在沒有記錄的首頁,搜索不到結果的搜索頁面,沒有回收記錄的回收站,不會因為沒有記錄而空在那里,而是會有一定的文字提醒
沒有記錄會提示點擊右下角添加
無搜索結果
無回收文件
實現(xiàn)
在Layout中同時放兩個Item都是matchparents
然后通過判斷select結果來判斷是要顯示Listview還是顯示提示性文字
以MainActivity為例
public boolean isNoteNull(){
String sql = "select * from note where category !='"+CATEGORY_DELETED+"'";
Log.d("sql",sql);
Cursor c = dbManager.executeSql(sql, null);
int number = c.getCount();
Log.d("Note number",number+"");
if(number == 0){
ListView listView = (ListView)findViewById(R.id.notelist);
TextView textView = (TextView)findViewById(R.id.main_text);
listView.setVisibility(View.GONE);
textView.setVisibility(View.VISIBLE);
return true;
}else{
ListView listView = (ListView)findViewById(R.id.notelist);
TextView textView = (TextView)findViewById(R.id.main_text);
textView.setVisibility(View.GONE);
listView.setVisibility(View.VISIBLE);
return false;
}
}