You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
pom7i43xf 6ef7dc9180
Update README.md
5 months ago
README.md Update README.md 5 months ago
src Add src 8 months ago
test1.txt test1 7 months ago

README.md

gitpractice

// Apache许可证协议 package net.micode.notes.ui;

import android.app.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;

public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener { private long mNoteId; private String mSnippet; private static final int SNIPPET_PREW_MAX_LEN = 60; MediaPlayer mPlayer;

@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);
    }

// 在Activity创建时被调用它首先设置Activity的特性然后获取传递过来的Intent从中获取提醒的ID和摘录信息并根据提醒的ID判断提醒是否可以在Note数据库中找到并显示对应的提醒对话框和播放提醒声音。

    Intent intent = getIntent();

    try {
        mNoteId = Long.parseLong(intent.getData().getPathSegments().get(1));
        mSnippet = DataUtils.getSnippetById(this.getContentResolver(), mNoteId);
        mSnippet = mSnippet.length() > SNIPPET_PREW_MAX_LEN ? mSnippet.substring(0,
                SNIPPET_PREW_MAX_LEN) + getResources().getString(R.string.notelist_string_info)
                : mSnippet;
    } catch (IllegalArgumentException e) {
        e.printStackTrace();
        return;
    }

    mPlayer = new MediaPlayer();
    if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) {
        showActionDialog();
        playAlarmSound();
    } else {
        finish();
    }
}

// 以一个try-catch块开始尝试从意图中提取一个笔记ID使用笔记ID从内容提供程序中检索与该笔记相关的文本片段并在文本片段太长时截断它。如果在此过程中抛出IllegalArgumentException异常则捕获该异常并返回方法。 //接下来的代码块创建一个新的MediaPlayer对象并检查与笔记ID关联的笔记是否在笔记数据库中可见。如果可见则调用名为showActionDialog()的方法该方法可能显示一个对话框给用户。还调用了playAlarmSound()方法,该方法可能会播放警报声。如果笔记在数据库中不可见,则方法仅完成而不显示对话框或播放声音。

private boolean isScreenOn() {
    PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
    return pm.isScreenOn();
}

// 用于判断屏幕是否打开。

private void playAlarmSound() {
    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);
        mPlayer.start();
    } catch (IllegalArgumentException | SecurityException | IllegalStateException |
             IOException e) {
        e.printStackTrace();
    }
}

// 用于播放提醒声音首先获取系统默认的闹铃铃声然后判断当前是否为静音模式如果是则设置MediaPlayer的音频流类型为系统当前可影响铃声的流否则设置为闹钟流类型。最后设置铃声数据源、循环播放并开始播放。

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);
}

// 用于显示提醒对话框,其中包含提醒的摘录信息和两个按钮(确认和进入)。

public void onClick(DialogInterface dialog, int which) {
    if (which == DialogInterface.BUTTON_NEGATIVE) {
        Intent intent = new Intent(this, NoteEditActivity.class);
        intent.setAction(Intent.ACTION_VIEW);
        intent.putExtra(Intent.EXTRA_UID, mNoteId);
        startActivity(intent);
    }
}

// 用于处理对话框中按钮的点击事件如果是进入按钮则启动NoteEditActivity并携带提醒的ID将它作为Extra参数传递给NoteEditActivity以便打开对应的Note。如果是确认按钮则什么都不做。

public void onDismiss(DialogInterface dialog) {
    stopAlarmSound();
    finish();
}

// 用于处理提醒对话框关闭事件停止播放提醒声音并结束Activity。

private void stopAlarmSound() {
    if (mPlayer != null) {
        mPlayer.stop();
        mPlayer.release();
        mPlayer = null;
    }
}

} // 用于停止播放提醒声音。

// Apache许可证协议

package 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;

//这里定义了一个包名为net.micode.notes.ui的Java类AlarmInitReceiver它继承了BroadcastReceiver类。引入了Android系统提供的AlarmManager、PendingIntent、BroadcastReceiver、ContentUris等类。还引入了笔记相关的数据模型Notes和NoteColumns。 //PROJECTION是笔记查询时的投影指定了需要查询的列。COLUMN_ID和COLUMN_ALERTED_DATE则是笔记查询结果中对应的列索引。 public class AlarmInitReceiver extends BroadcastReceiver {

private static final String [] PROJECTION = new String [] {
    NoteColumns.ID,
    NoteColumns.ALERTED_DATE
};

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();
    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);

    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)));
                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();
    }
}

// onReceive方法是接收广播后执行的方法。在这里首先获取当前时间戳currentDate然后通过getContentResolver().query方法查询所有需要提醒的笔记查询条件是提醒时间大于当前时间戳并且类型是笔记类型。 } // Apache许可证协议

package net.micode.notes.ui;

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) { intent.setClass(context, AlarmAlertActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); } // onReceive方法中首先获取传入的Intent对象并通过setClass方法将Intent的目标Activity设置为AlarmAlertActivity。然后通过addFlags方法设置Intent的标志位FLAG_ACTIVITY_NEW_TASK表示启动一个新的Task来显示Activity。最后通过context.startActivity方法启动Activity。 } //这段代码的作用是在系统闹钟触发时启动AlarmAlertActivity来显示提醒内容。 // Apache许可证协议

