You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
xiaomi/ui/NotesListActivity.java

305 lines
19 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// 内部类实现了ListView.MultiChoiceModeListener和OnMenuItemClickListener接口用于处理ListView的多选模式相关的各种操作逻辑
// 比如创建多选模式下的菜单、处理菜单项点击、选项状态改变以及退出多选模式等操作通过与NotesListAdapter等配合实现对笔记的批量操作功能。
private class ModeCallback implements ListView.MultiChoiceModeListener, OnMenuItemClickListener {
private DropdownMenu mDropDownMenu;
private ActionMode mActionMode;
private MenuItem mMoveMenu;
// 当多选模式创建时调用该方法,用于初始化多选模式下的菜单、设置菜单项的可见性和点击监听器等操作,
// 例如根据笔记所在文件夹等情况决定“移动”菜单项是否可见,同时设置自定义的视图用于展示下拉菜单等内容。
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
// 填充多选模式下的菜单布局R.menu.note_list_options到传入的菜单对象中使得菜单显示相应的选项。
getMenuInflater().inflate(R.menu.note_list_options, menu);
// 为“删除”菜单项设置点击监听器当点击该菜单项时会触发此内部类中对应的点击处理逻辑通过实现OnMenuItemClickListener接口
menu.findItem(R.id.delete).setOnMenuItemClickListener(this);
mMoveMenu = menu.findItem(R.id.move);
// 如果当前长按选中的笔记数据项所在的父文件夹是通话记录文件夹或者用户创建的文件夹数量为0那么隐藏“移动”菜单项
// 因为在这些情况下可能不适合或无法进行移动操作。
if (mFocusNoteDataItem.getParentId() == Notes.ID_CALL_RECORD_FOLDER
|| DataUtils.getUserFolderCount(mContentResolver) == 0) {
mMoveMenu.setVisible(false);
} else {
mMoveMenu.setVisible(true);
// 为“移动”菜单项设置点击监听器,以便后续处理点击该菜单项时的相应逻辑。
mMoveMenu.setOnMenuItemClickListener(this);
}
mActionMode = mode;
// 通知笔记列表适配器进入多选模式,以便适配器进行相应的数据和视图状态更新,例如标记选中的项等。
mNotesListAdapter.setChoiceMode(true);
// 设置ListView不再响应长按事件因为此时已经进入了多选模式长按相关逻辑由多选模式的操作来处理。
mNotesListView.setLongClickable(false);
// 隐藏“新建笔记”按钮,在多选模式下通常不需要该按钮显示,避免操作冲突等情况。
mAddNewNote.setVisibility(View.GONE);
// 通过LayoutInflater加载一个自定义的视图R.layout.note_list_dropdown_menu用于作为多选模式下的自定义视图
// 这个视图可能包含一些额外的交互元素,比如下拉菜单等。
View customView = LayoutInflater.from(NotesListActivity.this).inflate(
R.layout.note_list_dropdown_menu, null);
// 将自定义视图设置到ActionMode中使其在界面上显示出来替代默认的ActionMode样式。
mode.setCustomView(customView);
mDropDownMenu = new DropdownMenu(NotesListActivity.this,
(Button) customView.findViewById(R.id.selection_menu),
R.menu.note_list_dropdown);
// 为下拉菜单设置点击监听器,当点击下拉菜单中的菜单项时,会执行相应的逻辑,这里用于处理全选/反选操作以及更新菜单显示状态。
mDropDownMenu.setOnDropdownMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
public boolean onMenuItemClick(MenuItem item) {
// 调用笔记列表适配器的方法,实现全选或反选已选笔记的功能,根据当前的选中状态进行切换。
mNotesListAdapter.selectAll(!mNotesListAdapter.isAllSelected());
// 更新菜单相关的显示状态,比如更新全选按钮的文本和选中状态等。
updateMenu();
return true;
}
});
return true;
}
// 更新菜单相关显示状态的私有方法,比如根据已选笔记的数量更新下拉菜单的标题,以及更新全选菜单项的选中状态和文本等,
// 使得菜单显示能准确反映当前的多选情况。
private void updateMenu() {
int selectedCount = mNotesListAdapter.getSelectedCount();
// 获取格式化后的字符串资源,用于设置下拉菜单的标题,显示当前选中的笔记数量等信息。
String format = getResources().getString(R.string.menu_select_title, selectedCount);
mDropDownMenu.setTitle(format);
MenuItem item = mDropDownMenu.findItem(R.id.action_select_all);
if (item!= null) {
if (mNotesListAdapter.isAllSelected()) {
item.setChecked(true);
item.setTitle(R.string.menu_deselect_all);
} else {
item.setChecked(false);
item.setTitle(R.string.menu_select_all);
}
}
}
// 在准备多选模式下的菜单时调用每次菜单显示前可能会调用此方法来更新菜单项状态等目前此方法未实现具体逻辑直接返回false
// 可根据实际需求后续添加相应代码来动态调整菜单的准备工作,比如根据不同条件禁用或启用某些菜单项等。
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
// TODO Auto-generated method stub
return false;
}
// 处理多选模式下菜单项点击事件的方法,根据点击的菜单项执行相应的操作逻辑,目前此方法还未完整实现具体的业务逻辑,
// 后续需根据不同菜单项的功能需求添加代码来实现诸如删除、移动所选笔记等操作。
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
// TODO Auto-generated method stub
return false;
}
// 当多选模式销毁时调用的方法用于清理相关的状态和设置比如通知笔记列表适配器退出多选模式恢复ListView的长按响应功能
// 以及重新显示“新建笔记”按钮等,确保界面回到正常的交互状态。
public void onDestroyActionMode(ActionMode mode) {
mNotesListAdapter.setChoiceMode(false);
mNotesListView.setLongClickable(true);
mAddNewNote.setVisibility(View.VISIBLE);
}
// 结束当前的多选模式调用ActionMode的finish方法来关闭多选模式相关的界面和操作状态
// 通常在完成批量操作或者取消操作时调用此方法来退出多选模式。
public void finishActionMode() {
mActionMode.finish();
}
// 当多选模式下笔记的选中状态发生改变时调用的方法,用于通知笔记列表适配器更新对应笔记项的选中状态,
// 并调用updateMenu方法来更新菜单的显示状态以保持界面与数据状态的一致性。
public void onItemCheckedStateChanged(ActionMode mode, int position, long id,
boolean checked) {
mNotesListAdapter.setCheckedItem(position, checked);
updateMenu();
}
// 处理菜单项点击事件的方法,根据点击的具体菜单项执行相应的业务逻辑,比如点击“删除”菜单项时弹出确认对话框进行笔记删除操作,
// 点击“移动”菜单项时启动查询目标文件夹的操作等,实现对所选笔记的各种批量操作功能。
public boolean onMenuItemClick(MenuItem item) {
// 如果没有选中任何笔记项即选中数量为0则弹出提示Toast告知用户需要先选择笔记然后直接返回不执行后续逻辑。
if (mNotesListAdapter.getSelectedCount() == 0) {
Toast.makeText(NotesListActivity.this, getString(R.string.menu_select_none),
Toast.LENGTH_SHORT).show();
return true;
}
switch (item.getItemId()) {
case R.id.delete:
// 创建一个AlertDialog.Builder用于构建确认删除的对话框设置对话框的标题、图标、提示信息等内容。
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
builder.setTitle(getString(R.string.alert_title_delete));
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setMessage(getString(R.string.alert_message_delete_notes,
mNotesListAdapter.getSelectedCount()));
builder.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int which) {
// 点击确认按钮后调用batchDelete方法执行批量删除所选笔记的操作。
batchDelete();
}
});
builder.setNegativeButton(android.R.string.cancel, null);
builder.show();
break;
case R.id.move:
// 点击“移动”菜单项时调用startQueryDestinationFolders方法启动查询目标文件夹的操作
// 以便后续将所选笔记移动到指定的文件夹中。
startQueryDestinationFolders();
break;
default:
return false;
}
return true;
}
}
// 内部类实现了OnTouchListener接口用于处理“新建笔记”按钮的触摸事件比如判断触摸位置是否在按钮的特定透明区域
// 以及根据触摸动作按下、移动、抬起等进行相应的事件分发和处理逻辑例如将触摸事件分发给ListView等操作。
private class NewNoteOnTouchListener implements OnTouchListener {
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
// 获取设备屏幕的默认显示对象,用于获取屏幕相关的尺寸信息,比如屏幕高度等。
Display display = getWindowManager().getDefaultDisplay();
int screenHeight = display.getHeight();
int newNoteViewHeight = mAddNewNote.getHeight();
int start = screenHeight - newNoteViewHeight;
int eventY = start + (int) event.getY();
/**
* Minus TitleBar's height
* 如果当前处于子文件夹状态,需要减去标题栏的高度,因为坐标计算等可能需要基于去除标题栏后的区域来进行,
* 以更准确地判断触摸位置等情况。
*/
if (mState == ListEditState.SUB_FOLDER) {
eventY -= mTitleBar.getHeight();
start -= mTitleBar.getHeight();
}
/**
* HACKME:When click the transparent part of "New Note" button, dispatch
* the event to the list view behind this button. The transparent part of
* "New Note" button could be expressed by formula y=-0.12x+94Unit:pixel
* and the line top of the button. The coordinate based on left of the "New
* Note" button. The 94 represents maximum height of the transparent part.
* Notice that, if the background of the button changes, the formula should
* also change. This is very bad, just for the UI designer's strong requirement.
* 以下是一段临时解决办法HACKME的代码逻辑当点击“新建笔记”按钮的透明部分时将触摸事件分发给按钮后面的ListView进行处理。
* 透明部分的范围通过一个公式y = -0.12x + 94单位为像素坐标基于按钮左侧以及按钮顶部边界来界定
* 并且提示如果按钮背景改变该公式也需要相应更改这种处理方式不太理想只是为了满足UI设计师的特定要求。
*/
if (event.getY() < (event.getX() * (-0.12) + 94)) {
View view = mNotesListView.getChildAt(mNotesListView.getChildCount() - 1
- mNotesListView.getFooterViewsCount());
if (view!= null && view.getBottom() > start
&& (view.getTop() < (start + 94))) {
mOriginY = (int) event.getY();
mDispatchY = eventY;
event.setLocation(event.getX(), mDispatchY);
mDispatch = true;
return mNotesListView.dispatchTouchEvent(event);
}
}
break;
}
case MotionEvent.ACTION_MOVE: {
if (mDispatch) {
mDispatchY += (int) event.getY() - mOriginY;
event.setLocation(event.getX(), mDispatchY);
return mNotesListView.dispatchTouchEvent(event);
}
break;
}
default: {
if (mDispatch) {
event.setLocation(event.getX(), mDispatchY);
mDispatch = false;
return mNotesListView.dispatchTouchEvent(event);
}
break;
}
}
return false;
}
};
// 启动异步查询笔记列表数据的方法根据当前所在文件夹的ID来构建合适的查询条件然后通过异步查询处理实例mBackgroundQueryHandler
// 发起对笔记列表数据的查询操作,确保在后台线程进行数据库查询,不阻塞主线程,提高应用的响应性能。
private void startAsyncNotesListQuery() {
String selection = (mCurrentFolderId == Notes.ID_ROOT_FOLDER)? ROOT_FOLDER_SELECTION
: NORMAL_SELECTION;
mBackgroundQueryHandler.startQuery(FOLDER_NOTE_LIST_QUERY_TOKEN, null,
Notes.CONTENT_NOTE_URI, NoteItemData.PROJECTION, selection, new String[]{
String.valueOf(mCurrentFolderId)
}, NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC");
}
// 内部类继承自AsyncQueryHandler用于在后台线程处理数据库查询操作并在查询完成后通过回调方法处理相应的结果
// 根据不同的查询令牌token区分是查询笔记列表还是文件夹列表等情况然后执行对应的后续操作比如更新适配器数据等。
private final class BackgroundQueryHandler extends AsyncQueryHandler {
public BackgroundQueryHandler(ContentResolver contentResolver) {
super(contentResolver);
}
@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
switch (token) {
case FOLDER_NOTE_LIST_QUERY_TOKEN:
// 如果是查询笔记列表的操作完成调用笔记列表适配器的方法传入查询得到的游标Cursor数据
// 以便适配器更新界面上显示的笔记列表内容,反映最新的数据情况。
mNotesListAdapter.changeCursor(cursor);
break;
case FOLDER_LIST_QUERY_TOKEN:
if (cursor!= null && cursor.getCount() > 0) {
// 如果是查询文件夹列表操作完成且查询结果不为空即有文件夹数据则调用showFolderListMenu方法展示文件夹列表菜单
// 方便用户选择操作的目标文件夹等。
showFolderListMenu(cursor);
} else {
Log.e(TAG, "Query folder failed");
}
break;
default:
return;
}
}
}
// 展示文件夹列表菜单的方法通过AlertDialog.Builder构建一个包含文件夹列表的对话框使用FoldersListAdapter作为适配器来展示文件夹数据
// 并为对话框的列表项点击事件设置监听器当用户点击某个文件夹时执行将所选笔记移动到该文件夹的操作并弹出相应的提示Toast告知用户操作结果
// 最后关闭多选模式相关的操作界面。
private void showFolderListMenu(Cursor cursor) {
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
builder.setTitle(R.string.menu_title_select_folder);
final FoldersListAdapter adapter = new FoldersListAdapter(this, cursor);
builder.setAdapter(adapter, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
DataUtils.batchMoveToFolder(mContentResolver,
mNotesListAdapter.getSelectedItemIds(), adapter.getItemId(which));
Toast.makeText(
NotesListActivity.this,
getString(R.string.format_move_notes_to_folder,
mNotesListAdapter.getSelectedCount(),
adapter.getFolderName(NotesListActivity.this, which)),
Toast.LENGTH_SHORT).show();
mModeCallBack.finishActionMode();
}
});
builder.show();
}
// 创建新笔记的方法构建一个启动NoteEditActivity的Intent设置相应的动作Intent.ACTION_INSERT_OR_EDIT以及传递当前所在文件夹的ID作为额外参数
// 然后通过startActivityForResult方法启动该Activity以便在新建笔记操作完成后能获取返回结果并进行相应的数据更新等处理。
private void createNewNote() {
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mCurrentFolderId);
this.startActivityForResult(intent, REQUEST_CODE_NEW_NODE);
}
// 批量删除所选笔记的方法通过AsyncTask在后台线程执行删除操作根据是否处于同步模式通过isSyncMode方法判断来决定是直接删除笔记还是将笔记移动到回收站文件夹
// 在操作完成后onPostExecute方法中如果涉及到与小部件相关的数据更新比如所选笔记关联了小部件则调用updateWidget方法更新相应的小部件显示内容
// 最后关闭多选模式相关的操作界面。
private void batchDelete() {
new AsyncTask<Void, Void, HashSet<AppWidgetAttribute>>() {
protected HashSet<AppWidgetAttribute> doInBackground(Void... unused) {
HashSet<AppWidgetAttribute> widgets = mNotesListAdapter.getSelectedWidget();
if (!isSyncMode()) {
// 如果未处于同步模式直接调用DataUtils的方法批量删除所选笔记这里