Compare commits

...

4 Commits

Author SHA1 Message Date
ywzx bd5168322b 注释代码
2 months ago
ywzx f8040d695e 注释代码
4 months ago
ywzx 2a7af13304 测试
4 months ago
ywzx 531c5bbbef 注释代码
4 months ago

@ -0,0 +1,511 @@
/*
* 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.
*/
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;
// 自定义的日期时间选择器类继承自FrameLayout
public class DateTimePicker extends FrameLayout {
// 默认启用状态
private static final boolean DEFAULT_ENABLE_STATE = true;
// 半天和整天的小时数一周的天数以及日期、小时、分钟和AM/PM选择器的最小和最大值
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;
// NumberPicker组件用于选择日期、小时、分钟和在12小时模式下上午/下午
private final NumberPicker mDateSpinner;
private final NumberPicker mHourSpinner;
private final NumberPicker mMinuteSpinner;
private final NumberPicker mAmPmSpinner;
// 用于存储和计算日期的Calendar对象
private Calendar mDate;
// 存储日期显示值的数组
private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK];
// 标记当前是上午还是下午
private boolean mIsAm;
// 标记是否使用24小时制
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);
// 更新日期控件的显示
updateDateControl();
// 通知日期时间已改变
onDateTimeChanged();
}
};
// AM/PM选择器值改变时的监听器
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); // 如果是上午减去12小时
} else {
mDate.add(Calendar.HOUR_OF_DAY, HOURS_IN_HALF_DAY); // 如果是下午加上12小时
}
// 更新AM/PM控件的显示
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)); // 调用下一个构造方法根据系统设置决定是否使用24小时制
}
// 构造方法接收上下文、初始日期和是否使用24小时制的标志
public DateTimePicker(Context context, long date, boolean is24HourView) {
super(context); // 调用父类FrameLayout的构造方法
mDate = Calendar.getInstance(); // 初始化Calendar对象
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);
// ...(省略了小时选择器的监听器设置,可能在后续代码中)
// 初始化分钟选择器并设置监听器
mMinuteSpinner = (NumberPicker) findViewById(R.id.minute);
mMinuteSpinner.setMinValue(MINUT_SPINNER_MIN_VAL);
mMinuteSpinner.setMaxValue(MINUT_SPINNER_MAX_VAL);
mMinuteSpinner.setOnLongPressUpdateInterval(100); // 设置长按时的更新间隔
mMinuteSpinner.setOnValueChangedListener(mOnMinuteChangedListener); // 监听器在后续代码中定义
// 初始化AM/PM选择器并设置监听器和显示值
String[] stringsForAmPm = new DateFormatSymbols().getAmPmStrings(); // 获取系统定义的上午/下午字符串
mAmPmSpinner = (NumberPicker) findViewById(R.id.amPm);
mAmPmSpinner.setMinValue(AMPM_SPINNER_MIN_VAL);
mAmPmSpinner.setMaxValue(AMPM_SPINNER_MAX_VAL);
mAmPmSpinner.setDisplayedValues(stringsForAmPm); // 设置显示的上午/下午字符串
mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener); // 设置监听器
// 更新控件到初始状态
updateDateControl();
updateHourControl(); // 方法在后续代码中定义,用于更新小时控件
updateAmPmControl(); // 方法在后续代码中定义用于更新AM/PM控件
// 设置是否使用24小时制
set24HourView(is24HourView);
// 设置到当前时间
setCurrentDate(date); // 方法在后续代码中定义,用于设置当前日期时间
// 设置启用状态
setEnabled(isEnabled()); // 这里的isEnabled()可能是指mIsEnabled成员变量但更可能是调用父类方法
// 设置内容描述(用于无障碍功能)
// ...(可能省略了设置内容描述的代码)
mInitialising = false; // 标记初始化完成
}
// 重写setEnabled方法用于设置组件的启用状态
@Override
public void setEnabled(boolean enabled) {
// 如果当前状态与要设置的状态相同,则直接返回,不做任何操作
if (mIsEnabled == enabled) {
return;
}
// 调用父类的setEnabled方法设置当前组件的启用状态
super.setEnabled(enabled);
// 设置日期、分钟、小时和AM/PM选择器的启用状态
mDateSpinner.setEnabled(enabled);
mMinuteSpinner.setEnabled(enabled);
mHourSpinner.setEnabled(enabled);
mAmPmSpinner.setEnabled(enabled);
// 更新成员变量mIsEnabled记录当前组件的启用状态
mIsEnabled = enabled;
}
// 重写isEnabled方法用于获取组件的启用状态
@Override
public boolean isEnabled() {
return mIsEnabled;
}
/**
*
*
* @return
*/
public long getCurrentDateInTimeMillis() {
return mDate.getTimeInMillis();
}
/**
*
*
* @param date
*/
public void setCurrentDate(long date) {
// 创建一个Calendar实例并根据传入的时间戳设置其时间
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(date);
// 调用另一个setCurrentDate方法传入年、月、日、小时和分钟来设置日期
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));
}
/**
*
*
* @param year
* @param month 001
* @param dayOfMonth
* @param hourOfDay 24
* @param minute
*/
public void setCurrentDate(int year, int month,
int dayOfMonth, int hourOfDay, int minute) {
// 分别设置年、月、日、小时和分钟
setCurrentYear(year);
setCurrentMonth(month);
setCurrentDay(dayOfMonth);
setCurrentHour(hourOfDay);
setCurrentMinute(minute);
}
/**
*
*
* @return
*/
public int getCurrentYear() {
return mDate.get(Calendar.YEAR);
}
/**
*
*
* @param year
*/
public void setCurrentYear(int year) {
// 如果不是在初始化阶段且年份未改变,则直接返回
if (!mInitialising && year == getCurrentYear()) {
return;
}
// 使用Calendar的set方法设置年份
mDate.set(Calendar.YEAR, year);
// 更新日期控件的显示
updateDateControl();
// 通知日期时间已更改
onDateTimeChanged();
}
/**
* 001
*
* @return
*/
public int getCurrentMonth() {
return mDate.get(Calendar.MONTH);
}
/**
* Set current month in the year
*
* @param month The month in the year
*/
/**
*
*
* @param month 001
*/
public void setCurrentMonth(int month) {
// 如果不是在初始化阶段且月份未改变,则直接返回
if (!mInitialising && month == getCurrentMonth()) {
return;
}
// 使用Calendar的set方法设置月份
mDate.set(Calendar.MONTH, month);
// 更新日期控件的显示
updateDateControl();
// 通知日期时间已更改
onDateTimeChanged();
}
/**
*
*
* @return
*/
public int getCurrentDay() {
return mDate.get(Calendar.DAY_OF_MONTH);
}
/**
*
*
* @param dayOfMonth
*/
public void setCurrentDay(int dayOfMonth) {
// 如果不是在初始化阶段且天数未改变,则直接返回
if (!mInitialising && dayOfMonth == getCurrentDay()) {
return;
}
// 使用Calendar的set方法设置天数
mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
// 更新日期控件的显示
updateDateControl();
// 通知日期时间已更改
onDateTimeChanged();
}
/**
* 24023
*
* @return 24
*/
public int getCurrentHourOfDay() {
return mDate.get(Calendar.HOUR_OF_DAY);
}
/**
* 24
*
* @return 240~23121~12
*/
private int getCurrentHour() {
// 如果为24小时制则直接返回当前小时24小时制
if (mIs24HourView){
return getCurrentHourOfDay();
} else {
// 如果为12小时制则进行转换
int hour = getCurrentHourOfDay();
if (hour > HOURS_IN_HALF_DAY) { // HOURS_IN_HALF_DAY应为12表示半天的小时数
// 如果小时数大于12则转换为PM并减去12
return hour - HOURS_IN_HALF_DAY;
} else {
// 处理午夜和中午的特殊情况
return hour == 0 ? HOURS_IN_HALF_DAY : hour; // 午夜12点转换为12点AM其他情况保持不变
}
}
}
/**
* 24023
*
* @param hourOfDay 24
*/
public void setCurrentHour(int hourOfDay) {
// 如果不是在初始化阶段且小时未改变,则直接返回
if (!mInitialising && hourOfDay == getCurrentHourOfDay()) {
return;
}
// 使用Calendar的set方法设置小时24小时制
mDate.set(Calendar.HOUR_OF_DAY, hourOfDay);
// 如果不是24小时制则进行AM/PM转换
if (!mIs24HourView) {
if (hourOfDay >= HOURS_IN_HALF_DAY) { // 如果小时数大于等于12则设置为PM
mIsAm = false;
if (hourOfDay > HOURS_IN_HALF_DAY) { // 如果小时数大于12则减去12
hourOfDay -= HOURS_IN_HALF_DAY;
}
} else { // 如果小时数小于12则设置为AM
mIsAm = true;
if (hourOfDay == 0) { // 处理午夜12点的特殊情况转换为12点AM
hourOfDay = HOURS_IN_HALF_DAY;
}
}
// 更新AM/PM控件的显示
updateAmPmControl();
}
// 更新小时选择器的值注意这里可能需要额外的逻辑来确保小时选择器在12小时制下正确显示
mHourSpinner.setValue(hourOfDay); // 注意这里可能需要根据mIs24HourView来决定是否转换小时值
// 通知日期时间已更改
onDateTimeChanged();
}
/**
*
*
* @return
*/
public int getCurrentMinute() {
return mDate.get(Calendar.MINUTE); // 从mDate日历对象中获取当前分钟
}
/**
*
*
* @param minute
*/
public void setCurrentMinute(int minute) {
// 如果不在初始化阶段且分钟未改变,则直接返回
if (!mInitialising && minute == getCurrentMinute()) {
return;
}
mMinuteSpinner.setValue(minute); // 在分钟选择器中设置值
mDate.set(Calendar.MINUTE, minute); // 在mDate日历对象中设置分钟
onDateTimeChanged(); // 触发日期时间变化事件
}
/**
* 24
*
* @return 24truefalse
*/
public boolean is24HourView() {
return mIs24HourView; // 返回mIs24HourView的值
}
/**
* 24AM/PM
*
* @param is24HourView true24falseAM/PM
*/
public void set24HourView(boolean is24HourView) {
// 如果当前模式与要设置的模式相同,则直接返回
if (mIs24HourView == is24HourView) {
return;
}
mIs24HourView = is24HourView; // 设置新的模式
// 根据模式设置AM/PM选择器的可见性
mAmPmSpinner.setVisibility(is24HourView ? View.GONE : View.VISIBLE);
int hour = getCurrentHourOfDay(); // 获取当前小时
updateHourControl(); // 更新小时选择器控制
setCurrentHour(hour); // 设置当前小时
updateAmPmControl(); // 更新AM/PM控制
}
/**
*
*/
private void updateDateControl() {
Calendar cal = Calendar.getInstance(); // 获取当前日历实例
cal.setTimeInMillis(mDate.getTimeInMillis()); // 设置日历时间为mDate的时间
// 调整日历以显示一周内的日期
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(); // 刷新日期选择器
}
/**
* AM/PM
*/
private void updateAmPmControl() {
if (mIs24HourView) {
mAmPmSpinner.setVisibility(View.GONE); // 在24小时模式下隐藏AM/PM选择器
} else {
int index = mIsAm ? Calendar.AM : Calendar.PM; // 根据当前是上午还是下午设置索引
mAmPmSpinner.setValue(index); // 设置AM/PM选择器的值
mAmPmSpinner.setVisibility(View.VISIBLE); // 显示AM/PM选择器
}
}
/**
*
*/
private void updateHourControl() {
if (mIs24HourView) {
// 在24小时模式下设置小时选择器的最小值和最大值
mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW);
mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW);
} else {
// 在12小时模式下设置小时选择器的最小值和最大值
mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW);
mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW);
}
}
/**
*
*
* @param callback null
*/
public void setOnDateTimeChangedListener(OnDateTimeChangedListener callback) {
mOnDateTimeChangedListener = callback; // 设置监听器
}
/**
*
*/
private void onDateTimeChanged() {
if (mOnDateTimeChangedListener != null) {
// 如果设置了监听器则调用其onDateTimeChanged方法
mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(),
getCurrentMonth(), getCurrentDay(), getCurrentHourOfDay(), getCurrentMinute());
}
}