package 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;
private static final int HOURS_IN_ALL_DAY = 24;
private static final int DAYS_IN_ALL_WEEK = 7;
private static final int DATE_SPINNER_MIN_VAL = 0;
private static final int DATE_SPINNER_MAX_VAL = DAYS_IN_ALL_WEEK - 1;
private static final int HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW = 0;
private static final int HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW = 23;
private static final int HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW = 1;
private static final int HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW = 12;
private static final int MINUT_SPINNER_MIN_VAL = 0;
private static final int MINUT_SPINNER_MAX_VAL = 59;
private static final int AMPM_SPINNER_MIN_VAL = 0;
private static final int AMPM_SPINNER_MAX_VAL = 1;

private final NumberPicker mDateSpinner;
private final NumberPicker mHourSpinner;
private final NumberPicker mMinuteSpinner;
private final NumberPicker mAmPmSpinner;
private Calendar mDate;

private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK];

private boolean mIsAm;

private boolean mIs24HourView;

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);//    使用Calendar类的add方法将mDate对象的日期字段即Calendar.DAY_OF_YEAR增加newVal - oldVal天以更新日期。
        updateDateControl();//调用updateDateControl方法该方法用来更新日期控件的显示。
        onDateTimeChanged();//调用onDateTimeChanged方法该方法用来通知其他组件该日期时间已经发生了变化。
    }
};

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) {
            if (!mIsAm && oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) {
                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) {
                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) {
                mIsAm = !mIsAm;
                updateAmPmControl();
            }
        } else {
            if (oldVal == HOURS_IN_ALL_DAY - 1 && newVal == 0) {
                cal.setTimeInMillis(mDate.getTimeInMillis());
                cal.add(Calendar.DAY_OF_YEAR, 1);
                isDateChanged = true;
            } else if (oldVal == 0 && newVal == HOURS_IN_ALL_DAY - 1) {
                cal.setTimeInMillis(mDate.getTimeInMillis());
                cal.add(Calendar.DAY_OF_YEAR, -1);
                isDateChanged = true;
            }
        }

// 根据24小时制或12小时制以及上下午状态等情况计算新的时间并更新到mDate字段中。 int newHour = mHourSpinner.getValue() % HOURS_IN_HALF_DAY + (mIsAm ? 0 : HOURS_IN_HALF_DAY); mDate.set(Calendar.HOUR_OF_DAY, newHour); onDateTimeChanged(); // 调用onDateTimeChanged方法该方法用来通知其他组件该日期时间已经发生了变化。 if (isDateChanged) { setCurrentYear(cal.get(Calendar.YEAR)); setCurrentMonth(cal.get(Calendar.MONTH)); setCurrentDay(cal.get(Calendar.DAY_OF_MONTH)); } // 如果日期发生了变化则调用setCurrentYear和setCurrentMonth方法更新年份和月份控件的显示。 } }; // 这段代码定义了一个监听器对象用于监听小时NumberPicker控件的值变化事件。

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;
        if (oldVal == maxValue && newVal == minValue) {
            offset += 1;
        } else if (oldVal == minValue && newVal == maxValue) {
            offset -= 1;
        }

// 当监听器被触发时,它首先根据 NumberPicker 的旧值和新值计算出一个 offset。如果旧值是最大值且新值是最小值则将 offset 增加1。如果旧值是最小值且新值是最大值则将 offset 减少1。 if (offset != 0) { mDate.add(Calendar.HOUR_OF_DAY, offset); mHourSpinner.setValue(getCurrentHour()); updateDateControl(); int newHour = getCurrentHourOfDay(); if (newHour >= HOURS_IN_HALF_DAY) { mIsAm = false; updateAmPmControl(); } else { mIsAm = true; updateAmPmControl(); } } // 如果 offset 不为0则监听器会更新 mDate 变量,将 offset 添加到小时数中,将小时选择器的值设置为当前小时,调用 updateDateControl() 更新日期控件,并根据新的小时数更新 AM/PM 控件。 mDate.set(Calendar.MINUTE, newVal); onDateTimeChanged(); // 更新 mDate 变量后,监听器将分钟值设置为新值,并调用 onDateTimeChanged() 通知任何监听器日期和时间已更新。 } }; // 定义了一个私有字段 mOnMinuteChangedListener它是 NumberPicker.OnValueChangeListener 的一个实例。当表示分钟的 NumberPicker 的值发生变化时,此监听器将被触发。

private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() {
    @Override
    public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
        mIsAm = !mIsAm;
        if (mIsAm) {
            mDate.add(Calendar.HOUR_OF_DAY, -HOURS_IN_HALF_DAY);
        } else {
            mDate.add(Calendar.HOUR_OF_DAY, HOURS_IN_HALF_DAY);
        }
        updateAmPmControl();
        onDateTimeChanged();
    }

// 当监听器被触发时,它将 mIsAm 变量取反,表示用户选择了 AM 还是 PM。如果 mIsAm 是 true则说明用户选择了 AM此时监听器将 mDate 变量减去半天的小时数。如果 mIsAm 是 false则说明用户选择了 PM此时监听器将 mDate 变量加上半天的小时数。然后,监听器会调用 updateAmPmControl() 更新 AM/PM 控件,并调用 onDateTimeChanged() 通知任何监听器日期和时间已更新。 }; // 定义了一个私有字段 mOnAmPmChangedListener它是 NumberPicker.OnValueChangeListener 的一个实例。当表示 AM/PM 的 NumberPicker 的值发生变化时,此监听器将被触发。

