From d0ca745d2da18d902e2062ca8f15e19fe9b12598 Mon Sep 17 00:00:00 2001 From: wlj <1449591383@qq.com> Date: Sun, 9 Apr 2023 21:43:42 +0800 Subject: [PATCH] a --- .../AlarmAlertActivity.txt | 158 ++++ .../AlarmInitReceiver.txt | 66 ++ .../AlarmReceiver.txt | 30 + .../DateTimePicker.txt | 485 ++++++++++ .../DateTimePickerDialog.txt | 90 ++ .../DropdownMenu.txt | 61 ++ .../FoldersListAdapter.txt | 80 ++ .../NoteEditActivity.txt | 873 ++++++++++++++++++ .../NoteEditText.txt | 217 +++++ 9 files changed, 2060 insertions(+) create mode 100644 doc/吴林静代码精读部分/AlarmAlertActivity.txt create mode 100644 doc/吴林静代码精读部分/AlarmInitReceiver.txt create mode 100644 doc/吴林静代码精读部分/AlarmReceiver.txt create mode 100644 doc/吴林静代码精读部分/DateTimePicker.txt create mode 100644 doc/吴林静代码精读部分/DateTimePickerDialog.txt create mode 100644 doc/吴林静代码精读部分/DropdownMenu.txt create mode 100644 doc/吴林静代码精读部分/FoldersListAdapter.txt create mode 100644 doc/吴林静代码精读部分/NoteEditActivity.txt create mode 100644 doc/吴林静代码精读部分/NoteEditText.txt diff --git a/doc/吴林静代码精读部分/AlarmAlertActivity.txt b/doc/吴林静代码精读部分/AlarmAlertActivity.txt new file mode 100644 index 0000000..5e12045 --- /dev/null +++ b/doc/吴林静代码精读部分/AlarmAlertActivity.txt @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License.实现便签提醒功能 + */ + +package net.micode.notes.ui;//这个类包含在 net.micode.notes.ui包里 + +import android.app.Activity;//导入Activity包 +import android.app.AlertDialog; +import android.content.Context;//提供了有关应用程序的全局信息 +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener;//点击事件的监听器实现方式 +import android.content.DialogInterface.OnDismissListener; +import android.content.Intent; +import android.media.AudioManager;//引入媒体控制类 +import android.media.MediaPlayer;//媒体播放器 +import android.media.RingtoneManager; +import android.net.Uri; +import android.os.Bundle; +import android.os.PowerManager; +import android.provider.Settings;//设置 +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 java.io.IOException; +//调用了net.micode.notes.R、net.micode.notes.data.Notes、net.micode.notes.tools.DataUtils类,以及一些android的库函数 + +public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener {// 该类继承于Activity类,并实现了OnClickListener, OnDismissListener接口 + private long mNoteId;//声明便签的id + private String mSnippet;//闹钟提示时的文本内容 + private static final int SNIPPET_PREW_MAX_LEN = 60;//文字片段的最大长度 + MediaPlayer mPlayer;//多媒体播放器 + + @Override + protected void onCreate(Bundle savedInstanceState) {//重载onCreate方法 + 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); + } + + Intent intent = getIntent();//获取此次操作的数据 + + try {//获取便签id并读取其内容,如果超过长度限制则裁剪为规定长度 + mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1));//通过intent临时变量获取事先存入的标签的id号,存入mNoteId + mSnippet = DataUtils.getSnippetById(this.getContentResolver(), mNoteId);//通过该id号,从DataUtils中获取所对应便签的摘要,存入mSnippet中 + mSnippet = mSnippet.length() > SNIPPET_PREW_MAX_LEN ? mSnippet.substring(0, + SNIPPET_PREW_MAX_LEN) + getResources().getString(R.string.notelist_string_info) + : mSnippet;//判断mSnippet长度,过长则将其削减到合适长度 + } catch (IllegalArgumentException e) {//捕获IllegalArgumentException异常并进行处理 + e.printStackTrace();//输出异常信息 + return; + } + + mPlayer = new MediaPlayer();新建一个媒体播放器 + if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) {//作出判断语句,是否需要播放闹铃 如果需要弹出对话框,并播放闹铃声音,如果不需要,则直接finish结束 + showActionDialog();//弹出对话框 + playAlarmSound();//播放闹铃声音 + } else { + finish(); + } + } + + private boolean isScreenOn() {//定义一个布尔类型的函数,判断屏幕是否是亮的 + PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);//获得PowerManager的实例 + return pm.isScreenOn();//返回屏幕是否是亮的 + } + + private void playAlarmSound() {//播放闹钟声音 + Uri url = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM);//使用了android的铃声管理器方法,获取当前默认的系统提醒铃声的Uri,并存入变量url中 + + 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); + }//该判断中AudioManager.STREAM_ALARM代表的是当前系统的闹铃声音,通过移位,其第四位二进制数应该代表当前的响铃方式,为1响铃,为0静音 + try {//设置相关播放信息 + mPlayer.setDataSource(this, url);//根据 Uri设置多媒体数据来源 + mPlayer.prepare();//进行播放器的准备 + mPlayer.setLooping(true);//设置播放器循环播放 + mPlayer.start();//设置播放器开始播放 + } catch (IllegalArgumentException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (SecurityException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IllegalStateException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + }//上面的4个catch表示错误处理抛出异常,分别对应不同情况 + } + + private void showActionDialog() {//设置显示对话框的方法 + AlertDialog.Builder dialog = new AlertDialog.Builder(this);//创建对话框 + dialog.setTitle(R.string.app_name);//为对话框设置标题 + dialog.setMessage(mSnippet);//为对话框设置内容,内容存放在mSnippet中 + dialog.setPositiveButton(R.string.notealert_ok, this);//给对话框添加ok按钮 + if (isScreenOn()) {//判断屏幕是否为亮 + dialog.setNegativeButton(R.string.notealert_enter, this);//设置取消按钮‘查看’ + } + dialog.show().setOnDismissListener(this);//将对话框展示出来,并且设置一个监听器用来取消对话框 + } + + public void onClick(DialogInterface dialog, int which) {//方法会在对象被点击时被执行 + switch (which) {//用which来选择click后下一步的操作 + case DialogInterface.BUTTON_NEGATIVE://果取消返回到提醒的标签 + Intent intent = new Intent(this, NoteEditActivity.class); + intent.setAction(Intent.ACTION_VIEW); + intent.putExtra(Intent.EXTRA_UID, mNoteId); + startActivity(intent); + break; + default://其他则无操作退出 + break; + } + } + + public void onDismiss(DialogInterface dialog) {//对话框取消时停止提醒 + stopAlarmSound();//停止闹钟声音 + finish();//结束Activity的生命周期 + } + + private void stopAlarmSound() {//关闭闹钟声音 + if (mPlayer != null) {//若播放器存在 + mPlayer.stop(); + mPlayer.release(); + mPlayer = null; + }//清空播放器 + } +} diff --git a/doc/吴林静代码精读部分/AlarmInitReceiver.txt b/doc/吴林静代码精读部分/AlarmInitReceiver.txt new file mode 100644 index 0000000..a2ab55a --- /dev/null +++ b/doc/吴林静代码精读部分/AlarmInitReceiver.txt @@ -0,0 +1,66 @@ + +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License.闹钟初始时接收器 + */ + +package net.micode.notes.ui;//这个类包含在 net.micode.notes.ui包里 +//导入各种类 +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver;//监听开机广播 +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 {//该类继承BroadcastReceiver(广播接收器)类,实现闹钟响应的接收器 + + private static final String [] PROJECTION = new String [] {//声明PROJECTION,每个元素包括id和提醒日期 + NoteColumns.ID, + NoteColumns.ALERTED_DATE + }; +//设定ID和闹钟信息的初始值 + private static final int COLUMN_ID = 0; + private static final int COLUMN_ALERTED_DATE = 1; + + @Override + public void onReceive(Context context, Intent intent) {//接收广播后的处理过程 + long currentDate = System.currentTimeMillis();//通过系统函数获了当前时间,存入currentDate变量。 + Cursor c = context.getContentResolver().query(Notes.CONTENT_NOTE_URI, + PROJECTION, + NoteColumns.ALERTED_DATE + ">? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE, + new String[] { String.valueOf(currentDate) }, + null);//光标c遍历数据库,寻找出所有提醒时间大于现在时间的便签 + + if (c != null) {//当c != null时候,然后执行相关对信息的读取工作,直至读取结束,然后关闭cursor + if (c.moveToFirst()) {//将cursor移动到开始处 + do { + long alertDate = c.getLong(COLUMN_ALERTED_DATE);//获取要提醒的日期 + Intent sender = new Intent(context, AlarmReceiver.class);//通过定义一个Intent方法,与AlarmReceiver类建立连接 + sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(COLUMN_ID)));//改变数据为 新的uri 和id号 + PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, 0);//创建一个广播组件,可以在手机广播栏显示相应内容 + AlarmManager alermManager = (AlarmManager) context + .getSystemService(Context.ALARM_SERVICE);//新建闹钟管理者,使用系统的闹钟服务 + alermManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent);//设置一系列需要传输的数据 + } while (c.moveToNext());//游标移动到下一位置,不空则继续循环 + } + c.close(); + } + } +} diff --git a/doc/吴林静代码精读部分/AlarmReceiver.txt b/doc/吴林静代码精读部分/AlarmReceiver.txt new file mode 100644 index 0000000..c9db068 --- /dev/null +++ b/doc/吴林静代码精读部分/AlarmReceiver.txt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License.实现alarm这个功能最接近用户层的包 + */ + +package net.micode.notes.ui;//这个类包含在 net.micode.notes.ui包里 +//导入各种类 +import android.content.BroadcastReceiver;//接收广播并启动 AlarmAlertActivity类 +import android.content.Context;//环境 +import android.content.Intent;//文本 + +public class AlarmReceiver extends BroadcastReceiver {//闹钟接收器,继承自广播接收器 + @Override + public void onReceive(Context context, Intent intent) {//保持持续的监听状态 + intent.setClass(context, AlarmAlertActivity.class);从当前的Intent启动AlarmAlertActivity,即组件之间的跳转 + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//为该intent添加flag,使其加入一个新的task栈中 + context.startActivity(intent);//启动该intent所连接的类。 + } +} diff --git a/doc/吴林静代码精读部分/DateTimePicker.txt b/doc/吴林静代码精读部分/DateTimePicker.txt new file mode 100644 index 0000000..99823b8 --- /dev/null +++ b/doc/吴林静代码精读部分/DateTimePicker.txt @@ -0,0 +1,485 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui;//这个类包含在 net.micode.notes.ui包里 + +import java.text.DateFormatSymbols; +import java.util.Calendar;//引用日历 + +import net.micode.notes.R; + + +import android.content.Context;//文本环境 +import android.text.format.DateFormat; +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;//定义常量 + + private static final int HOURS_IN_HALF_DAY = 12;//定义常量,半天12小时 + private static final int HOURS_IN_ALL_DAY = 24;//定义常量,全天24小时 + private static final int DAYS_IN_ALL_WEEK = 7;//定义常量,一周7天 + private static final int DATE_SPINNER_MIN_VAL = 0;//定义常量,日期最小循环值为0 + private static final int DATE_SPINNER_MAX_VAL = DAYS_IN_ALL_WEEK - 1;//定义常量,日期最大循环值为6 + private static final int HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW = 0;//定义常量,24小时制最小循环值为0 + private static final int HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW = 23;//定义常量,24小时最大循环值为23 + private static final int HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW = 1;//定义常量,12小时最小循环值为1 + private static final int HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW = 12;//定义常量,12小时最小循环值为12 + private static final int MINUT_SPINNER_MIN_VAL = 0;//定义常量,分钟最小循环值为0 + private static final int MINUT_SPINNER_MAX_VAL = 59;//定义常量,分钟最大循环值为25 + private static final int AMPM_SPINNER_MIN_VAL = 0;//定义常量,上下午的最小循环值为0 + private static final int AMPM_SPINNER_MAX_VAL = 1;//定义常量,上下午的最大循环值为1 + + private final NumberPicker mDateSpinner;//定义只能更改一次的变量,与闹钟的日期设置有关 + private final NumberPicker mHourSpinner;//定义只能更改一次的变量,与闹钟的小时设置有关 + private final NumberPicker mMinuteSpinner;//定义只能更改一次的变量,与闹钟的分钟设置有关 + private final NumberPicker mAmPmSpinner;//定义只能更改一次的变量,与闹钟的上下午设置有关 + private Calendar mDate;//定义Calendar类型的变量mDate,用于操作时间 + + private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK];//定义一个展示时间的字符串 + + private boolean mIsAm;//定义变量表示是否是上午 + + private boolean mIs24HourView;//定义变量表示是否是24小时制 + + private boolean mIsEnabled = DEFAULT_ENABLE_STATE;//定义变量判断日期时间选择器是否已经启用 + + private boolean mInitialising;//定义变量表示是否在初始化 + + private OnDateTimeChangedListener mOnDateTimeChangedListener;//定义日期时间变化监听器 + + private NumberPicker.OnValueChangeListener mOnDateChangedListener = new NumberPicker.OnValueChangeListener() { + @Override + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + mDate.add(Calendar.DAY_OF_YEAR, newVal - oldVal);//根据日期新旧值,改变日历 + updateDateControl();//同步更新日期 + onDateTimeChanged();//监听日期时间变化 + }//重写监听日期值变化的方法 + };//监听日期变化,传送给mDate并进行同步更新操作 + + private NumberPicker.OnValueChangeListener mOnHourChangedListener = new NumberPicker.OnValueChangeListener() { + @Override + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + boolean isDateChanged = false;//默认时间没有变化 + Calendar cal = Calendar.getInstance();//创建一个日历 + if (!mIs24HourView) {//这里是针对12小时制的操作 + if (!mIsAm && oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) {//在夜晚11时变化到夜晚12时的时候更新 + cal.setTimeInMillis(mDate.getTimeInMillis());//将参数的值作为系统时间 + cal.add(Calendar.DAY_OF_YEAR, 1);//加一天 + isDateChanged = true;//表示日期发生变化 + } else if (mIsAm && oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) {//当由夜晚12点变回11点时 + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, -1);//减一天 + isDateChanged = true; + } + if (oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY || + oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) {//上半天的时钟处理,中午12更新 + mIsAm = !mIsAm;//上下午标志转换 + updateAmPmControl();//调用函数更新上下午 + } + } else {//如果是24小时制则 + if (oldVal == HOURS_IN_ALL_DAY - 1 && newVal == 0) {//晚上23点和0点交替时对日期的更改 + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, 1);//加一天 + isDateChanged = true; + } else if (oldVal == 0 && newVal == HOURS_IN_ALL_DAY - 1) {//0点变为23点时对日期的更改 + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, -1);//减一天 + isDateChanged = true; + } + } + int newHour = mHourSpinner.getValue() % HOURS_IN_HALF_DAY + (mIsAm ? 0 : HOURS_IN_HALF_DAY);//获得当前小时转轮表示的时间并转化为相应小时制 + mDate.set(Calendar.HOUR_OF_DAY, newHour);//设置新的小时数 + onDateTimeChanged();//监听时间变化 + if (isDateChanged) { + setCurrentYear(cal.get(Calendar.YEAR)); + setCurrentMonth(cal.get(Calendar.MONTH)); + setCurrentDay(cal.get(Calendar.DAY_OF_MONTH)); + }//如果日期发生变化则设置新日期 + }//改变小时,更新天数 + };//监听时钟变化,以小时为单位 + + private NumberPicker.OnValueChangeListener mOnMinuteChangedListener = new NumberPicker.OnValueChangeListener() { + @Override + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + int minValue = mMinuteSpinner.getMinValue(); + int maxValue = mMinuteSpinner.getMaxValue(); + int offset = 0;//定义offset小时偏移量 根据新旧时间的变化情况来加一或者是减一 + if (oldVal == maxValue && newVal == minValue) {//分钟数从最大变到最小则小时偏移量加1 + offset += 1; + } else if (oldVal == minValue && newVal == maxValue) {//分钟数从最小变到最大则小时偏移量减1 + offset -= 1; + } + if (offset != 0) {//如果存在时间偏移量,对时间进行修改 + mDate.add(Calendar.HOUR_OF_DAY, offset);//根据分钟变化修改小时的计数 + mHourSpinner.setValue(getCurrentHour());//获取当前时间来确定小时轮转数 + updateDateControl(); + int newHour = getCurrentHourOfDay();//得到现在的小时数,来决定是否修改am 或是pm + if (newHour >= HOURS_IN_HALF_DAY) {//根据新计算的小时数是否超过12小时,更新上下午控制 + mIsAm = false; + updateAmPmControl(); + } else {//新时间在12点前为上午 + mIsAm = true; + updateAmPmControl(); + } + } + mDate.set(Calendar.MINUTE, newVal);//更新分钟数 + onDateTimeChanged();//监听时间变化 + }//对分钟时间进行监听,并根据变化做出修改 + }; + + private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() {//实例化上下午变化监听器 + @Override + public void onValueChange(NumberPicker picker, int oldVal, int newVal) {//监听am与pm的变化,并根据情况修改日期的小时数 + mIsAm = !mIsAm; + if (mIsAm) {//下午变到上午,小时数减12 + mDate.add(Calendar.HOUR_OF_DAY, -HOURS_IN_HALF_DAY); + } else {//上午变到下午,小时数加12 + mDate.add(Calendar.HOUR_OF_DAY, HOURS_IN_HALF_DAY); + } + updateAmPmControl(); + onDateTimeChanged(); + } + }; + + public interface OnDateTimeChangedListener {//接口:日期变化监听器 + void onDateTimeChanged(DateTimePicker view, int year, int month, + int dayOfMonth, int hourOfDay, int minute);//时间变化监听器函数的定义 + } + + public DateTimePicker(Context context) {//构造函数,实例化时间日期选择器 + this(context, System.currentTimeMillis()); + } + + public DateTimePicker(Context context, long date) {//构造函数,获取系统的时间 + this(context, date, DateFormat.is24HourFormat(context)); + } + + public DateTimePicker(Context context, long date, boolean is24HourView) {//构造函数,实例化时间日期选择器 + super(context); + mDate = Calendar.getInstance();//获取系统时间 + mInitialising = true; + mIsAm = getCurrentHourOfDay() >= HOURS_IN_HALF_DAY; + inflate(context, R.layout.datetime_picker, this);//找出对话框的布局 + + mDateSpinner = (NumberPicker) findViewById(R.id.date);//显示设置日期的视图 + mDateSpinner.setMinValue(DATE_SPINNER_MIN_VAL);//设置组件最小值属性为日期最小值 + mDateSpinner.setMaxValue(DATE_SPINNER_MAX_VAL);//设置组件最大值属性为日期最大值 + mDateSpinner.setOnValueChangedListener(mOnDateChangedListener); + + mHourSpinner = (NumberPicker) findViewById(R.id.hour);//显示设置小时的视图 + mHourSpinner.setOnValueChangedListener(mOnHourChangedListener); + mMinuteSpinner = (NumberPicker) findViewById(R.id.minute);//显示设置分钟的视图 + mMinuteSpinner.setMinValue(MINUT_SPINNER_MIN_VAL);//设置组件最小值属性为分钟最小值 + mMinuteSpinner.setMaxValue(MINUT_SPINNER_MAX_VAL);//设置组件最大值属性为分钟最大值 + mMinuteSpinner.setOnLongPressUpdateInterval(100);//设置监听长按时间间隔为100ms + mMinuteSpinner.setOnValueChangedListener(mOnMinuteChangedListener); + + String[] stringsForAmPm = new DateFormatSymbols().getAmPmStrings();//对24小时制下的 am 与pm各属性值进行初始化 + mAmPmSpinner = (NumberPicker) findViewById(R.id.amPm);//显示设置上下午的视图 + mAmPmSpinner.setMinValue(AMPM_SPINNER_MIN_VAL);//设置组件最小值属性为上下午编码最小值 + mAmPmSpinner.setMaxValue(AMPM_SPINNER_MAX_VAL);//设置组件最大值属性为上下午编码最大值 + mAmPmSpinner.setDisplayedValues(stringsForAmPm);//设置显示上下午字符串的值 + mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener); + + // update controls to initial state,将日期的各参数更新为初始化状态 + updateDateControl(); + updateHourControl(); + updateAmPmControl(); + + set24HourView(is24HourView);//根据用户选择设置是否为24小时制显示,否则为12小时制显示 + + // set to current time,设置当前日期 + setCurrentDate(date); + + setEnabled(isEnabled()); + + // set the content descriptions,表示初始化已经结束 + mInitialising = false; + } + + @Override + public void setEnabled(boolean enabled) {//将当前监听器的状态设置为开启状态 + if (mIsEnabled == enabled) {//如果已经在使能状态,则返回 + return; + } + super.setEnabled(enabled);//调用父类方法 + mDateSpinner.setEnabled(enabled);//分别对四个分部件设置使能 + mMinuteSpinner.setEnabled(enabled); + mHourSpinner.setEnabled(enabled); + mAmPmSpinner.setEnabled(enabled); + mIsEnabled = enabled; + } + + @Override + public boolean isEnabled() { + return mIsEnabled; + }//判断时间日期选择器是否处于使能状态 + + /** + * Get the current date in millis + * + * @return the current date in millis + */ + public long getCurrentDateInTimeMillis() { + return mDate.getTimeInMillis(); + }//获取当前时间,以毫秒表示 + + /** + * Set the current date + * + * @param date The current date in millis + */ + public void setCurrentDate(long date) {//设置当前日期 + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(date); + setCurrentDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), + cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE));//设置当前日期 + } + + /** + * Set the current date + * + * @param year The current year + * @param month The current month + * @param dayOfMonth The current dayOfMonth + * @param hourOfDay The current hourOfDay + * @param minute The current minute + */ + public void setCurrentDate(int year, int month, + int dayOfMonth, int hourOfDay, int minute) {//设置当前日期(直接使用日期和时间的参数) + setCurrentYear(year); + setCurrentMonth(month); + setCurrentDay(dayOfMonth); + setCurrentHour(hourOfDay); + setCurrentMinute(minute); + } + + /** + * Get current year + * + * @return The current year + */ + public int getCurrentYear() { + return mDate.get(Calendar.YEAR); + }//获取当前年份 + + /** + * Set current year + * + * @param year The current year + */ + public void setCurrentYear(int year) {//设置当前年份 + if (!mInitialising && year == getCurrentYear()) {//如果已初始化且当前年份和真实年份相同,则不需设置 + return; + } + mDate.set(Calendar.YEAR, year); + updateDateControl(); + onDateTimeChanged(); + } + + /** + * Get current month in the year + * + * @return The current month in the year + */ + public int getCurrentMonth() { + return mDate.get(Calendar.MONTH); + }//获取当前月份 + + /** + * Set current month in the year + * + * @param month The month in the year + */ + public void setCurrentMonth(int month) {//设置当前月份 + if (!mInitialising && month == getCurrentMonth()) {//若输入月份等于当前月份则返回 + return; + } + mDate.set(Calendar.MONTH, month);//修改月份 + updateDateControl(); + onDateTimeChanged(); + } + + /** + * Get current day of the month + * + * @return The day of the month + */ + public int getCurrentDay() { + return mDate.get(Calendar.DAY_OF_MONTH); + }//获取当前日期 + + /** + * Set current day of the month + * + * @param dayOfMonth The day of the month + */ + public void setCurrentDay(int dayOfMonth) {//设置当前日期 + if (!mInitialising && dayOfMonth == getCurrentDay()) { + return; + } + mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); + updateDateControl(); + onDateTimeChanged(); + } + + /** + * Get current hour in 24 hour mode, in the range (0~23) + * @return The current hour in 24 hour mode + */ + public int getCurrentHourOfDay() { + return mDate.get(Calendar.HOUR_OF_DAY); + }//在24小时制下获取当天的小时数 + + private int getCurrentHour() {//获取当前小时数 + if (mIs24HourView){//获取当前小时数 + return getCurrentHourOfDay(); + } else {//否则返回12小时制的小时数 + int hour = getCurrentHourOfDay(); + if (hour > HOURS_IN_HALF_DAY) { + return hour - HOURS_IN_HALF_DAY; + } else { + return hour == 0 ? HOURS_IN_HALF_DAY : hour; + } + } + } + + /** + * Set current hour in 24 hour mode, in the range (0~23) + * + * @param hourOfDay + */ + public void setCurrentHour(int hourOfDay) {//在24小时制下设置小时数 + if (!mInitialising && hourOfDay == getCurrentHourOfDay()) { + return; + } + mDate.set(Calendar.HOUR_OF_DAY, hourOfDay); + if (!mIs24HourView) {//如果为12小时制,则根据12小时制下的数修改为对应24小时制下的数 + if (hourOfDay >= HOURS_IN_HALF_DAY) { + mIsAm = false; + if (hourOfDay > HOURS_IN_HALF_DAY) { + hourOfDay -= HOURS_IN_HALF_DAY; + } + } else { + mIsAm = true; + if (hourOfDay == 0) { + hourOfDay = HOURS_IN_HALF_DAY; + } + } + updateAmPmControl(); + } + mHourSpinner.setValue(hourOfDay); + onDateTimeChanged(); + } + + /** + * Get currentMinute + * + * @return The Current Minute + */ + public int getCurrentMinute() { + return mDate.get(Calendar.MINUTE); + }//获取当前分钟数 + + /** + * Set current minute + */ + public void setCurrentMinute(int minute) {//设置当前分钟数 + if (!mInitialising && minute == getCurrentMinute()) { + return; + } + mMinuteSpinner.setValue(minute); + mDate.set(Calendar.MINUTE, minute); + onDateTimeChanged(); + } + + /** + * @return true if this is in 24 hour view else false. + */ + public boolean is24HourView () { + return mIs24HourView; + }//返回是否是24小时制的时间 + + /** + * Set whether in 24 hour or AM/PM mode. + * + * @param is24HourView True for 24 hour mode. False for AM/PM mode. + */ + public void set24HourView(boolean is24HourView) {//设置24小时下的视图 + if (mIs24HourView == is24HourView) { + return; + } + mIs24HourView = is24HourView; + mAmPmSpinner.setVisibility(is24HourView ? View.GONE : View.VISIBLE);//如果是12小时制则显示上午还是下午 + int hour = getCurrentHourOfDay(); + updateHourControl(); + setCurrentHour(hour); + updateAmPmControl(); + } + + private void updateDateControl() {//更新日期控制 + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, -DAYS_IN_ALL_WEEK / 2 - 1);//加一天 + mDateSpinner.setDisplayedValues(null); + for (int i = 0; i < DAYS_IN_ALL_WEEK; ++i) { + cal.add(Calendar.DAY_OF_YEAR, 1); + mDateDisplayValues[i] = (String) DateFormat.format("MM.dd EEEE", cal); + } + mDateSpinner.setDisplayedValues(mDateDisplayValues); + mDateSpinner.setValue(DAYS_IN_ALL_WEEK / 2); + mDateSpinner.invalidate();//使日期转轮失效 + } + + private void updateAmPmControl() {//对上下午的操作 + if (mIs24HourView) {//24小时制的显示下,使得am与pm的转换在屏幕上不可见 + mAmPmSpinner.setVisibility(View.GONE); + } else {//根据时间得到am或者pm,并且在屏幕上显示 + int index = mIsAm ? Calendar.AM : Calendar.PM; + mAmPmSpinner.setValue(index); + mAmPmSpinner.setVisibility(View.VISIBLE); + } + } + + private void updateHourControl() {//更新小时控制 + if (mIs24HourView) {//根据当前小时制设置时间 + mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW); + mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW); + } else { + mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW); + mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW); + } + } + + /** + * Set the callback that indicates the 'Set' button has been pressed. + * @param callback the callback, if null will do nothing + */ + public void setOnDateTimeChangedListener(OnDateTimeChangedListener callback) {//设置时间日期监听器 + mOnDateTimeChangedListener = callback; + } + + private void onDateTimeChanged() {//监听日期时间变化 + if (mOnDateTimeChangedListener != null) {//输入年月日时分秒 + mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(), + getCurrentMonth(), getCurrentDay(), getCurrentHourOfDay(), getCurrentMinute()); + } + } +} diff --git a/doc/吴林静代码精读部分/DateTimePickerDialog.txt b/doc/吴林静代码精读部分/DateTimePickerDialog.txt new file mode 100644 index 0000000..0f0ca5d --- /dev/null +++ b/doc/吴林静代码精读部分/DateTimePickerDialog.txt @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui;//这个类包含在 net.micode.notes.ui包里 +//导入各种必要的类 +import java.util.Calendar;//日历 + +import net.micode.notes.R; +import net.micode.notes.ui.DateTimePicker; +import net.micode.notes.ui.DateTimePicker.OnDateTimeChangedListener;//日期选择器 + +import android.app.AlertDialog;//日期数字选择器的对话框 +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener;//与内部对话 +import android.text.format.DateFormat; +import android.text.format.DateUtils; + +public class DateTimePickerDialog extends AlertDialog implements OnClickListener {//日期时间选择器对话框,继承于AlertDialog类,实现了OnClickListener接口 + + private Calendar mDate = Calendar.getInstance();//创建一个Calendar类型的变量 mDate,方便时间的操作 + private boolean mIs24HourView;//判断当前设置是否为24小时制 + private OnDateTimeSetListener mOnDateTimeSetListener;//定义时间日期变化监听器 + private DateTimePicker mDateTimePicker;//定义时间日期变化监听器 + + public interface OnDateTimeSetListener {//设置一个接口当时期时间设置时进行的操作 + void OnDateTimeSet(AlertDialog dialog, long date); + } + + public DateTimePickerDialog(Context context, long date) {//实例化数字选择器对话框 + super(context); + mDateTimePicker = new DateTimePicker(context);//添加一个子视图 + setView(mDateTimePicker); + mDateTimePicker.setOnDateTimeChangedListener(new OnDateTimeChangedListener() { + public void onDateTimeChanged(DateTimePicker view, int year, int month, + int dayOfMonth, int hourOfDay, int minute) {//将时间设置为系统时间,分别设置年、月、日、小时、分钟信息,并更新title时间 + mDate.set(Calendar.YEAR, year); + mDate.set(Calendar.MONTH, month); + mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); + mDate.set(Calendar.HOUR_OF_DAY, hourOfDay); + mDate.set(Calendar.MINUTE, minute); + updateTitle(mDate.getTimeInMillis()); + } + }); + mDate.setTimeInMillis(date);//设置日期的毫秒级时间 + mDate.set(Calendar.SECOND, 0);//将秒数设置为0 + mDateTimePicker.setCurrentDate(mDate.getTimeInMillis());//设置当前日期 + setButton(context.getString(R.string.datetime_dialog_ok), this);//设置ok按钮 + setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null);//设置cancel按钮 + set24HourView(DateFormat.is24HourFormat(this.getContext()));设置为24小时制显示 + updateTitle(mDate.getTimeInMillis());//更新标题时间 + } + + public void set24HourView(boolean is24HourView) {//设置成24小时制 + mIs24HourView = is24HourView; + } + + public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) {//设置日期时间监听器 + mOnDateTimeSetListener = callBack; + } + + private void updateTitle(long date) {//将标题时间同步更新 + int flag = + DateUtils.FORMAT_SHOW_YEAR | + DateUtils.FORMAT_SHOW_DATE | + DateUtils.FORMAT_SHOW_TIME;//通过DataUtils按照24时制显示 + flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_24HOUR;/判断是否为24小时 + setTitle(DateUtils.formatDateTime(this.getContext(), date, flag));//设置标题 + } + + public void onClick(DialogInterface arg0, int arg1) {//当用户点击的时候进行时间的设置 + if (mOnDateTimeSetListener != null) { + mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis());//设置日期时间 + } + } + +} \ No newline at end of file diff --git a/doc/吴林静代码精读部分/DropdownMenu.txt b/doc/吴林静代码精读部分/DropdownMenu.txt new file mode 100644 index 0000000..f23c720 --- /dev/null +++ b/doc/吴林静代码精读部分/DropdownMenu.txt @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui;//这个类包含在 net.micode.notes.ui包里 +//导入各种必要的类 +import android.content.Context; +import android.view.Menu; +import android.view.MenuItem;//菜单条款 +import android.view.View; +import android.view.View.OnClickListener;//监听点击 +import android.widget.Button; +import android.widget.PopupMenu; +import android.widget.PopupMenu.OnMenuItemClickListener; + +import net.micode.notes.R;//继续引用工具包 + +public class DropdownMenu {//声明一个下拉菜单 + private Button mButton;//定义按钮控件 + private PopupMenu mPopupMenu;//定义弹出式菜单 + private Menu mMenu;//定义菜单 + + public DropdownMenu(Context context, Button button, int menuId) {//构造函数 + mButton = button;//设置button的样式 + mButton.setBackgroundResource(R.drawable.dropdown_icon);//设置其背景资源为下拉菜单的图标 + mPopupMenu = new PopupMenu(context, mButton);//初始化下拉菜单由mButton触发 + mMenu = mPopupMenu.getMenu(); //根据ID来确认menu的内容选项 + mPopupMenu.getMenuInflater().inflate(menuId, mMenu);//使用菜单项填充弹出式菜单的视图 + mButton.setOnClickListener(new OnClickListener() {//点击按钮控件对象mButton时,弹出显示菜单 + public void onClick(View v) { + mPopupMenu.show();//展示下拉的菜单 + } + }); + } + + public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) {//设置下拉菜单的点击监听方法 + if (mPopupMenu != null) {//对非空的菜单的具体条目设置监听 + mPopupMenu.setOnMenuItemClickListener(listener); + } + } + + public MenuItem findItem(int id) {//根据id获取菜单所需的选项 + return mMenu.findItem(id); + } + + public void setTitle(CharSequence title) {//根据索引搜索菜单需要的选项 + mButton.setText(title); + } +} diff --git a/doc/吴林静代码精读部分/FoldersListAdapter.txt b/doc/吴林静代码精读部分/FoldersListAdapter.txt new file mode 100644 index 0000000..6d8a3b6 --- /dev/null +++ b/doc/吴林静代码精读部分/FoldersListAdapter.txt @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui;//这个类包含在 net.micode.notes.ui包里 +//导入必要的类 +import android.content.Context; +import android.database.Cursor;//光标 +import android.view.View; +import android.view.ViewGroup; +import android.widget.CursorAdapter; +import android.widget.LinearLayout; +import android.widget.TextView;//文本视图 + +import net.micode.notes.R; +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.NoteColumns;//便签栏 + + +public class FoldersListAdapter extends CursorAdapter {//文件夹列表适配器,用于显示文件夹列表的视图 + public static final String [] PROJECTION = {//调用数据库中便签的ID和片段 + NoteColumns.ID, + NoteColumns.SNIPPET + }; + + public static final int ID_COLUMN = 0;//定义常量,文件夹名称栏编号 + public static final int NAME_COLUMN = 1;//定义常量,文件夹名称 + + public FoldersListAdapter(Context context, Cursor c) {//FoldersListAdapter类调用了父类CursorAdpater的构造函数 + super(context, c); + // TODO Auto-generated constructor stub + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) {//创建一个文件夹,对于各文件夹中子标签的初始化 + return new FolderListItem(context); + } + + @Override + public void bindView(View view, Context context, Cursor cursor) {//将各个布局文件绑定起来,形成一个整体 + if (view instanceof FolderListItem) {//判断视图是否是文件夹列表的一项 + 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); + } + } + + public String getFolderName(Context context, int position) {//根据数据库中标签的ID得到标签的各项内容 + 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);//选定当前文件夹 + } + + private class FolderListItem extends LinearLayout {//设置文件夹项目列表的名称 + private TextView mName; + + public FolderListItem(Context context) {//文件夹项的构造函数 + super(context); + inflate(context, R.layout.folder_list_item, this);//将一个xml中定义的布局找出来 + mName = (TextView) findViewById(R.id.tv_folder_name); + } + + public void bind(String name) {//绑定,设置文件标题 + mName.setText(name); + } + } + +} diff --git a/doc/吴林静代码精读部分/NoteEditActivity.txt b/doc/吴林静代码精读部分/NoteEditActivity.txt new file mode 100644 index 0000000..f79ebba --- /dev/null +++ b/doc/吴林静代码精读部分/NoteEditActivity.txt @@ -0,0 +1,873 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui;//这个类在net.micode.notes.ui包里面 +//导入各种必要的类 +import android.app.Activity; +import android.app.AlarmManager;// 安卓系统应用开发 +import android.app.AlertDialog; +import android.app.PendingIntent; +import android.app.SearchManager;//搜索管理器 +import android.appwidget.AppWidgetManager; +import android.content.ContentUris; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.graphics.Paint; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.TextUtils; +import android.text.format.DateUtils; +import android.text.style.BackgroundColorSpan; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.WindowManager; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.CompoundButton.OnCheckedChangeListener;//资源解析器 +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; + +import net.micode.notes.R; +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.TextNote; +import net.micode.notes.model.WorkingNote; +import net.micode.notes.model.WorkingNote.NoteSettingChangedListener; +import net.micode.notes.tool.DataUtils; +import net.micode.notes.tool.ResourceParser; +import net.micode.notes.tool.ResourceParser.TextAppearanceResources; +import net.micode.notes.ui.DateTimePickerDialog.OnDateTimeSetListener; +import net.micode.notes.ui.NoteEditText.OnTextViewChangeListener; +import net.micode.notes.widget.NoteWidgetProvider_2x; +import net.micode.notes.widget.NoteWidgetProvider_4x; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + +public class NoteEditActivity extends Activity implements OnClickListener, + NoteSettingChangedListener, OnTextViewChangeListener {//继承Activity,实现了便签的编辑功能,同时对三个监听接口的方法进行实现 + private class HeadViewHolder {//用于对界面进行设置,包括文本内容,图片内容等,此处是编辑界面的标题栏部分 + public TextView tvModified; + + public ImageView ivAlertIcon;//设置提醒的图标 + + public TextView tvAlertDate;//设置提醒的时间 + + public ImageView ibSetBgColor;//设置背景颜色 + } + + private static final Map sBgSelectorBtnsMap = new HashMap();//利用map进行数据存储 + static {//对HashMap进行初始化,主要是颜色按键与执行操作的对应 + sBgSelectorBtnsMap.put(R.id.iv_bg_yellow, ResourceParser.YELLOW);//黄色 + sBgSelectorBtnsMap.put(R.id.iv_bg_red, ResourceParser.RED);//红色 + sBgSelectorBtnsMap.put(R.id.iv_bg_blue, ResourceParser.BLUE);//蓝色 + sBgSelectorBtnsMap.put(R.id.iv_bg_green, ResourceParser.GREEN);//绿色 + sBgSelectorBtnsMap.put(R.id.iv_bg_white, ResourceParser.WHITE);//白色 + } + + private static final Map sBgSelectorSelectionMap = new HashMap();//运用Hashmap,将资源解析器中的对应颜色与已选择的颜色按钮关联起来 + static { + sBgSelectorSelectionMap.put(ResourceParser.YELLOW, R.id.iv_bg_yellow_select); + sBgSelectorSelectionMap.put(ResourceParser.RED, R.id.iv_bg_red_select); + sBgSelectorSelectionMap.put(ResourceParser.BLUE, R.id.iv_bg_blue_select); + sBgSelectorSelectionMap.put(ResourceParser.GREEN, R.id.iv_bg_green_select); + sBgSelectorSelectionMap.put(ResourceParser.WHITE, R.id.iv_bg_white_select); + } + + private static final Map sFontSizeBtnsMap = new HashMap();//提供各种大小字体的选择 + static { + sFontSizeBtnsMap.put(R.id.ll_font_large, ResourceParser.TEXT_LARGE);//大 + sFontSizeBtnsMap.put(R.id.ll_font_small, ResourceParser.TEXT_SMALL);//小 + sFontSizeBtnsMap.put(R.id.ll_font_normal, ResourceParser.TEXT_MEDIUM);//正常 + sFontSizeBtnsMap.put(R.id.ll_font_super, ResourceParser.TEXT_SUPER);//超大 + } + + private static final Map sFontSelectorSelectionMap = new HashMap();//实现资源解析器中字号ID与字体大小按钮(已选择)对应 + static { + sFontSelectorSelectionMap.put(ResourceParser.TEXT_LARGE, R.id.iv_large_select); + sFontSelectorSelectionMap.put(ResourceParser.TEXT_SMALL, R.id.iv_small_select); + sFontSelectorSelectionMap.put(ResourceParser.TEXT_MEDIUM, R.id.iv_medium_select); + sFontSelectorSelectionMap.put(ResourceParser.TEXT_SUPER, R.id.iv_super_select); + } + + private static final String TAG = "NoteEditActivity";//tag定为NoteEditActivity + + private HeadViewHolder mNoteHeaderHolder;//标题头的视图控制器 + + private View mHeadViewPanel;//对背景颜色的操作 + + private View mNoteBgColorSelector;//便签的背景色选择器 + + private View mFontSizeSelector;//便签的字体大小选择器 + + private EditText mNoteEditor;//便签编辑器 + + private View mNoteEditorPanel;//文本编辑控制板 + + private WorkingNote mWorkingNote;//活动便签 + + private SharedPreferences mSharedPrefs;//用来保存配置参数 + private int mFontSizeId;//用于操作字体的大小 + + private static final String PREFERENCE_FONT_SIZE = "pref_font_size";//字体大小设置 + + private static final int SHORTCUT_ICON_TITLE_MAX_LEN = 10;//图标标题的最大长度为10 + + public static final String TAG_CHECKED = String.valueOf('\u221A');//表明已标记 + public static final String TAG_UNCHECKED = String.valueOf('\u25A1');//表明未标记 + + private LinearLayout mEditTextList;//线性布局,用于编辑文本 + + private String mUserQuery;//用户请求 + private Pattern mPattern;//正则表达式模式 + + @Override + protected void onCreate(Bundle savedInstanceState) {//调用父类方法创建主界面 + super.onCreate(savedInstanceState); + this.setContentView(R.layout.note_edit);//设置视图 + + if (savedInstanceState == null && !initActivityState(getIntent())) {//如果未保存实例化状态且初始化活动状态未成功,就结束这个活动 + finish(); + return; + } + initResources();//初始化资源 + } + + /** + * 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 + */ + @Override + protected void onRestoreInstanceState(Bundle savedInstanceState) {//为防止内存不足或其他情况时程序的终止,一个保存现场的函数 + super.onRestoreInstanceState(savedInstanceState); + if (savedInstanceState != null && savedInstanceState.containsKey(Intent.EXTRA_UID)) {//如果保存了关闭前的实例状态且其中包含着便签标识符,则将该标识符添加到初始化Activity的Intent中 + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.putExtra(Intent.EXTRA_UID, savedInstanceState.getLong(Intent.EXTRA_UID));//利用此前的extra_uid初始化一个intent + if (!initActivityState(intent)) {//初始化活动状态失败,则结束 + finish(); + return; + } + Log.d(TAG, "Restoring from killed activity");//记录操作信息到日志中 + } + } + + private boolean initActivityState(Intent intent) {//初始化系统的状态 + /** + * If the user specified the {@link Intent#ACTION_VIEW} but not provided with id, + * then jump to the NotesListActivity + */ + mWorkingNote = null;//初始化便签数据 + if (TextUtils.equals(Intent.ACTION_VIEW, intent.getAction())) { + long noteId = intent.getLongExtra(Intent.EXTRA_UID, 0);//获取intent中的附加信息(字符串)EXTRA_UID标识noteId,默认值为0 + mUserQuery = "";//初始化用户请求为空字符串 + + /** + * Starting from the searched result + */ + if (intent.hasExtra(SearchManager.EXTRA_DATA_KEY)) { + noteId = Long.parseLong(intent.getStringExtra(SearchManager.EXTRA_DATA_KEY));//获取便签id + mUserQuery = intent.getStringExtra(SearchManager.USER_QUERY);//将用户请求字符串赋值给mUserQuery + } + + if (!DataUtils.visibleInNoteDatabase(getContentResolver(), noteId, Notes.TYPE_NOTE)) {//如果ID在数据库中未找到 + Intent jump = new Intent(this, NotesListActivity.class);//新建Intent,设置跳转的目标为NoteListActivity + startActivity(jump);//跳转 + showToast(R.string.error_note_not_exist);// 显示消息提示框,消息为便签不存在 + finish(); + return false; + } else {//如果找到了便签的标识符,就将对应标签加载到mWorkingNote中 + 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);//调节Edit界面,使之适应软键盘弹出 + } else if(TextUtils.equals(Intent.ACTION_INSERT_OR_EDIT, intent.getAction())) { + // New note,如果传入的intent属性为ACTION_INSERT_OR_EDIT,说明需要新建一个便签 + long folderId = intent.getLongExtra(Notes.INTENT_EXTRA_FOLDER_ID, 0);//获取intent中传入的父文件夹id,默认为0 + int widgetId = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_ID, + AppWidgetManager.INVALID_APPWIDGET_ID);//获取布局文件id,默认返回参数为无效参数 + int widgetType = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, + Notes.TYPE_WIDGET_INVALIDE);//获取布局文件类型,默认为无效参数 + int bgResId = intent.getIntExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, + ResourceParser.getDefaultBgId(this));//获取背景颜色资源id,默认为随机背景色 + + // Parse call-record note,解析电话簿记录 + String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER); + long callDate = intent.getLongExtra(Notes.INTENT_EXTRA_CALL_DATE, 0);//获取提醒时间,默认为0 + if (callDate != 0 && phoneNumber != null) {//如果便签的通话日期和通话号码均存在,则把其当做通话便签进行处理,否则当做普通便签进行初始化 + if (TextUtils.isEmpty(phoneNumber)) {//若电话号码为空,则输出警告信息 + Log.w(TAG, "The call record number is null"); + } + long noteId = 0; + if ((noteId = DataUtils.getNoteIdByPhoneNumberAndCallDate(getContentResolver(), + phoneNumber, callDate)) > 0) {//根据电话号码和日期找到对应标识符,赋给noteId + mWorkingNote = WorkingNote.load(this, noteId);//导入这个便签 + if (mWorkingNote == null) {//noteId对应的内容为空,输出报错信息 + Log.e(TAG, "load call note failed with note id" + noteId); + finish(); + return false; + } + } else { + mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, + widgetType, bgResId);//重新建立空便签,使用之前默认参量对便签进行初始化 + mWorkingNote.convertToCallNote(phoneNumber, callDate);//根据电话号码和通话日期转化为通话便签 + } + } else {//否则按照普通便签进行创建 + mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, widgetType, + bgResId); + } + + getWindow().setSoftInputMode(//软键盘输入设置 + WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE + | WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); + } else {//打印错误信息 + Log.e(TAG, "Intent not specified action, should not support"); + finish(); + return false; + } + mWorkingNote.setOnSettingStatusChangedListener(this);//设置状态改变监听器 + return true; + } + + @Override + protected void onResume() {//调用父类onRume函数,初始便签界面 + super.onResume(); + initNoteScreen(); + } + + 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));//设置查找到的高亮文本 + 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 + | DateUtils.FORMAT_SHOW_YEAR));/在便签顶部显示时间 + + /** + * TODO: Add the menu for setting alert. Currently disable it because the DateTimePicker + * is not ready + */ + showAlertHeader();//显示便签头部的时间和图标 + } + + private void showAlertHeader() {设置闹钟的显示 + if (mWorkingNote.hasClockAlert()) {//判断当前便签是否设置了闹钟 + long time = System.currentTimeMillis();//获取当前时间 + if (time > mWorkingNote.getAlertDate()) {//如果系统时间大于了闹钟设置的时间,那么闹钟失效 + mNoteHeaderHolder.tvAlertDate.setText(R.string.note_alert_expired); + } else {//如果提醒时间未到,则设置标题中显示提醒时间 + mNoteHeaderHolder.tvAlertDate.setText(DateUtils.getRelativeTimeSpanString( + mWorkingNote.getAlertDate(), time, DateUtils.MINUTE_IN_MILLIS)); + } + mNoteHeaderHolder.tvAlertDate.setVisibility(View.VISIBLE);//显示闹钟开启的图标 + mNoteHeaderHolder.ivAlertIcon.setVisibility(View.VISIBLE);//提醒时间图标显示可见 + } else {//如果没有设置闹钟就把闹钟相关的控件设置为不可见 + mNoteHeaderHolder.tvAlertDate.setVisibility(View.GONE); + mNoteHeaderHolder.ivAlertIcon.setVisibility(View.GONE); + }; + } + + @Override + protected void onNewIntent(Intent intent) {//新建一个传值对象 + super.onNewIntent(intent);//初始化活动状态 + initActivityState(intent); + } + + @Override + protected void onSaveInstanceState(Bundle outState) {//用于保存数据 + super.onSaveInstanceState(outState); + /** + * For new note without note id, we should firstly save it to + * generate a id. If the editing note is not worth saving, there + * is no id which is equivalent to create new note + */ + if (!mWorkingNote.existInDatabase()) {//没有就储存在数据库 + saveNote(); + } + outState.putLong(Intent.EXTRA_UID, mWorkingNote.getNoteId());//将生成的noteId保存到outState中 + Log.d(TAG, "Save working note id: " + mWorkingNote.getNoteId() + " onSaveInstanceState");//保存操作信息到日志中 + } + + @Override + public boolean dispatchTouchEvent(MotionEvent ev) {//对屏幕触控事件的判断 + if (mNoteBgColorSelector.getVisibility() == View.VISIBLE + && !inRangeOfView(mNoteBgColorSelector, ev)) {//判断如果背景颜色选择界面可见且触摸范围在背景范围外则设置选择背景界面为不可见 + mNoteBgColorSelector.setVisibility(View.GONE); + return true; + } + + if (mFontSizeSelector.getVisibility() == View.VISIBLE + && !inRangeOfView(mFontSizeSelector, ev)) {//设置字体选择界面不可见 + mFontSizeSelector.setVisibility(View.GONE); + return true; + } + return super.dispatchTouchEvent(ev); + } + + private boolean inRangeOfView(View view, MotionEvent ev) {//对屏幕触控的坐标进行操作 + int []location = new int[2];//声明location数组,用于存放屏幕位置的横纵坐标 + view.getLocationOnScreen(location);//获取屏幕上的起点位置 + int x = location[0]; + int y = location[1]; + if (ev.getX() < x + || ev.getX() > (x + view.getWidth()) + || ev.getY() < y + || ev.getY() > (y + view.getHeight())) { + return false; + }//如果触控的位置超出了给定的范围,返回false + return true; + } + + private void initResources() {//对标签各项属性内容的初始化 + mHeadViewPanel = findViewById(R.id.note_title);//初始化便签标题栏 + mNoteHeaderHolder = new HeadViewHolder();//创建HeadViewHolder()容器,并得到日期,图标,提醒日期,背景颜色的信息 + mNoteHeaderHolder.tvModified = (TextView) findViewById(R.id.tv_modified_date); + mNoteHeaderHolder.ivAlertIcon = (ImageView) findViewById(R.id.iv_alert_icon); + mNoteHeaderHolder.tvAlertDate = (TextView) findViewById(R.id.tv_alert_date); + mNoteHeaderHolder.ibSetBgColor = (ImageView) findViewById(R.id.btn_set_bg_color); + mNoteHeaderHolder.ibSetBgColor.setOnClickListener(this);//设置监听器 + mNoteEditor = (EditText) findViewById(R.id.note_edit_view);//得到文本编辑框 + mNoteEditorPanel = findViewById(R.id.sv_note_edit);//文件编辑面板 + mNoteBgColorSelector = findViewById(R.id.note_bg_color_selector);//设置背景颜色的选择器 + for (int id : sBgSelectorBtnsMap.keySet()) {//对于每一个背景颜色的选择按钮都设置监听器 + ImageView iv = (ImageView) findViewById(id); + iv.setOnClickListener(this);/设置点击事件监听器 + } + + mFontSizeSelector = findViewById(R.id.font_size_selector);//初始化便签文字字号选择器 + for (int id : sFontSizeBtnsMap.keySet()) {//对于每一个字体大小的选择按钮都设置监听器 + View view = findViewById(id); + view.setOnClickListener(this); + }; + mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); + 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} + */ + if(mFontSizeId >= TextAppearanceResources.getResourcesSize()) {//如果获取到的字体大小值的长度大于资源文件应有长度,则将其设置为默认长度,缺省值为1 + mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE; + } + mEditTextList = (LinearLayout) findViewById(R.id.note_edit_list);//获取标签编辑的列表 + } + + @Override + protected void onPause() {//重写onPause方法,将便签编辑界面暂停 + super.onPause(); + if(saveNote()) {//保存便签 + Log.d(TAG, "Note data was saved with length:" + mWorkingNote.getContent().length()); + }//暂停时即进行便签的存储 + clearSettingState();//清除状态设置 + } + + private void updateWidget() {//将便签同步到挂件 + Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);//新建一个包含改变widget动作的intent实例 + if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_2X) {//根据workingnote实例中存储的widget大小,设置intent中相应连接的类 + intent.setClass(this, NoteWidgetProvider_2x.class); + } else if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_4X) { + intent.setClass(this, NoteWidgetProvider_4x.class); + } else { + Log.e(TAG, "Unspported widget type");//输出error信息 + return; + } + + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] {//将桌面小部件的标识符放入intent中 + mWorkingNote.getWidgetId() + }); + + sendBroadcast(intent);//将指定好参数的Intent对象通过广播发送出去以启动相应的Activity + setResult(RESULT_OK, intent);//设置当前Activity结束后将结束信息发送给其父活动 + } + + public void onClick(View v) {//当点击事件发生时执行该方法 + int id = v.getId();//获取点击目标的id + if (id == R.id.btn_set_bg_color) {//如果点击的是设置背景颜色的按钮 + mNoteBgColorSelector.setVisibility(View.VISIBLE); + findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( + - View.VISIBLE);//背景颜色选择器设置为可见 + } else if (sBgSelectorBtnsMap.containsKey(id)) {//如果选择了颜色 + findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( + View.GONE);//背景颜色选择器设置为不可见 + mWorkingNote.setBgColorId(sBgSelectorBtnsMap.get(id));//修改颜色 + mNoteBgColorSelector.setVisibility(View.GONE);//将背景颜色选择器隐藏 + } else if (sFontSizeBtnsMap.containsKey(id)) {//如果点击的是文字大小设置器中的文字大小选项按钮 + findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.GONE);//将备选字体界面设为不可见 + mFontSizeId = sFontSizeBtnsMap.get(id);//设置字体为选中尺寸 + 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));//如果不是check模式,则设置文字编辑器中文字为相应字体大小 + } + mFontSizeSelector.setVisibility(View.GONE);//将文字大小选择器设置为不可见 + } + } + + @Override + public void onBackPressed() {//点击返回键时的操作 + if(clearSettingState()) {//如果无界面打开,则直接返回,否则关闭再返回 + return; + } + + saveNote();//保存便签 + super.onBackPressed(); + } + + 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());//将便签头的背景资源设置为标题的背景色 + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) {//对选择菜单的准备 + if (isFinishing()) {//如果窗口正在关闭,则不做处理 + return true; + } + clearSettingState();//清除设置状态 + menu.clear();//清除菜单项 + if (mWorkingNote.getFolderId() == Notes.ID_CALL_RECORD_FOLDER) {//如果是通话记录文件夹 + getMenuInflater().inflate(R.menu.call_note_edit, menu);//实例化Menu目录下的Menu布局文件 + } else {//其他 + getMenuInflater().inflate(R.menu.note_edit, menu); + } + if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {//如果workingNote模式为核对列表模式 + 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 { + menu.findItem(R.id.menu_delete_remind).setVisible(false); + } + return true; + } + + @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);//设置图标 + builder.setMessage(getString(R.string.alert_message_delete_note));//设置内容 + builder.setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() {//设置一个确定按键和监听器 + public void onClick(DialogInterface dialog, int which) {//点击所触发事件 + deleteCurrentNote();//删除当前的便签 + finish(); + } + }); + builder.setNegativeButton(android.R.string.cancel, null);//设置一个取消按键和监听器 + builder.show(); + break; + case R.id.menu_font_size://菜单字体大小的设置 + mFontSizeSelector.setVisibility(View.VISIBLE);//将字体的选择器置为可见 + findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE);//通过id找到相应字体的大小 + 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://选择删除提醒 + mWorkingNote.setAlertDate(0, false); + break; + default: + break; + } + return true; + } + + 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); + } + }); + d.show();//显示该控件 + } + + /** + * Share note to apps that support {@link Intent#ACTION_SEND} action + * and {@text/plain} type + */ + private void sendTo(Context context, String info) {//分享功能 + Intent intent = new Intent(Intent.ACTION_SEND);//新建发送活动的传值对象 + intent.putExtra(Intent.EXTRA_TEXT, info);//将需要传递的便签信息放入text文件中 + intent.setType("text/plain");//设置发送内容的类型 + context.startActivity(intent);//开始活动 + } + + 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());//将文件夹id附加到intent里 + startActivity(intent);//开始activity并链接 + } + + private void deleteCurrentNote() {//删除当前的便签 + if (mWorkingNote.existInDatabase()) {//先判断便签是否在数据库中 + HashSet ids = new HashSet(); + long id = mWorkingNote.getNoteId();//获得当前便签的ID + if (id != Notes.ID_ROOT_FOLDER) {//如果不是头文件夹建立一个hash表把便签id存起来 + ids.add(id); + } else {//否则输出错误 + Log.d(TAG, "Wrong note id, should not happen"); + } + if (!isSyncMode()) {//在非同步模式情况下 + if (!DataUtils.batchDeleteNotes(getContentResolver(), ids)) { + Log.e(TAG, "Delete Note error"); + } + } else {//将便签放入回收站 + if (!DataUtils.batchMoveToFolder(getContentResolver(), ids, Notes.ID_TRASH_FOLER)) { + Log.e(TAG, "Move notes to trash folder error, should not happens"); + } + } + } + mWorkingNote.markDeleted(true);//将删除的便签标记为true + } + + private boolean isSyncMode() {//判断是否为同步模式 + return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0; + }//返回同步的用户名长度,如果大于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) {//若当前编辑的便签的id>0,则执行下面函数 + Intent intent = new Intent(this, AlarmReceiver.class);//新建一个intent,指向活动AlarmReceiver + intent.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mWorkingNote.getNoteId()));//若有运行的便签就将便签的ID放置在URI中去 + PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0);//将intent设置为延时广播 + AlarmManager alarmManager = ((AlarmManager) getSystemService(ALARM_SERVICE));//新建一个闹钟管理器,获取系统的闹钟服务 + showAlertHeader();//设置提醒管理器 + if(!set) {//取消闹钟 + alarmManager.cancel(pendingIntent); + } else {//设置闹钟 + alarmManager.set(AlarmManager.RTC_WAKEUP, date, pendingIntent); + } + } else {//如果没有设置就报错 + /** + * There is the condition that user has input nothing (the note is + * not worthy saving), we have no note id, remind the user that he + * should input something + */ + Log.e(TAG, "Clock alert setting error"); + showToast(R.string.error_note_empty_for_clock);//显示消息提示框,为未编辑的便签设置闹钟 + } + } + + public void onWidgetChanged() {//Widget发生改变的所触发的事件 + updateWidget(); + } + + public void onEditTextDelete(int index, String text) {//删除编辑文本框时的操作 + int childCount = mEditTextList.getChildCount();//获取EditText列表里EditText项的数量 + if (childCount == 1) { + return; + } + + for (int i = index + 1; i < childCount; i++) {//将index后面的每一个项覆盖前面的项,即将index的项删除,后面补上去 + ((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text)) + .setIndex(i - 1); + } + + mEditTextList.removeViewAt(index);//移除特定位置的视图 + NoteEditText edit = null; + if(index == 0) {//如果index为0,则将edit定位到第一个EditText项 + edit = (NoteEditText) mEditTextList.getChildAt(0).findViewById( + R.id.et_edit_text); + } else {//编辑上一行 + edit = (NoteEditText) mEditTextList.getChildAt(index - 1).findViewById( + R.id.et_edit_text); + } + int length = edit.length();//获取当前项的长度 + edit.append(text);//在当前位置后附加文本 + edit.requestFocus();//获取焦点 + edit.setSelection(length);//定位到length位置处的条目 + } + + public void onEditTextEnter(int index, String text) {//进入编辑文本框所触发的事件 + /** + * Should not happen, check for debug + */ + if(index > mEditTextList.getChildCount()) {//如果越界就报错 + Log.e(TAG, "Index out of mEditTextList boundrary, should not happen"); + } + + View view = getListItem(text, index);//获得列表项 + mEditTextList.addView(view, index);//添加视图 + NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text);//建立一个新的视图并添加到编辑文本框内 + edit.requestFocus();//获取焦点 + edit.setSelection(0);//定位到起始位置 + for (int i = index + 1; i < mEditTextList.getChildCount(); i++) {//遍历子文本框并设置对应对下标 + ((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text)) + .setIndex(i); + } + } + + private void switchToListMode(String text) {//切换到清单模式 + mEditTextList.removeAllViews();//移除所有子视图 + String[] items = text.split("\n");//根据换行符分割字符串 + int index = 0;//初始化下标 + for (String item : items) {//遍历所有文本单元 + if(!TextUtils.isEmpty(item)) {//如果item中的文本不为空 + mEditTextList.addView(getListItem(item, index));//向EditTextList中添加该字符串 + index++; + } + } + mEditTextList.addView(getListItem("", index));//最后加入一空条目 + 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 spannable = new SpannableString(fullText == null ? "" : fullText);//新建一个效果选项 + if (!TextUtils.isEmpty(userQuery)) {//如果搜索结果不为空 + mPattern = Pattern.compile(userQuery);//为搜索条件设置正则表达式 + Matcher m = mPattern.matcher(fullText);//建立一个状态机检查Pattern并进行匹配 + int start = 0; + while (m.find(start)) {//将匹配到的部分显示为高亮 + spannable.setSpan(//将目标设置为高亮 + new BackgroundColorSpan(this.getResources().getColor( + R.color.user_query_highlight)), m.start(), m.end(), + Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + start = m.end();//更新起始位置 + } + } + return spannable; + } + + private View getListItem(String item, int index) {//获取列表项 + View view = LayoutInflater.from(this).inflate(R.layout.note_edit_list_item, null);//创建一个视图 + final NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text);//获得编辑文本 + edit.setTextAppearance(this, TextAppearanceResources.getTexAppearanceResource(mFontSizeId));//创建一个文本编辑框并置为可见 + CheckBox cb = ((CheckBox) view.findViewById(R.id.cb_edit_item));//建立一个打钩框并设置监听器 + cb.setOnCheckedChangeListener(new OnCheckedChangeListener() {//设置监听器 + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (isChecked) {//如果已经被标记 + edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); + } else { + edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG); + } + } + }); + + if (item.startsWith(TAG_CHECKED)) {//判断是否勾选 + cb.setChecked(true); + edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); + item = item.substring(TAG_CHECKED.length(), item.length()).trim();//去掉TAG_CHECKED和空格 + } else if (item.startsWith(TAG_UNCHECKED)) {//如果未勾选 + cb.setChecked(false); + edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG); + item = item.substring(TAG_UNCHECKED.length(), item.length()).trim(); + } + + edit.setOnTextViewChangeListener(this);//为edit设置监听文本视图改变的监听器 + edit.setIndex(index);//运行编辑框的监听器对该行为作出反应,并设置下标及文本内容 + 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 { + 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;//初始化check标记 + if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {//判断是否是清单模式 + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < mEditTextList.getChildCount(); i++) {//遍历所有子编辑框的视图 + View view = mEditTextList.getChildAt(i); + 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");//扩展字符串为已打钩并把标记置true + hasChecked = true; + } else { + sb.append(TAG_UNCHECKED).append(" ").append(edit.getText()).append("\n");//字符串设置为未勾选 + } + } + } + mWorkingNote.setWorkingText(sb.toString());//利用编辑好的字符串设置运行便签的内容 + } else {//若不是该模式直接用编辑器中的内容设置运行中标签的内容 + mWorkingNote.setWorkingText(mNoteEditor.getText().toString()); + } + return hasChecked; + } + + private boolean saveNote() {//保存便签 + getWorkingText();//获取到当前编辑便签的文本 + boolean saved = mWorkingNote.saveNote(); + if (saved) {//如果workingNote已经保存,则将结果设置为ok + /** + * There are two modes from List view to edit view, open one note, + * create/edit a node. Opening node requires to the original + * position in the list when back from edit view, while creating a + * new node requires to the top of the list. This code + * {@link #RESULT_OK} is used to identify the create/edit state + */ + setResult(RESULT_OK); + } + return saved; + } + + private void sendToDesktop() {//将便签发送至桌面 + /** + * Before send message to home, we should make sure that current + * editing note is exists in databases. So, for new note, firstly + * save it + */ + if (!mWorkingNote.existInDatabase()) {//若不存在于数据库,就保存 + saveNote(); + } + + if (mWorkingNote.getNoteId() > 0) {//若是有内容 + Intent sender = new Intent();//建立一个连接器 + Intent shortcutIntent = new Intent(this, NoteEditActivity.class);//建立一个连接器 + shortcutIntent.setAction(Intent.ACTION_VIEW);//链接为一个视图 + shortcutIntent.putExtra(Intent.EXTRA_UID, mWorkingNote.getNoteId());//快捷方式的名称 + sender.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);//设置快捷访问的Intent,将其保存至sender中,以便直接快速调用NoteEditActivity + sender.putExtra(Intent.EXTRA_SHORTCUT_NAME, + makeShortcutIconTitle(mWorkingNote.getContent()));//设置图标 + sender.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, + Intent.ShortcutIconResource.fromContext(this, R.drawable.icon_app));//将便签的相关信息都添加到要发送的文件里 + sender.putExtra("duplicate", true); + sender.setAction("com.android.launcher.action.INSTALL_SHORTCUT");//设置sneder的行为是发送 + showToast(R.string.info_note_enter_desktop);//显示消息提示框,表示文本已发送至桌面 + sendBroadcast(sender); + } else { + /** + * There is the condition that user has input nothing (the note is + * not worthy saving), we have no note id, remind the user that he + * should input something + */ + Log.e(TAG, "Send to desktop error");//空便签直接报错 + showToast(R.string.error_note_empty_for_send_to_desktop); + } + } + + private String makeShortcutIconTitle(String content) {//编辑小图标的标题 + content = content.replace(TAG_CHECKED, "");//去掉勾选标记 + content = content.replace(TAG_UNCHECKED, ""); + 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();//设置toast消息的文本内容 + } +} diff --git a/doc/吴林静代码精读部分/NoteEditText.txt b/doc/吴林静代码精读部分/NoteEditText.txt new file mode 100644 index 0000000..3d2cdb4 --- /dev/null +++ b/doc/吴林静代码精读部分/NoteEditText.txt @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui;//这个类在net.micode.notes.ui包里面 +//引入必要的类 +import android.content.Context; +import android.graphics.Rect; +import android.text.Layout; +import android.text.Selection; +import android.text.Spanned; +import android.text.TextUtils; +import android.text.style.URLSpan; +import android.util.AttributeSet; +import android.util.Log; +import android.view.ContextMenu; +import android.view.KeyEvent; +import android.view.MenuItem; +import android.view.MenuItem.OnMenuItemClickListener; +import android.view.MotionEvent; +import android.widget.EditText;//编辑文本 + +import net.micode.notes.R; + +import java.util.HashMap; +import java.util.Map; + +public class NoteEditText extends EditText {//继承edittext,设置便签设置文本框 + private static final String TAG = "NoteEditText";//定义常量标志为"NoteEditText" + private int mIndex; + private int mSelectionStartBeforeDelete;//声明整型变量,获取删除文本前的位置 + + private static final String SCHEME_TEL = "tel:" ;//声明字符串常量,标志电话 + private static final String SCHEME_HTTP = "http:" ;//声明字符串常量,标志网址 + private static final String SCHEME_EMAIL = "mailto:" ;声明字符串常量,标志邮件 + + private static final Map sSchemaActionResMap = new HashMap();//建立一个字符和整数的hash表,用于链接电话,网站,邮箱 + static {//这个接口将会被NoteEditActivity实现来删除或添加编辑文本 + sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel); + sSchemaActionResMap.put(SCHEME_HTTP, R.string.note_link_web); + sSchemaActionResMap.put(SCHEME_EMAIL, R.string.note_link_email); + } + + /** + * Call by the {@link NoteEditActivity} to delete or add edit text + */ + public interface OnTextViewChangeListener {//该接口用于实现对TextView组件中的文字信息进行修改 + /** + * Delete current edit text when {@link KeyEvent#KEYCODE_DEL} happens + * and the text is null + */ + void onEditTextDelete(int index, String text);//当触发删除文本KeyEvent时删除文本 + + /** + * Add edit text after current edit text when {@link KeyEvent#KEYCODE_ENTER} + * happen + */ + void onEditTextEnter(int index, String text);//当触发输入文本KeyEvent时增添文本 + + /** + * Hide or show item option when text change + */ + void onTextChange(int index, boolean hasText);//文字更改时隐藏或显示项目选项 + } + + private OnTextViewChangeListener mOnTextViewChangeListener;//文本是否被改变 + + public NoteEditText(Context context) {//构造函数,直接借用了父类的构造函数 + super(context, null); + mIndex = 0; + } + + public void setIndex(int index) {//初始化文本修改标记 + mIndex = index; + } + + public void setOnTextViewChangeListener(OnTextViewChangeListener listener) {//设置文本视图变化监听器 + mOnTextViewChangeListener = listener; + } + + public NoteEditText(Context context, AttributeSet attrs) {//构造函数,初始化便签 + super(context, attrs, android.R.attr.editTextStyle); + } + + public NoteEditText(Context context, AttributeSet attrs, int defStyle) {//构造函数,自动初始化 + super(context, attrs, defStyle); + // TODO Auto-generated constructor stub + } + + @Override + public boolean onTouchEvent(MotionEvent event) {//触摸屏幕编辑便签时触发 + switch (event.getAction()) {//屏幕触发事件 + case MotionEvent.ACTION_DOWN://获得坐标 + + int x = (int) event.getX();//获取坐标 + int y = (int) event.getY(); + x -= getTotalPaddingLeft();//减去左边控件的距离 + y -= getTotalPaddingTop();//减去上方控件的距离 + x += getScrollX();//加上滚轮滚过的距离 + y += getScrollY();//加上滚轮滚过的距离 + + Layout layout = getLayout();//用布局控件layout根据x,y的新值设置新的位置 + int line = layout.getLineForVertical(y);//获取纵向的行数 + int off = layout.getOffsetForHorizontal(line, x);//获取横向的偏移量 + Selection.setSelection(getText(), off);//更新光标位置 + break; + } + + return super.onTouchEvent(event);//继续调用父类的监听事件方法 + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) {//当用户按下按键瞬间系统的响应 + switch (keyCode) {//根据按键的KeyCode来处理 + case KeyEvent.KEYCODE_ENTER://按下进入按键 + if (mOnTextViewChangeListener != null) { + return false; + } + break; + case KeyEvent.KEYCODE_DEL://按下删除按键 + mSelectionStartBeforeDelete = getSelectionStart();//获取删除文本的开始位置 + break; + default: + break; + } + return super.onKeyDown(keyCode, event);//继续执行父类的其他点击事件 + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) {//当用户松开按键瞬间系统的响应 + switch(keyCode) { + case KeyEvent.KEYCODE_DEL://若触发修改且文档不为空,则调用前面代码的onEditTextDelete函数进行文本删除 + if (mOnTextViewChangeListener != null) { + if (0 == mSelectionStartBeforeDelete && mIndex != 0) { + mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString()); + return true; + } + } else {//其他情况报错,文档的改动监听器并没有建立 + Log.d(TAG, "OnTextViewChangeListener was not seted"); + } + break; + case KeyEvent.KEYCODE_ENTER://若文档改动监听器已建立,则获取当前位置和文本,并根据获取的信息调用onEditTextEnter函数进行文本增添 + if (mOnTextViewChangeListener != null) { + int selectionStart = getSelectionStart(); + String text = getText().subSequence(selectionStart, length()).toString(); + setText(getText().subSequence(0, selectionStart)); + mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text);//添加新文本 + } else { + Log.d(TAG, "OnTextViewChangeListener was not seted"); + } + break; + default: + break; + } + return super.onKeyUp(keyCode, event);//继续执行父类的其他按键弹起的事件 + } + + @Override + protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {//这个函数规定了编辑文本焦点改变时的系统响应 + if (mOnTextViewChangeListener != null) {//若监听器已经建立 + if (!focused && TextUtils.isEmpty(getText())) {//获取到焦点并且文本不为空 + mOnTextViewChangeListener.onTextChange(mIndex, false); + } else { + mOnTextViewChangeListener.onTextChange(mIndex, true);//显示事件选项 + } + } + super.onFocusChanged(focused, direction, previouslyFocusedRect);//继续执行父类的其他焦点变化的事件 + } + + @Override + protected void onCreateContextMenu(ContextMenu menu) {//生成上下文菜单 + if (getText() instanceof Spanned) {//获取高亮元素 + int selStart = getSelectionStart();//获取文本开始位置 + int selEnd = getSelectionEnd();//获取文本结尾位置 + + int min = Math.min(selStart, selEnd);//获取开始到结尾的最小值 + int max = Math.max(selStart, selEnd);//获取开始到结尾的最大值 + + final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class);//针对不同的高亮元素,使用不同的操作进行处理 + if (urls.length == 1) {//设置url的信息的范围值 + int defaultResId = 0; + for(String schema: sSchemaActionResMap.keySet()) {//获取计划表中所有的key值 + if(urls[0].getURL().indexOf(schema) >= 0) {//若url可以添加则在添加后将defaultResId置为key所映射的值 + defaultResId = sSchemaActionResMap.get(schema); + break; + } + } + + if (defaultResId == 0) {//无改变则置为连接其他SchemaActionResMap的值 + defaultResId = R.string.note_link_other; + } + + menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener(//建立菜单 + new OnMenuItemClickListener() {//实例化菜单监听器 + public boolean onMenuItemClick(MenuItem item) {//如果点击菜单执行操作 + // goto a new intent + urls[0].onClick(NoteEditText.this);//根据相应的文本设置菜单的按键 + return true; + } + }); + } + } + super.onCreateContextMenu(menu);//执行父类的创建文本菜单 + } +}