@ -0,0 +1,105 @@
/*
* 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.
*/
// 导入必要的包
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 {
// 成员变量
private Calendar mDate = Calendar.getInstance(); // 用于存储当前选择的日期和时间
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) {
// 更新Calendar对象的日期和时间
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);
setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null);
// 根据系统设置设置24小时制显示
set24HourView(DateFormat.is24HourFormat(this.getContext()));
// 更新对话框的标题
updateTitle(mDate.getTimeInMillis());
}
// 设置是否使用24小时制显示时间
public void set24HourView(boolean is24HourView) {
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; // 显示时间
// 根据是否使用24小时制设置显示格式
flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_12HOUR;
// 设置对话框的标题
setTitle(DateUtils.formatDateTime(this.getContext(), date, flag));
}
// 当点击确定按钮时调用
public void onClick(DialogInterface arg0, int arg1) {
if (mOnDateTimeSetListener != null) {
// 调用回调接口,传递对话框和选择的日期时间
mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis());
}
}
}

@ -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;
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;
mButton.setBackgroundResource(R.drawable.dropdown_icon);
mPopupMenu = new PopupMenu(context, mButton);
mMenu = mPopupMenu.getMenu();
mPopupMenu.getMenuInflater().inflate(menuId, mMenu);
mButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
mPopupMenu.show();
}
});
}
public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) {
if (mPopupMenu != null) {
mPopupMenu.setOnMenuItemClickListener(listener);
}
}
public MenuItem findItem(int id) {
return mMenu.findItem(id);
}
public void setTitle(CharSequence title) {
mButton.setText(title);
}
}

