Mi Note 项目来源 MiCode
便签是小米便签的社区开源版,由MIUI团队(www.miui.com ) 发起并贡献第一批代码,遵循NOTICE文件所描述的开源协议,今后为MiCode
社区(www.micode.net ) 拥有,并由社区发布和维护。
项目源地址:https://github.com/MiCode/Notes 本项目地址:https://github.com/Shawnicsc/MiNote
环境配置 Android Studio 2022.3
Gradle 8.1.3
SDK 11
JDK 17
阿里云数据库 – MySQL
使用说明 可安装的程序包(apk文件) 在 release 文件下,直接安装运行。
新增功能介绍 1、登录注册功能
2、上传下载功能
3、加密文件功能 首先给note
数据库添加属性password locker
,当用户编辑便签,选择设置密码时,调用setPassword
方法
1 2 3 if (itemId == R.id.menu_set_passwd) { setPassword(); }
在setPassword
中,首先定义对话框,以及对话框的布局和内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 final AlertDialog.Builder builder = new AlertDialog .Builder(this ); final View view = LayoutInflater.from(this ).inflate(R.layout.dialog_edit_text,null ); final EditText etNameOld= (EditText)view.findViewById(R.id.old_password); etNameOld.setVisibility(view.GONE); final TextView etNameOld_View=(TextView)view.findViewById(R.id.old_password_view); etNameOld_View.setVisibility(view.GONE); if (mWorkingNote.hasPassword()){ etNameOld.setVisibility(view.VISIBLE); etNameOld_View.setVisibility(view.VISIBLE); etNameOld.setText("" ); etNameOld.setHint("请输入原密码" ); } final EditText etName = (EditText)view.findViewById(R.id.new_password); final EditText etNameAgain=(EditText)view.findViewById(R.id.new_password_again); etName.setText("" ); etName.setHint("请输入密码" ); etNameAgain.setText("" ); etNameAgain.setHint("请确认密码" ); builder.setTitle("设置密码" );
在点击ok 后进行错误判断和操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 builder.setPositiveButton("OK" , new DialogInterface .OnClickListener() { @Override public void onClick (DialogInterface dialog, int which) { String password = etName.getText().toString(); String password_again=etNameAgain.getText().toString(); String password_old = null ; if (mWorkingNote.hasPassword()){ password_old= etNameOld.getText().toString(); } if (mWorkingNote.hasPassword()) { if (!password_old.equals(mWorkingNote.getmPassword())) { Toast.makeText(NoteEditActivity.this , "原密码错误" , Toast.LENGTH_SHORT).show(); return ; } } if (password==null || password.length()==0 ){ InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); inputMethodManager.hideSoftInputFromWindow(etName.getWindowToken(),0 ); dialog.dismiss(); Toast.makeText(NoteEditActivity.this , "密码不能为空" , Toast.LENGTH_SHORT).show(); return ; } if (!password.equals(password_again)){ Toast.makeText(NoteEditActivity.this , "密码不一致" , Toast.LENGTH_SHORT).show(); return ; } mWorkingNote.setPassword(password); mWorkingNote.setLocker("1" ); Toast.makeText(NoteEditActivity.this , "密码设置成功" , Toast.LENGTH_SHORT).show(); InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); inputMethodManager.hideSoftInputFromWindow(etName.getWindowToken(),0 ); dialog.dismiss(); } });
点击取消按钮时,对应操作
1 2 3 4 5 6 7 8 9 10 11 12 builder.setNegativeButton("cancel" , new DialogInterface .OnClickListener() { @Override public void onClick (DialogInterface dialog, int which) { InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); inputMethodManager.hideSoftInputFromWindow(etName.getWindowToken(),0 ); } }); final Dialog dialog = builder.setView(view).show(); dialog.show(); }
4、更换字体功能 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 public void showSingleAlertDiglog () { final String[] items = {"方正舒体" ,"仿宋" ,"黑体" ,"隶书" ,"行楷" ,"幼圆" }; AlertDialog.Builder alertBuilder = new AlertDialog .Builder(this ); alertBuilder.setTitle("字体选择" ); alertBuilder.setSingleChoiceItems(items, 0 , new DialogInterface .OnClickListener() { @Override public void onClick (DialogInterface dialog, int i) { switch (i){ case 0 : Typeface typeface0 = Typeface.createFromAsset(getAssets(),"font/FZSTK.TTF" ); mNoteEditor.setTypeface(typeface0); break ; case 1 : Typeface typeface1 = Typeface.createFromAsset(getAssets(),"font/SIMYOU.TTF" ); mNoteEditor.setTypeface(typeface1); break ; case 2 : Typeface typeface2 = Typeface.createFromAsset(getAssets(),"font/STLITI.TTF" ); mNoteEditor.setTypeface(typeface2); break ; case 3 : Typeface typeface3 = Typeface.createFromAsset(getAssets(),"font/STXINGKA.TTF" ); mNoteEditor.setTypeface(typeface3); break ; case 4 : Typeface typeface4 = Typeface.createFromAsset(getAssets(),"font/simfang.ttf" ); mNoteEditor.setTypeface(typeface4); break ; case 5 : Typeface typeface5 = Typeface.createFromAsset(getAssets(),"font/simhei.ttf" ); mNoteEditor.setTypeface(typeface5); break ; } } }); alertBuilder.setPositiveButton("确定" , new DialogInterface .OnClickListener() { @Override public void onClick (DialogInterface dialog, int i) { alertDialog2.dismiss(); } }); alertBuilder.setNegativeButton("取消" , new DialogInterface .OnClickListener() { @Override public void onClick (DialogInterface dialog, int i) { alertDialog2.dismiss(); } }); alertDialog2 = alertBuilder.create(); alertDialog2.show(); }
5、实时模糊搜索功能 首先在layout
创建一个note_search_list.xml
, 显示搜索界面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:orientation ="vertical" android:layout_width ="match_parent" android:layout_height ="match_parent" > <SearchView android:id ="@+id/search_view" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:iconifiedByDefault ="false" android:queryHint ="输入搜索内容..." android:layout_alignParentTop ="true" > </SearchView > <ListView android:id ="@android:id/list" android:layout_width ="match_parent" android:layout_height ="wrap_content" > </ListView > </LinearLayout >
在新建一个notelist_item.xml
,用于显示搜索结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:id ="@+id/layout" android:layout_width ="match_parent" android:layout_height ="match_parent" android:orientation ="vertical" > <TextView xmlns:android ="http://schemas.android.com/apk/res/android" android:id ="@android:id/text1" android:layout_width ="match_parent" android:layout_height ="?android:attr/listPreferredItemHeight" android:textAppearance ="?android:attr/textAppearanceLarge" android:gravity ="center_vertical" android:paddingLeft ="5dip" android:singleLine ="true" /> <TextView android:id ="@+id/text1_time" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:textAppearance ="?android:attr/textAppearanceSmall" android:paddingLeft ="5dip" /> </LinearLayout >
在NoteListActivity
中onOptionsItemSelected
中使用 Intent
开启 Search Activity
1 2 3 4 5 6 7 8 9 10 11 12 13 if (itemId == R.id.menu_search) { onSearchRequested(); return true ; } @Override public boolean onSearchRequested () { Intent intent = new Intent (); intent.setClass(NotesListActivity.this ,NoteSearch.class); NotesListActivity.this .startActivity(intent); return true ; }
NoteSearch
首先继承 ListActivity
用于显示结果的垂直布局,在实现SearchView.OnQueryTextListener
,用于搜索查询。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 @SuppressLint("NewApi") public class NoteSearch extends ListActivity implements SearchView .OnQueryTextListener { @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.note_search_list); Intent intent = getIntent(); if (intent.getData() == null ) { intent.setData(Notes.CONTENT_NOTE_URI); } SearchView searchview = (SearchView)findViewById(R.id.search_view); searchview.setOnQueryTextListener(NoteSearch.this ); } @Override public boolean onQueryTextSubmit (String s) { return false ; } @Override public boolean onQueryTextChange (String s) { String searchText = "%" + s + "%" ; Uri contentUri = Notes.CONTENT_NOTE_URI; ContentResolver resolver = getContentResolver(); String[] projection = NoteItemData.PROJECTION; String selection = Notes.NoteColumns.SNIPPET + " Like ? " ; String[] selectionArgs = { searchText }; Cursor cursor = resolver.query(contentUri, projection, selection, selectionArgs, null ); String[] dataColumns = { Notes.NoteColumns.SNIPPET , Notes.NoteColumns.MODIFIED_DATE}; int [] viewIDs = { android.R.id.text1 , R.id.text1_time }; MyCursorAdapter adapter = new MyCursorAdapter ( this , R.layout.noteslist_item, cursor, dataColumns, viewIDs ); setListAdapter(adapter); return true ; } @Override protected void onListItemClick (ListView l, View v, int position, long id) { super .onListItemClick(l, v, position, id); Intent intent = new Intent (this , NoteEditActivity.class); Cursor cursor = (Cursor) l.getItemAtPosition(position); long noteId = cursor.getLong(cursor.getColumnIndexOrThrow(Notes.DataColumns.ID)); intent.setAction(Intent.ACTION_VIEW); intent.putExtra(Intent.EXTRA_UID, data.getId()); this .startActivityForResult(intent, REQUEST_CODE_OPEN_NODE); } }
在MyCursorAdapter
中,对样式进行处理
1 2 3 4 5 6 public class MyCursorAdapter extends SimpleCursorAdapter { public MyCursorAdapter (Context context, int layout, Cursor c, String[] from, int [] to) { super (context, layout, c, from, to); } }
6、更换背景图片功能 首先添加item表示图片切换,menu\note_list.xml
1 2 3 4 5 6 7 8 9 10 11 12 <item android:id ="@+id/Lisa" android:title ="@string/menu_Lisa" /> <item android:id ="@+id/Rose" android:title ="@string/menu_Rose" /> <item android:id ="@+id/Waiwai" android:title ="@string/menu_Waiwai" /> <item android:id ="@+id/background_default" android:title ="@string/menu_background_default" />
然后再NoteListActivity
中,首先在onCreate
方法中,设置默认背景
1 getWindow().setBackgroundDrawableResource(R.drawable.background_default);
然后添加变量mode
用于切换页面,在onOptionsItemSelected
方法中设置切换背景
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 if (itemId == R.id.background_default){ mode = -2 ; getWindow().setBackgroundDrawableResource(R.drawable.background_default); } else if (itemId == R.id.Rose){ mode = -1 ; getWindow().setBackgroundDrawableResource(R.drawable.rose); } else if (itemId == R.id.Lisa){ mode = 0 ; getWindow().setBackgroundDrawableResource(R.drawable.lisa); } else if (itemId == R.id.Waiwai){ mode = 1 ; getWindow().setBackgroundDrawableResource(R.drawable.waiwai); }
最后在onPrepareOptionsMenu
设置按键隐藏
1 2 3 4 5 6 7 8 if (mode == -1 ) menu.findItem(R.id.Rose).setVisible(false ); else if (mode == 0 ) menu.findItem(R.id.Lisa).setVisible(false ); else if (mode == 1 ) menu.findItem(R.id.Waiwai).setVisible(false ); else if (mode == -2 ) menu.findItem(R.id.background_default).setVisible(false );
7、插入图片功能 首先,在note_edit
中添加插入图片按钮
1 2 3 4 5 6 7 8 9 <ImageButton android:id ="@+id/add_img_btn" android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:layout_marginLeft ="10dp" android:layout_marginTop ="500dp" android:layout_marginBottom ="7dp" android:src ="@android:drawable/ic_menu_gallery" tools:ignore ="TouchTargetSizeCheck,SpeakableTextPresentCheck" />
NoteEditActivity
中onCreate
中为按钮绑定监听器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 final ImageButton add_img_btn = (ImageButton) findViewById(R.id.add_img_btn); add_img_btn.setOnClickListener(new View .OnClickListener() { @Override public void onClick (View view) { Log.d(TAG, "onClick: click add image button" ); Intent loadImage = new Intent (Intent.ACTION_GET_CONTENT); loadImage.addCategory(Intent.CATEGORY_OPENABLE); loadImage.setType("image/*" ); startActivityForResult(loadImage, PHOTO_REQUEST); } });
重写onActivityResult
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 @Override protected void onActivityResult (int requestCode, int resultCode, Intent intent) { super .onActivityResult(requestCode, resultCode, intent); ContentResolver resolver = getContentResolver(); switch (requestCode) { case PHOTO_REQUEST: Uri originalUri = intent.getData(); Bitmap bitmap = null ; try { bitmap = BitmapFactory.decodeStream(resolver.openInputStream(originalUri)); } catch (FileNotFoundException e) { Log.d(TAG, "onActivityResult: get file_exception" ); e.printStackTrace(); } if (bitmap != null ){ Log.d(TAG, "onActivityResult: bitmap is not null" ); ImageSpan imageSpan = new ImageSpan (NoteEditActivity.this , bitmap); String path = getPath(this ,originalUri); String img_fragment= "[local]" + path + "[/local]" ; SpannableString spannableString = new SpannableString (img_fragment); spannableString.setSpan(imageSpan, 0 , img_fragment.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); NoteEditText e = (NoteEditText) findViewById(R.id.note_edit_view); int index = e.getSelectionStart(); Log.d(TAG, "Index是: " + index); Editable edit_text = e.getEditableText(); edit_text.insert(index, spannableString); mWorkingNote.mContent = e.getText().toString(); ContentResolver contentResolver = getContentResolver(); ContentValues contentValues = new ContentValues (); final long id = mWorkingNote.getNoteId(); contentValues.put("snippet" ,mWorkingNote.mContent); contentResolver.update(Uri.parse("content://micode_notes/note" ), contentValues,"_id=?" ,new String []{"" +id}); ContentValues contentValues1 = new ContentValues (); contentValues1.put("content" ,mWorkingNote.mContent); contentResolver.update(Uri.parse("content://micode_notes/data" ), contentValues1,"mime_type=? and note_id=?" , new String []{"vnd.android.cursor.item/text_note" ,"" +id}); }else { Toast.makeText(NoteEditActivity.this , "获取图片失败" , Toast.LENGTH_SHORT).show(); } break ; default : break ; } }
获取图片路径
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public String getPath (final Context context, final Uri uri) { final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { 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; } final String selection = "_id=?" ; final String[] selectionArgs = new String []{split[1 ]}; return getDataColumn(context, contentUri, selection, selectionArgs); } } else if ("content" .equalsIgnoreCase(uri.getScheme())) { return getDataColumn(context, uri, null , null ); } else if ("file" .equalsIgnoreCase(uri.getScheme())) { return uri.getPath(); } return null ; }
判断是否为媒体文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public boolean isMediaDocument (Uri uri) { return "com.android.providers.media.documents" .equals(uri.getAuthority()); } @Override protected void onRestoreInstanceState (Bundle savedInstanceState) { super .onRestoreInstanceState(savedInstanceState); if (savedInstanceState != null && savedInstanceState.containsKey(Intent.EXTRA_UID)) { Intent intent = new Intent (Intent.ACTION_VIEW); intent.putExtra(Intent.EXTRA_UID, savedInstanceState.getLong(Intent.EXTRA_UID)); if (!initActivityState(intent)) { finish(); return ; } Log.d(TAG, "Restoring from killed activity" ); } }
获取列内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public 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 ; }