public interface OnDateTimeChangedListener {
    void onDateTimeChanged(DateTimePicker view, int year, int month,
            int dayOfMonth, int hourOfDay, int minute);

// 该接口有一个抽象方法 onDateTimeChanged(),它在日期或时间发生变化时被调用。该方法接收 6 个参数view 表示当前 DateTimePicker 实例year 表示年份month 表示月份(从 0 开始dayOfMonth 表示月中的某一天hourOfDay 表示小时数24 小时制minute 表示分钟数。 } //定义了一个接口 OnDateTimeChangedListener它用于监听日期和时间的变化。当日期或时间发生变化时可以调用该接口的 onDateTimeChanged() 方法通知任何实现该接口的监听器。通过实现该接口并在需要的地方注册监听器,可以在日期或时间发生变化时执行自定义操作,例如更新 UI 或执行某些计算。

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);

// 首先调用了父类 ViewGroup 的构造函数 super(context) 来初始化 DateTimePicker 实例。然后,代码使用 Calendar.getInstance() 获取一个 Calendar 对象,该对象表示当前日期和时间。变量 mIsAm 被初始化为当前小时数是否大于等于 12以确定当前用户选择的是 AM 还是 PM。

    mDateSpinner = (NumberPicker) findViewById(R.id.date);
    mDateSpinner.setMinValue(DATE_SPINNER_MIN_VAL);
    mDateSpinner.setMaxValue(DATE_SPINNER_MAX_VAL);
    mDateSpinner.setOnValueChangedListener(mOnDateChangedListener);

// 获取了布局文件中 ID 为 date 的 NumberPicker 控件,并将其赋值给 mDateSpinner 变量。然后,使用 setMinValue() 和 setMaxValue() 方法将 mDateSpinner 的最小值和最大值分别设置为 DATE_SPINNER_MIN_VAL 和 DATE_SPINNER_MAX_VAL。使用 setOnValueChangedListener() 方法将 mDateSpinner 的值更改监听器设置为 mOnDateChangedListener。这意味着当 mDateSpinner 的值更改时,将会调用 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);
    mMinuteSpinner.setOnValueChangedListener(mOnMinuteChangedListener);

// 获取了布局文件中 ID 为 hour 和 minute 的 NumberPicker 控件,并将它们分别赋值给 mHourSpinner 和 mMinuteSpinner 变量。然后,使用 setOnValueChangedListener() 方法将 mHourSpinner 和 mMinuteSpinner 控件的值更改监听器分别设置为 mOnHourChangedListener 和 mOnMinuteChangedListener。 // 接下来,使用 setMinValue() 和 setMaxValue() 方法将 mMinuteSpinner 的最小值和最大值分别设置为 MINUT_SPINNER_MIN_VAL 和 MINUT_SPINNER_MAX_VAL以限制分钟选择器的范围。 // 最后,使用 setOnLongPressUpdateInterval() 方法将 mMinuteSpinner 的长按更新间隔设置为 100 毫秒。这意味着当用户长按 mMinuteSpinner 中的增加或减少按钮时,它将以每 100 毫秒的速度连续增加或减少 mMinuteSpinner 的值。 // 通过设置 NumberPicker 的值更改监听器和其他属性,代码实现了一个可以选择小时和分钟的控件,并将它们分别绑定到 mHourSpinner 和 mMinuteSpinner 变量上,以便在之后的操作中使用。

    String[] stringsForAmPm = new DateFormatSymbols().getAmPmStrings();//获取了当前设备的日期格式符号,并使用 getAmPmStrings() 方法从中获取 AM/PM 格式符号的字符串数组,并将其赋值给 stringsForAmPm 变量。
    mAmPmSpinner = (NumberPicker) findViewById(R.id.amPm);
    mAmPmSpinner.setMinValue(AMPM_SPINNER_MIN_VAL);
    mAmPmSpinner.setMaxValue(AMPM_SPINNER_MAX_VAL);

// 获取了布局文件中 ID 为 amPm 的 NumberPicker 控件,并将其赋值给 mAmPmSpinner 变量。使用 setMinValue() 和 setMaxValue() 方法将 mAmPmSpinner 的最小值和最大值分别设置为 AMPM_SPINNER_MIN_VAL 和 AMPM_SPINNER_MAX_VAL以限制 AM/PM 选择器的范围。 mAmPmSpinner.setDisplayedValues(stringsForAmPm);//使用 setDisplayedValues() 方法将 stringsForAmPm 数组设置为 mAmPmSpinner 的显示值。这意味着 AM/PM 选择器将显示 stringsForAmPm 数组中的字符串,而不是默认的数字值。 mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener);//使用 setOnValueChangedListener() 方法将 mAmPmSpinner 的值更改监听器设置为 mOnAmPmChangedListener。这意味着当用户更改 AM/PM 选择器的值时,将调用 mOnAmPmChangedListener 中的方法来处理这个事件。

    // update controls to initial state
    updateDateControl();
    updateHourControl();
    updateAmPmControl();

    set24HourView(is24HourView);

// 代码调用了 updateDateControl()、updateHourControl() 和 updateAmPmControl() 方法,以便将控件显示为当前日期和时间的值。然后,代码调用了 set24HourView() 方法将选择器的时间格式设置为 24 小时制或 12 小时制。

    // set to current time
    setCurrentDate(date);

    setEnabled(isEnabled());

    // set the content descriptions
    mInitialising = false;