@ -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;
import android.content.Context; // 引入Android的Context类用于访问应用的特定资源和类以及调用应用级操作
import android.view.Menu; // 引入Android的Menu类用于创建菜单
import android.view.MenuItem; // 引入Android的MenuItem类表示菜单中的一项
import android.view.View; // 引入Android的View类是所有用户界面组件的基类
import android.view.View.OnClickListener; // 引入Android的OnClickListener接口用于点击事件的监听
import android.widget.Button; // 引入Android的Button类用于创建一个按钮
import android.widget.PopupMenu; // 引入Android的PopupMenu类用于创建一个弹出菜单
import android.widget.PopupMenu.OnMenuItemClickListener; // 引入Android的OnMenuItemClickListener接口用于菜单项点击事件的监听
import net.micode.notes.R; // 引入项目的资源类,用于访问项目中的资源
// DropdownMenu类用于创建和管理一个下拉菜单
public class DropdownMenu {
private Button mButton; // 声明一个Button变量用于显示下拉菜单的触发按钮
private PopupMenu mPopupMenu; // 声明一个PopupMenu变量用于创建和管理下拉菜单
private Menu mMenu; // 声明一个Menu变量用于访问和操作下拉菜单的菜单项
// DropdownMenu类的构造函数用于初始化下拉菜单
public DropdownMenu(Context context, Button button, int menuId) {
mButton = button; // 将传入的Button赋值给mButton变量
mButton.setBackgroundResource(R.drawable.dropdown_icon); // 设置按钮的背景资源,即下拉菜单的图标
mPopupMenu = new PopupMenu(context, mButton); // 创建一个PopupMenu实例并将其与按钮关联
mMenu = mPopupMenu.getMenu(); // 获取PopupMenu的Menu对象用于操作菜单项
mPopupMenu.getMenuInflater().inflate(menuId, mMenu); // 使用MenuInflater将指定的菜单资源文件填充到Menu对象中
mButton.setOnClickListener(new OnClickListener() { // 为按钮设置点击事件监听器
public void onClick(View v) {
mPopupMenu.show(); // 当按钮被点击时,显示下拉菜单
}
});
}
// 设置下拉菜单项点击事件的监听器
public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) {
if (mPopupMenu != null) {
mPopupMenu.setOnMenuItemClickListener(listener); // 为PopupMenu设置菜单项点击事件的监听器
}
}
// 根据菜单项的ID查找菜单项
public MenuItem findItem(int id) {
return mMenu.findItem(id); // 在Menu中查找具有指定ID的菜单项并返回
}
// 设置按钮的标题
public void setTitle(CharSequence title) {
mButton.setText(title); // 设置按钮的文本为指定的标题
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,239 @@
/*
* 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;
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;
// 定义一个名为NoteEditText的类它继承自EditText
public class NoteEditText extends EditText {
// 定义一个用于日志记录的标签
private static final String TAG = "NoteEditText";
// 定义当前NoteEditText的索引
private int mIndex;
// 定义在删除操作前光标所在的起始位置
private int mSelectionStartBeforeDelete;
// 定义一些URL方案常量
private static final String SCHEME_TEL = "tel:"; // 电话方案
private static final String SCHEME_HTTP = "http:"; // HTTP方案
private static final String SCHEME_EMAIL = "mailto:"; // 电子邮件方案
// 定义一个静态的HashMap用于存储URL方案与对应的资源ID的映射
private static final Map<String, Integer> sSchemaActionResMap = new HashMap<String, Integer>();
// 静态代码块用于初始化sSchemaActionResMap
static {
sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel); // 电话方案对应的资源ID
sSchemaActionResMap.put(SCHEME_HTTP, R.string.note_link_web); // HTTP方案对应的资源ID
sSchemaActionResMap.put(SCHEME_EMAIL, R.string.note_link_email); // 电子邮件方案对应的资源ID
}
// 定义一个接口,用于监听文本视图的变化
public interface OnTextViewChangeListener {
// 当按下删除键且文本为空时删除当前的EditText
void onEditTextDelete(int index, String text);
// 当按下回车键时在当前EditText后添加一个新的EditText
void onEditTextEnter(int index, String text);
// 当文本变化时,隐藏或显示选项项
void onTextChange(int index, boolean hasText);
}
// 定义一个OnTextViewChangeListener类型的成员变量用于存储监听器
private OnTextViewChangeListener mOnTextViewChangeListener;
// 构造函数通过传入上下文来初始化NoteEditText
public NoteEditText(Context context) {
super(context, null); // 调用父类的构造函数
mIndex = 0; // 初始化索引为0
}
// 设置NoteEditText的索引
public void setIndex(int index) {
mIndex = index;
}
// 设置OnTextViewChangeListener监听器
public void setOnTextViewChangeListener(OnTextViewChangeListener listener) {
mOnTextViewChangeListener = listener;
}
// 构造函数通过传入上下文和属性集来初始化NoteEditText
// 这里使用了android.R.attr.editTextStyle作为默认样式
public NoteEditText(Context context, AttributeSet attrs) {
super(context, attrs, android.R.attr.editTextStyle);
}
// 构造函数通过传入上下文、属性集和默认样式来初始化NoteEditText
public NoteEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO: 这个构造函数是自动生成的,可以根据需要进行修改
}
// 重写onTouchEvent方法用于处理触摸事件
@Override
public boolean onTouchEvent(MotionEvent event) {
// 根据触摸事件的类型进行处理
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: // 当用户按下屏幕时
// 获取触摸点的坐标
int x = (int) event.getX();
int y = (int) event.getY();
// 减去左边的内边距和顶部的内边距得到相对于EditText内部的坐标
x -= getTotalPaddingLeft();
y -= getTotalPaddingTop();
// 加上滚动偏移量,得到真实的坐标
x += getScrollX();
y += getScrollY();
// 获取Layout对象它描述了文本的布局
Layout layout = getLayout();
// 根据y坐标获取所在的行号
int line = layout.getLineForVertical(y);
// 根据行号和x坐标获取在该行的字符偏移量
int off = layout.getOffsetForHorizontal(line, x);
// 设置光标位置到触摸点对应的字符位置
Selection.setSelection(getText(), off);
break;
}
// 调用父类的onTouchEvent方法继续处理其他触摸事件
return super.onTouchEvent(event);
}
} // 重写onKeyDown方法用于处理按键按下事件
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_ENTER: // 当按下回车键时
if (mOnTextViewChangeListener != null) {
// 如果设置了监听器但不处理回车键事件直接返回false
return false;
}
break;
case KeyEvent.KEYCODE_DEL: // 当按下删除键时
mSelectionStartBeforeDelete = getSelectionStart(); // 记录删除前的光标位置
break;
default:
break;
}
// 如果没有特别处理调用父类的onKeyDown方法
return super.onKeyDown(keyCode, event);
}
// 重写onKeyUp方法用于处理按键抬起事件
@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;
}
// 如果没有特别处理调用父类的onKeyUp方法
return super.onKeyUp(keyCode, event);
}
// 重写onFocusChanged方法用于处理焦点变化事件
@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); // 否则,通知监听器文本已变化
}
}
// 调用父类的onFocusChanged方法
super.onFocusChanged(focused, direction, previouslyFocusedRect);
}
// 重写onCreateContextMenu方法用于创建上下文菜单
@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); // 获取选择范围内的所有URL链接
if (urls.length == 1) { // 如果只有一个URL链接
int defaultResId = 0;
for(String schema: sSchemaActionResMap.keySet()) { // 遍历所有支持的协议
if(urls[0].getURL().indexOf(schema) >= 0) { // 如果URL包含该协议
defaultResId = sSchemaActionResMap.get(schema); // 获取对应的资源ID
break;
}
}
if (defaultResId == 0) { // 如果没有找到匹配的协议使用默认资源ID
defaultResId = R.string.note_link_other;
}
menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener( // 向菜单添加项并设置点击监听器
new OnMenuItemClickListener() {
public boolean onMenuItemClick(MenuItem item) {
// 调用URLSpan的onClick方法通常用于打开链接
urls[0].onClick(NoteEditText.this);
return true; // 事件已处理
}
});
}
}
// 调用父类的onCreateContextMenu方法
super.onCreateContextMenu(menu);
}

@ -0,0 +1,121 @@
/*
* 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;
import android.content.Context;
import android.database.Cursor;
import android.text.TextUtils;
import net.micode.notes.data.Contact;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.tool.DataUtils;
// NoteItemData类用于封装从数据库查询得到的笔记项数据
public class NoteItemData {
// 定义从数据库中查询笔记项时需要返回的列
static final String [] PROJECTION = new String [] {
NoteColumns.ID, // 笔记ID
NoteColumns.ALERTED_DATE, // 提醒日期
NoteColumns.BG_COLOR_ID, // 背景颜色ID
NoteColumns.CREATED_DATE, // 创建日期
NoteColumns.HAS_ATTACHMENT, // 是否有附件
NoteColumns.MODIFIED_DATE, // 修改日期
NoteColumns.NOTES_COUNT, // 笔记数量(对于文件夹类型有效)
NoteColumns.PARENT_ID, // 父ID如果是文件夹内的笔记则为文件夹ID
NoteColumns.SNIPPET, // 摘要
NoteColumns.TYPE, // 笔记类型(笔记、文件夹、系统笔记等)
NoteColumns.WIDGET_ID, // 小部件ID
NoteColumns.WIDGET_TYPE, // 小部件类型
};
// 列索引常量用于从Cursor中获取对应列的值
private static final int ID_COLUMN = 0;
private static final int ALERTED_DATE_COLUMN = 1;
private static final int BG_COLOR_ID_COLUMN = 2;
private static final int CREATED_DATE_COLUMN = 3;
private static final int HAS_ATTACHMENT_COLUMN = 4;
private static final int MODIFIED_DATE_COLUMN = 5;
private static final int NOTES_COUNT_COLUMN = 6;
private static final int PARENT_ID_COLUMN = 7;
private static final int SNIPPET_COLUMN = 8;
private static final int TYPE_COLUMN = 9;
private static final int WIDGET_ID_COLUMN = 10;
private static final int WIDGET_TYPE_COLUMN = 11;
// 笔记项的属性
private long mId; // 笔记ID
private long mAlertDate; // 提醒日期
private int mBgColorId; // 背景颜色ID
private long mCreatedDate; // 创建日期
private boolean mHasAttachment; // 是否有附件
private long mModifiedDate; // 修改日期
private int mNotesCount; // 笔记数量
private long mParentId; // 父ID
private String mSnippet; // 摘要
private int mType; // 笔记类型
private int mWidgetId; // 小部件ID
private int mWidgetType; // 小部件类型
private String mName; // 联系人姓名(如果是通话记录笔记)
private String mPhoneNumber; // 联系电话(如果是通话记录笔记)
// 笔记项的位置信息
private boolean mIsLastItem; // 是否是最后一个项目
private boolean mIsFirstItem; // 是否是第一个项目
private boolean mIsOnlyOneItem; // 是否是唯一的项目
private boolean mIsOneNoteFollowingFolder; // 是否是一个笔记紧跟在文件夹后面
private boolean mIsMultiNotesFollowingFolder; // 是否是多个笔记紧跟在文件夹后面
// 构造函数通过Cursor初始化NoteItemData对象
public NoteItemData(Context context, Cursor cursor) {
// 从Cursor中读取数据并赋值给成员变量
// ...(省略了具体的赋值代码)
// 如果是通话记录笔记,则通过电话号码获取联系人姓名
// ...(省略了具体的逻辑代码)
// 初始化名称,如果获取不到联系人姓名,则使用空字符串
if (mName == null) {
mName = "";
}
// 检查当前笔记项的位置信息
checkPostion(cursor);
}
// 检查当前笔记项的位置信息
private void checkPostion(Cursor cursor) {
// ...(省略了具体的逻辑代码)
}
// Getter方法用于获取笔记项的各种属性
// ...省略了具体的Getter方法
// 判断是否有提醒
public boolean hasAlert() {
return (mAlertDate > 0);
}
// 判断是否是通话记录笔记
public boolean isCallRecord() {
return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber));
}
// 静态方法用于从Cursor中获取笔记类型
public static int getNoteType(Cursor cursor) {
return cursor.getInt(TYPE_COLUMN);
}
}

@ -0,0 +1,156 @@
/*
* 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.widget;
import android.app.PendingIntent; // 用于处理延迟执行的Intent
import android.appwidget.AppWidgetManager; // 管理应用小部件的类
import android.appwidget.AppWidgetProvider; // 小部件的基类
import android.content.ContentValues; // 用于向ContentProvider提供新值
import android.content.Context; // 提供应用环境信息的类
import android.content.Intent; // 用于不同组件间通信的类
import android.database.Cursor; // 用于遍历查询结果的类
import android.util.Log; // 用于打印日志信息
import android.widget.RemoteViews; // 用于在远程视图中更新小部件的UI
import net.micode.notes.R; // 应用的资源类
import net.micode.notes.data.Notes; // 笔记的数据访问类
import net.micode.notes.data.Notes.NoteColumns; // 笔记数据表的列定义
import net.micode.notes.tool.ResourceParser; // 资源解析工具类
import net.micode.notes.ui.NoteEditActivity; // 编辑笔记的Activity
import net.micode.notes.ui.NotesListActivity; // 笔记列表的Activity
// 定义一个抽象类,用于实现自定义笔记小部件
public abstract class NoteWidgetProvider extends AppWidgetProvider {
// 定义查询笔记时需要的列
public static final String [] PROJECTION = new String [] {
NoteColumns.ID, // 笔记ID
NoteColumns.BG_COLOR_ID, // 笔记背景颜色ID
NoteColumns.SNIPPET // 笔记摘要
};
// 定义列的索引用于从Cursor中获取数据
public static final int COLUMN_ID = 0;
public static final int COLUMN_BG_COLOR_ID = 1;
public static final int COLUMN_SNIPPET = 2;
// 定义日志标签
private static final String TAG = "NoteWidgetProvider";
// 当小部件被删除时调用
@Override
public void onDeleted(Context context, int[] appWidgetIds) {
// 更新被删除小部件对应的笔记中的WIDGET_ID为无效值
ContentValues values = new ContentValues();
values.put(NoteColumns.WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
for (int i = 0; i < appWidgetIds.length; i++) {
context.getContentResolver().update(Notes.CONTENT_NOTE_URI,
values,
NoteColumns.WIDGET_ID + "=?",
new String[] { String.valueOf(appWidgetIds[i])});
}
}
// 根据小部件ID获取笔记信息
private Cursor getNoteWidgetInfo(Context context, int widgetId) {
return context.getContentResolver().query(Notes.CONTENT_NOTE_URI,
PROJECTION,
NoteColumns.WIDGET_ID + "=? AND " + NoteColumns.PARENT_ID + "<>?",
new String[] { String.valueOf(widgetId), String.valueOf(Notes.ID_TRASH_FOLER) },
null);
}
// 更新小部件的UI公开方法
protected void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
update(context, appWidgetManager, appWidgetIds, false);
}
// 更新小部件的UI私有方法支持隐私模式
private void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds,
boolean privacyMode) {
for (int i = 0; i < appWidgetIds.length; i++) {
if (appWidgetIds[i] != AppWidgetManager.INVALID_APPWIDGET_ID) {
// 获取默认背景ID和摘要文本
int bgId = ResourceParser.getDefaultBgId(context);
String snippet = "";
// 创建跳转到编辑笔记Activity的Intent
Intent intent = new Intent(context, NoteEditActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.putExtra(Notes.INTENT_EXTRA_WIDGET_ID, appWidgetIds[i]);
intent.putExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, getWidgetType());
// 查询小部件对应的笔记信息
Cursor c = getNoteWidgetInfo(context, appWidgetIds[i]);
if (c != null && c.moveToFirst()) {
if (c.getCount() > 1) {
// 如果有多条记录打印错误日志并关闭Cursor
Log.e(TAG, "Multiple message with same widget id:" + appWidgetIds[i]);
c.close();
return;
}
// 从Cursor中获取摘要和背景ID并设置Intent的额外数据
snippet = c.getString(COLUMN_SNIPPET);
bgId = c.getInt(COLUMN_BG_COLOR_ID);
intent.putExtra(Intent.EXTRA_UID, c.getLong(COLUMN_ID));
intent.setAction(Intent.ACTION_VIEW);
} else {
// 如果没有找到笔记设置默认摘要文本和Intent动作
snippet = context.getResources().getString(R.string.widget_havenot_content);
intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
}
// 关闭Cursor
if (c != null) {
c.close();
}
// 创建RemoteViews对象并设置背景和资源
RemoteViews rv = new RemoteViews(context.getPackageName(), getLayoutId());
rv.setImageViewResource(R.id.widget_bg_image, getBgResourceId(bgId));
intent.putExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, bgId);
// 根据隐私模式设置不同的PendingIntent
PendingIntent pendingIntent = null;
if (privacyMode) {
// 隐私模式下设置文本为提示信息并创建跳转到笔记列表的PendingIntent
rv.setTextViewText(R.id.widget_text,
context.getString(R.string.widget_under_visit_mode));
pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], new Intent(
context, NotesListActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
} else {
// 非隐私模式下设置文本为摘要并创建跳转到编辑笔记的PendingIntent
rv.setTextViewText(R.id.widget_text, snippet);
pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], intent,
PendingIntent.FLAG_UPDATE_CURRENT);
}
// 设置点击事件
rv.setOnClickPendingIntent(R.id.widget_text, pendingIntent);
// 更新小部件
appWidgetManager.updateAppWidget(appWidgetIds[i], rv);
}
}
}
// 抽象方法用于获取背景资源的ID
protected abstract int getBgResourceId(int bgId);
// 抽象方法用于获取小部件的布局ID
protected abstract int getLayoutId();
// 抽象方法,用于获取小部件的类型
protected abstract int getWidgetType();
}

@ -0,0 +1,56 @@
/*
* 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.widget; // 指定类所在的包名
import android.appwidget.AppWidgetManager; // 导入AppWidgetManager类用于管理应用小部件
import android.content.Context; // 导入Context类用于提供应用环境的全局信息
import net.micode.notes.R; // 导入R类用于访问应用的资源如布局文件、字符串等
import net.micode.notes.data.Notes; // 导入Notes类可能包含笔记的存储、类型定义等
import net.micode.notes.tool.ResourceParser; // 导入ResourceParser工具类用于解析资源
// NoteWidgetProvider_2x类继承自NoteWidgetProvider用于显示2x大小的笔记小部件
public class NoteWidgetProvider_2x extends NoteWidgetProvider {
// 当小部件需要更新时调用此方法
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
// 调用父类的update方法传入上下文、小部件管理器和小部件ID数组
super.update(context, appWidgetManager, appWidgetIds);
}
// 获取当前小部件布局的资源ID
@Override
protected int getLayoutId() {
// 返回R.layout.widget_2x这是定义2x大小小部件布局的XML文件的资源ID
return R.layout.widget_2x;
}
// 根据传入的背景ID获取对应的背景资源ID
@Override
protected int getBgResourceId(int bgId) {
// 使用ResourceParser.WidgetBgResources的getWidget2xBgResource方法根据背景ID获取2x小部件的背景资源ID
return ResourceParser.WidgetBgResources.getWidget2xBgResource(bgId);
}
// 获取当前小部件的类型
@Override
protected int getWidgetType() {
// 返回Notes.TYPE_WIDGET_2X这是一个定义在Notes类中的常量表示2x大小的小部件类型
return Notes.TYPE_WIDGET_2X;
}
}

@ -0,0 +1,65 @@
/*
* 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.widget;
// 导入必要的Android和项目相关的类
import android.appwidget.AppWidgetManager; // 用于管理应用小部件的类
import android.content.Context; // 提供应用环境全局信息的类
import net.micode.notes.R; // 访问应用资源的类
import net.micode.notes.data.Notes; // 可能包含笔记数据的存储、类型定义等
import net.micode.notes.tool.ResourceParser; // 用于解析资源的工具类
// NoteWidgetProvider_4x类继承自NoteWidgetProvider用于显示4x大小的笔记小部件
public class NoteWidgetProvider_4x extends NoteWidgetProvider {
// 当小部件需要更新时,系统会调用此方法
// 参数包括上下文Context、小部件管理器AppWidgetManager和小部件ID数组int[]
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
// 调用父类的update方法将更新请求传递给父类处理
// 父类可能会根据小部件ID数组更新一个或多个小部件
super.update(context, appWidgetManager, appWidgetIds);
}
// 获取当前小部件布局的资源ID
// 这个方法被父类中的某些方法调用用于加载小部件的UI布局
@Override
protected int getLayoutId() {
// 返回R.layout.widget_4x这是定义4x大小小部件布局的XML文件的资源ID
return R.layout.widget_4x;
}
// 根据传入的背景ID获取对应的背景资源ID
// 这个方法可能被父类中的某些方法调用,用于设置小部件的背景
@Override
protected int getBgResourceId(int bgId) {
// 使用ResourceParser.WidgetBgResources的getWidget4xBgResource方法
// 根据背景ID获取4x小部件的背景资源ID
return ResourceParser.WidgetBgResources.getWidget4xBgResource(bgId);
}
// 获取当前小部件的类型
// 这个方法可能被父类中的某些方法调用,用于区分不同类型的小部件
@Override
protected int getWidgetType() {
// 返回Notes.TYPE_WIDGET_4X这是一个定义在Notes类中的常量
// 表示4x大小的小部件类型
return Notes.TYPE_WIDGET_4X;
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,203 @@
/*
* 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;
import android.content.Context;
import android.database.Cursor;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import net.micode.notes.data.Notes;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
// NotesListAdapter类继承自CursorAdapter用于管理笔记列表的展示
public class NotesListAdapter extends CursorAdapter {
// 定义日志标签
private static final String TAG = "NotesListAdapter";
// 上下文对象,用于访问应用资源
private Context mContext;
// 用于记录哪些项被选中的哈希映射,键为位置索引,值为选中状态
private HashMap<Integer, Boolean> mSelectedIndex;
// 笔记总数
private int mNotesCount;
// 标记是否处于选择模式
private boolean mChoiceMode;
// 内部静态类,用于表示应用小部件的属性(此处可能与笔记列表功能不直接相关)
public static class AppWidgetAttribute {
public int widgetId; // 小部件ID
public int widgetType; // 小部件类型
};
// 构造函数,初始化上下文、选中索引映射和笔记计数
public NotesListAdapter(Context context) {
super(context, null); // 调用父类构造函数传入上下文和null的Cursor稍后通过swapCursor设置
mSelectedIndex = new HashMap<Integer, Boolean>(); // 初始化选中索引映射
mContext = context; // 保存上下文
mNotesCount = 0; // 初始化笔记计数为0可能通过其他方式更新
}
// 当需要创建新视图以展示数据时调用此方法
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return new NotesListItem(context); // 创建并返回NotesListItem实例
}
// 当需要绑定视图和数据时调用此方法
@Override
public void bindView(View view, Context context, Cursor cursor) {
if (view instanceof NotesListItem) { // 确保视图是NotesListItem的实例
NoteItemData itemData = new NoteItemData(context, cursor); // 根据Cursor创建NoteItemData实例
((NotesListItem) view).bind(context, itemData, mChoiceMode,
isSelectedItem(cursor.getPosition())); // 绑定数据到视图,包括选择模式和选中状态
}
}
// 设置指定位置的项为选中或未选中状态,并通知适配器数据已更改
public void setCheckedItem(final int position, final boolean checked) {
mSelectedIndex.put(position, checked); // 更新选中索引映射
notifyDataSetChanged(); // 通知适配器数据已更改,触发视图更新
}
// 检查当前是否处于选择模式
public boolean isInChoiceMode() {
return mChoiceMode; // 返回选择模式的状态
}
// 设置选择模式的状态,并清除之前的选中项
public void setChoiceMode(boolean mode) {
mSelectedIndex.clear(); // 清除之前的选中项
mChoiceMode = mode; // 更新选择模式的状态
}
// 全选或全不选笔记项(仅针对笔记类型)
public void selectAll(boolean checked) {
Cursor cursor = getCursor(); // 获取当前的Cursor
for (int i = 0; i < getCount(); i++) { // 遍历所有项
if (cursor.moveToPosition(i)) { // 移动到指定位置
if (NoteItemData.getNoteType(cursor) == Notes.TYPE_NOTE) { // 检查是否为笔记类型
setCheckedItem(i, checked); // 设置选中状态
}
}
}
}
// 获取所有被选中的项的ID集合
public HashSet<Long> getSelectedItemIds() {
HashSet<Long> itemSet = new HashSet<Long>(); // 创建HashSet用于存储选中项的ID
for (Integer position : mSelectedIndex.keySet()) { // 遍历选中索引映射的键集合
if (mSelectedIndex.get(position) == true) { // 检查是否选中
Long id = getItemId(position); // 获取项的ID
if (id == Notes.ID_ROOT_FOLDER) { // 检查是否为根文件夹ID通常不应被选中
Log.d(TAG, "Wrong item id, should not happen"); // 记录日志
} else {
itemSet.add(id); // 将ID添加到HashSet中
}
}
}
return itemSet; // 返回包含所有选中项ID的HashSet
}
} // 获取已选择的小部件集合
public HashSet<AppWidgetAttribute> getSelectedWidget() {
HashSet<AppWidgetAttribute> itemSet = new HashSet<AppWidgetAttribute>(); // 创建一个用于存储小部件属性的HashSet
for (Integer position : mSelectedIndex.keySet()) { // 遍历所有已索引的位置
if (mSelectedIndex.get(position) == true) { // 如果该位置被选中
Cursor c = (Cursor) getItem(position); // 从适配器中获取该位置的Cursor
if (c != null) { // 如果Cursor不为空
AppWidgetAttribute widget = new AppWidgetAttribute(); // 创建一个新的AppWidgetAttribute对象
NoteItemData item = new NoteItemData(mContext, c); // 使用Cursor和上下文创建一个NoteItemData对象
widget.widgetId = item.getWidgetId(); // 设置小部件ID
widget.widgetType = item.getWidgetType(); // 设置小部件类型
itemSet.add(widget); // 将小部件属性添加到HashSet中
/**
* Cursor
*/
} else {
Log.e(TAG, "Invalid cursor"); // 如果Cursor为空记录错误日志
return null; // 返回null表示出错
}
}
}
return itemSet; // 返回包含所有已选择小部件属性的HashSet
}
// 获取已选择项的数量
public int getSelectedCount() {
Collection<Boolean> values = mSelectedIndex.values(); // 获取所有索引值的集合
if (null == values) {
return 0; // 如果集合为空返回0
}
Iterator<Boolean> iter = values.iterator(); // 创建迭代器遍历集合
int count = 0; // 初始化计数器
while (iter.hasNext()) { // 遍历集合
if (true == iter.next()) { // 如果值为true
count++; // 计数器加1
}
}
return count; // 返回计数器的值
}
// 判断是否全部项都被选中
public boolean isAllSelected() {
int checkedCount = getSelectedCount(); // 获取已选择项的数量
return (checkedCount != 0 && checkedCount == mNotesCount); // 如果已选择项的数量不为0且等于总项数则返回true
}
// 判断指定位置的项是否被选中
public boolean isSelectedItem(final int position) {
if (null == mSelectedIndex.get(position)) {
return false; // 如果指定位置的索引值为null返回false
}
return mSelectedIndex.get(position); // 返回指定位置的索引值
}
// 当内容发生变化时调用
@Override
protected void onContentChanged() {
super.onContentChanged(); // 调用父类方法
calcNotesCount(); // 计算笔记数量
}
// 当Cursor改变时调用
@Override
public void changeCursor(Cursor cursor) {
super.changeCursor(cursor); // 调用父类方法
calcNotesCount(); // 计算笔记数量
}
// 计算笔记数量
private void calcNotesCount() {
mNotesCount = 0; // 初始化笔记数量为0
for (int i = 0; i < getCount(); i++) { // 遍历所有项
Cursor c = (Cursor) getItem(i); // 获取当前项的Cursor
if (c != null) { // 如果Cursor不为空
if (NoteItemData.getNoteType(c) == Notes.TYPE_NOTE) { // 如果Cursor指向的笔记类型是NOTE
mNotesCount++; // 笔记数量加1
}
} else {
Log.e(TAG, "Invalid cursor"); // 如果Cursor为空记录错误日志
return; // 退出方法
}
}
}

