From 38c4621805612b4bc02bfc79254ac41f904ef475 Mon Sep 17 00:00:00 2001 From: YangT-ao <1728326545@qq.com> Date: Sun, 8 Oct 2023 20:17:42 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=AF=A6=E7=BB=86?= =?UTF-8?q?=E7=94=A8=E4=BE=8B=E6=8F=8F=E8=BF=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/详细用例描述.docx | Bin 0 -> 16341 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 doc/详细用例描述.docx diff --git a/doc/详细用例描述.docx b/doc/详细用例描述.docx new file mode 100644 index 0000000000000000000000000000000000000000..60a6d405cdf76040d59c6e84e288937d13be4ef4 GIT binary patch literal 16341 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 Q-ajDk6592?{|f;8Kko0W5dZ)H literal 0 HcmV?d00001 From 4dedadaed0f560b77dcc335b3f638ce6c65af7ab Mon Sep 17 00:00:00 2001 From: YangT-ao <1728326545@qq.com> Date: Sun, 8 Oct 2023 20:25:21 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=AF=A6=E7=BB=86?= =?UTF-8?q?=E7=94=A8=E4=BE=8B=E6=8F=8F=E8=BF=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...便签同步便签内容详细用例描述.docx} | Bin 1 file changed, 0 insertions(+), 0 deletions(-) rename doc/{详细用例描述.docx => 羊涛-小米便签同步便签内容详细用例描述.docx} (100%) diff --git a/doc/详细用例描述.docx b/doc/羊涛-小米便签同步便签内容详细用例描述.docx similarity index 100% rename from doc/详细用例描述.docx rename to doc/羊涛-小米便签同步便签内容详细用例描述.docx From ca74cb308325d28a45de94ff658ffc041318cede Mon Sep 17 00:00:00 2001 From: YangT-ao <1728326545@qq.com> Date: Sun, 29 Oct 2023 21:21:42 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E6=A0=87=E6=B3=A8ui=E4=B8=8B=E9=9D=A2?= =?UTF-8?q?=E7=9A=84=E9=83=A8=E5=88=86=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mi_note/app/build.gradle | 2 +- .../java/net/micode/notes/tool/DataUtils.java | 4 +- .../micode/notes/ui/AlarmAlertActivity.java | 48 +++- .../micode/notes/ui/AlarmInitReceiver.java | 10 +- .../net/micode/notes/ui/AlarmReceiver.java | 1 + .../net/micode/notes/ui/DateTimePicker.java | 1 + .../micode/notes/ui/DateTimePickerDialog.java | 1 + .../net/micode/notes/ui/DropdownMenu.java | 9 +- .../micode/notes/ui/FoldersListAdapter.java | 11 +- .../net/micode/notes/ui/NoteEditActivity.java | 260 ++++++++++++++++-- .../net/micode/notes/ui/NoteEditText.java | 2 +- src/mi_note/local.properties | 8 +- 12 files changed, 305 insertions(+), 52 deletions(-) diff --git a/src/mi_note/app/build.gradle b/src/mi_note/app/build.gradle index 46ef6de..3d5cc9b 100644 --- a/src/mi_note/app/build.gradle +++ b/src/mi_note/app/build.gradle @@ -3,7 +3,7 @@ apply plugin: 'com.android.application' android { useLibrary 'org.apache.http.legacy' compileSdkVersion 31 - buildToolsVersion "33.0.2" + buildToolsVersion "30" defaultConfig { applicationId "com.example.mi_note" diff --git a/src/mi_note/app/src/main/java/net/micode/notes/tool/DataUtils.java b/src/mi_note/app/src/main/java/net/micode/notes/tool/DataUtils.java index 2a14982..03314bb 100644 --- a/src/mi_note/app/src/main/java/net/micode/notes/tool/DataUtils.java +++ b/src/mi_note/app/src/main/java/net/micode/notes/tool/DataUtils.java @@ -136,13 +136,15 @@ public class DataUtils { return count; } + //通过ContentResolver查询判断便签是否存在 public static boolean visibleInNoteDatabase(ContentResolver resolver, long noteId, int type) { + //查询便签,返回查询结果 Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null, NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER, new String [] {String.valueOf(type)}, null); - + //判断便签是否存在 boolean exist = false; if (cursor != null) { if (cursor.getCount() > 0) { diff --git a/src/mi_note/app/src/main/java/net/micode/notes/ui/AlarmAlertActivity.java b/src/mi_note/app/src/main/java/net/micode/notes/ui/AlarmAlertActivity.java index 85723be..6550efb 100644 --- a/src/mi_note/app/src/main/java/net/micode/notes/ui/AlarmAlertActivity.java +++ b/src/mi_note/app/src/main/java/net/micode/notes/ui/AlarmAlertActivity.java @@ -34,12 +34,13 @@ import android.view.Window; import android.view.WindowManager; import net.micode.notes.R; -import net.micode.notes.data.Notes; -import net.micode.notes.tool.DataUtils; +//import net.micode.notes.data.Notes; +//import net.micode.notes.tool.DataUtils; import java.io.IOException; +//便签定时提醒活动 public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener { private long mNoteId; private String mSnippet; @@ -49,23 +50,33 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + //设置当前窗体无标题 requestWindowFeature(Window.FEATURE_NO_TITLE); + //获取当前窗口 final Window win = getWindow(); + //设置窗口在锁屏情况下显示 win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); - + //设置熄屏时窗口显示状态 if (!isScreenOn()) { - win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON - | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON - | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON - | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR); + win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON //当这个window对用户是可见状态,则保持设备屏幕不关闭且不变暗 + | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON //当window被添加或者显示,系统会点亮屏幕 + | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON //只要这个window对用户是可见的,则允许在屏幕开启的时候锁定屏幕 + | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR); //这个flag只能配合 FLAG_LAYOUT_IN_SCREEN 一起使用. + // 当在屏幕中请求layout时,window可能在一些装饰物(如状态栏)之上或者之后,当使用这个flag时, + //window manager会报告插入window的矩形大小,来确保你的内容不会被装饰物(如状态栏)掩盖. + //这个flag一般用Window类的 setFlags(int, int)方法来设置 } + //获取启动当前活动时的Intent内容 Intent intent = getIntent(); try { + //intent.getData()返回uri,getPathSegments()将uri拆分成列表,以/为分隔,get(1)返回第二项,即mNoteId mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1)); + //查找便签信息 mSnippet = DataUtils.getSnippetById(this.getContentResolver(), mNoteId); + //限制显示的便签内容的长度,超过60个字符就截断,后面加省略号 mSnippet = mSnippet.length() > SNIPPET_PREW_MAX_LEN ? mSnippet.substring(0, SNIPPET_PREW_MAX_LEN) + getResources().getString(R.string.notelist_string_info) : mSnippet; @@ -73,33 +84,41 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD e.printStackTrace(); return; } - + //媒体播放器 mPlayer = new MediaPlayer(); + //在便签提醒前先判断便签是否存在,若是不存在则直接销毁活动 if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) { + //显示便签提醒对话框 showActionDialog(); + //播放便签提醒时的音乐 playAlarmSound(); } else { finish(); } } + //判断屏幕是否亮屏 private boolean isScreenOn() { PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); return pm.isScreenOn(); } + //播放便签提醒时的音乐 private void playAlarmSound() { + //获取当前默认声音的uri Uri url = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM); - + //获取 int silentModeStreams = Settings.System.getInt(getContentResolver(), Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0); + //设置音频属性 if ((silentModeStreams & (1 << AudioManager.STREAM_ALARM)) != 0) { mPlayer.setAudioStreamType(silentModeStreams); } else { mPlayer.setAudioStreamType(AudioManager.STREAM_ALARM); } try { + //媒体播放器播放音乐 mPlayer.setDataSource(this, url); mPlayer.prepare(); mPlayer.setLooping(true); @@ -121,20 +140,29 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD private void showActionDialog() { AlertDialog.Builder dialog = new AlertDialog.Builder(this); + //设置对话框的标题 dialog.setTitle(R.string.app_name); + //显示便签内容 dialog.setMessage(mSnippet); + //设置对话框确定按钮(即确认收到提醒)的文字以及监视器 dialog.setPositiveButton(R.string.notealert_ok, this); + //判断如果屏幕是亮的, if (isScreenOn()) { + //设置对话框查看便签按钮的文字以及监视器 dialog.setNegativeButton(R.string.notealert_enter, this); } + //设置对话框取消后的监听器,即取消后释放媒体播放器资源,销毁活动 dialog.show().setOnDismissListener(this); } + //重写OnClickListener中的onClick方法,点击时开启活动,进入便签编辑页面 public void onClick(DialogInterface dialog, int which) { switch (which) { case DialogInterface.BUTTON_NEGATIVE: Intent intent = new Intent(this, NoteEditActivity.class); + intent.setAction(Intent.ACTION_VIEW); + //保存便签的id intent.putExtra(Intent.EXTRA_UID, mNoteId); startActivity(intent); break; @@ -147,7 +175,7 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD stopAlarmSound(); finish(); } - + //释放媒体播放器资源 private void stopAlarmSound() { if (mPlayer != null) { mPlayer.stop(); diff --git a/src/mi_note/app/src/main/java/net/micode/notes/ui/AlarmInitReceiver.java b/src/mi_note/app/src/main/java/net/micode/notes/ui/AlarmInitReceiver.java index f221202..271cf7d 100644 --- a/src/mi_note/app/src/main/java/net/micode/notes/ui/AlarmInitReceiver.java +++ b/src/mi_note/app/src/main/java/net/micode/notes/ui/AlarmInitReceiver.java @@ -23,11 +23,10 @@ import android.content.ContentUris; import android.content.Context; import android.content.Intent; import android.database.Cursor; - import net.micode.notes.data.Notes; import net.micode.notes.data.Notes.NoteColumns; - +//闹钟提醒初始化广播接收器类 public class AlarmInitReceiver extends BroadcastReceiver { private static final String [] PROJECTION = new String [] { @@ -40,7 +39,9 @@ public class AlarmInitReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { + //获取系统的当前时间 long currentDate = System.currentTimeMillis(); + //查询便签中提醒时间大于当前时间的便签 Cursor c = context.getContentResolver().query(Notes.CONTENT_NOTE_URI, PROJECTION, NoteColumns.ALERTED_DATE + ">? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE, @@ -50,12 +51,17 @@ public class AlarmInitReceiver extends BroadcastReceiver { if (c != null) { if (c.moveToFirst()) { do { + //获取提醒时间 long alertDate = c.getLong(COLUMN_ALERTED_DATE); Intent sender = new Intent(context, AlarmReceiver.class); sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(COLUMN_ID))); + //从系统取得一个用于向BroadcastReceiver的Intent广播的PendingIntent对象 + //pendingIntent执行的操作实质上是参数传进来的Intent的操作,使用pendingIntent的目的在于它所包含的Intent的操作的执行是需要满足某些条件的 PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, 0); + //获取系统提供的闹钟服务 AlarmManager alermManager = (AlarmManager) context .getSystemService(Context.ALARM_SERVICE); + //设置闹钟,RTC_WAKEUP:闹钟在睡眠状态下可用,使用的是真实时间,到提醒时间时广播pendingIntent,开启闹钟提醒活动 alermManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent); } while (c.moveToNext()); } diff --git a/src/mi_note/app/src/main/java/net/micode/notes/ui/AlarmReceiver.java b/src/mi_note/app/src/main/java/net/micode/notes/ui/AlarmReceiver.java index 54e503b..85eb323 100644 --- a/src/mi_note/app/src/main/java/net/micode/notes/ui/AlarmReceiver.java +++ b/src/mi_note/app/src/main/java/net/micode/notes/ui/AlarmReceiver.java @@ -20,6 +20,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +//闹钟提醒广播接收器,收到广播时开启闹钟提醒便签活动 public class AlarmReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { diff --git a/src/mi_note/app/src/main/java/net/micode/notes/ui/DateTimePicker.java b/src/mi_note/app/src/main/java/net/micode/notes/ui/DateTimePicker.java index 043aaf4..6921b63 100644 --- a/src/mi_note/app/src/main/java/net/micode/notes/ui/DateTimePicker.java +++ b/src/mi_note/app/src/main/java/net/micode/notes/ui/DateTimePicker.java @@ -28,6 +28,7 @@ import android.view.View; import android.widget.FrameLayout; import android.widget.NumberPicker; +//自定义的定时提醒日期选择布局 public class DateTimePicker extends FrameLayout { private static final boolean DEFAULT_ENABLE_STATE = true; diff --git a/src/mi_note/app/src/main/java/net/micode/notes/ui/DateTimePickerDialog.java b/src/mi_note/app/src/main/java/net/micode/notes/ui/DateTimePickerDialog.java index 2c47ba4..a116bee 100644 --- a/src/mi_note/app/src/main/java/net/micode/notes/ui/DateTimePickerDialog.java +++ b/src/mi_note/app/src/main/java/net/micode/notes/ui/DateTimePickerDialog.java @@ -29,6 +29,7 @@ import android.content.DialogInterface.OnClickListener; import android.text.format.DateFormat; import android.text.format.DateUtils; +//自定义的设置定时提醒时的日期选择对话框类 public class DateTimePickerDialog extends AlertDialog implements OnClickListener { private Calendar mDate = Calendar.getInstance(); diff --git a/src/mi_note/app/src/main/java/net/micode/notes/ui/DropdownMenu.java b/src/mi_note/app/src/main/java/net/micode/notes/ui/DropdownMenu.java index 613dc74..c4a2e8a 100644 --- a/src/mi_note/app/src/main/java/net/micode/notes/ui/DropdownMenu.java +++ b/src/mi_note/app/src/main/java/net/micode/notes/ui/DropdownMenu.java @@ -27,6 +27,7 @@ import android.widget.PopupMenu.OnMenuItemClickListener; import net.micode.notes.R; +//点击弹出菜单类 public class DropdownMenu { private Button mButton; private PopupMenu mPopupMenu; @@ -35,9 +36,12 @@ public class DropdownMenu { public DropdownMenu(Context context, Button button, int menuId) { mButton = button; mButton.setBackgroundResource(R.drawable.dropdown_icon); + //PopupMenu依附于一个View,这里为一个Button mPopupMenu = new PopupMenu(context, mButton); + //获取菜单名称 mMenu = mPopupMenu.getMenu(); mPopupMenu.getMenuInflater().inflate(menuId, mMenu); + //按钮点击事件,点击时弹出PopupMenu菜单 mButton.setOnClickListener(new OnClickListener() { public void onClick(View v) { mPopupMenu.show(); @@ -45,16 +49,17 @@ public class DropdownMenu { }); } + //菜单子项设置监听 public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) { if (mPopupMenu != null) { mPopupMenu.setOnMenuItemClickListener(listener); } } - + //获取菜单子项 public MenuItem findItem(int id) { return mMenu.findItem(id); } - + //设置菜单项标题 public void setTitle(CharSequence title) { mButton.setText(title); } diff --git a/src/mi_note/app/src/main/java/net/micode/notes/ui/FoldersListAdapter.java b/src/mi_note/app/src/main/java/net/micode/notes/ui/FoldersListAdapter.java index 96b77da..df0f395 100644 --- a/src/mi_note/app/src/main/java/net/micode/notes/ui/FoldersListAdapter.java +++ b/src/mi_note/app/src/main/java/net/micode/notes/ui/FoldersListAdapter.java @@ -28,7 +28,7 @@ import net.micode.notes.R; import net.micode.notes.data.Notes; import net.micode.notes.data.Notes.NoteColumns; - +//CursorAdapter继承自BaseAdapter,为Cursor和ListView之间建立桥梁 public class FoldersListAdapter extends CursorAdapter { public static final String [] PROJECTION = { NoteColumns.ID, @@ -43,35 +43,42 @@ public class FoldersListAdapter extends CursorAdapter { // TODO Auto-generated constructor stub } + //创建视图View @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { return new FolderListItem(context); } + //为视图View绑定数据 @Override public void bindView(View view, Context context, Cursor cursor) { if (view instanceof FolderListItem) { + //首先判断cursor指向的那一行是否有数据,如果有就查询,没有就用string.menu_move_parent_folder里的数据替代 String folderName = (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context .getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN); + //绑定数据 ((FolderListItem) view).bind(folderName); } } - + //返回View中要绑定的数据 public String getFolderName(Context context, int position) { Cursor cursor = (Cursor) getItem(position); return (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context .getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN); } + //自定义视图类,继承自LinearLayout private class FolderListItem extends LinearLayout { private TextView mName; public FolderListItem(Context context) { super(context); + //加载视图布局 inflate(context, R.layout.folder_list_item, this); mName = (TextView) findViewById(R.id.tv_folder_name); } + //为视图View绑定数据 public void bind(String name) { mName.setText(name); } diff --git a/src/mi_note/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java b/src/mi_note/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java index 18c2d6e..54d5db4 100644 --- a/src/mi_note/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java +++ b/src/mi_note/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java @@ -86,9 +86,10 @@ import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; - +//便签编辑活动 public class NoteEditActivity extends AppCompatActivity implements OnClickListener, NoteSettingChangedListener, OnTextViewChangeListener { + //存放便签编辑页面顶部四个组件的内部类 private class HeadViewHolder { public TextView tvModified; @@ -98,7 +99,7 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen public ImageView ibSetBgColor; } - + //存储背景颜色图标id的Map private static final Map sBgSelectorBtnsMap = new HashMap(); static { sBgSelectorBtnsMap.put(R.id.iv_bg_yellow, ResourceParser.YELLOW); @@ -107,7 +108,7 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen sBgSelectorBtnsMap.put(R.id.iv_bg_green, ResourceParser.GREEN); sBgSelectorBtnsMap.put(R.id.iv_bg_white, ResourceParser.WHITE); } - + //存储背景颜色图标下方勾选图标id的Map private static final Map sBgSelectorSelectionMap = new HashMap(); static { sBgSelectorSelectionMap.put(ResourceParser.YELLOW, R.id.iv_bg_yellow_select); @@ -116,7 +117,7 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen sBgSelectorSelectionMap.put(ResourceParser.GREEN, R.id.iv_bg_green_select); sBgSelectorSelectionMap.put(ResourceParser.WHITE, R.id.iv_bg_white_select); } - + //存储每一个字体选择布局id的Map private static final Map sFontSizeBtnsMap = new HashMap(); static { sFontSizeBtnsMap.put(R.id.ll_font_large, ResourceParser.TEXT_LARGE); @@ -124,7 +125,7 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen sFontSizeBtnsMap.put(R.id.ll_font_normal, ResourceParser.TEXT_MEDIUM); sFontSizeBtnsMap.put(R.id.ll_font_super, ResourceParser.TEXT_SUPER); } - + //存储字体大小图标旁边勾选图标id的Map private static final Map sFontSelectorSelectionMap = new HashMap(); static { sFontSelectorSelectionMap.put(ResourceParser.TEXT_LARGE, R.id.iv_large_select); @@ -155,8 +156,9 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen private static final String PREFERENCE_FONT_SIZE = "pref_font_size"; private static final int SHORTCUT_ICON_TITLE_MAX_LEN = 10; - + //勾型符号 public static final String TAG_CHECKED = String.valueOf('\u221A'); + //空心方形符号 public static final String TAG_UNCHECKED = String.valueOf('\u25A1'); private LinearLayout mEditTextList; @@ -167,12 +169,14 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + //加载便签编辑页面布局 this.setContentView(R.layout.note_edit); if (savedInstanceState == null && !initActivityState(getIntent())) { finish(); return; } + //初始化活动中的资源,包括各种组件等 initResources(); } @@ -180,12 +184,16 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen * Current activity may be killed when the memory is low. Once it is killed, for another time * user load this activity, we should restore the former state */ + //进程被系统gc回收后,若是再次启动,调用该方法,读取之前保存的数据 @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); + //首先判断进程被回收前是否保存了数据 if (savedInstanceState != null && savedInstanceState.containsKey(Intent.EXTRA_UID)) { Intent intent = new Intent(Intent.ACTION_VIEW); + //保存便签的ID intent.putExtra(Intent.EXTRA_UID, savedInstanceState.getLong(Intent.EXTRA_UID)); + //初始化内容 if (!initActivityState(intent)) { finish(); return; @@ -200,35 +208,46 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen * then jump to the NotesListActivity */ mWorkingNote = null; + //判断活动启动的方式,是否为Intent.ACTION_VIEW类型,即为直接打开一个便签 if (TextUtils.equals(Intent.ACTION_VIEW, intent.getAction())) { + //读取NotesListActivity传来的便签id,若是没有则为0 long noteId = intent.getLongExtra(Intent.EXTRA_UID, 0); mUserQuery = ""; /** * Starting from the searched result */ + //判断用户是否是通过搜索后打开便签 if (intent.hasExtra(SearchManager.EXTRA_DATA_KEY)) { + //读取便签id noteId = Long.parseLong(intent.getStringExtra(SearchManager.EXTRA_DATA_KEY)); mUserQuery = intent.getStringExtra(SearchManager.USER_QUERY); } - + //若是没有查找到对应的便签,则说明便签不存在,直接结束该活动 if (!DataUtils.visibleInNoteDatabase(getContentResolver(), noteId, Notes.TYPE_NOTE)) { + //跳转到便签列表活动,即NotesListActivity Intent jump = new Intent(this, NotesListActivity.class); startActivity(jump); + //显示便签不存在 showToast(R.string.error_note_not_exist); + //结束该活动 finish(); return false; } else { + //加载对应id的便签 mWorkingNote = WorkingNote.load(this, noteId); + //判断是否加载成功 if (mWorkingNote == null) { Log.e(TAG, "load note failed with note id" + noteId); finish(); return false; } } + //调整软盘布局,当用户进入活动时隐藏软盘,当输入法显示时,允许窗口重新计算尺寸,使内容不被输入法所覆盖 getWindow().setSoftInputMode( WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); + //判断活动启动的方式,是否为Intent.ACTION_INSERT_OR_EDIT类型,即为创建一个便签 } else if(TextUtils.equals(Intent.ACTION_INSERT_OR_EDIT, intent.getAction())) { // New note long folderId = intent.getLongExtra(Notes.INTENT_EXTRA_FOLDER_ID, 0); @@ -264,7 +283,7 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, widgetType, bgResId); } - + //调整软盘布局,当用户进入活动时显示软盘,并且允许窗口重新计算尺寸,使内容不被输入法所覆盖 getWindow().setSoftInputMode( WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE | WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); @@ -273,10 +292,12 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen finish(); return false; } + //为便签设置项设置监听 mWorkingNote.setOnSettingStatusChangedListener(this); return true; } + //activity生命周期回调方法 @Override protected void onResume() { super.onResume(); @@ -284,20 +305,26 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen } private void initNoteScreen() { + //设置便签内容字体的外观 mNoteEditor.setTextAppearance(this, TextAppearanceResources .getTexAppearanceResource(mFontSizeId)); + //判断是否是清单模式 if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { switchToListMode(mWorkingNote.getContent()); } else { + //设置编辑区域便签的初始内容(内容经过处理) mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery)); + //设置EditText中光标位置 mNoteEditor.setSelection(mNoteEditor.getText().length()); } + //设置背景颜色选择下方的勾选图标不可见 for (Integer id : sBgSelectorSelectionMap.keySet()) { findViewById(sBgSelectorSelectionMap.get(id)).setVisibility(View.GONE); } + //设置便签编辑页面编辑区域和头部的背景颜色 mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId()); mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId()); - + //设置便签编辑页面头部显示的时间 mNoteHeaderHolder.tvModified.setText(DateUtils.formatDateTime(this, mWorkingNote.getModifiedDate(), DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NUMERIC_DATE | DateUtils.FORMAT_SHOW_TIME @@ -310,19 +337,29 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen showAlertHeader(); } + //设置显示定时提醒 private void showAlertHeader() { + //判断便签是否设置定时提醒 if (mWorkingNote.hasClockAlert()) { + //获取系统当前时间 long time = System.currentTimeMillis(); + //判断当前时间是否超过了设置的提醒时间 if (time > mWorkingNote.getAlertDate()) { + //设置显示提醒时间的TextView显示"已过期" mNoteHeaderHolder.tvAlertDate.setText(R.string.note_alert_expired); } else { + //设置显示提醒时间的TextView显示设置时间距离现在的时长 mNoteHeaderHolder.tvAlertDate.setText(DateUtils.getRelativeTimeSpanString( mWorkingNote.getAlertDate(), time, DateUtils.MINUTE_IN_MILLIS)); } + //设置显示提醒时间的TextView可见 mNoteHeaderHolder.tvAlertDate.setVisibility(View.VISIBLE); + //设置显示提醒时间的TextView旁边的闹钟图标可见 mNoteHeaderHolder.ivAlertIcon.setVisibility(View.VISIBLE); } else { + //设置显示提醒时间的TextView不可见 mNoteHeaderHolder.tvAlertDate.setVisibility(View.GONE); + //设置显示提醒时间的TextView旁边的闹钟图标不可见 mNoteHeaderHolder.ivAlertIcon.setVisibility(View.GONE); }; } @@ -333,6 +370,7 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen initActivityState(intent); } + //进程被杀死或者结束前,调用该方法进行数据保存,防止程序因内存不足被杀死而丢失数据 @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); @@ -341,21 +379,26 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen * generate a id. If the editing note is not worth saving, there * is no id which is equivalent to create new note */ + //对于新建的便签,在没保存之前是没有id的,为了得到它的id,将其先保存 if (!mWorkingNote.existInDatabase()) { saveNote(); } + //保存便签的id outState.putLong(Intent.EXTRA_UID, mWorkingNote.getNoteId()); Log.d(TAG, "Save working note id: " + mWorkingNote.getNoteId() + " onSaveInstanceState"); } + //设置触摸事件 @Override public boolean dispatchTouchEvent(MotionEvent ev) { + //当具体的背景颜色选择界面可见并且点击事件并不在该View中时,将具体的背景颜色选择界面设置不可见 if (mNoteBgColorSelector.getVisibility() == View.VISIBLE && !inRangeOfView(mNoteBgColorSelector, ev)) { + // mNoteBgColorSelector.setVisibility(View.GONE); return true; } - + //当具体的字体大小选择界面可见并且点击事件并不在该View中时,将具体的字体大小选择界面设置不可见 if (mFontSizeSelector.getVisibility() == View.VISIBLE && !inRangeOfView(mFontSizeSelector, ev)) { mFontSizeSelector.setVisibility(View.GONE); @@ -364,6 +407,7 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen return super.dispatchTouchEvent(ev); } + //返回时间发生的位置是否在某个视图内的boolean private boolean inRangeOfView(View view, MotionEvent ev) { int []location = new int[2]; view.getLocationOnScreen(location); @@ -379,45 +423,63 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen } private void initResources() { + //获取编辑页面头部布局,为一个LinearLayout mHeadViewPanel = findViewById(R.id.note_title); + //创建一个存放头部布局里面四个组件的容器 mNoteHeaderHolder = new HeadViewHolder(); + //获取第一个组件,即为一个TextView,即用来显示最近更新时间 mNoteHeaderHolder.tvModified = (TextView) findViewById(R.id.tv_modified_date); + //获取第二个组件,即为一个ImageView,显示闹钟图标 mNoteHeaderHolder.ivAlertIcon = (ImageView) findViewById(R.id.iv_alert_icon); + //获取第三个组件,即为一个TextView,即用来显示闹钟提醒时间,即为距现在还有多长时间 mNoteHeaderHolder.tvAlertDate = (TextView) findViewById(R.id.tv_alert_date); + //获取第四个组件,为一个ImageButton,即为用来切换便签背景的按钮 mNoteHeaderHolder.ibSetBgColor = (ImageView) findViewById(R.id.btn_set_bg_color); + //为切换便签背景的按钮设置监听 mNoteHeaderHolder.ibSetBgColor.setOnClickListener(this); + //获取便签编辑区域EditText mNoteEditor = (EditText) findViewById(R.id.note_edit_view); + //获取便签编辑区域整体布局,为一个LinearLayout mNoteEditorPanel = findViewById(R.id.sv_note_edit); + //获取具体背景选择的布局,为一个LinearLayout mNoteBgColorSelector = findViewById(R.id.note_bg_color_selector); + //遍历每一个可选的背景图标,并为其设置监听 for (int id : sBgSelectorBtnsMap.keySet()) { ImageView iv = (ImageView) findViewById(id); iv.setOnClickListener(this); } - + //获取具体字体大小选择的布局,为一个LinearLayout mFontSizeSelector = findViewById(R.id.font_size_selector); + //获取每一个存放可选字体图标的布局,为FrameLayout,并为其设置监听 for (int id : sFontSizeBtnsMap.keySet()) { View view = findViewById(id); view.setOnClickListener(this); }; + //可以获得整个应用程序的默认共享首选项文件,无需加名字,以我们 App 的包名加 _preferences 并以 xml 的格式保存在 /data/data/包名/shared_prefs/下 mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); + //读取保存的字体大小的id(即为数组中的位置),存在默认值,为BG_DEFAULT_FONT_SIZE mFontSizeId = mSharedPrefs.getInt(PREFERENCE_FONT_SIZE, ResourceParser.BG_DEFAULT_FONT_SIZE); /** * HACKME: Fix bug of store the resource id in shared preference. * The id may larger than the length of resources, in this case, * return the {@link ResourceParser#BG_DEFAULT_FONT_SIZE} */ + //判断读取的字体大小的id(即为数组中的位置)是否超过存放字体大小数组的长度,如果超过了该长度,则字体大小的id设置为默认值 if(mFontSizeId >= TextAppearanceResources.getResourcesSize()) { mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE; } + //获取清单模式下存放每一个行布局的容器,为一个LinearLayout mEditTextList = (LinearLayout) findViewById(R.id.note_edit_list); } @Override protected void onPause() { super.onPause(); + //保存便签相关信息 if(saveNote()) { Log.d(TAG, "Note data was saved with length:" + mWorkingNote.getContent().length()); } + //清理界面的背景颜色选择界面或者字体大小选择界面,将其设置为不可见,即为GONE clearSettingState(); } @@ -440,79 +502,113 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen setResult(RESULT_OK, intent); } + + //重写OnClickListener中的onclick方法 @SuppressLint("WrongConstant") public void onClick(View v) { + //获取点击的View的id int id = v.getId(); if (id == R.id.btn_set_bg_color) { + //设置存储便签背景选择的布局可见 mNoteBgColorSelector.setVisibility(View.VISIBLE); + //设置当前便签已选定的颜色图标下方的勾选图标可见 findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( - View.VISIBLE); + //判断点击的是否时展开背景选择后的具体的选择颜色的View } else if (sBgSelectorBtnsMap.containsKey(id)) { + //取消先前已选定的颜色下方的勾选图标 findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( View.GONE); + //重新设置便签的颜色属性,即保存对应颜色的id mWorkingNote.setBgColorId(sBgSelectorBtnsMap.get(id)); + //设置背景选择不可见 mNoteBgColorSelector.setVisibility(View.GONE); + //判断点击的是否是展开字体大小选择后的具体的一个字体选择View } else if (sFontSizeBtnsMap.containsKey(id)) { + //取消先前已选定的字体大小旁边的勾选图标 findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.GONE); + //从Map中读出选择的字体大小对应的id mFontSizeId = sFontSizeBtnsMap.get(id); + //将字体大小写入应用首选的SharedPreferences文件中 mSharedPrefs.edit().putInt(PREFERENCE_FONT_SIZE, mFontSizeId).commit(); + //将选中的字体大小图标旁边的勾选符设置为可见 findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE); + //判断当前是否是清单模式 if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { + //对便签内容进行处理和保存 getWorkingText(); + //传入便签内容,切换到清单模式 switchToListMode(mWorkingNote.getContent()); } else { + //非清单模式时设置字体的样式 mNoteEditor.setTextAppearance(this, TextAppearanceResources.getTexAppearanceResource(mFontSizeId)); } + //将具体字体选择界面设置为不可见 mFontSizeSelector.setVisibility(View.GONE); } } + //按返回键时执行的操作 @Override public void onBackPressed() { + //首先处理界面内展开的选项,如背景选择、字体大小选择 if(clearSettingState()) { return; } - + //在上述都处理完后,点击后销毁该活动,在该操作前保存便签相关内容 saveNote(); super.onBackPressed(); } - + //清理界面的背景颜色选择界面或者字体大小选择界面,将其设置为不可见,即为GONE private boolean clearSettingState() { + //判断便签背景选择是否可见 if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) { + //便签背景选择设置为不可见 mNoteBgColorSelector.setVisibility(View.GONE); return true; + //判断字体大小选择是否可见 } else if (mFontSizeSelector.getVisibility() == View.VISIBLE) { + //设置字体大小选择不可见 mFontSizeSelector.setVisibility(View.GONE); return true; } return false; } - + //便签背景颜色发生改变时调用 public void onBackgroundColorChanged() { + //设置便签选的颜色下方的勾选符号可见 findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( View.VISIBLE); + //设置便签编辑区域的颜色 mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId()); + //设置便签编辑区域上方头部的颜色 mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId()); } + //点击menu时调用,用于显示menu内容 @Override public boolean onPrepareOptionsMenu(Menu menu) { + //首先判断activity是否处于活跃状态 if (isFinishing()) { return true; } + //点击菜单生效前先,清理界面的背景颜色选择界面或者字体大小选择界面 clearSettingState(); menu.clear(); + //根据FolderId加载不同的菜单布局 if (mWorkingNote.getFolderId() == Notes.ID_CALL_RECORD_FOLDER) { getMenuInflater().inflate(R.menu.call_note_edit, menu); } else { getMenuInflater().inflate(R.menu.note_edit, menu); } + //如果当前模式是清单模式,则菜单中应该显示退出清单模式,否则应该显示进入清单模式 if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_normal_mode); } else { menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_list_mode); } + //如果当前便签有闹钟提醒,则菜单中应该只显示删除提醒,否则应该只显示提醒我 if (mWorkingNote.hasClockAlert()) { menu.findItem(R.id.menu_alert).setVisible(false); } else { @@ -521,14 +617,19 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen return true; } + + //菜单项监听 @RequiresApi(api = Build.VERSION_CODES.O) @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { + //点击新建便签,则会新建一个便签 case R.id.menu_new_note: createNewNote(); break; + //点击删除 case R.id.menu_delete: + //弹出对话框是否确认删除 AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(getString(R.string.alert_title_delete)); builder.setIcon(android.R.drawable.ic_dialog_alert); @@ -536,7 +637,9 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { + //删除便签方法 deleteCurrentNote(); + //结束当前活动 finish(); } }); @@ -544,24 +647,32 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen builder.show(); break; case R.id.menu_font_size: + //设置字体选择界面可见 mFontSizeSelector.setVisibility(View.VISIBLE); + //初始时选中之前选择过的字体 findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE); break; case R.id.menu_list_mode: + //便签设置查看模式 mWorkingNote.setCheckListMode(mWorkingNote.getCheckListMode() == 0 ? TextNote.MODE_CHECK_LIST : 0); break; case R.id.menu_share: + //通过该方法先对便签内容进行处理、保存 getWorkingText(); + //分享便签内容 sendTo(this, mWorkingNote.getContent()); break; case R.id.menu_send_to_desktop: + //便签发送到桌面方法 sendToDesktop(); break; case R.id.menu_alert: + //设置便签提醒时间 setReminder(); break; case R.id.menu_delete_remind: + //设置便签提醒时间为0,即删除该提醒 mWorkingNote.setAlertDate(0, false); break; default: @@ -571,9 +682,12 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen } private void setReminder() { + //创建一个时间选择对话框 DateTimePickerDialog d = new DateTimePickerDialog(this, System.currentTimeMillis()); + //注册时间选择监听 d.setOnDateTimeSetListener(new OnDateTimeSetListener() { public void OnDateTimeSet(AlertDialog dialog, long date) { + //便签设置提醒时间 mWorkingNote.setAlertDate(date , true); } }); @@ -584,7 +698,9 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen * Share note to apps that support {@link Intent#ACTION_SEND} action * and {@text/plain} type */ + //分享便签内容方法 private void sendTo(Context context, String info) { + //根据Intent意图自动创建活动,这里为创建一个系统活动 Intent intent = new Intent(Intent.ACTION_SEND); intent.putExtra(Intent.EXTRA_TEXT, info); intent.setType("text/plain"); @@ -593,19 +709,24 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen private void createNewNote() { // Firstly, save current editing notes + //保存当前便签相关内容 saveNote(); // For safety, start a new NoteEditActivity + //结束当前活动 finish(); + //创建一个新的便签编辑活动 Intent intent = new Intent(this, NoteEditActivity.class); intent.setAction(Intent.ACTION_INSERT_OR_EDIT); intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mWorkingNote.getFolderId()); startActivity(intent); } - + //删除便签方法 private void deleteCurrentNote() { + //先判断当前便签是否已经保存 if (mWorkingNote.existInDatabase()) { HashSet ids = new HashSet(); + //获取便签id long id = mWorkingNote.getNoteId(); if (id != Notes.ID_ROOT_FOLDER) { ids.add(id); @@ -628,24 +749,31 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen private boolean isSyncMode() { return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0; } - + //便签提醒状态发生改变时调用该方法 public void onClockAlertChanged(long date, boolean set) { /** * User could set clock to an unsaved note, so before setting the * alert clock, we should save the note first */ + //如果便签没有保存,先保存 if (!mWorkingNote.existInDatabase()) { saveNote(); } if (mWorkingNote.getNoteId() > 0) { Intent intent = new Intent(this, AlarmReceiver.class); + //保存便签内容的uri,便于后续查找 intent.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mWorkingNote.getNoteId())); + //pendingIntent封装具体执行的intent PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0); + //获取系统服务 AlarmManager alarmManager = ((AlarmManager) getSystemService(ALARM_SERVICE)); showAlertHeader(); + //根据用户具体选择, if(!set) { + //取消定时任务 alarmManager.cancel(pendingIntent); } else { + //设置单次定时任务 alarmManager.set(AlarmManager.RTC_WAKEUP, date, pendingIntent); } } else { @@ -655,6 +783,7 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen * should input something */ Log.e(TAG, "Clock alert setting error"); + //输出提示消息 showToast(R.string.error_note_empty_for_clock); } } @@ -662,20 +791,23 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen public void onWidgetChanged() { updateWidget(); } - + //清单模式下删除便签内容时调用 public void onEditTextDelete(int index, String text) { + //获取清单模式下便签中总共有多少个行编辑布局,即有多少“行” int childCount = mEditTextList.getChildCount(); + //当只有一个行布局时,不再允许删除 if (childCount == 1) { return; } - + //重新调整删除便签中一行(即一个行布局)之后的所有行的位置 for (int i = index + 1; i < childCount; i++) { ((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text)) .setIndex(i - 1); } - + //移除删除的行布局 mEditTextList.removeViewAt(index); NoteEditText edit = null; + //获取删除的便签行布局的前一个行布局中的EditText,处理index为0的特殊情况 if(index == 0) { edit = (NoteEditText) mEditTextList.getChildAt(0).findViewById( R.id.et_edit_text); @@ -683,12 +815,16 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen edit = (NoteEditText) mEditTextList.getChildAt(index - 1).findViewById( R.id.et_edit_text); } + //记录EditText中字符串的长度 int length = edit.length(); + //将删除的行布局中的EditText中的内容拼接到上一个行布局的EditText edit.append(text); + //上一行的EditText获取焦点 edit.requestFocus(); + //调整上一行的EditText焦点的位置 edit.setSelection(length); } - + //清单模式下新建一行便签内容 public void onEditTextEnter(int index, String text) { /** * Should not happen, check for debug @@ -696,12 +832,15 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen if(index > mEditTextList.getChildCount()) { Log.e(TAG, "Index out of mEditTextList boundrary, should not happen"); } - + //清单模式下新建一个编辑行布局 View view = getListItem(text, index); mEditTextList.addView(view, index); + //在编辑行布局中获取EditText,并获取焦点 NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); edit.requestFocus(); + //EditText光标设置为起始位置 edit.setSelection(0); + //重新设定从新建的位置之后每一个编辑行的位置 for (int i = index + 1; i < mEditTextList.getChildCount(); i++) { ((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text)) .setIndex(i); @@ -709,33 +848,46 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen } private void switchToListMode(String text) { + //移除清单模式下的父布局LinearLayout的所有子视图 mEditTextList.removeAllViews(); + //将便签内容以字符串分隔,形成清单模式下的每一条内容 String[] items = text.split("\n"); int index = 0; + //遍历每一条内容 for (String item : items) { + //判断内容是否为空 if(!TextUtils.isEmpty(item)) { + //在清单模式下的父布局LinearLayout中添加子布局,并在子布局中显示便签中的内容 mEditTextList.addView(getListItem(item, index)); index++; } } + //在清单模式下的父布局LinearLayout中的最后多添加一条内容为空的子布局 mEditTextList.addView(getListItem("", index)); + //空的子布局中的EditText获取焦点,以便用户输入 mEditTextList.getChildAt(index).findViewById(R.id.et_edit_text).requestFocus(); - + //设置正常模式(即非清单模式)下显示便签内容的布局不可见 mNoteEditor.setVisibility(View.GONE); + //设置清单模式下的显示便签内容的布局可见 mEditTextList.setVisibility(View.VISIBLE); } private Spannable getHighlightQueryResult(String fullText, String userQuery) { + //SpannableString类用于便捷解决字符串中部分需要特殊显示效果处理 SpannableString spannable = new SpannableString(fullText == null ? "" : fullText); if (!TextUtils.isEmpty(userQuery)) { + //便签内容模式匹配用户的搜索内容 mPattern = Pattern.compile(userQuery); Matcher m = mPattern.matcher(fullText); int start = 0; + //第一次从字符串起始位置查找,查找所有匹配的位置 while (m.find(start)) { + //设置匹配内容的字体颜色为R.color.user_query_highlight spannable.setSpan( new BackgroundColorSpan(this.getResources().getColor( R.color.user_query_highlight)), m.start(), m.end(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + //设置查找位置为上一次匹配的末位置,再次查找 start = m.end(); } } @@ -743,69 +895,101 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen } private View getListItem(String item, int index) { + //加载清单模式下的每一条内容的布局 View view = LayoutInflater.from(this).inflate(R.layout.note_edit_list_item, null); + //获取布局中的EditText final NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); + //设置EditText重的字体的样式 edit.setTextAppearance(this, TextAppearanceResources.getTexAppearanceResource(mFontSizeId)); + //获取勾选组件CheckBox CheckBox cb = ((CheckBox) view.findViewById(R.id.cb_edit_item)); + //为勾选组件设置监听 cb.setOnCheckedChangeListener(new OnCheckedChangeListener() { public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (isChecked) { + //给EditText中的内容添加划线 edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); } else { + //取消EditText中的内容的划线 edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG); } } }); - + //判断便签中的一条内容是否以TAG_CHECKED开头,即是否勾选 if (item.startsWith(TAG_CHECKED)) { + //设置CheckBox为勾选状态 cb.setChecked(true); + //给EditText中的内容添加划线 edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); + //去掉一行内容中的TAG_CHECKED前缀 item = item.substring(TAG_CHECKED.length(), item.length()).trim(); } else if (item.startsWith(TAG_UNCHECKED)) { + //设置CheckBox为非勾选状态 cb.setChecked(false); + //设置EditText中的内容为正常状态,即无划线 edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG); + //去掉一行内容中的TAG_UNCHECKED前缀 item = item.substring(TAG_UNCHECKED.length(), item.length()).trim(); } - + //为EditText注册监听器 edit.setOnTextViewChangeListener(this); + //记录EditText的位置 edit.setIndex(index); + //设置EditText中显示的为清单模式中一行的内容,并且对于用户通过搜索之后进入清单模式,便签内容与搜索内容匹配的部分的颜色特殊处理 edit.setText(getHighlightQueryResult(item, mUserQuery)); return view; } public void onTextChange(int index, boolean hasText) { + //判断清单模式下这一行的位置是否超过了总行数 if (index >= mEditTextList.getChildCount()) { Log.e(TAG, "Wrong index, should not happen"); return; } + //首先判断清单模式下这一行是否有内容 if(hasText) { + //设置这一行的布局可见 mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.VISIBLE); } else { + //设置这一行的布局不可见 mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.GONE); } } + //监测编辑便签内容时模式的改变(清单模式还是普通) public void onCheckListModeChanged(int oldMode, int newMode) { + //判断是否是清单模式 if (newMode == TextNote.MODE_CHECK_LIST) { + //切换到清单模式 switchToListMode(mNoteEditor.getText().toString()); } else { + //去掉字符串中所有的TAG_UNCHECKED + " ",实际上这里并没有处理勾选符号 if (!getWorkingText()) { mWorkingNote.setWorkingText(mWorkingNote.getContent().replace(TAG_UNCHECKED + " ", "")); } + //处理便签内容与用户搜索匹配的部分 mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery)); + //设置清单模式下的布局不可见 mEditTextList.setVisibility(View.GONE); + //设置普通模式下的布局可见 mNoteEditor.setVisibility(View.VISIBLE); } } private boolean getWorkingText() { + //记录清单模式中是否存在有被选中的内容 boolean hasChecked = false; + //判断是否是清单模式 if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { + //拼接便签内容 StringBuilder sb = new StringBuilder(); for (int i = 0; i < mEditTextList.getChildCount(); i++) { + //获取清单模式一行的布局,为一个LinearLayout View view = mEditTextList.getChildAt(i); + //获取清单模式一个布局中(即一行)的EditText NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); + //拼接清单模式一行中的内容,包括符号和具体内容 if (!TextUtils.isEmpty(edit.getText())) { if (((CheckBox) view.findViewById(R.id.cb_edit_item)).isChecked()) { sb.append(TAG_CHECKED).append(" ").append(edit.getText()).append("\n"); @@ -815,6 +999,7 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen } } } + //记录便签的内容以及编辑日期等信息 mWorkingNote.setWorkingText(sb.toString()); } else { mWorkingNote.setWorkingText(mNoteEditor.getText().toString()); @@ -822,7 +1007,9 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen return hasChecked; } + //保存便签内容 private boolean saveNote() { + //记录好便签中的内容 getWorkingText(); boolean saved = mWorkingNote.saveNote(); if (saved) { @@ -839,6 +1026,7 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen } + //将便签发送到桌面 @RequiresApi(api = Build.VERSION_CODES.O) private void sendToDesktop() { /** @@ -846,7 +1034,9 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen * editing note is exists in databases. So, for new note, firstly * save it */ + //根据便签id判断便签是否存在 if (!mWorkingNote.existInDatabase()) { + //保存便签内容 saveNote(); } @@ -880,16 +1070,24 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen List lst = new ArrayList<>(); lst.add(shortcut); ShortcutManagerCompat.addDynamicShortcuts(this, lst);*/ - + //创建快捷方式管理对象 ShortcutManager shortcutManager = getSystemService(ShortcutManager.class); - + //验证设备的默认启动器是否支持应用内固定快捷方式 if (shortcutManager.isRequestPinShortcutSupported()) { // Assumes there's already a shortcut with the ID "my-shortcut". // The shortcut must be enabled. + //创建一个用于活动启动的Intent Intent shortcutIntent = new Intent(this, NoteEditActivity.class); + //设置Intent的意图 shortcutIntent.setAction(Intent.ACTION_VIEW); + //Intent中存入便签的id shortcutIntent.putExtra(Intent.EXTRA_UID, mWorkingNote.getNoteId()); + //构建快捷方式 + //以便签的id作为快捷方式的ID + //以处理后的便签内容作为简短说明快捷方式用途的词组和详细说明快捷方式用途的词组 + //设置快捷方式的图标 + //设置快捷方式的启动操作 ShortcutInfo pinShortcutInfo = new ShortcutInfo.Builder(this, String.valueOf(mWorkingNote.getNoteId())) .setShortLabel(makeShortcutIconTitle(mWorkingNote.getContent())) .setLongLabel(makeShortcutIconTitle(mWorkingNote.getContent())) @@ -901,14 +1099,17 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen // pinning operation fails, your app isn't notified. We assume here that the // app has implemented a method called createShortcutResultIntent() that // returns a broadcast intent. + //创建一个意图 Intent pinnedShortcutCallbackIntent = shortcutManager.createShortcutResultIntent(pinShortcutInfo); // Configure the intent so that your app's broadcast receiver gets // the callback successfully.For details, see PendingIntent.getBroadcast(). + //和其他系统控件交互一样需要延时意图PendingIntent + //该Intent执行的操作其实是实际传来的Intent的操作,自带Context PendingIntent successCallback = PendingIntent.getBroadcast(this, 0, pinnedShortcutCallbackIntent, 0); - + //创建固定快捷方式 shortcutManager.requestPinShortcut(pinShortcutInfo, successCallback.getIntentSender()); } @@ -924,16 +1125,19 @@ public class NoteEditActivity extends AppCompatActivity implements OnClickListen } private String makeShortcutIconTitle(String content) { + //去掉清单模式下保存内容时加的TAG_CHECKED和TAG_UNCHECKED标识符 content = content.replace(TAG_CHECKED, ""); content = content.replace(TAG_UNCHECKED, ""); + //如果保存内容超过10个字符则截取前10个字符作为标题,否则直接以保存的内容作为标题 return content.length() > SHORTCUT_ICON_TITLE_MAX_LEN ? content.substring(0, SHORTCUT_ICON_TITLE_MAX_LEN) : content; } + //输出提示信息 private void showToast(int resId) { showToast(resId, Toast.LENGTH_SHORT); } - + //输出提示信息 private void showToast(int resId, int duration) { Toast.makeText(this, resId, duration).show(); } diff --git a/src/mi_note/app/src/main/java/net/micode/notes/ui/NoteEditText.java b/src/mi_note/app/src/main/java/net/micode/notes/ui/NoteEditText.java index 8f88208..866895a 100644 --- a/src/mi_note/app/src/main/java/net/micode/notes/ui/NoteEditText.java +++ b/src/mi_note/app/src/main/java/net/micode/notes/ui/NoteEditText.java @@ -36,7 +36,7 @@ import net.micode.notes.R; import java.util.HashMap; import java.util.Map; - +//自定义的便签编辑页面 public class NoteEditText extends androidx.appcompat.widget.AppCompatEditText { private static final String TAG = "NoteEditText"; private int mIndex; diff --git a/src/mi_note/local.properties b/src/mi_note/local.properties index 3ed92fb..5e8ffa0 100644 --- a/src/mi_note/local.properties +++ b/src/mi_note/local.properties @@ -1,10 +1,8 @@ -## This file is automatically generated by Android Studio. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# -# This file should *NOT* be checked into Version Control Systems, +## This file must *NOT* be checked into Version Control Systems, # as it contains information specific to your local configuration. # # Location of the SDK. This is only used by Gradle. # For customization when using a Version Control System, please read the # header note. -sdk.dir=D\:\\env\\android-sdk \ No newline at end of file +#Tue Sep 19 22:24:10 CST 2023 +sdk.dir=C\:\\Users\\YangTao\\AppData\\Local\\Android\\Sdk