// 调用了 setCurrentDate() 方法将选择器的日期和时间设置为指定的日期和时间,调用 setEnabled() 方法将选择器的可用状态设置为指定的值,并将 mInitialising 标志设置为 false以便通知选择器已完成初始化。 }

@Override
public void setEnabled(boolean enabled) {
    if (mIsEnabled == enabled) {
        return;
    }

// 当调用 setEnabled() 方法时,如果传入的参数 enabled 与当前的 mIsEnabled 变量的值相同,说明选择器的可用状态没有发生变化,直接返回即可。 super.setEnabled(enabled); mDateSpinner.setEnabled(enabled); mMinuteSpinner.setEnabled(enabled); mHourSpinner.setEnabled(enabled); mAmPmSpinner.setEnabled(enabled); mIsEnabled = enabled; // 代码首先调用父类的 setEnabled() 方法,将整个时间选择器的可用状态设置为传入的参数 enabled。然后分别调用 mDateSpinner、mMinuteSpinner、mHourSpinner 和 mAmPmSpinner 的 setEnabled() 方法,将它们的可用状态也设置为传入的参数 enabled。 //最后,将 mIsEnabled 变量的值设置为传入的参数 enabled以便在下一次调用 setEnabled() 方法时使用。 } // 重写 setEnabled() 方法,代码实现了一个可以同时设置整个时间选择器的可用状态的功能。

@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);
}