@ -0,0 +1,147 @@
/*
* 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;
import android.content.Context;
import android.text.format.DateUtils; // 提供日期和时间格式化的工具类
import android.view.View; // Android视图系统的基类
import android.widget.CheckBox; // 复选框控件
import android.widget.ImageView; // 图像视图控件
import android.widget.LinearLayout; // 线性布局控件
import android.widget.TextView; // 文本视图控件
import net.micode.notes.R; // 引用应用的资源文件
import net.micode.notes.data.Notes; // 引用Notes数据类
import net.micode.notes.tool.DataUtils; // 引用数据工具类
import net.micode.notes.tool.ResourceParser.NoteItemBgResources; // 引用背景资源解析类
// NotesListItem类继承自LinearLayout用于表示笔记列表中的每一项
public class NotesListItem extends LinearLayout {
// 成员变量,分别表示警告图标、标题、时间、联系人名称、数据项和复选框
private ImageView mAlert;
private TextView mTitle;
private TextView mTime;
private TextView mCallName;
private NoteItemData mItemData; // 自定义类,用于存储笔记项的数据
private CheckBox mCheckBox;
// 构造函数初始化NotesListItem
public NotesListItem(Context context) {
super(context); // 调用父类的构造函数
inflate(context, R.layout.note_item, this); // 加载布局文件
// 初始化各个控件
mAlert = (ImageView) findViewById(R.id.iv_alert_icon);
mTitle = (TextView) findViewById(R.id.tv_title);
mTime = (TextView) findViewById(R.id.tv_time);
mCallName = (TextView) findViewById(R.id.tv_name);
mCheckBox = (CheckBox) findViewById(android.R.id.checkbox); // 注意这里使用的是Android系统提供的ID
}
// bind方法用于绑定数据和设置UI
public void bind(Context context, NoteItemData data, boolean choiceMode, boolean checked) {
// 根据choiceMode和数据类型设置复选框的可见性
if (choiceMode && data.getType() == Notes.TYPE_NOTE) {
mCheckBox.setVisibility(View.VISIBLE);
mCheckBox.setChecked(checked);
} else {
mCheckBox.setVisibility(View.GONE);
}
mItemData = data; // 保存数据项
// 根据数据项的不同类型设置UI
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
// 如果是通话记录文件夹
mCallName.setVisibility(View.GONE);
mAlert.setVisibility(View.VISIBLE);
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); // 设置标题的文本样式
// 设置标题文本,包括文件夹名称和文件数量
mTitle.setText(context.getString(R.string.call_record_folder_name)
+ context.getString(R.string.format_folder_files_count, data.getNotesCount()));
mAlert.setImageResource(R.drawable.call_record); // 设置警告图标的资源
} else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) {
// 如果是通话记录文件夹下的项目
mCallName.setVisibility(View.VISIBLE);
mCallName.setText(data.getCallName()); // 设置联系人名称
mTitle.setTextAppearance(context,R.style.TextAppearanceSecondaryItem); // 设置标题的文本样式
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet())); // 设置标题文本
// 根据是否有警告设置警告图标的可见性
if (data.hasAlert()) {
mAlert.setImageResource(R.drawable.clock);
mAlert.setVisibility(View.VISIBLE);
} else {
mAlert.setVisibility(View.GONE);
}
} else {
// 其他情况
mCallName.setVisibility(View.GONE);
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); // 设置标题的文本样式
if (data.getType() == Notes.TYPE_FOLDER) {
// 如果是文件夹
mTitle.setText(data.getSnippet()
+ context.getString(R.string.format_folder_files_count,
data.getNotesCount())); // 设置标题文本,包括文件夹名称和文件数量
mAlert.setVisibility(View.GONE); // 隐藏警告图标
} else {
// 如果是笔记
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet())); // 设置标题文本
// 根据是否有警告设置警告图标的可见性
if (data.hasAlert()) {
mAlert.setImageResource(R.drawable.clock);
mAlert.setVisibility(View.VISIBLE);
} else {
mAlert.setVisibility(View.GONE);
}
}
}
// 设置时间文本
mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate()));
// 设置背景
setBackground(data);
}
// 根据数据项设置背景
private void setBackground(NoteItemData data) {
int id = data.getBgColorId(); // 获取背景颜色ID
if (data.getType() == Notes.TYPE_NOTE) {
// 如果是笔记
if (data.isSingle() || data.isOneFollowingFolder()) {
// 如果是单独一条笔记或者后面紧跟着一个文件夹
setBackgroundResource(NoteItemBgResources.getNoteBgSingleRes(id));
} else if (data.isLast()) {
// 如果是最后一条笔记
setBackgroundResource(NoteItemBgResources.getNoteBgLastRes(id));
} else if (data.isFirst() || data.isMultiFollowingFolder()) {
// 如果是第一条笔记或者前面有多个文件夹
setBackgroundResource(NoteItemBgResources.getNoteBgFirstRes(id));
} else {
// 其他情况
setBackgroundResource(NoteItemBgResources.getNoteBgNormalRes(id));
}
} else {
// 如果是文件夹
setBackgroundResource(NoteItemBgResources.getFolderBgRes());
}
}
// 获取当前绑定的数据项
public NoteItemData getItemData() {
return mItemData;
}
}

@ -0,0 +1,477 @@
/*
* 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;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.ActionBar;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceActivity;
import android.preference.PreferenceCategory;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
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.NoteColumns;
import net.micode.notes.gtask.remote.GTaskSyncService;
public class NotesPreferenceActivity extends PreferenceActivity {
public static final String PREFERENCE_NAME = "notes_preferences";
public static final String PREFERENCE_SYNC_ACCOUNT_NAME = "pref_key_account_name";
public static final String PREFERENCE_LAST_SYNC_TIME = "pref_last_sync_time";
public static final String PREFERENCE_SET_BG_COLOR_KEY = "pref_key_bg_random_appear";
private static final String PREFERENCE_SYNC_ACCOUNT_KEY = "pref_sync_account_key";
private static final String AUTHORITIES_FILTER_KEY = "authorities";
private PreferenceCategory mAccountCategory;
private GTaskReceiver mReceiver;
private Account[] mOriAccounts;
private boolean mHasAddedAccount;
// 重写onCreate方法这是Activity生命周期中的一个重要方法用于初始化Activity
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle); // 调用父类的onCreate方法
// 设置应用图标作为导航按钮,并启用向上导航功能
getActionBar().setDisplayHomeAsUpEnabled(true);
// 从资源文件中加载偏好设置布局
addPreferencesFromResource(R.xml.preferences);
// 获取同步账户的分类偏好设置项
mAccountCategory = (PreferenceCategory) findPreference(PREFERENCE_SYNC_ACCOUNT_KEY);
// 实例化广播接收器用于接收来自GTaskSyncService的广播
mReceiver = new GTaskReceiver();
// 创建IntentFilter并添加要监听的广播动作
IntentFilter filter = new IntentFilter();
filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME);
// 注册广播接收器
registerReceiver(mReceiver, filter);
// 初始化原始账户数组为空
mOriAccounts = null;
// 使用LayoutInflater从布局资源中创建视图并将其作为头部添加到偏好设置的列表中
View header = LayoutInflater.from(this).inflate(R.layout.settings_header, null);
getListView().addHeaderView(header, null, true);
}
// 重写onResume方法当Activity重新进入用户视野时调用
@Override
protected void onResume() {
super.onResume();
// 如果用户添加了新账户,则自动设置同步账户
if (mHasAddedAccount) {
// 获取Google账户数组
Account[] accounts = getGoogleAccounts();
// 如果原始账户数组不为空且当前账户数量多于原始账户数量
if (mOriAccounts != null && accounts.length > mOriAccounts.length) {
// 遍历当前账户数组
for (Account accountNew : accounts) {
boolean found = false; // 标记是否找到匹配的原始账户
// 遍历原始账户数组
for (Account accountOld : mOriAccounts) {
// 如果账户名称相同,则标记为找到并跳出循环
if (TextUtils.equals(accountOld.name, accountNew.name)) {
found = true;
break;
}
}
// 如果未找到匹配的原始账户,则设置新账户为同步账户并跳出循环
if (!found) {
setSyncAccount(accountNew.name);
break;
}
}
}
}
// 刷新用户界面
refreshUI();
}
// 重写onDestroy方法当Activity即将被销毁时调用
@Override
protected void onDestroy() {
// 如果广播接收器不为空,则注销广播接收器
if (mReceiver != null) {
unregisterReceiver(mReceiver);
}
super.onDestroy(); // 调用父类的onDestroy方法
}
// 私有方法,用于加载账户偏好设置
private void loadAccountPreference() {
// 清空同步账户分类下的所有偏好设置项
mAccountCategory.removeAll();
// 创建一个新的偏好设置项
Preference accountPref = new Preference(this);
// 获取当前设置的同步账户名称
final String defaultAccount = getSyncAccountName(this);
// 设置偏好设置项的标题和摘要
accountPref.setTitle(getString(R.string.preferences_account_title));
accountPref.setSummary(getString(R.string.preferences_account_summary));
// 设置偏好设置项的点击监听器
accountPref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
// 如果当前不在同步中
if (!GTaskSyncService.isSyncing()) {
// 如果当前没有设置同步账户,则显示选择账户对话框
if (TextUtils.isEmpty(defaultAccount)) {
showSelectAccountAlertDialog();
} else {
// 如果已经设置了同步账户,则显示更改账户确认对话框
showChangeAccountConfirmAlertDialog();
}
} else {
// 如果当前在同步中则显示无法更改账户的Toast提示
Toast.makeText(NotesPreferenceActivity.this,
R.string.preferences_toast_cannot_change_account, Toast.LENGTH_SHORT)
.show();
}
return true; // 表示事件已处理
}
});
}
// 将一个偏好项(可能是账户相关的设置)添加到偏好分类中
mAccountCategory.addPreference(accountPref);
// 加载并设置同步按钮的状态和文本
private void loadSyncButton() {
// 从布局文件中找到同步按钮和最后同步时间显示的TextView
Button syncButton = (Button) findViewById(R.id.preference_sync_button);
TextView lastSyncTimeView = (TextView) findViewById(R.id.preference_sync_status_textview); // 注意这里的ID拼写错误已修正
// 根据是否正在同步来设置按钮的文本和点击事件
if (GTaskSyncService.isSyncing()) { // 如果正在同步
syncButton.setText(getString(R.string.preferences_button_sync_cancel)); // 设置按钮文本为取消同步
syncButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
GTaskSyncService.cancelSync(NotesPreferenceActivity.this); // 点击时取消同步
}
});
} else { // 如果不在同步
syncButton.setText(getString(R.string.preferences_button_sync_immediately)); // 设置按钮文本为立即同步
syncButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
GTaskSyncService.startSync(NotesPreferenceActivity.this); // 点击时开始同步
}
});
}
// 根据是否有同步账户来启用或禁用同步按钮
syncButton.setEnabled(!TextUtils.isEmpty(getSyncAccountName(this)));
// 设置最后同步时间的显示
if (GTaskSyncService.isSyncing()) { // 如果正在同步
lastSyncTimeView.setText(GTaskSyncService.getProgressString()); // 显示同步进度
lastSyncTimeView.setVisibility(View.VISIBLE); // 显示TextView
} else { // 如果不在同步
long lastSyncTime = getLastSyncTime(this); // 获取最后同步时间
if (lastSyncTime != 0) { // 如果有最后同步时间
lastSyncTimeView.setText(getString(R.string.preferences_last_sync_time,
DateFormat.format(getString(R.string.preferences_last_sync_time_format),
lastSyncTime))); // 格式化并显示最后同步时间
lastSyncTimeView.setVisibility(View.VISIBLE); // 显示TextView
} else { // 如果没有最后同步时间
lastSyncTimeView.setVisibility(View.GONE); // 隐藏TextView
}
}
}
// 刷新用户界面,包括加载账户偏好和同步按钮的状态
private void refreshUI() {
loadAccountPreference(); // 加载账户偏好(方法未在代码段中给出)
loadSyncButton(); // 加载并设置同步按钮
}
// 显示一个对话框让用户选择一个同步账户
private void showSelectAccountAlertDialog() {
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
// 自定义对话框的标题部分
View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null);
TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title);
titleTextView.setText(getString(R.string.preferences_dialog_select_account_title)); // 设置标题文本
TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle);
subtitleTextView.setText(getString(R.string.preferences_dialog_select_account_tips)); // 设置副标题文本
dialogBuilder.setCustomTitle(titleView); // 设置自定义标题
dialogBuilder.setPositiveButton(null, null); // 不设置默认的确定按钮及其事件
// 获取Google账户列表和当前同步的账户
Account[] accounts = getGoogleAccounts();
String defAccount = getSyncAccountName(this);
// 初始化一些变量
mOriAccounts = accounts; // 原始账户列表
mHasAddedAccount = false; // 是否添加了新账户(未在代码段中明确使用)
if (accounts.length > 0) { // 如果有账户
CharSequence[] items = new CharSequence[accounts.length]; // 创建账户名称数组
final CharSequence[] itemMapping = items; // 创建一个引用,用于在点击事件中访问
int checkedItem = -1; // 默认没有选中的项
int index = 0;
for (Account account : accounts) { // 遍历账户列表
if (TextUtils.equals(account.name, defAccount)) { // 如果当前账户是默认同步账户
checkedItem = index; // 设置选中项
}
items[index++] = account.name; // 添加账户名称到数组
}
dialogBuilder.setSingleChoiceItems(items, checkedItem, // 设置单选列表项和默认选中项
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
setSyncAccount(itemMapping[which].toString()); // 设置选中的账户为同步账户
dialog.dismiss(); // 关闭对话框
refreshUI(); // 刷新用户界面
}
});
}
// 创建一个对话框,用于添加账户
// 首先通过LayoutInflater从当前上下文this加载add_account_text布局
View addAccountView = LayoutInflater.from(this).inflate(R.layout.add_account_text, null);
// 设置对话框的内容视图为addAccountView
dialogBuilder.setView(addAccountView);
// 显示对话框
final AlertDialog dialog = dialogBuilder.show();
// 为addAccountView设置点击监听器
addAccountView.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// 标记账户已添加
mHasAddedAccount = true;
// 创建一个意图,用于打开添加账户的设置页面
Intent intent = new Intent("android.settings.ADD_ACCOUNT_SETTINGS");
// 为意图添加额外的数据指定账户类型过滤器为gmail-lsGmail账户
intent.putExtra(AUTHORITIES_FILTER_KEY, new String[] {"gmail-ls"});
// 启动活动以响应意图,并等待结果(这里请求码使用了-1通常应使用有意义的正整数
startActivityForResult(intent, -1);
// 关闭对话框
dialog.dismiss();
}
});
// 显示一个确认更改账户的对话框
private void showChangeAccountConfirmAlertDialog() {
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
// 通过LayoutInflater加载account_dialog_title布局
View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null);
// 设置标题文本
TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title);
titleTextView.setText(getString(R.string.preferences_dialog_change_account_title, getSyncAccountName(this)));
// 设置副标题文本
TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle);
subtitleTextView.setText(getString(R.string.preferences_dialog_change_account_warn_msg));
// 设置自定义标题视图
dialogBuilder.setCustomTitle(titleView);
// 设置对话框的选项菜单
CharSequence[] menuItemArray = new CharSequence[] {
getString(R.string.preferences_menu_change_account),
getString(R.string.preferences_menu_remove_account),
getString(R.string.preferences_menu_cancel)
};
// 为选项菜单设置点击监听器
dialogBuilder.setItems(menuItemArray, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
// 根据用户的选择执行不同的操作
if (which == 0) {
showSelectAccountAlertDialog(); // 显示选择账户的对话框
} else if (which == 1) {
removeSyncAccount(); // 移除同步账户
refreshUI(); // 刷新用户界面
}
}
});
// 显示对话框
dialogBuilder.show();
}
// 获取设备上所有的Google账户
private Account[] getGoogleAccounts() {
AccountManager accountManager = AccountManager.get(this);
return accountManager.getAccountsByType("com.google"); // 获取Google类型的账户
}
// 设置同步账户
private void setSyncAccount(String account) {
// 如果当前设置的同步账户与传入的账户不同
if (!getSyncAccountName(this).equals(account)) {
// 更新SharedPreferences中的同步账户名称
SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
if (account != null) {
editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, account);
} else {
editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, "");
}
editor.commit();
// 清理上一次的同步时间
setLastSyncTime(this, 0);
// 清理本地与Google任务相关的信息
new Thread(new Runnable() {
public void run() {
ContentValues values = new ContentValues();
values.put(NoteColumns.GTASK_ID, ""); // 清除GTASK_ID
values.put(NoteColumns.SYNC_ID, 0); // 重置SYNC_ID
getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null); // 更新数据库
}
}).start();
// 显示成功设置账户的Toast消息
Toast.makeText(NotesPreferenceActivity.this,
getString(R.string.preferences_toast_success_set_accout, account),
Toast.LENGTH_SHORT).show();
}
}
// 移除同步账户
private void removeSyncAccount() {
// 从SharedPreferences中移除同步账户名称和最后一次同步时间
SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
if (settings.contains(PREFERENCE_SYNC_ACCOUNT_NAME)) {
editor.remove(PREFERENCE_SYNC_ACCOUNT_NAME);
}
if (settings.contains(PREFERENCE_LAST_SYNC_TIME)) {
editor.remove(PREFERENCE_LAST_SYNC_TIME);
}
editor.commit();
// 清理本地与Google任务相关的信息
new Thread(new Runnable() {
public void run() {
ContentValues values = new ContentValues();
values.put(NoteColumns.GTASK_ID, ""); // 清除GTASK_ID
values.put(NoteColumns.SYNC_ID, 0); // 重置SYNC_ID
getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null); // 更新数据库
}
}).start();
}
// 定义一个方法用于从SharedPreferences中获取同步账户名称
public static String getSyncAccountName(Context context) {
// 使用context获取SharedPreferences实例PREFERENCE_NAME是偏好文件的名称Context.MODE_PRIVATE表示只有当前应用可以访问
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
// 从偏好设置中获取字符串类型的同步账户名称,如果不存在则返回空字符串""
return settings.getString(PREFERENCE_SYNC_ACCOUNT_NAME, "");
}
// 定义一个方法用于设置最后同步时间到SharedPreferences中
public static void setLastSyncTime(Context context, long time) {
// 获取SharedPreferences实例
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
// 获取SharedPreferences.Editor对象用于修改偏好设置
SharedPreferences.Editor editor = settings.edit();
// 将最后同步时间保存到偏好设置中
editor.putLong(PREFERENCE_LAST_SYNC_TIME, time);
// 提交更改
editor.commit();
}
// 定义一个方法用于从SharedPreferences中获取最后同步时间
public static long getLastSyncTime(Context context) {
// 获取SharedPreferences实例
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
// 从偏好设置中获取最后同步时间如果不存在则返回0
return settings.getLong(PREFERENCE_LAST_SYNC_TIME, 0);
}
// 定义一个内部类GTaskReceiver它继承自BroadcastReceiver
private class GTaskReceiver extends BroadcastReceiver {
// 当接收到广播时调用此方法
@Override
public void onReceive(Context context, Intent intent) {
// 刷新用户界面
refreshUI();
// 检查广播是否包含表示正在同步的额外数据
if (intent.getBooleanExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_IS_SYNCING, false)) {
// 获取同步状态文本视图,并设置其文本为广播中的进度消息
TextView syncStatus = (TextView) findViewById(R.id.preference_sync_status_textview);
syncStatus.setText(intent
.getStringExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_PROGRESS_MSG));
}
}
}
// 定义一个方法,用于处理菜单项的点击事件
public boolean onOptionsItemSelected(MenuItem item) {
// 根据点击的菜单项ID执行不同的操作
switch (item.getItemId()) {
// 如果是点击了应用的“返回”图标(通常位于左上角)
case android.R.id.home:
// 创建一个Intent用于启动NotesListActivity
Intent intent = new Intent(this, NotesListActivity.class);
// 添加标志,以清除当前活动之上的所有活动
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
// 启动目标Activity
startActivity(intent);
// 表示事件已处理
return true;
// 默认情况下返回false表示事件未处理
default:
return false;
}
}
Loading…
Cancel
Save