diff --git a/doc/小米便签开源代码的泛读报告.docx b/doc/小米便签开源代码的泛读报告.docx index 1fe4f39..52e47f6 100644 Binary files a/doc/小米便签开源代码的泛读报告.docx and b/doc/小米便签开源代码的泛读报告.docx differ diff --git a/src/代码- 注释.txt b/src/代码- 注释.txt new file mode 100644 index 0000000..fa16e4e --- /dev/null +++ b/src/代码- 注释.txt @@ -0,0 +1,1244 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + android:dataExtractionRules="@xml/data_extraction_rules" + + android:fullBackupContent="@xml/backup_rules" + + android:icon="@mipmap/ic_launcher" + + android:label="@string/app_name" + + android:roundIcon="@mipmap/ic_launcher_round" + + android:supportsRtl="true" + + android:theme="@style/Theme.Notesmaster" + + tools:targetApi="31"> + + + + android:configChanges="keyboardHidden|orientation|screenSize" + + android:label="@string/app_name" + + android:launchMode="singleTop" + + android:theme="@style/NoteTheme" + + android:uiOptions="splitActionBarWhenNarrow" + + android:windowSoftInputMode="adjustPan" + + android:exported="true"> + + + + + + + + + + + + + android:configChanges="keyboardHidden|orientation|screenSize" + + android:launchMode="singleTop" + + android:theme="@style/NoteTheme" + + android:exported="true"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + android:resource="@xml/searchable" /> + + + + + + android:authorities="micode_notes" + + android:multiprocess="true" /> + + + + android:label="@string/app_widget2x2" + + android:exported="true"> + + + + + + + + + + + + + android:resource="@xml/widget_2x_info" /> + + + + android:label="@string/app_widget4x4" + + android:exported="true"> + + + + + + + + + + + + + android:resource="@xml/widget_4x_info" /> + + + + + android:exported="true"> + + + + + + + + + android:process=":remote" > + + + + + android:label="@string/app_name" + + android:launchMode="singleInstance" + + android:theme="@android:style/Theme.Holo.Wallpaper.NoTitleBar" > + + + + + android:label="@string/preferences_title" + + android:launchMode="singleTop" + + android:theme="@android:style/Theme.Holo.Light" > + + + + + android:exported="false" > + + + + + android:value=".ui.NoteEditActivity" /> + + + + + + + + + + + + + + + + + + +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + +/* + * 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 TYPE, 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; + + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + // 使用应用图标作为导航 + getActionBar().setDisplayHomeAsUpEnabled(true); + // 从资源文件中添加偏好设置 + addPreferencesFromResource(R.xml.preferences); + // 找到偏好设置中的账户类别 + mAccountCategory = (PreferenceCategory) findPreference(PREFERENCE_SYNC_ACCOUNT_KEY); + // 创建广播接收器 + mReceiver = new GTaskReceiver(); + // 创建意图过滤器 + IntentFilter filter = new IntentFilter(); + // 添加同步服务的广播名称到过滤器 + filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME); + // 注册广播接收器 + registerReceiver(mReceiver, filter); + // 初始化为 null + mOriAccounts = null; + // 从布局文件中加载头部视图 + View header = LayoutInflater.from(this).inflate(R.layout.settings_header, null); + // 将头部视图添加到列表视图中 + getListView().addHeaderView(header, null, true); + } + + @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; + } + } + } + } + // 刷新 UI + refreshUI(); + } + + @Override + protected void onDestroy() { + if (mReceiver!= null) { + // 注销广播接收器 + unregisterReceiver(mReceiver); + } + super.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() { + public boolean onPreferenceClick(Preference preference) { + // 如果同步服务未同步 + if (!GTaskSyncService.isSyncing()) { + if (TextUtils.isEmpty(defaultAccount)) { + // 第一次设置账户,显示选择账户的对话框 + showSelectAccountAlertDialog(); + } else { + // 如果已经设置过账户,提示更改账户的风险 + showChangeAccountConfirmAlertDialog(); + } + } else { + // 显示无法更改账户的提示 + Toast.makeText(NotesPreferenceActivity.this, + R.string.preferences_toast_cannot_change_account, Toast.LENGTH_SHORT) + .show(); + } + return true; + } + }); + // 将偏好设置添加到账户类别中 + mAccountCategory.addPreference(accountPref); + } + + private void loadSyncButton() { + // 找到同步按钮 + Button syncButton = (Button) findViewById(R.id.preference_sync_button); + // 找到最后同步时间的文本视图 + TextView lastSyncTimeView = (TextView) findViewById(R.id.prefenerece_sync_status_textview); + // 设置按钮状态 + 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); + } 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); + } else { + // 隐藏最后同步时间 + lastSyncTimeView.setVisibility(View.GONE); + } + } + } + + 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(); + } + }); + } + // 从布局文件中加载添加账户视图 + View addAccountView = LayoutInflater.from(this).inflate(R.layout.add_account_text, null); + // 将添加账户视图添加到对话框 + dialogBuilder.setView(addAccountView); + // 显示对话框 + final AlertDialog dialog = dialogBuilder.show(); + // 为添加账户视图添加点击监听器 + addAccountView.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + // 标记已添加新账户 + mHasAddedAccount = true; + // 创建添加账户的意图 + Intent intent = new Intent("android.settings.ADD_ACCOUNT_SETTINGS"); + intent.putExtra(AUTHORITIES_FILTER_KEY, new String[] { + "gmail-ls" + }); + // 启动添加账户的活动 + startActivityForResult(intent, -1); + dialog.dismiss(); + } + }); + } + + private void showChangeAccountConfirmAlertDialog() { + // 创建对话框构建器 + 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_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(); + } + + private Account[] getGoogleAccounts() { + // 获取账户管理器 + AccountManager accountManager = AccountManager.get(this); + // 获取 Google 账户 + return accountManager.getAccountsByType("com.google"); + } + + private void setSyncAccount(String account) { + // 如果新账户和当前账户不同 + if (!getSyncAccountName(this).equals(account)) { + // 获取偏好设置 + 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); + // 清除本地 GTask 相关信息 + new Thread(new Runnable() { + public void run() { + ContentValues values = new ContentValues(); + values.put(NoteColumns.GTASK_ID, ""); + values.put(NoteColumns.SYNC_ID, 0); + getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null); + } + }).start(); + // 显示设置账户成功的提示 + Toast.makeText(NotesPreferenceActivity.this, + getString(R.string.preferences_toast_success_set_accout, account), + Toast.LENGTH_SHORT).show(); + } + } + + private void removeSyncAccount() { + // 获取偏好设置 + 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(); + // 清除本地 GTask 相关信息 + new Thread(new Runnable() { + public void run() { + ContentValues values = new ContentValues(); + values.put(NoteColumns.GTASK_ID, ""); + values.put(NoteColumns.SYNC_ID, 0); + getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null); + } + }).start(); + } + + public static String getSyncAccountName(Context context) { + // 获取同步账户名称 + SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, + Context.MODE_PRIVATE); + return settings.getString(PREFERENCE_SYNC_ACCOUNT_NAME, ""); + } + + public static void setLastSyncTime(Context context, long time) { + // 设置最后同步时间 + SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, + Context.MODE_PRIVATE); + SharedPreferences.Editor editor = settings.edit(); + editor.putLong(PREFERENCE_LAST_SYNC_TIME, time); + editor.commit(); + } + + public static long getLastSyncTime(Context context) { + // 获取最后同步时间 + SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, + Context.MODE_PRIVATE); + return settings.getLong(PREFERENCE_LAST_SYNC_TIME, 0); + } + + private class GTaskReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + // 刷新 UI + refreshUI(); + if (intent.getBooleanExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_IS_SYNCING, false)) { + // 获取同步状态文本视图 + TextView syncStatus = (TextView) findViewById(R.id.prefenerece_sync_status_textview); + // 显示同步进度 + syncStatus.setText(intent + .getStringExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_PROGRESS_MSG)); + } + } + } + + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + // 点击导航图标时的操作 + Intent intent = new Intent(this, NotesListActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(intent); + return true; + default: + return false; + } + } +} + +------------------------------------------------------------------------------------------------------------------ +- + +/* + * 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 code 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 TYPE, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +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; + // 24 小时制的小时选择器的最小值 + private static final int HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW = 0; + // 24 小时制的小时选择器的最大值 + private static final int HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW = 23; + // 12 小时制的小时选择器的最小值 + private static final int HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW = 1; + // 12 小时制的小时选择器的最大值 + 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; + // AM/PM 选择器的最小值 + private static final int AMPM_SPINNER_MIN_VAL = 0; + // AM/PM 选择器的最大值 + private static final int AMPM_SPINNER_MAX_VAL = 1; + + // 日期选择器 + private final NumberPicker mDateSpinner; + // 小时选择器 + private final NumberPicker mHourSpinner; + // 分钟选择器 + private final NumberPicker mMinuteSpinner; + // AM/PM 选择器 + private final NumberPicker mAmPmSpinner; + // 存储日期的 Calendar 对象 + private Calendar mDate; + // 存储日期显示值的数组 + private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK]; + // 表示是否为 AM + 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(); + } + }; + + // 小时选择器的监听器 + private NumberPicker.OnValueChangeListener mOnHourChangedListener = new NumberPicker.OnValueChangeListener() { + @Override + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + boolean isDateChanged = false; + Calendar cal = Calendar.getInstance(); + if (!mIs24HourView) { + // 对于 12 小时制,处理跨 AM/PM 转换和日期变化 + 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 { + // 对于 24 小时制,处理跨天的时间变化 + 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; + } + } + int newHour = mHourSpinner.getValue() % HOURS_IN_HALF_DAY + (mIsAm? 0 : HOURS_IN_HALF_DAY); + mDate.set(Calendar.HOUR_OF_DAY, newHour); + onDateTimeChanged(); + if (isDateChanged) { + setCurrentYear(cal.get(Calendar.YEAR)); + setCurrentMonth(cal.get(Calendar.MONTH)); + setCurrentDay(cal.get(Calendar.DAY_OF_MONTH)); + } + } + }; + + // 分钟选择器的监听器 + private NumberPicker.OnValueChangeListener mOnMinuteChangedListener = new NumberPicker.OnValueChangeListener() { + @Override + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + int minValue = mMinuteSpinner.getMinValue(); + int maxValue = mMinuteSpinner.getMaxValue(); + int offset = 0; + // 处理分钟选择器的跨小时变化 + if (oldVal == maxValue && newVal == minValue) { + offset += 1; + } else if (oldVal == minValue && newVal == maxValue) { + 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(); + } + } + mDate.set(Calendar.MINUTE, newVal); + onDateTimeChanged(); + } + }; + + // AM/PM 选择器的监听器 + private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() { + @Override + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + mIsAm =!mIsAm; + // 处理 AM/PM 切换时的时间调整 + 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(); + } + }; + + // 日期时间更改监听器接口 + 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 小时制 + public DateTimePicker(Context context, long date, boolean is24HourView) { + super(context); + mDate = Calendar.getInstance(); + mInitialising = true; + mIsAm = getCurrentHourOfDay() >= HOURS_IN_HALF_DAY; + // 填充布局 + inflate(context, R.layout.datetime_picker, this); + + mDateSpinner = (NumberPicker) findViewById(R.id.date); + mDateSpinner.setMinValue(DATE_SPINNER_MIN_VAL); + mDateSpinner.setMaxValue(DATE_SPINNER_MAX_VAL); + mDateSpinner.setOnValueChangedListener(mOnDateChangedListener); + + mHourSpinner = (NumberPicker) findViewById(R.id.hour); + mHourSpinner.setOnValueChangedListener(mOnHourChangedListener); + mMinuteSpinner = (NumberPicker) findViewById(R.id.minute); + mMinuteSpinner.setMinValue(MINUT_SPINNER_MIN_VAL); + mMinuteSpinner.setMaxValue(MINUT_SPINNER_MAX_VAL); + mMinuteSpinner.setOnLongPressUpdateInterval(100); + mMinuteSpinner.setOnValueChangedListener(mOnMinuteChangedListener); + + 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(); + + set24HourView(is24HourView); + + // 设置为当前时间 + setCurrentDate(date); + + setEnabled(isEnabled()); + + // 设置内容描述 + mInitialising = false; + } + + // 设置启用状态 + @Override + public void setEnabled(boolean enabled) { + if (mIsEnabled == enabled) { + return; + } + super.setEnabled(enabled); + mDateSpinner.setEnabled(enabled); + mMinuteSpinner.setEnabled(enabled); + mHourSpinner.setEnabled(enabled); + mAmPmSpinner.setEnabled(enabled); + mIsEnabled = enabled; + } + + // 获取启用状态 + @Override + public boolean isEnabled() { + return mIsEnabled; + } + + // 获取当前日期的毫秒表示 + /** + * Get the current date in millis + * + * @return the current date in millis + */ + public long getCurrentDateInTimeMillis() { + return mDate.getTimeInMillis(); + } + + // 设置当前日期(以毫秒表示) + /** + * Set the current date + * + * @param date The current date in millis + */ + public void setCurrentDate(long date) { + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(date); + setCurrentDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), + cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE)); + } + + // 设置当前日期 + /** + * Set the current date + * + * @param year The current year + * @param month The current month + * @param dayOfMonth The current dayOfMonth + * @param hourOfDay The current hourOfDay + * @param minute The current minute + */ + public void setCurrentDate(int year, int month, + int dayOfMonth, int hourOfDay, int minute) { + setCurrentYear(year); + setCurrentMonth(month); + setCurrentDay(dayOfMonth); + setCurrentHour(hourOfDay); + setCurrentMinute(minute); + } + + // 获取当前年份 + /** + * Get current year + * + * @return The current year + */ + public int getCurrentYear() { + return mDate.get(Calendar.YEAR); + } + + // 设置当前年份 + /** + * Set current year + * + * @param year The current year + */ + public void setCurrentYear(int year) { + if (!mInitialising && year == getCurrentYear()) { + return; + } + mDate.set(Calendar.YEAR, year); + updateDateControl(); + onDateTimeChanged(); + } + + // 获取当前月份 + /** + * Get current month in the year + * + * @return The current month in the year + */ + public int getCurrentMonth() { + return mDate.get(Calendar.MONTH); + } + + // 设置当前月份 + /** + * Set current month in the year + * + * @param month The month in the year + */ + public void setCurrentMonth(int month) { + if (!mInitialising && month == getCurrentMonth()) { + return; + } + mDate.set(Calendar.MONTH, month); + updateDateControl(); + onDateTimeChanged(); + } + + // 获取当前日期的日 + /** + * Get current day of the month + * + * @return The day of the month + */ + public int getCurrentDay() { + return mDate.get(Calendar.DAY_OF_MONTH); + } + + // 设置当前日期的日 + /** + * Set current day of the month + * + * @param dayOfMonth The day of the month + */ + public void setCurrentDay(int dayOfMonth) { + if (!mInitialising && dayOfMonth == getCurrentDay()) { + return; + } + mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); + updateDateControl(); + onDateTimeChanged(); + } + + // 获取 24 小时制的当前小时 + /** + * Get current hour in 24 hour mode, in the range (0~23) + * @return The current hour in 24 hour mode + */ + public int getCurrentHourOfDay() { + return mDate.get(Calendar.HOUR_OF_DAY); + } + + // 获取当前小时(根据是否 24 小时制调整) + private int getCurrentHour() { + if (mIs24HourView){ + return getCurrentHourOfDay(); + } else { + int hour = getCurrentHourOfDay(); + if (hour > HOURS_IN_HALF_DAY) { + return hour - HOURS_IN_HALF_DAY; + } else { + return hour == 0? HOURS_IN_HALF_DAY : hour; + } + } + } + + // 设置 24 小时制的当前小时 + /** + * 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(); + } + + // 判断是否为 24 小时制 + /** + * @return true if this is in 24 hour view else false. + */ + public boolean is24HourView () { + return mIs24HourView; + } + + // 设置是否为 24 小时制 + /** + * Set whether in 24 hour or AM/PM mode. + * + * @param is24HourView True for 24 hour mode. False for AM/PM mode. + */ + public void set24HourView(boolean is24HourView) { + if (mIs24HourView == is24HourView) { + return; + } + mIs24HourView = is24HourView; + mAmPmSpinner.setVisibility(is24HourView? View.GONE : View.VISIBLE); + int hour = getCurrentHourOfDay(); + updateHourControl(); + setCurrentHour(hour); + updateAmPmControl(); + } + + // 更新日期选择器的显示值 + private void updateDateControl() { + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, -DAYS_IN_ALL_WEEK / 2 - 1); + mDateSpinner.setDisplayedValues(null); + for (int i = 0; i < DAYS_IN_ALL_WEEK; ++i) { + cal.add(Calendar.DAY_OF_YEAR, 1); + mDateDisplayValues[i] = (String) DateFormat.format("MM.dd EEEE", cal); + } + mDateSpinner.setDisplayedValues(mDateDisplayValues); + mDateSpinner.setValue(DAYS_IN_ALL_WEEK / 2); + mDateSpinner.invalidate(); + } + + // 更新 AM/PM 选择器的显示 + private void updateAmPmControl() { + if (mIs24HourView) { + mAmPmSpinner.setVisibility(View.GONE); + } else { + int index = mIsAm? Calendar.AM : Calendar.PM; + mAmPmSpinner.setValue(index); + mAmPmSpinner.setVisibility(View.VISIBLE); + } + } + + // 更新小时选择器的范围 + private void updateHourControl() { + if (mIs24HourView) { + mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW); + mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW); + } else { + mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW); + mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW); + } + } + + // 设置日期时间更改监听器 + /** + * Set the callback that indicates the 'Set' button has been pressed. + * @param callback the callback, if null will do nothing + */ + public void setOnDateTimeChangedListener(OnDateTimeChangedListener callback) { + mOnDateTimeChangedListener = callback; + } + + // 日期时间更改时的处理方法 + private void onDateTimeChanged() { + if (mOnDateTimeChangedListener!= null) { + mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(), + getCurrentMonth(), getCurrentDay(), getCurrentHourOfDay(), getCurrentMinute()); + } + } +} \ No newline at end of file