private int getCurrentHour() {
    if (mIs24HourView){
        return getCurrentHourOfDay();
    } else {
        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) {
    if (!mInitialising && hourOfDay == getCurrentHourOfDay()) {
        return;
    }
    mDate.set(Calendar.HOUR_OF_DAY, hourOfDay);
    if (!mIs24HourView) {
        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;
}

/**
 * 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) {
    if (mIs24HourView == is24HourView) {
        return;
    }
    mIs24HourView = is24HourView;
    mAmPmSpinner.setVisibility(is24HourView ? View.GONE : View.VISIBLE);

// 当传入的参数 is24HourView 与当前的 mIs24HourView 变量的值相同时,方法直接返回。否则,将 mIs24HourView 变量的值设置为传入的参数 is24HourView表示时间选择器的显示格式已经更改。 int hour = getCurrentHourOfDay(); updateHourControl(); setCurrentHour(hour); updateAmPmControl(); // 获取当前的小时数并调用 updateHourControl() 方法更新小时选择器,以保持小时选择器的一致性。接着,使用 setCurrentHour() 方法将当前的小时数设置回时间选择器,并调用 updateAmPmControl() 方法更新 AM/PM 选择器。 } //通过设置 AM/PM 选择器的可见性和更新时间选择器的小时控件和 AM/PM 控件,代码实现了一个可以切换时间选择器显示格式的功能。

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();
}

// 用于更新日期选择器的显示值。它首先获取当前日期并将其设置为日历实例 cal 的时间。然后cal 被设置为一周的第一天,以确保日期选择器显示一周的日期。 //接下来,使用 setDisplayedValues() 方法将日期选择器的显示值设置为 null以便重新生成新的日期显示值。然后使用循环从 cal 中获取每一天的日期,并将其格式化为 MM.dd EEEE 的格式,并将其存储在一个字符串数组中。最后,使用 setDisplayedValues() 方法将新的日期显示值设置为 mDateSpinner并将当前选择的日期值设置为一周的中间值以保持日期选择器的一致性。

private void updateAmPmControl() {
    if (mIs24HourView) {
        mAmPmSpinner.setVisibility(View.GONE);
    } else {
        int index = mIsAm ? Calendar.AM : Calendar.PM;
        mAmPmSpinner.setValue(index);
        mAmPmSpinner.setVisibility(View.VISIBLE);
    }
}

// 用于更新 AM/PM 选择器的显示值。如果时间选择器为 24 小时格式,则将 AM/PM 选择器隐藏;否则,根据当前是否为 AM将 AM/PM 选择器的值设置为 Calendar.AM 或 Calendar.PM并将其可见性设置为 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);
    }
}

// 用于更新小时选择器的显示值。如果时间选择器为 24 小时格式,则将小时选择器的最小值和最大值分别设置为 HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW 和 HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW否则将小时选择器的最小值和最大值分别设置为 HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW 和 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());
    }
}

}

// Apache许可证协议

package net.micode.notes.ui;

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;

import net.micode.notes.R; import net.micode.notes.ui.DateTimePicker.OnDateTimeChangedListener;

import java.util.Calendar;

public class DateTimePickerDialog extends AlertDialog implements OnClickListener {

private Calendar mDate = Calendar.getInstance();
private boolean mIs24HourView;
private OnDateTimeSetListener mOnDateTimeSetListener;
private DateTimePicker mDateTimePicker;

public interface OnDateTimeSetListener {
    void OnDateTimeSet(AlertDialog dialog, long date);
}

// 实现了一个 OnDateTimeSetListener 接口,该接口定义了一个 OnDateTimeSet() 方法,用于在用户选择日期时间后通知调用者。

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) {
            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());
        }
    });

// 包含了一个 DateTimePicker 控件,用于让用户选择日期和时间。通过 mDateTimePicker.setOnDateTimeChangedListener() 方法,当用户选择日期和时间时,将更新 mDate 的值,并使用 updateTitle() 方法更新对话框的标题。 mDate.setTimeInMillis(date); mDate.set(Calendar.SECOND, 0); mDateTimePicker.setCurrentDate(mDate.getTimeInMillis()); setButton(context.getString(R.string.datetime_dialog_ok), this); setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null); set24HourView(DateFormat.is24HourFormat(this.getContext())); updateTitle(mDate.getTimeInMillis()); }

public void set24HourView(boolean is24HourView) {
    mIs24HourView = is24HourView;
}

//set24HourView() 方法,用于设置日期时间选择器是否为 24 小时格式。默认情况下,该值将由系统的设置决定。

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;
    flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_24HOUR;
    setTitle(DateUtils.formatDateTime(this.getContext(), date, flag));
}

public void onClick(DialogInterface arg0, int arg1) {
    if (mOnDateTimeSetListener != null) {
        mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis());
    }
}

// 重写了 onClick() 方法,以便在用户点击“确定”按钮后,将用户选择的日期时间作为参数传递到 OnDateTimeSet() 方法中,从而通知调用者。

} // Apache许可证协议 package net.micode.notes.ui;

import android.content.Context; import android.view.Menu; import android.view.MenuItem; 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; // 一个 Button 控件,用于显示菜单的标题,以及一个 PopupMenu 对象,用于显示菜单项。在构造函数中,该类接受一个菜单资源 ID并使用 mPopupMenu.getMenuInflater().inflate() 方法从 XML 资源中加载菜单项,然后将菜单项添加到 mMenu 对象中。

public DropdownMenu(Context context, Button button, int menuId) {
    mButton = button;
    mButton.setBackgroundResource(R.drawable.dropdown_icon);
    mPopupMenu = new PopupMenu(context, mButton);
    mMenu = mPopupMenu.getMenu();
    mPopupMenu.getMenuInflater().inflate(menuId, mMenu);
    mButton.setOnClickListener(v -> mPopupMenu.show());

// 重写了 setOnClickListener() 方法,以便在用户点击 Button 控件时显示下拉菜单。 } // 在 Button 控件上注册一个单击事件监听器,并在该监听器中实现下拉菜单的显示逻辑。

public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) {
    if (mPopupMenu != null) {
        mPopupMenu.setOnMenuItemClickListener(listener);
    }
}

// setOnDropdownMenuItemClickListener() 方法,用于设置菜单项的点击监听器。通过这个方法,应用程序可以在用户选择菜单项时执行相应的操作。

public MenuItem findItem(int id) {
    return mMenu.findItem(id);
}//findItem() 方法用于查找指定 ID 的菜单项

public void setTitle(CharSequence title) {
    mButton.setText(title);
}//用于设置菜单的标题。

} // Apache许可证协议

package 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 = { NoteColumns.ID, NoteColumns.SNIPPET };

public static final int ID_COLUMN   = 0;
public static final int NAME_COLUMN = 1;

public FoldersListAdapter(Context context, Cursor c) {
    super(context, c);
}

@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
    return new FolderListItem(context);
}

// 用于创建新的视图,该方法返回一个新的 FolderListItem 对象,它是一个自定义的 LinearLayout用于显示文件夹列表项的布局。

@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);
    }
}

// 用于绑定视图和数据,该方法会将数据从 Cursor 对象中读取出来,并将其绑定到 FolderListItem 视图中。

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);
}

// 获取文件夹列表中某个位置的文件夹名称。

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);
    }

    public void bind(String name) {
        mName.setText(name);
    }
}

// FolderListItem 类是一个自定义的 LinearLayout用于显示文件夹列表项的布局。它包含一个 TextView 控件,用于显示文件夹名称。

} //这段代码是一个自定义的 CursorAdapter 类 FoldersListAdapter用于在 Android 应用程序中显示一个文件夹列表。 // Apache许可证协议

package 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 { private class HeadViewHolder { public TextView tvModified;

    public ImageView ivAlertIcon;

    public TextView tvAlertDate;

    public ImageView ibSetBgColor;
}

// HeadViewHolder类定义了一组视图用于在列表或网格中显示信息。这些视图包括一个TextView用于显示修改信息一个ImageView用于显示警报图标另一个TextView用于显示警报日期还有一个ImageView用于选择背景颜色。

private static final Map<Integer, Integer> sBgSelectorBtnsMap = new HashMap<>();

static {
    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<Integer, Integer> sBgSelectorSelectionMap = new 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);
}

// sBgSelectorBtnsMap是一个HashMap将用于选择背景颜色的ImageView视图的资源ID映射到整数值这些整数值在ResourceParser类中定义。同样sBgSelectorSelectionMap将整数值映射到所选ImageView视图的资源ID。

private static final Map<Integer, Integer> 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<Integer, Integer> sFontSelectorSelectionMap = new HashMap<>();

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);
}

// sFontSizeBtnsMap将用于选择字体大小的LinearLayout视图的资源ID映射到整数值而sFontSelectorSelectionMap将整数值映射到所选ImageView视图的资源ID。

private static final String 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;

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)) {
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.putExtra(Intent.EXTRA_UID, savedInstanceState.getLong(Intent.EXTRA_UID));
        if (!initActivityState(intent)) {
            finish();
            return;
        }
        Log.d(TAG, "Restoring from killed activity");
    }
}

private boolean initActivityState(Intent intent) {
    /**
     * If the user specified the {@link Intent#ACTION_VIEW} but not provided with id,
     * then jump to the NotesListActivity
     */

// 果用户指定了 Intent.ACTION_VIEW 动作但没有提供ID则跳转到 NotesListActivity mWorkingNote = null;//在方法中首先将mWorkingNote设置为null。然后根据传入的Intent的动作(action)进行选择。 if (TextUtils.equals(Intent.ACTION_VIEW, intent.getAction())) { // Intent中获取笔记ID long noteId = intent.getLongExtra(Intent.EXTRA_UID, 0); // 将用户查询字符串设置为空 mUserQuery = ""; // 如果Intent包含了搜索结果的额外数据则从额外数据中获取笔记ID并将用户查询字符串设置为搜索管理器中的用户查询字符串

        /**
         * Starting from the searched result
         */
        if (intent.hasExtra(SearchManager.EXTRA_DATA_KEY)) {
            noteId = Long.parseLong(intent.getStringExtra(SearchManager.EXTRA_DATA_KEY));
            mUserQuery = intent.getStringExtra(SearchManager.USER_QUERY);
        }

        // 如果笔记在笔记数据库中不存在,则跳转到 NotesListActivity 并显示错误信息的Toast最后结束 Activity 并返回 false
        if (!DataUtils.visibleInNoteDatabase(getContentResolver(), noteId, Notes.TYPE_NOTE)) {
            Intent jump = new Intent(this, NotesListActivity.class);
            startActivity(jump);
            showToast(R.string.error_note_not_exist);
            finish();
            return false;
        } else {
            // 加载笔记
            mWorkingNote = WorkingNote.load(this, noteId);
            if (mWorkingNote == null) {
                Log.e(TAG, "load note failed with note id" + noteId);
                finish();
                return false;
            }
        }

// 如果该Intent的动作(action)为Intent.ACTION_VIEW则从该Intent中获取笔记的ID(noteId)。如果该Intent还包含了一个搜索结果(Search)的额外数据(extra data key)则将noteId从额外数据中获取并将用户查询字符串(mUserQuery)设置为搜索管理器中的用户查询字符串(SearchManager.USER_QUERY)。如果noteId在笔记数据库中不存在则将Activity转到NotesListActivity并显示一个错误信息的Toast最后结束Activity并返回false。如果noteId在笔记数据库中存在则加载该笔记的工作副本(mWorkingNote)如果加载失败则结束Activity并返回false。 getWindow().setSoftInputMode( WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); } else if (TextUtils.equals(Intent.ACTION_INSERT_OR_EDIT, intent.getAction())) { // New note 如果用户指定了 Intent.ACTION_INSERT_OR_EDIT 动作则获取笔记ID如果笔记ID存在则加载笔记否则创建一个新的笔记 long folderId = intent.getLongExtra(Notes.INTENT_EXTRA_FOLDER_ID, 0); int widgetId = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); int widgetType = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, Notes.TYPE_WIDGET_INVALID); int bgResId = intent.getIntExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, ResourceParser.getDefaultBgId(this));

        // Parse call-record note
        String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
        long callDate = intent.getLongExtra(Notes.INTENT_EXTRA_CALL_DATE, 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) {
                mWorkingNote = WorkingNote.load(this, noteId);
                if (mWorkingNote == null) {
                    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() {
    super.onResume();
    initNoteScreen();
}

private void initNoteScreen() {
    // 使用mFontSizeId设置笔记编辑器的文本外观。
    mNoteEditor.setTextAppearance(this, TextAppearanceResources
            .getTexAppearanceResource(mFontSizeId));
    // 如果mWorkingNote的CheckListMode为TextNote.MODE_CHECK_LIST则将编辑器转换为列表模式。
    if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
        switchToListMode(mWorkingNote.getContent());
    } else {
        // 如果mWorkingNote的CheckListMode不是TextNote.MODE_CHECK_LIST则在编辑器中显示笔记内容并使用mUserQuery高亮显示查询结果。
        mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery));
        mNoteEditor.setSelection(mNoteEditor.getText().length());
    }
    // 隐藏背景选择器中所有不在sBgSelectorSelectionMap中的ID的视图。
    for (Integer id : sBgSelectorSelectionMap.keySet()) {
        findViewById(sBgSelectorSelectionMap.get(id)).setVisibility(View.GONE);
    }
    // 设置mHeadViewPanel和mNoteEditorPanel的背景颜色为mWorkingNote的标题背景ID和背景颜色ID。
    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() {
    // 如果mWorkingNote具有闹钟提醒则检查当前时间是否超过提醒时间如果超过则在标题栏中显示“note_alert_expired”文本否则在标题栏中显示相对时间。
    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 {
        // 如果mWorkingNote没有闹钟提醒则将标题栏中的提醒日期文本和提醒图标设置为不可见。
        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());
    Log.d(TAG, "Save working note id: " + mWorkingNote.getNoteId() + " onSaveInstanceState");
}

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    // 如果笔记背景颜色选择器可见并且触摸事件不在笔记背景颜色选择器范围内则隐藏笔记背景颜色选择器并返回true。
    if (mNoteBgColorSelector.getVisibility() == View.VISIBLE
            && !inRangeOfView(mNoteBgColorSelector, ev)) {
        mNoteBgColorSelector.setVisibility(View.GONE);
        return true;
    }
    // 如果字体大小选择器可见并且触摸事件不在字体大小选择器范围内则隐藏字体大小选择器并返回true。

    if (mFontSizeSelector.getVisibility() == View.VISIBLE
            && !inRangeOfView(mFontSizeSelector, ev)) {
        mFontSizeSelector.setVisibility(View.GONE);
        return true;
    }
    // 返回父类的dispatchTouchEvent方法。
    return super.dispatchTouchEvent(ev);
}

private boolean inRangeOfView(View view, MotionEvent ev) {
    // 获取视图在屏幕上的位置。
    int[] location = new int[2];
    view.getLocationOnScreen(location);
    int x = location[0];
    int y = location[1];
    // 如果触摸事件的x坐标小于视图的x坐标、大于视图的宽度和x坐标之和、y坐标小于视图的y坐标、或大于视图的高度和y坐标之和则返回false否则返回true。
    if (ev.getX() < x
            || ev.getX() > (x + view.getWidth())
            || ev.getY() < y
            || ev.getY() > (y + view.getHeight())) {
        return false;
    }
    return true;
}

private void initResources() {
    mHeadViewPanel = findViewById(R.id.note_title);
    mNoteHeaderHolder = new 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()) {
        mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE;
    }
    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());
    }
    clearSettingState();
}//onPause()方法在活动即将暂停时被调用。如果有任何更改,它会保存当前笔记,并清除任何设置状态。

private void updateWidget() {
    Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
    if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_2X) {
        intent.setClass(this, NoteWidgetProvider_2x.class);
    } else if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_4X) {
        intent.setClass(this, NoteWidgetProvider_4x.class);
    } else {
        Log.e(TAG, "Unsupported widget type");
        return;
    }

    intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[]{
            mWorkingNote.getWidgetId()
    });

    sendBroadcast(intent);
    setResult(RESULT_OK, intent);
}//updateWidget()方法更新与当前笔记相关联的小部件。它创建一个意图并根据笔记的小部件类型设置相应的小部件提供程序类。然后它发送一个广播带有小部件ID以更新小部件。


public void onClick(View v) {
    int id = v.getId();
    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));
        }
        mFontSizeSelector.setVisibility(View.GONE);
    }
}//onClick()方法处理UI中各种按钮的点击事件。当单击相应的按钮时它会显示颜色选择器或字体大小选择器。它还根据所选选项更新笔记的字体大小或背景颜色。

@Override
public void onBackPressed() {
    if (clearSettingState()) {
        return;
    }

    saveNote();
    super.onBackPressed();
}//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;
}//clearSettingState()方法检查当前是否有任何设置状态处于活动状态,并清除它。

public void onBackgroundColorChanged() {
    findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
            View.VISIBLE);
    mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId());
    mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId());
}//onBackgroundColorChanged()方法在选择新的背景颜色时被调用。它更新笔记的背景颜色,并更新颜色选择器的颜色。

@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);
    } 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 {
        menu.findItem(R.id.menu_delete_remind).setVisible(false);
    }
    return true;
}//onFontSizeChanged()方法在选择新的字体大小时被调用。它更新笔记的字体大小,并更新字体大小选择器的值。

@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);
            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);
    intent.setType("text/plain");
    context.startActivity(intent);
}//sendTo(Context context, String info)方法创建一个新的意图并使用Intent.EXTRA_TEXT将信息作为文本传递。它的目的是启动共享对话框让用户分享笔记的内容。


private void createNewNote() {

    // 首先,保存当前正在编辑的笔记
    saveNote();

    // 为了安全起见启动一个新的NoteEditActivity
    finish();
    // 创建一个新的笔记
    Intent intent = new Intent(this, NoteEditActivity.class);
    // 设置Intent.ACTION_INSERT_OR_EDIT和Notes.INTENT_EXTRA_FOLDER_ID指示NoteEditActivity启动以插入或编辑笔记并指定所选文件夹的ID。
    intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
    // 设置Intent.EXTRA_TITLE指示NoteEditActivity启动以编辑笔记。
    intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mWorkingNote.getFolderId());
    // 启动NoteEditActivity
    startActivity(intent);
}//createNewNote()方法保存当前正在编辑的笔记然后启动一个新的NoteEditActivity以创建一个新的笔记。通过设置Intent.ACTION_INSERT_OR_EDIT和Notes.INTENT_EXTRA_FOLDER_ID该方法指示NoteEditActivity启动以插入或编辑笔记并指定所选文件夹的ID。

private void deleteCurrentNote() {
    if (mWorkingNote.existInDatabase()) {
        HashSet<Long> ids = new HashSet<>();
        long id = mWorkingNote.getNoteId();
        if (id != Notes.ID_ROOT_FOLDER) {
            ids.add(id);
        } else {
            Log.d(TAG, "Wrong note id, should not happen");
        }
        if (!DataUtils.batchDeleteNotes(getContentResolver(), ids)) {
            Log.e(TAG, "Delete Note error");
        }
    }
    mWorkingNote.markDeleted(true);
}//deleteCurrentNote()方法删除当前笔记。如果该笔记已存储在数据库中,则将其从数据库中删除。如果应用程序处于同步模式下,则将其移动到垃圾文件夹中,而不是永久删除。无论是删除还是移动,该方法都会将当前笔记标记为已删除。

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);
        intent.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mWorkingNote.getNoteId()));
        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 {
        /**
         * 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() {
    updateWidget();
}

// 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;
    if (index == 0) {
        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);
}

// onEditTextDelete(int index, String text)方法会在删除NoteEditText视图时被调用。该方法首先检查是否只有一个NoteEditText视图存在如果是则直接返回。然后该方法会将所有后续视图的索引递减1。接下来该方法会删除指定索引处的NoteEditText视图并将其文本追加到前一个视图的文本中以便将文本合并到一个视图中。最后该方法将焦点设置在前一个视图中并将光标移动到文本末尾。

public void onEditTextEnter(int index, String text) {
    // 不应该发生,检查调试
    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)) {
            mEditTextList.addView(getListItem(item, index));
            index++;
        }
    }
    mEditTextList.addView(getListItem("", index));
    mEditTextList.getChildAt(index).findViewById(R.id.et_edit_text).requestFocus();

    mNoteEditor.setVisibility(View.GONE);
    mEditTextList.setVisibility(View.VISIBLE);
}

// switchToListMode(String text)方法用于切换到清单模式。该方法首先清除所有视图然后将文本根据换行符分隔为多个条目并将每个条目添加到mEditTextList中。同时该方法会添加一个新的空条目以便用户可以在列表末尾添加新的条目。最后该方法会将焦点设置在最后一个条目上并将编辑器视图隐藏将列表视图显示。

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);
        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;
}

// getHighlightQueryResult(String fullText, String userQuery)方法用于标记在搜索查询中匹配的文本。该方法将返回一个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();
    } 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.setIndex(index);
    edit.setText(getHighlightQueryResult(item, mUserQuery));
    return view;
}

// getListItem(String item, int index)方法用于获取一个清单视图。该方法将从R.layout.note_edit_list_item文件中充气视图。在充气视图之后该方法会将文本添加到NoteEditText视图中并将复选框设置为选中或未选中状态。如果条目已选中则文本将具有删除线。最后该方法将返回该视图。

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);
    }
}

// onTextChange(int index, boolean hasText)方法用于在清单视图中标记是否有文本。如果hasText为true则将显示复选框否则将隐藏复选框。

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);
    }
}

// onCheckListModeChanged(int oldMode, int newMode)方法用于在清单模式和文本编辑模式之间切换。如果新模式为清单模式,则将文本转换为清单视图;否则,将清单视图转换为文本编辑视图。

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++) {
            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");
                    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;
}

// getWorkingText()方法用于获取当前工作文本。如果当前模式为清单模式,则将所有条目合并为一个字符串,并添加检查框状态。否则,将返回当前编辑器文本。

private boolean saveNote() {
    getWorkingText();
    boolean saved = mWorkingNote.saveNote();
    if (saved) {
        /**
         * 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);
        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");
        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();
}

}// Apache许可证协议

package 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 { private static final String TAG = "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<String, Integer> sSchemaActionResMap = new HashMap<String, Integer>();
static {
    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 {
    /**
     * Delete current edit text when {@link KeyEvent#KEYCODE_DEL} happens
     * and the text is null
     */
    void onEditTextDelete(int index, String text);

    /**
     * Add edit text after current edit text when {@link KeyEvent#KEYCODE_ENTER}
     * happen
     */
    void onEditTextEnter(int index, String text);

    /**
     * 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);
}

@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();
            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) {
        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:
            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:
            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);
}

// onKeyDown和onKeyUp方法实现了按下和松开键盘按键时的响应。

@Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
    if (mOnTextViewChangeListener != null) {
        mOnTextViewChangeListener.onTextChange(mIndex, focused || !TextUtils.isEmpty(getText()));
    }
    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) {
            int defaultResId = 0;
            for(String schema: sSchemaActionResMap.keySet()) {
                if(urls[0].getURL().indexOf(schema) >= 0) {
                    defaultResId = sSchemaActionResMap.get(schema);
                    break;
                }
            }

            if (defaultResId == 0) {
                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);
}

// 创建上下文菜单,当用户长按控件中的链接时,根据链接的协议类型创建不同的菜单项,点击菜单项后跳转到相应的链接。 }

// Apache许可证协议

package 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 { private static final String TAG = "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<String, Integer> sSchemaActionResMap = new HashMap<String, Integer>();
static {
    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 {
    /**
     * Delete current edit text when {@link KeyEvent#KEYCODE_DEL} happens
     * and the text is null
     */
    void onEditTextDelete(int index, String text);

    /**
     * Add edit text after current edit text when {@link KeyEvent#KEYCODE_ENTER}
     * happen
     */
    void onEditTextEnter(int index, String text);

    /**
     * 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);
}

@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();
            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) {
        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:
            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:
            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);
}

// onKeyDown和onKeyUp方法实现了按下和松开键盘按键时的响应。

@Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
    if (mOnTextViewChangeListener != null) {
        mOnTextViewChangeListener.onTextChange(mIndex, focused || !TextUtils.isEmpty(getText()));
    }
    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) {
            int defaultResId = 0;
            for(String schema: sSchemaActionResMap.keySet()) {
                if(urls[0].getURL().indexOf(schema) >= 0) {
                    defaultResId = sSchemaActionResMap.get(schema);
                    break;
                }
            }

            if (defaultResId == 0) {
                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);
}

// 创建上下文菜单,当用户长按控件中的链接时,根据链接的协议类型创建不同的菜单项,点击菜单项后跳转到相应的链接。 }