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.
2Q1/ui/NotesListActivity.java

1494 lines
120 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.

/*
* Copyright (c) 2010 - 2011, The MiCode Open Source Community (www.micode.net)
*
* 以下是版权相关声明说明该代码遵循Apache License 2.0许可证进行开源使用。
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 意味着只有在符合许可证规定的情况下才能使用本文件。
* 你可以通过以下网址获取许可证副本:
* http://www.apache.org/licenses/LICENSE-2.0
*
* 除非适用法律要求或者书面同意,否则依据本许可证分发的软件按“原样”提供,
* 不附带任何明示或暗示的保证或条件,关于具体的权限和限制,可查看许可证内容。
* 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.
*/
// 包声明表明该类所属的包名这里属于net.micode.notes.ui包通常意味着该类与应用的用户界面相关功能有关
package net.micode.notes.ui;
// 导入各种Android系统相关的类用于实现Activity的基本功能、对话框、小部件管理、异步查询、内容提供器操作等功能
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.appwidget.AppWidgetManager;
import android.content.AsyncQueryHandler;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.view.ActionMode;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.Display;
import android.view.HapticFeedbackConstants;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnCreateContextMenuListener;
import android.view.View.OnTouchListener;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.PopupMenu;
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;
import net.micode.notes.model.WorkingNote;
import net.micode.notes.tool.BackupUtils;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.ResourceParser;
import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute;
import net.micode.notes.widget.NoteWidgetProvider_2x;
import net.micode.notes.widget.NoteWidgetProvider_4x;
// 导入Java标准库中用于处理输入流读取的相关类可能用于读取文件等操作
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashSet;
// NotesListActivity类继承自Activity是Android应用中一个用于展示笔记列表的界面类同时实现了点击和长按列表项的监听器接口
public class NotesListActivity extends Activity implements OnClickListener, OnItemLongClickListener {
// 定义一个常量用于标识文件夹内笔记列表查询任务的令牌token在异步查询机制中作为任务的唯一标识方便区分不同类型的查询操作
private static final int FOLDER_NOTE_LIST_QUERY_TOKEN = 0;
// 定义一个常量用于标识文件夹列表查询任务的令牌token同样用于异步查询时区分不同的查询任务
private static final int FOLDER_LIST_QUERY_TOKEN = 1;
// 定义一个常量作为菜单中“删除文件夹”菜单项的唯一ID用于在菜单操作中识别该菜单项
private static final int MENU_FOLDER_DELETE = 0;
// 定义一个常量作为菜单中“查看文件夹”菜单项的唯一ID用于在菜单操作中识别该菜单项
private static final int MENU_FOLDER_VIEW = 1;
// 定义一个常量作为菜单中“修改文件夹名称”菜单项的唯一ID用于在菜单操作中识别该菜单项
private static final int MENU_FOLDER_CHANGE_NAME = 2;
// 定义一个字符串常量作为存储是否添加应用介绍相关偏好设置的键名用于在SharedPreferences中进行相应设置的读写操作
private static final String PREFERENCE_ADD_INTRODUCTION = "net.micode.notes.introduction";
// 定义一个枚举类型,用于表示列表编辑的不同状态,包含笔记列表、子文件夹、通话记录文件夹三种情况,方便根据不同状态执行不同的业务逻辑
private enum ListEditState {
NOTE_LIST,
SUB_FOLDER,
CALL_RECORD_FOLDER
};
// 定义一个变量用于记录当前列表的编辑状态初始化为NOTE_LIST状态表示默认处于笔记列表编辑状态
private ListEditState mState = ListEditState.NOTE_LIST;
// 定义一个变量,用于处理后台数据库查询操作的异步查询处理器,通过它可以在后台线程中执行数据库相关的查询任务,避免阻塞主线程
private BackgroundQueryHandler mBackgroundQueryHandler;
// 定义一个变量作为笔记列表的适配器负责将笔记数据适配到ListView上进行展示管理数据与视图之间的绑定关系
private NotesListAdapter mNotesListAdapter;
// 定义一个变量代表用于展示笔记列表的ListView控件用户可以在界面上看到笔记列表并与之交互
private ListView mNotesListView;
// 定义一个变量,代表“新建笔记”按钮,用户点击该按钮可触发新建笔记的操作
private Button mAddNewNote;
// 定义一个布尔变量用于标记是否进行事件分发操作初始值为false在触摸事件处理逻辑中根据具体情况进行设置和判断
private boolean mDispatch = false;
// 定义一个整型变量用于记录触摸事件起始的Y坐标在处理触摸相关的操作如滑动等用于计算位置变化等情况
private int mOriginY = 0;
// 定义一个整型变量用于记录在分发触摸事件时的Y坐标同样在触摸事件处理流程中发挥作用辅助传递准确的触摸位置信息
private int mDispatchY = 0;
// 定义一个变量代表标题栏的TextView控件通常用于显示页面的标题信息也可能包含其他相关的文本提示等内容
private TextView mTitleBar;
// 定义一个长整型变量用于记录当前所在文件夹的ID通过该ID可以确定当前操作的笔记所属的文件夹方便进行数据筛选和展示等操作
private long mCurrentFolderId;
// 定义一个变量用于获取应用的ContentResolver实例通过它可以与应用的内容提供器进行交互实现数据的增删改查等操作
private ContentResolver mContentResolver;
// 定义一个变量作为实现了ListView的多选模式监听器和菜单项点击监听器接口的回调类实例用于处理ListView在多选模式下以及菜单项点击时的相关业务逻辑
private ModeCallback mModeCallBack;
// 定义一个字符串常量作为日志输出的标签方便在调试时通过该标签在Logcat中筛选出该类相关的日志信息便于排查问题和查看运行状态
private static final String TAG = "NotesListActivity";
// 定义一个公共静态常量,用于表示笔记列表视图滚动的速率,具体的单位和实际使用场景需要结合滚动相关的具体逻辑来确定,可能是像素/单位时间等,用于控制列表滚动的速度效果
public static final int NOTES_LISTVIEW_SCROLL_RATE = 30;
// 定义一个变量用于记录当前获取焦点的笔记数据项通过该对象可以获取和操作当前用户关注的笔记相关的各种属性信息比如笔记的内容、ID等
private NoteItemData mFocusNoteDataItem;
// 定义一个静态字符串常量作为查询普通笔记的选择条件语句通过匹配笔记的父级ID来筛选相应的笔记其中的问号部分在实际查询时会传入具体的文件夹ID值进行查询
private static final String NORMAL_SELECTION = NoteColumns.PARENT_ID + "=?";
// 定义一个静态字符串常量,作为查询根文件夹下笔记的选择条件语句,该语句包含了比较复杂的逻辑,不仅涉及笔记类型的判断,还考虑了通话记录文件夹相关的条件,用于准确筛选出根文件夹下符合特定条件的笔记
private static final String ROOT_FOLDER_SELECTION = "(" + NoteColumns.TYPE + "<>"
+ Notes.TYPE_SYSTEM + " AND " + NoteColumns.PARENT_ID + "=?)" + " OR ("
+ NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER + " AND "
+ NoteColumns.NOTES_COUNT + ">0)";
// 定义一个静态常量作为请求码用于标识打开笔记节点的操作在Activity的onActivityResult方法中通过该请求码来判断是哪个操作返回的结果以便进行相应的后续处理
private final static int REQUEST_CODE_OPEN_NODE = 102;
// 定义一个静态常量作为请求码用于标识新建笔记节点的操作同样在onActivityResult方法中用于区分不同来源的返回结果执行对应的逻辑
private final static int REQUEST_CODE_NEW_NODE = 103;
// 重写Activity的onCreate方法该方法在Activity创建时被调用用于进行各种初始化操作
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 设置该Activity对应的布局文件这里通过R.layout.note_list找到对应的XML布局资源将界面布局加载到Activity中
setContentView(R.layout.note_list);
// 调用initResources方法初始化Activity中需要使用的各种资源如视图控件、数据适配器等
initResources();
/**
* 在用户首次使用该应用时插入一个应用介绍,可能是弹出一个引导界面或者添加一条示例笔记等形式,
* 具体实现逻辑在setAppInfoFromRawRes方法中目的是帮助用户快速了解和使用应用。
*/
setAppInfoFromRawRes();
}
// 重写Activity的onActivityResult方法该方法用于接收来自其他Activity返回的结果
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// 首先判断返回的结果码是否为RESULT_OK表示操作成功完成并且请求码是打开笔记节点REQUEST_CODE_OPEN_NODE或者新建笔记节点REQUEST_CODE_NEW_NODE的请求码
if (resultCode == RESULT_OK
&& (requestCode == REQUEST_CODE_OPEN_NODE || requestCode == REQUEST_CODE_NEW_NODE)) {
// 如果满足上述条件调用笔记列表适配器mNotesListAdapter的changeCursor方法并传入null参数。
// 这可能是为了通知适配器数据发生了变化(比如新的笔记创建或者已有笔记被打开等情况),使其刷新显示内容,
// 传入null可能是让适配器重新获取数据的一种触发方式具体取决于适配器内部的实现逻辑。
mNotesListAdapter.changeCursor(null);
} else {
// 如果不满足上述判断条件,即返回结果不符合预期或者不是来自打开或新建笔记节点的操作,
// 则调用父类Activity的onActivityResult方法继续执行默认的结果处理逻辑确保不会遗漏其他可能的情况。
super.onActivityResult(requestCode, resultCode, data);
}
}
// 私有方法,用于从原始资源文件中设置应用信息,可能是创建一个初始的引导笔记之类的功能
private void setAppInfoFromRawRes() {
// 获取默认的共享偏好设置实例通过PreferenceManager来获取应用的默认SharedPreferences对象
// 可以用于读取和存储一些简单的键值对形式的配置信息,比如应用的各种设置选项、首次使用标记等。
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
// 判断在共享偏好设置中是否不存在名为PREFERENCE_ADD_INTRODUCTION其值为"net.micode.notes.introduction"的键对应的布尔值为true的情况
// 即判断是否还没有添加过应用介绍信息,如果是首次使用或者还未执行过此操作,则进入以下逻辑。
if (!sp.getBoolean(PREFERENCE_ADD_INTRODUCTION, false)) {
// 创建一个可变的字符串构建器对象,用于逐步构建要设置的应用介绍信息内容,后续会从资源文件中读取相关文本并添加到这里。
StringBuilder sb = new StringBuilder();
InputStream in = null;
try {
// 通过当前上下文this获取名为introduction的原始资源文件对应的输入流
// 这个资源文件应该是存储在项目的res/raw目录下的文本文件之类的资源用于提供应用介绍的内容。
in = getResources().openRawResource(R.raw.introduction);
if (in!= null) {
// 创建一个基于输入流in的InputStreamReader对象它将字节流转换为字符流方便按字符读取文件内容
// 为后续使用BufferedReader进行高效字符读取做准备。
InputStreamReader isr = new InputStreamReader(in);
// 创建一个BufferedReader对象它带有缓冲功能可以更高效地读取字符流减少每次读取的系统开销
// 并提高读取大文件时的性能。
BufferedReader br = new BufferedReader(isr);
// 创建一个长度为1024的字符数组用于每次从文件中读取一定量的字符作为缓冲区
// 可以根据实际文件大小和性能需求适当调整这个缓冲区大小。
char[] buf = new char[1024];
int len = 0;
// 通过循环不断从文件中读取字符到缓冲区buf中只要读取的字符长度大于0就表示还有内容可读
// 然后将读取到的字符添加到字符串构建器sb中逐步构建完整的文件内容字符串。
while ((len = br.read(buf)) > 0) {
sb.append(buf, 0, len);
}
} else {
// 如果无法成功打开资源文件获取输入流,在日志中输出错误信息,标记为读取介绍文件出错,
// 并直接返回,不再继续后续操作,因为没有有效的文件内容可供使用了。
Log.e(TAG, "Read introduction file error");
return;
}
} catch (IOException e) {
// 如果在读取文件过程中出现IO异常比如文件不存在、权限问题等打印异常的堆栈信息方便排查问题
// 然后直接返回,不再继续后续操作,因为出现了异常情况导致无法正常读取文件内容。
e.printStackTrace();
return;
} finally {
// 在无论是否出现异常的情况下,都要尝试关闭输入流,释放相关的系统资源,避免资源泄漏。
// 如果输入流不为null表示之前成功打开了输入流就执行关闭操作
// 如果关闭过程中出现异常,会再次打印异常的堆栈信息,不过不会影响程序的正常流程继续向下执行。
if (in!= null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 创建一个空的工作笔记WorkingNote实例通过WorkingNote类的createEmptyNote静态方法来创建
// 传入当前上下文this、根文件夹的IDNotes.ID_ROOT_FOLDER其具体值应该在Notes类中定义用于标识根文件夹
// 无效的部件AppWidgetIDAppWidgetManager.INVALID_APPWIDGET_ID用于表示不是与某个有效小部件关联的情况
// 无效的部件类型Notes.TYPE_WIDGET_INVALIDE同样在Notes类中定义的表示无效部件类型的常量以及一个颜色相关的值ResourceParser.RED具体颜色含义需看ResourceParser类定义
// 这个工作笔记实例后续会用于存储和操作要添加的应用介绍信息内容。
WorkingNote note = WorkingNote.createEmptyNote(this, Notes.ID_ROOT_FOLDER,
AppWidgetManager.INVALID_APPWIDGET_ID, Notes.TYPE_WIDGET_INVALIDE,
ResourceParser.RED);
// 将从资源文件中读取到的内容存储在字符串构建器sb中通过toString方法转换为字符串设置为工作笔记的文本内容
// 这样就把应用介绍信息填充到了笔记对象中,准备进行保存操作。
note.setWorkingText(sb.toString());
// 调用工作笔记note的saveNote方法尝试保存这个包含应用介绍信息的笔记
// 如果保存操作成功saveNote方法返回true则表示成功将应用介绍信息以笔记的形式保存到了相应的数据存储中可能是数据库等
// 此时将共享偏好设置中名为PREFERENCE_ADD_INTRODUCTION的键对应的布尔值设置为true表示已经添加过应用介绍信息了
// 并通过commit方法提交这个设置更改使其生效。
if (note.saveNote()) {
sp.edit().putBoolean(PREFERENCE_ADD_INTRODUCTION, true).commit();
} else {
// 如果保存笔记失败,在日志中输出错误信息,标记为保存介绍笔记出错,然后直接返回,不再继续后续操作,
// 因为未能成功保存应用介绍信息到相应的数据存储中。
Log.e(TAG, "Save introduction note error");
return;
}
}
}
// 重写Activity的onStart方法该方法在Activity开始可见时被调用通常用于执行一些需要在界面可见时启动的操作
@Override
protected void onStart() {
super.onStart();
// 调用startAsyncNotesListQuery方法该方法应该是用于启动一个异步的笔记列表查询操作
// 目的是在Activity启动并可见后尽快获取笔记数据并展示在界面上避免阻塞主线程提升用户体验。
// 具体的查询逻辑和实现细节应该在startAsyncNotesListQuery方法内部定义此处未展示完整代码
startAsyncNotesListQuery();
}
// 私有方法用于初始化Activity中需要使用的各种资源比如获取ContentResolver、设置ListView的相关属性和监听器等
private void initResources() {
// 获取当前上下文this对应的ContentResolver实例ContentResolver用于与应用的内容提供器进行交互
// 通过它可以实现对应用内部数据(如数据库中的笔记数据等)的查询、插入、更新、删除等操作,是进行数据操作的重要接口。
mContentResolver = this.getContentResolver();
// 创建一个BackgroundQueryHandler实例传入刚刚获取的ContentResolver对象
// BackgroundQueryHandler通常用于在后台线程中处理数据库查询相关的操作避免在主线程执行耗时的查询任务导致界面卡顿
// 具体的查询处理逻辑应该在BackgroundQueryHandler类内部定义此处未展示完整代码
mBackgroundQueryHandler = new BackgroundQueryHandler(this.getContentResolver());
// 将当前所在文件夹的ID设置为根文件夹的IDNotes.ID_ROOT_FOLDER其具体值在Notes类中定义用于标识根文件夹
// 表示初始状态下操作的是根文件夹下的笔记数据,后续可能会根据用户操作等情况进行更改。
mCurrentFolderId = Notes.ID_ROOT_FOLDER;
// 通过findViewById方法根据布局文件中定义的IDR.id.notes_list找到对应的ListView控件
// 这个ListView就是用于展示笔记列表的视图组件后续会将笔记数据通过适配器绑定到这个ListView上进行展示。
mNotesListView = (ListView) findViewById(R.id.notes_list);
// 为ListView添加一个页脚视图通过LayoutInflater从当前上下文this加载名为note_list_footer.xml的布局文件并实例化为视图对象
// 添加页脚视图可以用于展示一些额外的信息或者操作按钮等,比如加载更多、提示信息等,
// 第二个参数传入null表示不需要传递额外的数据给页脚视图第三个参数false表示这个页脚视图不是可选择的如果是列表项则可以设置为true
mNotesListView.addFooterView(LayoutInflater.from(this).inflate(R.layout.note_list_footer, null),
null, false);
// 设置ListView的点击监听器当用户点击列表中的某个笔记项时会触发OnListItemClickListener类中定义的逻辑
// 具体的点击处理逻辑在OnListItemClickListener类内部实现此处未展示完整代码
mNotesListView.setOnItemClickListener(new OnListItemClickListener());
// 设置ListView的长按监听器为当前类因为当前类实现了OnItemLongClickListener接口
// 这样当用户长按列表中的笔记项时会触发当前类中重写的onItemLongClick方法用于处理长按相关的操作比如弹出上下文菜单等。
mNotesListView.setOnItemLongClickListener(this);
// 创建一个NotesListAdapter实例传入当前上下文this这个适配器用于将笔记数据适配到ListView上进行展示
// 它会管理数据与视图之间的绑定关系比如将笔记的各个属性显示在ListView的每一项对应的视图中
// 具体的适配逻辑在NotesListAdapter类内部定义此处未展示完整代码
mNotesListAdapter = new NotesListAdapter(this);
// 将创建好的笔记列表适配器mNotesListAdapter设置给ListView使得ListView能够通过这个适配器获取数据并展示笔记列表
// 这样就建立了数据与视图之间的关联,让用户可以在界面上看到笔记信息。
mNotesListView.setAdapter(mNotesListAdapter);
// 通过findViewById方法根据布局文件中定义的IDR.id.btn_new_note找到对应的“新建笔记”按钮控件
// 这个按钮用于用户点击后触发新建笔记的操作,后续会设置相应的点击监听器来处理点击事件。
mAddNewNote = (Button) findViewById(R.id.btn_new_note);
// 设置“新建笔记”按钮的点击监听器为当前类因为当前类实现了OnClickListener接口
// 这样当用户点击这个按钮时会触发当前类中重写的onClick方法用于处理新建笔记相关的操作逻辑
// 具体的点击处理逻辑在当前类重写的onClick方法中实现此处未展示完整代码
mAddNewNote.setOnClickListener(this);
// 设置“新建笔记”按钮的触摸监听器通过传入一个新创建的NewNoteOnTouchListener实例来处理触摸按钮相关的操作逻辑
// 比如触摸滑动、按下抬起等事件的处理具体的触摸事件处理逻辑在NewNoteOnTouchListener类内部定义此处未展示完整代码
mAddNewNote.setOnTouchListener(new NewNoteOnTouchListener());
// 初始化用于标记是否进行事件分发的布尔变量mDispatch为false表示初始状态下不进行事件分发操作
// 这个变量会在触摸事件处理逻辑中根据具体情况进行更新和判断,用于控制触摸事件的流向等情况。
mDispatch = false;
// 初始化用于记录在分发触摸事件时的Y坐标的变量mDispatchY为0用于在触摸事件处理过程中记录和传递准确的触摸位置信息
// 特别是在涉及触摸滑动等操作时,会根据这个坐标的变化来进行相应的逻辑处理。
mDispatchY = 0;
// 初始化用于记录触摸事件起始的Y坐标的变量mOriginY为0同样在触摸事件处理中起到记录起始位置的作用
// 与mDispatchY配合使用用于计算触摸操作过程中的位置变化等情况。
mOriginY = 0;
// 通过findViewById方法根据布局文件中定义的IDR.id.tv_title_bar找到对应的标题栏TextView控件
// 这个TextView通常用于显示页面的标题信息也可能包含其他相关的文本提示等内容具体显示内容会根据应用的逻辑进行设置。
mTitleBar = (TextView) findViewById(R.id.tv_title_bar);
// 设置当前列表的编辑状态为笔记列表状态ListEditState.NOTE_LIST表示初始情况下处于普通的笔记列表编辑模式
// 后续可能会根据用户操作等情况更改为子文件夹、通话记录文件夹等其他编辑状态,不同状态下会有不同的操作逻辑和界面展示效果。
mState = ListEditState.NOTE_LIST;
// 创建一个ModeCallback实例这个实例实现了ListView的多选模式监听器和菜单项点击监听器接口
// 用于处理ListView在多选模式下以及菜单项点击时的相关业务逻辑比如显示多选操作菜单、处理菜单点击事件等
// 具体的逻辑在ModeCallback类内部实现此处未展示完整代码
mModeCallBack = new ModeCallback();
}
// 定义一个私有内部类ModeCallback它实现了ListView.MultiChoiceModeListener用于处理ListView的多选模式相关事件
// 和OnMenuItemClickListener用于处理菜单项点击事件两个接口用于处理笔记列表在多选模式下的各种交互逻辑
private class ModeCallback implements ListView.MultiChoiceModeListener, OnMenuItemClickListener {
// 定义一个成员变量,用于存储下拉菜单实例,这个下拉菜单可能用于展示一些额外的操作选项或者提供快捷操作入口等功能
private DropdownMenu mDropDownMenu;
// 定义一个成员变量用于保存当前的ActionMode实例ActionMode用于管理多选模式下的操作界面以及相关状态等
private ActionMode mActionMode;
// 定义一个成员变量,用于指向菜单中的“移动”菜单项,方便后续对该菜单项进行显示隐藏以及点击事件处理等操作
private MenuItem mMoveMenu;
// 当ListView进入多选模式时会调用此方法来创建多选模式的操作界面进行一些初始化设置
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
// 通过菜单填充器getMenuInflater将名为note_list_options.xml的菜单布局文件加载到传入的菜单menu
// 这样就为多选模式创建了对应的操作菜单,菜单中的各个菜单项可以在这个布局文件中定义,包含了如删除、移动等操作选项。
getMenuInflater().inflate(R.menu.note_list_options, menu);
// 找到菜单中ID为R.id.delete的菜单项通常是“删除”操作菜单项并设置它的点击监听器为当前类this
// 意味着点击这个“删除”菜单项时会触发当前类中实现的onMenuItemClick方法来处理相应逻辑。
menu.findItem(R.id.delete).setOnMenuItemClickListener(this);
// 获取菜单中ID为R.id.move的菜单项即“移动”菜单项并赋值给mMoveMenu变量方便后续操作。
mMoveMenu = menu.findItem(R.id.move);
// 判断当前获取焦点的笔记数据项mFocusNoteDataItem的父级ID是否等于通话记录文件夹的IDNotes.ID_CALL_RECORD_FOLDER
// 或者通过DataUtils工具类获取的用户创建的文件夹数量是否为0如果满足这两个条件之一说明当前情况可能不适合进行笔记移动操作。
if (mFocusNoteDataItem.getParentId() == Notes.ID_CALL_RECORD_FOLDER
|| DataUtils.getUserFolderCount(mContentResolver) == 0) {
// 如果不适合移动操作,将“移动”菜单项设置为不可见,避免用户误操作或者操作无效的情况。
mMoveMenu.setVisible(false);
} else {
// 如果适合移动操作将“移动”菜单项设置为可见并设置它的点击监听器为当前类this
// 这样点击“移动”菜单项时同样会触发当前类中实现的onMenuItemClick方法来处理移动相关的逻辑。
mMoveMenu.setVisible(true);
mMoveMenu.setOnMenuItemClickListener(this);
}
// 将传入的ActionMode实例保存到成员变量mActionMode中方便后续在其他方法中对这个多选模式进行操作比如结束多选模式等。
mActionMode = mode;
// 调用笔记列表适配器mNotesListAdapter的setChoiceMode方法传入true参数将笔记列表设置为多选模式
// 使得ListView可以支持多选操作用户能够选择多个笔记项进行批量操作。
mNotesListAdapter.setChoiceMode(true);
// 将ListView的长按可点击属性设置为false因为进入多选模式后长按操作通常由多选模式的逻辑来处理
// 避免长按操作出现重复响应或者冲突的情况。
mNotesListView.setLongClickable(false);
// 将“新建笔记”按钮mAddNewNote的可见性设置为不可见View.GONE在多选模式下可能不需要显示该按钮
// 避免干扰用户对多选操作的注意力以及操作界面的简洁性。
mAddNewNote.setVisibility(View.GONE);
// 通过LayoutInflater从NotesListActivity的上下文this加载名为note_list_dropdown_menu.xml的布局文件
// 并实例化为一个视图对象customView这个视图将作为自定义的下拉菜单视图用于展示一些额外的操作选项等。
View customView = LayoutInflater.from(NotesListActivity.this).inflate(
R.layout.note_list_dropdown_menu, null);
// 将刚刚创建的自定义视图customView设置为当前ActionMode的自定义视图这样就可以在多选模式的界面上展示这个自定义的下拉菜单视图了。
mode.setCustomView(customView);
// 创建一个DropdownMenu实例传入NotesListActivity的上下文this、自定义视图中ID为R.id.selection_menu的按钮通常作为触发下拉菜单展开的按钮
// 以及名为note_list_dropdown的菜单资源用于定义下拉菜单中的具体菜单项内容用于管理和操作这个下拉菜单相关的逻辑。
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()
// 这个操作的目的是切换笔记列表的全选状态,如果当前不是全选则全选所有笔记项,如果当前是全选则取消全选,实现一个快捷的全选/取消全选功能。
mNotesListAdapter.selectAll(!mNotesListAdapter.isAllSelected());
// 调用updateMenu方法用于更新下拉菜单的显示内容比如根据当前选中的笔记数量更新菜单标题以及相关菜单项的文本和选中状态等。
updateMenu();
return true;
}
});
return true;
}
// 用于更新下拉菜单的显示内容的私有方法,根据当前选中的笔记数量等情况来调整菜单的标题、菜单项的选中状态和文本等信息
private void updateMenu() {
// 获取笔记列表适配器mNotesListAdapter中当前被选中的笔记数量通过调用getSelectedCount方法来获取。
int selectedCount = mNotesListAdapter.getSelectedCount();
// 通过当前上下文getResources获取一个格式化字符串资源这个字符串资源的格式定义在strings.xml文件中
// 名为menu_select_title它可能包含一个占位符用于显示选中的笔记数量这里将选中数量作为参数传入
// 得到一个格式化后的字符串,用于设置下拉菜单的标题,直观地展示当前选中的笔记数量情况。
String format = getResources().getString(R.string.menu_select_title, selectedCount);
mDropDownMenu.setTitle(format);
// 在下拉菜单mDropDownMenu中查找ID为R.id.action_select_all的菜单项通常是用于全选/取消全选的菜单项),
// 如果找到了这个菜单项item不为null则进行以下操作来更新它的选中状态和文本显示内容。
MenuItem item = mDropDownMenu.findItem(R.id.action_select_all);
if (item!= null) {
// 如果笔记列表适配器mNotesListAdapter当前处于全选状态通过调用isAllSelected方法判断
// 则将这个全选菜单项设置为选中状态setChecked(true)并将它的标题文本设置为取消全选的提示文本R.string.menu_deselect_all
// 这样用户可以直观地看到当前的全选状态以及点击该菜单项后会执行的操作。
if (mNotesListAdapter.isAllSelected()) {
item.setChecked(true);
item.setTitle(R.string.menu_deselect_all);
} else {
// 如果笔记列表适配器当前不是全选状态则将这个全选菜单项设置为未选中状态setChecked(false)
// 并将它的标题文本设置为全选的提示文本R.string.menu_select_all同样方便用户了解操作功能和当前状态。
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;
}
// 当用户点击多选模式操作菜单中的菜单项时会调用此方法来处理相应的点击事件目前此方法体中没有具体实现逻辑只是一个占位符返回false
// 如果要处理具体的菜单项点击操作逻辑,比如根据不同菜单项执行不同的数据操作、界面更新等,需要在这里添加相应的代码来实现功能。
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
// TODO Auto-generated method stub
return false;
}
// 当多选模式结束时(例如用户点击了返回键或者通过代码主动结束多选模式等情况),会调用此方法来进行一些清理和界面恢复操作
public void onDestroyActionMode(ActionMode mode) {
// 调用笔记列表适配器mNotesListAdapter的setChoiceMode方法传入false参数将笔记列表从多选模式恢复为普通模式
// 使得ListView不再支持多选操作恢复到正常的单选项点击等交互方式。
mNotesListAdapter.setChoiceMode(false);
// 将ListView的长按可点击属性设置为true恢复长按操作的响应能力因为多选模式已经结束长按操作可以按照常规逻辑进行处理了。
mNotesListView.setLongClickable(true);
// 将“新建笔记”按钮mAddNewNote的可见性设置为可见View.VISIBLE在多选模式结束后重新显示该按钮方便用户继续进行新建笔记等操作。
mAddNewNote.setVisibility(View.VISIBLE);
}
// 用于结束当前的多选模式通过调用保存的ActionMode实例mActionMode的finish方法来实现
// 这会触发onDestroyActionMode等相关方法来进行多选模式结束后的清理和界面恢复操作。
public void finishActionMode() {
mActionMode.finish();
}
// 当ListView中的某个笔记项的选中状态发生改变时例如用户点击选择或者取消选择某个笔记项会调用此方法来处理相应的逻辑
public void onItemCheckedStateChanged(ActionMode mode, int position, long id,
boolean checked) {
// 调用笔记列表适配器mNotesListAdapter的setCheckedItem方法传入笔记项的位置position和新的选中状态checked参数
// 用于更新笔记列表适配器中对应笔记项的选中状态记录,确保数据和界面显示的选中状态保持一致。
mNotesListAdapter.setCheckedItem(position, checked);
// 调用updateMenu方法更新下拉菜单的显示内容因为笔记项的选中状态改变了可能需要相应地更新菜单标题、全选菜单项状态等信息
// 保持下拉菜单展示的信息与当前选中情况相符。
updateMenu();
}
// 当用户点击菜单项时(无论是多选模式操作菜单还是下拉菜单中的菜单项),会调用此方法来处理相应的点击事件逻辑
public boolean onMenuItemClick(MenuItem item) {
// 首先判断笔记列表适配器mNotesListAdapter中当前被选中的笔记数量是否为0如果是0表示没有选中任何笔记项
// 在这种情况下弹出一个Toast提示信息告知用户没有选中任何笔记显示的文本内容通过getString方法获取strings.xml文件中定义的
// menu_select_none字符串资源显示时长为短暂Toast.LENGTH_SHORT然后直接返回true表示已经处理了这个点击事件避免继续执行其他不必要的逻辑。
if (mNotesListAdapter.getSelectedCount() == 0) {
Toast.makeText(NotesListActivity.this, getString(R.string.menu_select_none),
Toast.LENGTH_SHORT).show();
return true;
}
// 根据点击的菜单项的IDitem.getItemId())来判断具体是哪个菜单项被点击了,然后执行相应的业务逻辑。
switch (item.getItemId()) {
case R.id.delete:
// 如果点击的是“删除”菜单项ID为R.id.delete创建一个AlertDialog.Builder实例用于构建一个确认删除的对话框
// 传入NotesListActivity的上下文this以便对话框能够正确显示在当前界面上。
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
// 设置对话框的标题文本通过getString方法获取strings.xml文件中定义的alert_title_delete字符串资源作为标题内容
// 通常用于提示用户当前操作是删除相关的操作。
builder.setTitle(getString(R.string.alert_title_delete));
// 设置对话框的图标使用Android系统自带的警告图标android.R.drawable.ic_dialog_alert增强提示效果让用户直观地知道这是一个重要操作的提示对话框。
builder.setIcon(android.R.drawable.ic_dialog_alert);
// 设置对话框的消息内容通过getString方法获取strings.xml文件中定义的alert_message_delete_notes字符串资源
// 并传入笔记列表适配器中当前被选中的笔记数量mNotesListAdapter.getSelectedCount())作为参数进行格式化,
// 这样消息内容可以准确地提示用户将要删除的笔记数量,让用户明确操作的影响范围。
builder.setMessage(getString(R.string.alert_message_delete_notes,
mNotesListAdapter.getSelectedCount()));
// 设置对话框的“确定”按钮PositiveButton传入Android系统自带的确认文本android.R.string.ok作为按钮文本
// 并设置点击监听器当用户点击“确定”按钮时会触发这里定义的逻辑即调用batchDelete方法来执行批量删除选中笔记的操作
// 具体的批量删除逻辑应该在batchDelete方法中定义此处未展示完整代码
builder.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int which) {
batchDelete();
}
});
// 设置对话框的“取消”按钮NegativeButton传入Android系统自带的取消文本android.R.string.cancel作为按钮文本
// 并设置点击监听器为null表示点击“取消”按钮时不执行额外的操作只是关闭对话框取消当前的删除操作。
builder.setNegativeButton(android.R.string.cancel, null);
// 最后调用show方法显示这个构建好的确认删除对话框让用户进行确认操作确保重要的删除操作是经过用户明确同意的。
builder.show();
break;
case R.id.move:
// 如果点击的是“移动”菜单项ID为R.id.move调用startQueryDestinationFolders方法
// 这个方法应该是用于启动查询目标文件夹的操作,比如弹出一个界面让用户选择要将选中的笔记移动到哪个文件夹,
// 具体的查询目标文件夹逻辑应该在startQueryDestinationFolders方法中定义此处未展示完整代码
startQueryDestinationFolders();
break;
default:
// 如果点击的菜单项不是上述处理的“删除”或“移动”菜单项返回false表示当前类没有处理这个菜单项的点击事件
// 可能会按照默认的菜单项点击逻辑或者由其他相关的监听器来继续处理这个点击事件。
return false;
}
return true;
}
}
// 定义一个私有内部类NewNoteOnTouchListener它实现了OnTouchListener接口用于处理“新建笔记”按钮mAddNewNote的触摸事件
private class NewNoteOnTouchListener implements OnTouchListener {
// 重写OnTouchListener接口中的onTouch方法该方法会在“新建笔记”按钮被触摸时触发根据触摸事件的不同动作如按下、移动、抬起等执行相应的逻辑
public boolean onTouch(View v, MotionEvent event) {
// 根据触摸事件event的动作类型通过event.getAction()获取进行不同的处理这里使用switch语句来区分不同的动作情况
switch (event.getAction()) {
// 当触摸事件的动作为ACTION_DOWN即手指按下按钮时的情况
case MotionEvent.ACTION_DOWN: {
// 获取当前窗口的默认显示对象,用于获取屏幕相关的信息,比如屏幕的尺寸等
Display display = getWindowManager().getDefaultDisplay();
// 通过显示对象获取屏幕的高度,单位通常是像素,用于后续计算触摸位置等相关操作
int screenHeight = display.getHeight();
// 获取“新建笔记”按钮mAddNewNote的高度单位同样是像素用于确定按钮在屏幕中的位置以及触摸区域的判断等
int newNoteViewHeight = mAddNewNote.getHeight();
// 计算一个起始位置坐标,这里是屏幕高度减去“新建笔记”按钮的高度,大致表示按钮底部在屏幕中的垂直位置坐标,
// 后续可以基于这个坐标来判断触摸点是否在按钮的有效区域或者透明区域等范围内。
int start = screenHeight - newNoteViewHeight;
// 计算触摸事件的Y坐标在起始位置坐标的基础上加上触摸点相对按钮左上角的Y坐标偏移量通过event.getY()获取),
// 这样得到的eventY就是触摸点在整个屏幕坐标系下相对按钮底部位置的Y坐标值方便后续的位置判断和逻辑处理。
int eventY = start + (int) event.getY();
/**
* 如果当前列表的编辑状态mState是子文件夹状态ListEditState.SUB_FOLDER
* 需要减去标题栏mTitleBar的高度因为在这种情况下触摸位置的计算可能需要考虑标题栏占据的空间
* 使得触摸坐标的计算更加准确符合实际的界面布局和交互逻辑。同时也对起始位置start做同样的高度减去操作保持计算的一致性。
*/
if (mState == ListEditState.SUB_FOLDER) {
eventY -= mTitleBar.getHeight();
start -= mTitleBar.getHeight();
}
/**
* HACKME注释部分这里描述了一种针对“新建笔记”按钮透明部分触摸事件处理的特殊逻辑这种处理方式不太理想只是为了满足UI设计师的特定要求。
* 当点击“新建笔记”按钮的透明部分时需要将触摸事件分发给按钮后面的列表视图ListView即mNotesListView进行处理。
* “新建笔记”按钮的透明部分可以用公式 y = -0.12x + 94单位像素以及按钮的上边界来表示这里的坐标是基于“新建笔记”按钮的左侧边缘为原点的坐标系。
* 其中的94表示透明部分的最大高度需要注意的是如果按钮的背景发生变化这个公式也需要相应地改变因为透明部分的形状和范围可能会随之改变。
*/
if (event.getY() < (event.getX() * (-0.12) + 94)) {
// 获取ListView中的最后一个可见子视图不包含页脚视图通过getChildAt方法来获取
// 传入的参数是ListView的子视图总数减去1获取最后一个子视图再减去ListView的页脚视图数量
// 这样得到的就是列表中实际显示数据的最后一个子视图,用于后续判断触摸点是否在这个子视图的相关范围内。
View view = mNotesListView.getChildAt(mNotesListView.getChildCount() - 1
- mNotesListView.getFooterViewsCount());
// 判断获取到的这个子视图不为空并且子视图的底部坐标大于前面计算的起始位置start
// 同时子视图的顶部坐标小于起始位置加上透明部分最大高度start + 94这意味着触摸点在这个子视图与“新建笔记”按钮透明部分重叠的有效范围内。
if (view!= null && view.getBottom() > start
&& (view.getTop() < (start + 94))) {
// 记录触摸事件起始的Y坐标相对按钮左上角通过event.getY()获取并赋值给mOriginY变量
// 这个坐标用于后续在触摸移动等操作中计算位置变化量。
mOriginY = (int) event.getY();
// 将前面计算的基于按钮底部位置的触摸事件Y坐标已经考虑了各种情况调整后的坐标赋值给mDispatchY变量
// 用于在后续触摸事件分发等操作中传递准确的触摸位置信息。
mDispatchY = eventY;
// 通过event.setLocation方法重新设置触摸事件的坐标位置将X坐标保持不变event.getX()
// Y坐标设置为前面计算的mDispatchY这样就调整了触摸事件的坐标使其符合后续分发到ListView的坐标要求。
event.setLocation(event.getX(), mDispatchY);
// 将mDispatch标记变量设置为true表示当前需要进行触摸事件的分发操作后续在触摸移动和抬起等操作中会根据这个标记来执行相应的逻辑。
mDispatch = true;
// 调用ListViewmNotesListView的dispatchTouchEvent方法将调整后的触摸事件event分发给ListView进行处理
// 这样就实现了将“新建笔记”按钮透明部分的触摸事件传递给列表视图,让列表视图能够响应用户在这个特殊区域的触摸操作,
// 并返回ListView对这个触摸事件处理的结果true表示处理了该事件false表示未处理
return mNotesListView.dispatchTouchEvent(event);
}
}
break;
}
// 当触摸事件的动作为ACTION_MOVE即手指在按钮上滑动时的情况
case MotionEvent.ACTION_MOVE: {
// 判断mDispatch标记变量是否为true即前面在ACTION_DOWN阶段是否已经确定需要进行触摸事件的分发操作
// 如果是,则进行以下触摸事件坐标调整和分发逻辑。
if (mDispatch) {
// 根据当前触摸点的Y坐标event.getY()与触摸起始的Y坐标mOriginY的差值更新mDispatchY变量
// 这样mDispatchY就始终保持着触摸事件在分发过程中的准确Y坐标位置反映了手指在屏幕上滑动过程中的位置变化。
mDispatchY += (int) event.getY() - mOriginY;
// 通过event.setLocation方法再次重新设置触摸事件的坐标位置X坐标保持不变event.getX()
// Y坐标设置为更新后的mDispatchY确保传递给ListView的触摸事件坐标是准确的符合实际的触摸位置变化情况。
event.setLocation(event.getX(), mDispatchY);
// 调用ListViewmNotesListView的dispatchTouchEvent方法将更新坐标后的触摸事件event继续分发给ListView进行处理
// 使得ListView能够持续响应手指在按钮透明区域滑动过程中的触摸操作并返回处理结果true或false
return mNotesListView.dispatchTouchEvent(event);
}
break;
}
// 当触摸事件的动作为默认情况除了ACTION_DOWN和ACTION_MOVE之外的其他动作比如手指抬起等情况
default: {
// 同样先判断mDispatch标记变量是否为true即之前是否进行了触摸事件的分发操作如果是则进行以下收尾操作。
if (mDispatch) {
// 再次通过event.setLocation方法设置触摸事件的坐标位置将当前的mDispatchY坐标赋值给触摸事件的Y坐标
// 确保最后一次传递给ListView的触摸事件坐标是准确的虽然此时触摸动作已经结束但可能ListView还需要根据这个最终坐标来处理一些收尾逻辑。
event.setLocation(event.getX(), mDispatchY);
// 将mDispatch标记变量设置为false表示触摸事件分发操作结束后续如果再有触摸操作需要重新根据ACTION_DOWN阶段的逻辑来判断是否进行分发。
mDispatch = false;
// 调用ListViewmNotesListView的dispatchTouchEvent方法将最终调整后的触摸事件event分发给ListView进行处理
// 让ListView能够处理触摸动作结束时的相关逻辑比如判断是否触发了点击、滑动结束等操作并返回处理结果true或false
return mNotesListView.dispatchTouchEvent(event);
}
break;
}
}
// 如果触摸事件不属于上述处理的情况比如触摸点不在按钮透明区域等返回false表示当前类没有处理这个触摸事件
// 可能会按照默认的触摸事件处理逻辑或者由其他相关的监听器来继续处理这个触摸事件。
return false;
}
};
// 启动异步笔记列表查询的方法,用于在后台获取笔记列表数据并更新界面显示
private void startAsyncNotesListQuery() {
// 根据当前所在文件夹的IDmCurrentFolderId来确定查询的选择条件语句selection
// 如果当前所在文件夹是根文件夹mCurrentFolderId == Notes.ID_ROOT_FOLDER则使用根文件夹的查询条件ROOT_FOLDER_SELECTION
// 否则使用普通的通过父级ID查询笔记的条件NORMAL_SELECTION。这样可以根据不同的文件夹情况准确筛选出要展示的笔记数据。
String selection = (mCurrentFolderId == Notes.ID_ROOT_FOLDER)? ROOT_FOLDER_SELECTION
: NORMAL_SELECTION;
// 调用BackgroundQueryHandler继承自AsyncQueryHandler用于在后台处理数据库查询操作的startQuery方法启动一个异步查询任务。
// 参数含义如下:
// FOLDER_NOTE_LIST_QUERY_TOKEN是此次查询任务的令牌token用于在查询完成后区分不同类型的查询任务这里表示是文件夹内笔记列表的查询任务。
// null一般用于传递额外的上下文相关信息cookie这里暂时传入null表示没有额外信息需要传递。
// Notes.CONTENT_NOTE_URI代表笔记数据的内容URI用于指定从哪里获取笔记数据类似于数据库表的地址指明了要查询的数据来源。
// NoteItemData.PROJECTION是一个字符串数组用于指定要从查询结果中返回的列名即确定查询出来的数据具体包含哪些字段信息类似于数据库查询语句中的SELECT子句指定要查询的列。
// selection前面根据文件夹情况确定的查询条件语句用于筛选符合条件的笔记数据类似数据库查询语句中的WHERE子句。
// new String[] { String.valueOf(mCurrentFolderId) }是查询条件语句中占位符如果有的话对应的具体值这里将当前文件夹的ID转换为字符串后作为参数传入用于替换查询条件中的相应占位符确保查询的准确性。
// NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC"是指定查询结果的排序方式按照笔记的类型NoteColumns.TYPE降序排列若类型相同再按照修改日期NoteColumns.MODIFIED_DATE降序排列确保查询结果按照特定顺序返回方便界面展示等操作。
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");
}
// 定义一个私有内部类BackgroundQueryHandler继承自AsyncQueryHandler用于在后台线程处理数据库查询相关的操作并在查询完成后进行相应的后续处理
private final class BackgroundQueryHandler extends AsyncQueryHandler {
// 构造方法接收一个ContentResolver对象调用父类AsyncQueryHandler的构造方法将ContentResolver传递给父类
// 使得该查询处理器能够通过ContentResolver与内容提供器进行交互实现对数据库等数据源的查询操作。
public BackgroundQueryHandler(ContentResolver contentResolver) {
super(contentResolver);
}
// 重写AsyncQueryHandler的onQueryComplete方法该方法会在异步查询任务完成后被调用根据不同的查询任务令牌token来执行相应的后续处理逻辑
@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
// 根据查询任务令牌token进行判断区分不同类型的查询任务并执行对应的操作。
switch (token) {
// 如果令牌是FOLDER_NOTE_LIST_QUERY_TOKEN表示是文件夹内笔记列表的查询任务完成了执行以下操作。
case FOLDER_NOTE_LIST_QUERY_TOKEN:
// 调用笔记列表适配器mNotesListAdapter的changeCursor方法传入查询得到的游标cursor对象
// 这样适配器就可以根据新的游标数据更新笔记列表的显示内容,将从数据库中查询到的最新笔记信息展示在界面上。
mNotesListAdapter.changeCursor(cursor);
break;
// 如果令牌是FOLDER_LIST_QUERY_TOKEN表示是文件夹列表查询任务完成了执行以下操作。
case FOLDER_LIST_QUERY_TOKEN:
// 判断游标cursor不为空并且游标中的数据行数大于0即查询到了有效的文件夹数据才执行展示文件夹列表菜单的操作showFolderListMenu方法
if (cursor!= null && cursor.getCount() > 0) {
showFolderListMenu(cursor);
} else {
// 如果游标为空或者没有查询到数据,在日志中输出错误信息,标记为查询文件夹失败,方便排查问题,了解为什么没有获取到期望的文件夹数据。
Log.e(TAG, "Query folder failed");
}
break;
// 如果是其他未知的令牌,表示不是预期的查询任务完成情况,直接返回,不做任何处理。
default:
return;
}
}
}
// 展示文件夹列表菜单的方法,通常会弹出一个对话框,里面包含可供选择的文件夹列表,用户可以选择目标文件夹进行相关操作(比如移动笔记到选择的文件夹等)
private void showFolderListMenu(Cursor cursor) {
// 创建一个AlertDialog.Builder实例用于构建一个对话框传入NotesListActivity的上下文this以便对话框能够正确显示在当前界面上。
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
// 设置对话框的标题文本通过R.string.menu_title_select_folder获取在strings.xml文件中定义的对应字符串资源作为标题内容
// 一般用于提示用户当前对话框是用于选择文件夹的操作。
builder.setTitle(R.string.menu_title_select_folder);
// 创建一个FoldersListAdapter实例传入当前上下文this和查询得到的游标cursor这个适配器用于将文件夹数据适配到对话框的列表展示中
// 使得对话框能够以列表形式展示各个文件夹选项方便用户选择具体的适配逻辑在FoldersListAdapter类内部定义此处未展示完整代码
final FoldersListAdapter adapter = new FoldersListAdapter(this, cursor);
// 设置对话框的列表适配器将前面创建的文件夹列表适配器adapter设置给对话框这样对话框就会展示文件夹列表内容
// 并设置点击监听器当用户点击列表中的某个文件夹选项时会触发这里定义的逻辑即调用onClick方法进行相应的操作处理。
builder.setAdapter(adapter, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
// 调用DataUtils工具类的batchMoveToFolder方法传入ContentResolver用于操作数据、笔记列表适配器中当前选中的笔记项的ID数组mNotesListAdapter.getSelectedItemIds()
// 以及用户点击选择的文件夹的ID通过adapter.getItemId(which)获取which是点击的文件夹在列表中的位置索引
// 目的是将选中的笔记批量移动到用户选择的文件夹中具体的移动逻辑在DataUtils类的batchMoveToFolder方法中定义此处未展示完整代码
DataUtils.batchMoveToFolder(mContentResolver,
mNotesListAdapter.getSelectedItemIds(), adapter.getItemId(which));
// 弹出一个Toast提示信息告知用户笔记移动操作的结果通过getString方法获取strings.xml文件中定义的format_move_notes_to_folder字符串资源
// 并传入两个参数笔记列表适配器中当前选中的笔记数量mNotesListAdapter.getSelectedCount()以及用户选择的文件夹名称通过adapter.getFolderName方法获取
// 这样可以显示一个包含具体移动笔记数量和目标文件夹名称的提示信息让用户清楚操作的结果显示时长为短暂Toast.LENGTH_SHORT
Toast.makeText(
NotesListActivity.this,
getString(R.string.format_move_notes_to_folder,
mNotesListAdapter.getSelectedCount(),
adapter.getFolderName(NotesListActivity.this, which)),
Toast.LENGTH_SHORT).show();
// 调用ModeCallback实例mModeCallBack的finishActionMode方法结束当前可能处于的多选模式比如在多选笔记后进行移动操作的情况
// 该方法会进行一些界面恢复和相关状态清理的操作具体逻辑在ModeCallback类的finishActionMode方法中定义此处未展示完整代码
mModeCallBack.finishActionMode();
}
});
// 最后调用show方法显示这个构建好的包含文件夹列表的对话框让用户能够进行文件夹选择操作。
builder.show();
}
// 创建新笔记的方法用于启动一个新的Activity进入笔记编辑界面让用户可以创建新的笔记内容
private void createNewNote() {
// 创建一个Intent对象用于启动一个新的Activity指定要启动的Activity类为NoteEditActivity.class
// 这意味着点击“新建笔记”按钮等操作触发此方法后会跳转到NoteEditActivity这个用于编辑笔记的界面。
Intent intent = new Intent(this, NoteEditActivity.class);
// 设置Intent的动作Action为Intent.ACTION_INSERT_OR_EDIT这个动作通常表示要进行插入或编辑数据的操作
// 在这里明确了进入NoteEditActivity界面是为了创建新笔记插入操作或者编辑已有笔记虽然此处主要是新建但可能复用该界面逻辑用于编辑
intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
// 通过Intent的putExtra方法向即将启动的Activity传递额外的数据这里传递的是当前所在文件夹的IDmCurrentFolderId
// 键名为Notes.INTENT_EXTRA_FOLDER_ID在NoteEditActivity中可以通过获取这个额外数据来确定新笔记所属的文件夹等相关信息方便后续操作。
intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mCurrentFolderId);
// 调用当前上下文this的startActivityForResult方法传入创建好的Intent和请求码REQUEST_CODE_NEW_NODE
// 启动NoteEditActivity并期望在该Activity操作完成后返回结果通过请求码可以在onActivityResult方法中区分不同来源的返回结果进行相应的后续处理。
this.startActivityForResult(intent, REQUEST_CODE_NEW_NODE);
}
// 批量删除笔记的方法通过异步任务AsyncTask在后台执行删除操作同时根据是否处于同步模式等情况进行不同的处理逻辑
private void batchDelete() {
// 创建一个继承自AsyncTask的匿名内部类实例用于在后台线程执行批量删除笔记的操作并在操作完成后在主线程进行相应的界面更新等后续处理。
new AsyncTask<Void, Void, HashSet<AppWidgetAttribute>>() {
// 重写AsyncTask的doInBackground方法这个方法在后台线程中执行用于执行耗时的批量删除笔记操作以及相关的数据处理逻辑返回一个HashSet<AppWidgetAttribute>类型的结果用于在后续操作中传递一些与小部件AppWidget相关的属性信息。
protected HashSet<AppWidgetAttribute> doInBackground(Void... unused) {
// 获取笔记列表适配器mNotesListAdapter中当前选中的小部件相关属性信息集合HashSet<AppWidgetAttribute>
// 这些属性可能与笔记关联的小部件显示等情况有关,用于后续在必要时更新小部件的显示等操作。
HashSet<AppWidgetAttribute> widgets = mNotesListAdapter.getSelectedWidget();
// 判断是否不处于同步模式通过isSyncMode方法判断此处未展示该方法具体实现但应该是返回一个布尔值表示是否同步状态
if (!isSyncMode()) {
// 如果不是同步模式直接删除选中的笔记调用DataUtils工具类的batchDeleteNotes方法传入ContentResolver用于操作数据和笔记列表适配器中当前选中的笔记项的ID数组mNotesListAdapter.getSelectedItemIds()
// 尝试执行批量删除操作,如果删除成功则继续后续逻辑,如果删除失败,在日志中输出错误信息,标记为删除笔记出错,这里理论上不应该出现错误,方便排查问题。
if (DataUtils.batchDeleteNotes(mContentResolver, mNotesListAdapter
.getSelectedItemIds())) {
} else {
Log.e(TAG, "Delete notes error, should not happens");
}
} else {
// 如果处于同步模式将选中的笔记移动到回收站文件夹Notes.ID_TRASH_FOLER其具体值在Notes类中定义用于标识回收站文件夹
// 调用DataUtils工具类的batchMoveToFolder方法传入ContentResolver、选中的笔记项的ID数组以及回收站文件夹的ID
// 尝试执行批量移动操作,如果移动失败,在日志中输出错误信息,标记为移动笔记到回收站文件夹出错,同样这里理论上不应该出现错误,方便排查问题。
if (!DataUtils.batchMoveToFolder(mContentResolver, mNotesListAdapter
.getSelectedItemIds(), Notes.ID_TRASH_FOLER)) {
Log.e(TAG, "Move notes to trash folder error, should not happens");
}
}
// 将获取到的小部件相关属性信息集合widgets作为结果返回以便在后续的onPostExecute方法中使用这些信息进行相关操作。
return widgets;
}
// 重写AsyncTask的onPostExecute方法这个方法在后台的doInBackground方法执行完成后在主线程中被调用用于根据后台操作的结果进行界面更新等后续处理。
@Override
protected void onPostExecute(HashSet<AppWidgetAttribute> widgets) {
// 判断返回的小部件相关属性信息集合widgets不为空即存在与笔记关联的小部件相关属性信息才进行以下操作。
if (widgets!= null) {
// 遍历小部件相关属性信息集合中的每个AppWidgetAttribute对象widget进行相应的小部件更新操作。
for (AppWidgetAttribute widget : widgets) {
// 判断小部件的IDwidget.widgetId不是无效的小部件IDAppWidgetManager.INVALID_APPWIDGET_ID并且小部件的类型widget.widgetType也不是无效的小部件类型Notes.TYPE_WIDGET_INVALIDE
// 只有满足这两个条件的有效小部件才执行更新操作,避免对无效的小部件进行不必要的操作。
if (widget.widgetId!= AppWidgetManager.INVALID_APPWIDGET_ID
&& widget.widgetType!= Notes.TYPE_WIDGET_INVALIDE) {
// 调用updateWidget方法此处未展示该方法具体实现但应该是用于更新小部件显示等相关操作的方法传入小部件的IDwidget.widgetId和小部件的类型widget.widgetType
// 对符合条件的小部件进行更新,确保小部件的显示内容等与笔记数据的变化保持一致,比如笔记被删除或移动后,小部件显示相应更新。
updateWidget(widget.widgetId, widget.widgetType);
}
}
}
// 调用ModeCallback实例mModeCallBack的finishActionMode方法结束当前可能处于的多选模式比如在多选笔记后进行删除操作的情况
// 进行一些界面恢复和相关状态清理的操作如恢复ListView的长按可点击属性、重新显示“新建笔记”按钮等具体逻辑在ModeCallback类的finishActionMode方法中定义此处未展示完整代码
mModeCallBack.finishActionMode();
}
}.execute();
}
private void deleteFolder(long folderId) {
// 这段代码可能是用于处理文件夹相关操作的逻辑,比如删除文件夹等情况,根据文件夹是否处于同步模式来执行不同的处理方式,并对相关的小部件进行更新
if (folderId == Notes.ID_ROOT_FOLDER) {
// 如果传入的文件夹IDfolderId等于根文件夹的IDNotes.ID_ROOT_FOLDER则在日志中输出错误信息
// 提示不应该出现这种情况,因为根文件夹通常不应该被当作普通文件夹进行某些操作(比如删除等),然后直接返回,不再继续后续操作。
Log.e(TAG, "Wrong folder id, should not happen " + folderId);
return;
}
// 创建一个HashSet集合用于存储要操作的文件夹ID这里先将传入的folderId添加到集合中方便后续批量操作时使用这个集合传递参数。
HashSet<Long> ids = new HashSet<Long>();
ids.add(folderId);
// 通过DataUtils工具类的getFolderNoteWidget方法获取与指定文件夹folderId相关联的小部件属性信息集合HashSet<AppWidgetAttribute>
// 这些小部件属性信息可能包含小部件的ID、类型等内容用于后续在文件夹操作后对相关小部件进行相应的更新操作确保小部件显示与文件夹数据的变化保持一致。
HashSet<AppWidgetAttribute> widgets = DataUtils.getFolderNoteWidget(mContentResolver,
folderId);
// 判断是否不处于同步模式通过isSyncMode方法判断此处未展示该方法具体实现但应该是返回一个布尔值表示是否同步状态
if (!isSyncMode()) {
// 如果不是同步模式直接删除文件夹对应的笔记调用DataUtils工具类的batchDeleteNotes方法传入ContentResolver用于操作数据和包含文件夹ID的集合ids
// 执行批量删除操作这里可能意味着将该文件夹下的所有笔记都删除具体的删除逻辑在DataUtils类的batchDeleteNotes方法中定义此处未展示完整代码
DataUtils.batchDeleteNotes(mContentResolver, ids);
} else {
// 如果处于同步模式将该文件夹下的笔记移动到回收站文件夹Notes.ID_TRASH_FOLER其具体值在Notes类中定义用于标识回收站文件夹
// 调用DataUtils工具类的batchMoveToFolder方法传入ContentResolver、包含文件夹ID的集合ids以及回收站文件夹的ID
// 执行批量移动操作将相关笔记移动到回收站具体的移动逻辑在DataUtils类的batchMoveToFolder方法中定义此处未展示完整代码
DataUtils.batchMoveToFolder(mContentResolver, ids, Notes.ID_TRASH_FOLER);
}
// 判断获取到的与文件夹相关的小部件属性信息集合widgets不为空即存在与该文件夹关联的小部件相关属性信息才进行以下操作对这些小部件进行更新。
if (widgets!= null) {
// 遍历小部件属性信息集合中的每个AppWidgetAttribute对象widget进行相应的小部件更新操作。
for (AppWidgetAttribute widget : widgets) {
// 判断小部件的IDwidget.widgetId不是无效的小部件IDAppWidgetManager.INVALID_APPWIDGET_ID并且小部件的类型widget.widgetType也不是无效的小部件类型Notes.TYPE_WIDGET_INVALIDE
// 只有满足这两个条件的有效小部件才执行更新操作,避免对无效的小部件进行不必要的操作,确保只更新与实际相关且有效的小部件显示等内容。
if (widget.widgetId!= AppWidgetManager.INVALID_APPWIDGET_ID
&& widget.widgetType!= Notes.TYPE_WIDGET_INVALIDE) {
// 调用updateWidget方法此处未展示该方法具体实现但应该是用于更新小部件显示等相关操作的方法传入小部件的IDwidget.widgetId和小部件的类型widget.widgetType
// 对符合条件的小部件进行更新,使其显示内容等与文件夹及笔记数据的变化保持一致,比如文件夹被删除或移动后,小部件显示相应更新。
updateWidget(widget.widgetId, widget.widgetType);
}
}
}
// 用于打开一个笔记节点的方法通常是跳转到笔记编辑界面NoteEditActivity并以查看VIEW模式打开指定的笔记数据方便用户查看笔记详情
private void openNode(NoteItemData data) {
// 创建一个Intent对象用于启动一个新的Activity指定要启动的Activity类为NoteEditActivity.class
// 意味着此操作会跳转到NoteEditActivity这个用于编辑笔记的界面在这里主要用于查看笔记内容。
Intent intent = new Intent(this, NoteEditActivity.class);
// 设置Intent的动作Action为Intent.ACTION_VIEW明确表示此次进入NoteEditActivity界面是为了查看笔记而不是进行编辑等其他操作。
intent.setAction(Intent.ACTION_VIEW);
// 通过Intent的putExtra方法向即将启动的Activity传递额外的数据这里传递的是笔记的唯一标识符data.getId()
// 键名为Intent.EXTRA_UID在NoteEditActivity中可以通过获取这个额外数据来确定要查看的具体笔记方便加载相应的笔记内容进行展示。
intent.putExtra(Intent.EXTRA_UID, data.getId());
// 调用当前上下文this的startActivityForResult方法传入创建好的Intent和请求码REQUEST_CODE_OPEN_NODE
// 启动NoteEditActivity并期望在该Activity操作完成后返回结果通过请求码可以在onActivityResult方法中区分不同来源的返回结果进行相应的后续处理。
this.startActivityForResult(intent, REQUEST_CODE_OPEN_NODE);
}
// 用于打开一个文件夹的方法根据传入的笔记数据NoteItemData类型的数据可能包含文件夹相关信息来更新当前界面显示的文件夹相关内容比如切换显示的笔记列表、更新标题栏等
private void openFolder(NoteItemData data) {
// 将当前所在文件夹的IDmCurrentFolderId设置为传入的笔记数据对应的IDdata.getId()
// 这样后续操作(如查询笔记列表等)就会基于新打开的这个文件夹来进行,确保获取和展示该文件夹下的相关笔记数据。
mCurrentFolderId = data.getId();
// 调用startAsyncNotesListQuery方法启动一个异步的笔记列表查询操作目的是获取并展示新打开的文件夹下的笔记列表信息
// 使得界面能够及时更新显示新文件夹对应的笔记内容具体的查询逻辑在startAsyncNotesListQuery方法中定义此处未展示完整代码
startAsyncNotesListQuery();
// 判断如果打开的文件夹IDdata.getId()等于通话记录文件夹的IDNotes.ID_CALL_RECORD_FOLDER则将当前列表的编辑状态mState设置为通话记录文件夹状态ListEditState.CALL_RECORD_FOLDER
// 并且将“新建笔记”按钮mAddNewNote的可见性设置为不可见View.GONE因为在通话记录文件夹下可能不允许新建笔记等操作具体根据业务逻辑设定。
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
mState = ListEditState.CALL_RECORD_FOLDER;
mAddNewNote.setVisibility(View.GONE);
} else {
// 如果不是通话记录文件夹则将当前列表的编辑状态设置为子文件夹状态ListEditState.SUB_FOLDER表示处于普通的子文件夹操作模式。
mState = ListEditState.SUB_FOLDER;
}
// 再次判断如果打开的文件夹ID等于通话记录文件夹的ID将标题栏mTitleBar的文本内容设置为通话记录文件夹的名称通过R.string.call_record_folder_name获取在strings.xml文件中定义的对应字符串资源
// 用于在界面上准确显示当前打开的文件夹名称,方便用户识别。
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
mTitleBar.setText(R.string.call_record_folder_name);
} else {
// 如果不是通话记录文件夹将标题栏的文本内容设置为传入的笔记数据中的摘要信息data.getSnippet()),同样用于展示当前打开文件夹的相关提示信息,可能是文件夹的简短描述等内容。
mTitleBar.setText(data.getSnippet());
}
// 将标题栏mTitleBar的可见性设置为可见View.VISIBLE确保标题栏在界面上显示出来展示相应的文件夹名称等信息。
mTitleBar.setVisibility(View.VISIBLE);
}
// 实现了OnClickListener接口的onClick方法用于处理各种点击事件根据点击的视图View的ID来判断具体是哪个控件被点击了并执行相应的操作逻辑
public void onClick(View v) {
switch (v.getId()) {
// 如果点击的视图的ID是“新建笔记”按钮的IDR.id.btn_new_note则调用createNewNote方法
// 该方法用于启动一个新的Activity进入笔记编辑界面让用户可以创建新的笔记内容具体逻辑在createNewNote方法中定义此处未展示完整代码
case R.id.btn_new_note:
createNewNote();
break;
default:
break;
}
}
// 用于显示软键盘的方法通过获取系统的输入法服务InputMethodManager来强制显示软键盘
private void showSoftInput() {
// 获取系统的输入法服务通过getSystemService方法传入Context.INPUT_METHOD_SERVICE参数来获取InputMethodManager实例
// 这个实例用于管理输入法相关的操作,比如显示、隐藏软键盘等。
InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
// 判断获取到的输入法管理器inputMethodManager不为空即成功获取到输入法服务后才执行显示软键盘的操作。
if (inputMethodManager!= null) {
// 调用输入法管理器的toggleSoftInput方法传入InputMethodManager.SHOW_FORCED表示强制显示软键盘和0表示无额外的标志位参数
// 触发软键盘显示在屏幕上,方便用户输入内容,比如在需要用户输入文本的编辑框获取焦点时可以调用此方法来显示键盘。
inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
}
}
// 用于隐藏软键盘的方法通过获取系统的输入法服务InputMethodManager并根据指定的视图View来隐藏软键盘
private void hideSoftInput(View view) {
// 获取系统的输入法服务与前面显示软键盘的获取方式相同通过getSystemService方法传入Context.INPUT_METHOD_SERVICE参数来获取InputMethodManager实例。
InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
// 调用输入法管理器的hideSoftInputFromWindow方法传入视图的窗口令牌view.getWindowToken()用于标识该视图所在的窗口输入法管理器通过这个令牌来确定要隐藏键盘的相关窗口和0表示无额外的标志位参数
// 触发隐藏软键盘的操作,使得软键盘从屏幕上消失,通常在不需要用户输入内容或者编辑操作完成后调用此方法来隐藏键盘,提升界面整洁性和用户体验。
inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
// 用于显示创建或修改文件夹对话框的方法根据传入的布尔值create来确定是创建文件夹还是修改文件夹的操作模式并进行相应的对话框初始化设置
private void showCreateOrModifyFolderDialog(final boolean create) {
// 创建一个AlertDialog.Builder实例用于构建一个对话框传入当前上下文this以便对话框能够正确显示在当前界面上。
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
// 通过LayoutInflater从当前上下文this加载名为dialog_edit_text.xml的布局文件并实例化为一个视图对象view
// 这个布局文件应该包含了对话框中用于输入文件夹名称等相关的控件内容比如可能有一个EditText用于输入文件夹名字。
View view = LayoutInflater.from(this).inflate(R.layout.dialog_edit_text, null);
// 从加载的视图view中通过findViewById方法找到ID为R.id.et_foler_name的EditText控件用于后续获取用户输入的文件夹名称等操作
// 这个EditText就是对话框中供用户输入文件夹名称的文本编辑框。
final EditText etName = (EditText) view.findViewById(R.id.et_foler_name);
// 调用showSoftInput方法显示软键盘方便用户在弹出的对话框中直接输入文件夹名称提升操作便捷性。
showSoftInput();
// 根据传入的布尔值create来判断是创建文件夹还是修改文件夹的操作如果create为false表示是修改文件夹操作执行以下逻辑。
if (!create) {
// 判断当前获取焦点的笔记数据项mFocusNoteDataItem不为空因为修改文件夹名称可能需要基于已有的某个笔记数据项相关信息来进行
// 如果为空则无法进行修改操作,在日志中输出错误信息提示数据项为空,然后直接返回,不再继续后续操作。
if (mFocusNoteDataItem!= null) {
// 如果有有效的笔记数据项将EditTextetName的文本内容设置为当前获取焦点的笔记数据项中的摘要信息mFocusNoteDataItem.getSnippet()
// 这样可以将原文件夹名称等相关信息预先填充到编辑框中,方便用户修改,具体填充内容根据业务逻辑和数据项中的信息而定。
etName.setText(mFocusNoteDataItem.getSnippet());
// 设置对话框的标题文本通过getString方法获取strings.xml文件中定义的menu_folder_change_name字符串资源作为标题内容
// 用于提示用户当前对话框是用于修改文件夹名称的操作。
builder.setTitle(getString(R.string.menu_folder_change_name));
} else {
Log.e(TAG, "The long click data item is null");
return;
}
} else {
// 如果create为true表示是创建文件夹操作将EditTextetName的文本内容设置为空字符串准备让用户输入新的文件夹名称。
etName.setText("");
// 设置对话框的标题文本通过this.getString方法获取strings.xml文件中定义的menu_create_folder字符串资源作为标题内容
// 用于提示用户当前对话框是用于创建文件夹的操作。
builder.setTitle(this.getString(R.string.menu_create_folder));
}
// 设置对话框的“确定”按钮PositiveButton传入Android系统自带的确认文本android.R.string.ok作为按钮文本这里暂时将点击监听器设置为null
// 可能后续还需要根据具体业务逻辑来添加点击“确定”按钮后的具体操作处理代码,比如验证文件夹名称合法性、创建或修改文件夹等操作。
builder.setPositiveButton(android.R.string.ok, null);
// 设置对话框的“取消”按钮NegativeButton传入Android系统自带的取消文本android.R.string.cancel作为按钮文本
// 并设置点击监听器当用户点击“取消”按钮时会触发这里定义的逻辑即调用hideSoftInput方法隐藏软键盘传入当前的EditText控件etName避免软键盘在对话框关闭后仍然显示影响界面美观和后续操作。
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
hideSoftInput(etName);
}
});
}
// 通过之前创建的AlertDialog.Builder对象builder设置要显示的视图view然后调用show方法显示对话框并将显示后的对话框实例赋值给dialog变量
// 这样后续就可以通过这个dialog变量来操作对话框内的各种控件以及处理相关逻辑。
final Dialog dialog = builder.setView(view).show();
// 从显示的对话框dialog中通过findViewById方法查找ID为android.R.id.button1的按钮在Android系统默认的对话框布局中
// 这个ID通常对应的是“确定”按钮PositiveButton不同的主题或系统版本可能会遵循此约定来标识该按钮方便后续为其添加点击事件处理逻辑。
final Button positive = (Button) dialog.findViewById(android.R.id.button1);
// 为查找到的“确定”按钮positive设置点击监听器当用户点击该按钮时会触发以下定义的onClick方法内的逻辑。
positive.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
// 调用hideSoftInput方法隐藏软键盘传入当前用于输入文件夹名称的EditText控件etName这样做是为了在用户完成对话框操作后
// 关闭软键盘,避免其继续显示在屏幕上影响界面美观以及后续可能的操作交互,提升用户体验。
hideSoftInput(etName);
// 获取EditTextetName中的文本内容并转换为字符串形式该字符串将作为文件夹的名称用于后续的创建或修改文件夹操作判断等逻辑中。
String name = etName.getText().toString();
// 调用DataUtils工具类的checkVisibleFolderName方法传入ContentResolver用于与应用的数据提供器交互实现数据操作和获取到的文件夹名称name
// 该方法用于检查给定的文件夹名称是否已经存在具体的检查逻辑在DataUtils类的checkVisibleFolderName方法内部实现此处未展示完整代码
// 如果检查发现文件夹名称已存在,执行以下操作。
if (DataUtils.checkVisibleFolderName(mContentResolver, name)) {
// 弹出一个Toast提示信息告知用户文件夹已存在通过getString方法获取strings.xml文件中定义的folder_exist字符串资源
// 并将当前的文件夹名称name作为参数传入进行格式化使得提示信息能够明确指出哪个文件夹名称已存在
// 显示时长设置为较长Toast.LENGTH_LONG以便用户能清晰地看到提示内容知晓当前操作存在的问题。
Toast.makeText(NotesListActivity.this, getString(R.string.folder_exist, name),
Toast.LENGTH_LONG).show();
// 将EditTextetName的光标定位到文本开头0位置并选中整个文本内容etName.length()),这样做方便用户直接修改已存在的文件夹名称,
// 用户无需手动重新定位光标或全选文本,提升了操作的便捷性,使其可以快速对重复的名称进行修改后再次尝试操作。
etName.setSelection(0, etName.length());
return;
}
// 判断当前是否是修改文件夹操作create为false表示修改操作如果是修改操作执行以下逻辑。
if (!create) {
// 进一步判断获取到的文件夹名称不为空字符串通过TextUtils.isEmpty方法进行判断若返回false则表示不为空
// 只有名称不为空时才进行实际的文件夹信息更新操作,避免更新空名称导致数据异常等情况。
if (!TextUtils.isEmpty(name)) {
// 创建一个ContentValues对象它用于存储要更新的数据以键值对的形式表示类似于数据库中的插入或更新操作时的数据封装形式
// 键对应数据库表中的列名,值对应要插入或更新的具体数据内容。
ContentValues values = new ContentValues();
// 将文件夹的摘要信息通常可以理解为文件夹名称等相关描述信息对应数据库中的NoteColumns.SNIPPET列设置为新获取的名称name
// 通过put方法将列名和对应的值添加到ContentValues对象中以便后续更新操作使用。
values.put(NoteColumns.SNIPPET, name);
// 设置文件夹的类型为普通文件夹类型Notes.TYPE_FOLDER其具体值在Notes类中定义用于明确标识该数据为文件夹类型
// 同样使用put方法将NoteColumns.TYPE列名与对应的文件夹类型值添加到ContentValues对象中确保数据的类型信息准确更新。
values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER);
// 设置文件夹的本地修改标志为1表示该文件夹有本地修改操作发生这个标志可能用于后续的数据同步等相关逻辑判断
// 通过put方法将NoteColumns.LOCAL_MODIFIED列名与值1添加到ContentValues对象中记录文件夹的修改状态信息。
values.put(NoteColumns.LOCAL_MODIFIED, 1);
// 通过ContentResolver的update方法来执行实际的文件夹信息更新操作传入以下参数
// Notes.CONTENT_NOTE_URI代表笔记数据的内容URI它指明了要更新的数据所在的数据源位置类似于数据库表的地址
// 告知系统从哪里获取和更新相应的数据,这里明确了是针对笔记相关的数据进行操作。
// values前面创建的包含要更新的文件夹相关信息的ContentValues对象其中封装了要更新的列名及对应的值
// 系统会根据这些信息来更新相应的数据记录。
// NoteColumns.ID + "=?"是更新操作的条件语句用于指定要更新的具体文件夹记录通过文件夹的ID来进行筛选
// 这里的问号是占位符后续需要传入具体的ID值来确定要更新的是哪条文件夹记录。
// new String[] { String.valueOf(mFocusNoteDataItem.getId()) }:是前面条件语句中占位符对应的具体值,
// 将当前获取焦点的笔记数据项mFocusNoteDataItem的ID转换为字符串后作为参数传入这样就能准确更新对应的文件夹信息了。
mContentResolver.update(Notes.CONTENT_NOTE_URI, values, NoteColumns.ID
+ "=?", new String[] {
String.valueOf(mFocusNoteDataItem.getId())
});
}
} else if (!TextUtils.isEmpty(name)) {
// 如果当前是创建文件夹操作create为true并且获取到的文件夹名称也不为空字符串执行以下创建文件夹的逻辑。
ContentValues values = new ContentValues();
// 将文件夹的摘要信息对应NoteColumns.SNIPPET列设置为新获取的名称name通过put方法添加到ContentValues对象中
// 这就确定了新创建文件夹的名称信息。
values.put(NoteColumns.SNIPPET, name);
// 设置文件夹的类型为普通文件夹类型Notes.TYPE_FOLDER同样使用put方法添加到ContentValues对象中明确创建的是文件夹类型的数据记录。
values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER);
// 通过ContentResolver的insert方法执行实际的文件夹创建操作传入以下参数
// Notes.CONTENT_NOTE_URI代表笔记数据的内容URI指明了要在哪个数据源位置插入新的文件夹数据记录
// 告知系统在哪里创建新的文件夹相关信息。
// values前面创建的包含要插入的文件夹相关信息的ContentValues对象其中封装了新文件夹的名称和类型等信息
// 系统会根据这些信息在指定数据源中插入一条新的文件夹记录。
mContentResolver.insert(Notes.CONTENT_NOTE_URI, values);
}
// 无论当前是创建还是修改文件夹操作完成后都调用dialog的dismiss方法关闭对话框使得界面恢复到正常状态
// 避免对话框一直显示在屏幕上影响用户后续的操作和界面的整洁性。
dialog.dismiss();
}
});
// 判断EditTextetName中的文本内容是否为空字符串通过TextUtils.isEmpty方法判断如果为空
// 则将“确定”按钮positive设置为不可用状态通过setEnabled(false)方法实现),这样可以防止用户在没有输入文件夹名称的情况下点击“确定”按钮,
// 避免无效的操作发生,提升用户操作的准确性和逻辑性。
if (TextUtils.isEmpty(etName.getText())) {
positive.setEnabled(false);
}
// 以下是为EditTextetName添加文本变化监听器TextWatcher的逻辑用于实时监听文本内容的变化情况并根据文本是否为空来动态设置“确定”按钮的可用状态。
etName.addTextChangedListener(new TextWatcher() {
// 在文本内容即将发生变化前会调用此方法,目前此方法体中没有具体实现逻辑(只是一个占位符,通常如果有需要在文本变化前进行的操作可以在这里添加代码)。
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// TODO Auto-generated method stub
}
// 当文本内容发生变化时会调用此方法在这里根据EditTextetName中的文本是否为空来动态设置“确定”按钮positive的可用状态。
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (TextUtils.isEmpty(etName.getText())) {
positive.setEnabled(false);
} else {
positive.setEnabled(true);
}
}
// 在文本内容变化完成后会调用此方法,目前此方法体中没有具体实现逻辑(只是一个占位符,通常如果有需要在文本变化后进行的操作可以在这里添加代码)。
public void afterTextChanged(Editable s) {
// TODO Auto-generated method stub
}
});
// 重写Activity的onBackPressed方法用于处理用户按下手机返回键时的操作逻辑根据当前列表的编辑状态mState来执行不同的操作。
@Override
public void onBackPressed() {
switch (mState) {
// 如果当前列表编辑状态是子文件夹状态SUB_FOLDER执行以下逻辑。
case SUB_FOLDER:
// 将当前所在文件夹的IDmCurrentFolderId设置为根文件夹的IDNotes.ID_ROOT_FOLDER意味着从当前子文件夹返回回到根文件夹层级。
mCurrentFolderId = Notes.ID_ROOT_FOLDER;
// 将列表编辑状态mState设置为笔记列表状态ListEditState.NOTE_LIST表示回到普通的笔记列表展示模式。
mState = ListEditState.NOTE_LIST;
// 调用startAsyncNotesListQuery方法启动一个异步的笔记列表查询操作目的是获取并展示根文件夹下的笔记列表信息
// 使得界面能够及时更新显示回到根文件夹后的笔记内容具体的查询逻辑在startAsyncNotesListQuery方法中定义此处未展示完整代码
startAsyncNotesListQuery();
// 将标题栏mTitleBar的可见性设置为不可见View.GONE因为回到根文件夹后可能不需要显示之前子文件夹对应的标题栏内容了
// 这样可以保持界面的简洁性,符合不同层级下的界面展示需求。
mTitleBar.setVisibility(View.GONE);
break;
// 如果当前列表编辑状态是通话记录文件夹状态CALL_RECORD_FOLDER执行以下逻辑。
case CALL_RECORD_FOLDER:
// 将当前所在文件夹的IDmCurrentFolderId设置为根文件夹的IDNotes.ID_ROOT_FOLDER同样是从通话记录文件夹返回回到根文件夹层级。
mCurrentFolderId = Notes.ID_ROOT_FOLDER;
// 将列表编辑状态mState设置为笔记列表状态ListEditState.NOTE_LIST切换回普通的笔记列表展示模式。
mState = ListEditState.NOTE_LIST;
// 将“新建笔记”按钮mAddNewNote的可见性设置为可见View.VISIBLE因为回到根文件夹后通常需要显示该按钮方便用户继续进行新建笔记等操作
// 而在通话记录文件夹状态下可能之前将其隐藏了(根据业务逻辑设定)。
mAddNewNote.setVisibility(View.VISIBLE);
// 将标题栏mTitleBar的可见性设置为不可见View.GONE与前面子文件夹返回时类似回到根文件夹后不需要显示通话记录文件夹对应的标题栏内容了。
mTitleBar.setVisibility(View.GONE);
// 调用startAsyncNotesListQuery方法启动异步笔记列表查询操作获取并展示根文件夹下的笔记列表信息更新界面显示。
startAsyncNotesListQuery();
break;
// 如果当前列表编辑状态是笔记列表状态NOTE_LIST直接调用父类Activity的onBackPressed方法
// 这意味着按照系统默认的返回键处理逻辑进行操作比如可能会关闭当前Activity或者执行其他默认的返回相关行为具体取决于Activity的任务栈等相关机制。
case NOTE_LIST:
super.onBackPressed();
break;
default:
break;
}
}
// 此方法用于更新桌面小部件Widget
private void updateWidget(int appWidgetId, int appWidgetType) {
// 创建一个意图Intent指定动作为更新桌面小部件的动作
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
// 根据传入的小部件类型appWidgetType来设置不同的小部件提供类Widget Provider Class
if (appWidgetType == Notes.TYPE_WIDGET_2X) {
intent.setClass(this, NoteWidgetProvider_2x.class);
} else if (appWidgetType == Notes.TYPE_WIDGET_4X) {
intent.setClass(this, NoteWidgetProvider_4x.class);
} else {
// 如果传入的小部件类型不被支持,则记录错误日志并直接返回,不进行后续操作
Log.e(TAG, "Unspported widget type");
return;
}
// 将指定的小部件ID添加到意图的额外数据中这里是以数组形式传递单个小部件ID
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] {
appWidgetId
});
// 发送广播,触发对应的小部件更新操作
sendBroadcast(intent);
// 设置结果为操作成功RESULT_OK并关联对应的意图
setResult(RESULT_OK, intent);
}
// 定义一个上下文菜单创建监听器OnCreateContextMenuListener用于处理特定视图的上下文菜单创建事件
private final OnCreateContextMenuListener mFolderOnCreateContextMenuListener = new OnCreateContextMenuListener() {
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
// 如果当前有焦点的笔记数据项mFocusNoteDataItem不为空
if (mFocusNoteDataItem!= null) {
// 设置上下文菜单的标题为焦点笔记数据项的摘要信息(片段内容)
menu.setHeaderTitle(mFocusNoteDataItem.getSnippet());
// 向菜单中添加一个菜单项用于查看文件夹菜单项ID为MENU_FOLDER_VIEW显示文本通过资源字符串获取
menu.add(0, MENU_FOLDER_VIEW, 0, R.string.menu_folder_view);
// 向菜单中添加一个菜单项用于删除文件夹菜单项ID为MENU_FOLDER_DELETE显示文本通过资源字符串获取
menu.add(0, MENU_FOLDER_DELETE, 0, R.string.menu_folder_delete);
// 向菜单中添加一个菜单项用于更改文件夹名称菜单项ID为MENU_FOLDER_CHANGE_NAME显示文本通过资源字符串获取
menu.add(0, MENU_FOLDER_CHANGE_NAME, 0, R.string.menu_folder_change_name);
}
}
};
// 当上下文菜单关闭时触发的回调方法
@Override
public void onContextMenuClosed(Menu menu) {
// 如果笔记列表视图mNotesListView不为空则移除其上下文菜单创建监听器
if (mNotesListView!= null) {
mNotesListView.setOnCreateContextMenuListener(null);
}
// 调用父类的onContextMenuClosed方法执行默认的或父类中定义的关闭后逻辑
super.onContextMenuClosed(menu);
}
// 当上下文菜单中的某个菜单项被选中时触发的回调方法
@Override
public boolean onContextItemSelected(MenuItem item) {
// 如果当前有焦点的笔记数据项mFocusNoteDataItem为空则记录错误日志并返回false表示不处理该事件
if (mFocusNoteDataItem == null) {
Log.e(TAG, "The long click data item is null");
return false;
}
// 根据选中菜单项的ID进行不同的操作处理
switch (item.getItemId()) {
case MENU_FOLDER_VIEW:
// 调用openFolder方法打开对应的文件夹具体功能由openFolder方法实现
openFolder(mFocusNoteDataItem);
break;
case MENU_FOLDER_DELETE:
// 创建一个警告对话框构建器AlertDialog.Builder
AlertDialog.Builder builder = new AlertDialog.Builder(this);
// 设置对话框的标题,通过资源字符串获取
builder.setTitle(getString(R.string.alert_title_delete));
// 设置对话框的图标,使用系统默认的警告图标
builder.setIcon(android.R.drawable.ic_dialog_alert);
// 设置对话框的提示信息,通过资源字符串获取
builder.setMessage(getString(R.string.alert_message_delete_folder));
// 设置对话框的确认按钮(正按钮),按钮文本为系统默认的"确定"点击时调用deleteFolder方法删除文件夹传入对应文件夹的ID
builder.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
deleteFolder(mFocusNoteDataItem.getId());
}
});
// 设置对话框的取消按钮负按钮点击时不做额外操作传入null
builder.setNegativeButton(android.R.string.cancel, null);
// 显示构建好的警告对话框
builder.show();
break;
case MENU_FOLDER_CHANGE_NAME:
// 调用showCreateOrModifyFolderDialog方法传入false参数用于显示创建或修改文件夹名称的对话框具体功能由该方法实现
showCreateOrModifyFolderDialog(false);
break;
default:
break;
}
// 返回true表示已处理该菜单项选择事件
return true;
}
// 在准备选项菜单Options Menu时触发的回调方法用于动态设置菜单内容
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
// 先清空菜单中的所有菜单项
menu.clear();
// 根据当前所处的编辑状态mState来动态加载不同的菜单布局资源
if (mState == ListEditState.NOTE_LIST) {
getMenuInflater().inflate(R.menu.note_list, menu);
// 根据同步服务GTaskSyncService是否正在同步来设置"同步"菜单项的标题文本(显示同步或取消同步)
menu.findItem(R.id.menu_sync).setTitle(
GTaskSyncService.isSyncing()? R.string.menu_sync_cancel : R.string.menu_sync);
} else if (mState == ListEditState.SUB_FOLDER) {
getMenuInflater().inflate(R.menu.sub_folder, menu);
} else if (mState == ListEditState.CALL_RECORD_FOLDER) {
getMenuInflater().inflate(R.menu.call_record_folder, menu);
} else {
// 如果处于不识别的状态,则记录错误日志
Log.e(TAG, "Wrong state:" + mState);
}
return true;
}
// 当选项菜单中的某个菜单项被选中时触发的回调方法,用于处理不同菜单项对应的操作逻辑
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_new_folder: {
// 调用showCreateOrModifyFolderDialog方法传入true参数用于显示创建或修改文件夹的对话框具体功能由该方法实现
showCreateOrModifyFolderDialog(true);
break;
}
case R.id.menu_export_text: {
// 调用exportNoteToText方法用于将笔记导出为文本文件具体功能由该方法实现
exportNoteToText();
break;
}
case R.id.menu_sync: {
// 判断是否处于同步模式isSyncMode方法的具体逻辑未在代码中体现
if (isSyncMode()) {
// 如果当前菜单项标题为"同步"则调用startSync方法启动同步服务GTaskSyncService
if (TextUtils.equals(item.getTitle(), getString(R.string.menu_sync))) {
GTaskSyncService.startSync(this);
} else {
// 如果当前菜单项标题为"取消同步"则调用cancelSync方法取消同步服务
GTaskSyncService.cancelSync(this);
}
} else {
// 如果不处于同步模式则调用startPreferenceActivity方法启动偏好设置相关的活动具体功能由该方法实现
startPreferenceActivity();
}
break;
}
case R.id.menu_setting: {
// 调用startPreferenceActivity方法启动偏好设置相关的活动具体功能由该方法实现
startPreferenceActivity();
break;
}
case R.id.menu_new_note: {
// 调用createNewNote方法创建新的笔记具体功能由该方法实现
createNewNote();
break;
}
case R.id.menu_search:
// 调用onSearchRequested方法触发搜索相关的操作具体功能由该方法实现
onSearchRequested();
break;
default:
break;
}
return true;
}
// 当触发搜索请求时调用的方法
@Override
public boolean onSearchRequested() {
// 调用startSearch方法来启动搜索功能传入相关参数这里传入的参数分别表示搜索关键词为null、不启用语音搜索false、应用数据为null、不限制搜索范围false
startSearch(null, false, null /* appData */, false);
// 返回true表示已处理搜索请求
return true;
}
// 用于将笔记导出为文本文件的方法
private void exportNoteToText() {
// 获取BackupUtils的单例实例传入当前的NotesListActivity作为上下文
final BackupUtils backup = BackupUtils.getInstance(NotesListActivity.this);
// 创建一个异步任务AsyncTask用于在后台执行导出操作其参数类型依次为输入参数这里不需要所以是Void、进度参数这里不需要所以是Void、结果参数返回的是一个整数用于表示导出操作的状态
new AsyncTask<Void, Void, Integer>() {
// 在后台线程中执行的方法用于执行实际的导出到文本的操作返回导出操作的状态码由BackupUtils的exportToText方法决定
@Override
protected Integer doInBackground(Void... unused) {
return backup.exportToText();
}
// 在后台操作完成后,在主线程中执行的方法,用于根据返回的结果状态码来进行相应的提示处理
@Override
protected void onPostExecute(Integer result) {
// 如果结果状态码表示SD卡未挂载
if (result == BackupUtils.STATE_SD_CARD_UNMOUONTED) {
// 创建一个警告对话框构建器AlertDialog.Builder传入当前的NotesListActivity作为上下文
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
// 设置对话框的标题,通过资源字符串获取对应的文本
builder.setTitle(NotesListActivity.this.getString(R.string.failed_sdcard_export));
// 设置对话框的提示信息,通过资源字符串获取对应的文本
builder.setMessage(NotesListActivity.this.getString(R.string.error_sdcard_unmounted));
// 设置对话框的确认按钮(正按钮),按钮文本为系统默认的"确定"点击时不做额外操作传入null
builder.setPositiveButton(android.R.string.ok, null);
// 显示构建好的警告对话框
builder.show();
} else if (result == BackupUtils.STATE_SUCCESS) {
// 创建一个警告对话框构建器AlertDialog.Builder传入当前的NotesListActivity作为上下文
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
// 设置对话框的标题,通过资源字符串获取对应的文本
builder.setTitle(NotesListActivity.this.getString(R.string.success_sdcard_export));
// 设置对话框的提示信息通过资源字符串获取对应的格式化文本其中格式化参数通过BackupUtils实例获取导出的文本文件名和文件目录
builder.setMessage(NotesListActivity.this.getString(
R.string.format_exported_file_location, backup.getExportedTextFileName(), backup.getExportedTextFileDir()));
// 设置对话框的确认按钮(正按钮),按钮文本为系统默认的"确定"点击时不做额外操作传入null
builder.setPositiveButton(android.R.string.ok, null);
// 显示构建好的警告对话框
builder.show();
} else if (result == BackupUtils.STATE_SYSTEM_ERROR) {
// 创建一个警告对话框构建器AlertDialog.Builder传入当前的NotesListActivity作为上下文
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
// 设置对话框的标题,通过资源字符串获取对应的文本
builder.setTitle(NotesListActivity.this.getString(R.string.failed_sdcard_export));
// 设置对话框的提示信息,通过资源字符串获取对应的文本
builder.setMessage(NotesListActivity.this.getString(R.string.error_sdcard_export));
// 设置对话框的确认按钮(正按钮),按钮文本为系统默认的"确定"点击时不做额外操作传入null
builder.setPositiveButton(android.R.string.ok, null);
// 显示构建好的警告对话框
builder.show();
}
}
}.execute();
}
// 判断是否处于同步模式的方法通过获取同步账户名称从NotesPreferenceActivity中获取并检查其去除空格后的长度是否大于0来判断
private boolean isSyncMode() {
return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0;
}
// 用于启动偏好设置相关活动NotesPreferenceActivity的方法
private void startPreferenceActivity() {
// 获取启动该活动的源Activity如果当前Activity有父Activity则使用父Activity否则使用当前Activity本身
Activity from = getParent()!= null? getParent() : this;
// 创建一个意图Intent指定要启动的目标Activity为NotesPreferenceActivity.class
Intent intent = new Intent(from, NotesPreferenceActivity.class);
// 根据意图启动活动,如果活动已经存在则复用(根据传入的请求码 -1 来决定具体行为,这里暂不详细分析请求码相关细节)
from.startActivityIfNeeded(intent, -1);
}
// 实现了OnItemClickListener接口的内部类用于处理列表项点击事件
private class OnListItemClickListener implements OnItemClickListener {
// 当列表项被点击时触发的方法
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// 如果被点击的视图是NotesListItem类型的实例
if (view instanceof NotesListItem) {
// 获取该视图对应的笔记数据项NoteItemData
NoteItemData item = ((NotesListItem) view).getItemData();
// 如果笔记列表适配器mNotesListAdapter处于选择模式例如多选模式等
if (mNotesListAdapter.isInChoiceMode()) {
// 如果数据项类型是笔记Notes.TYPE_NOTE
if (item.getType() == Notes.TYPE_NOTE) {
// 调整位置参数,减去列表头部视图的数量,以获取在实际数据列表中的正确位置
position = position - mNotesListView.getHeaderViewsCount();
// 调用回调接口mModeCallBack的方法通知该项的选中状态发生改变传入相关参数这里传入的参数表示视图为null可能在某些情况下不需要具体视图引用、调整后的位置、数据项ID、取反当前该项是否已被选中的状态即切换选中状态
mModeCallBack.onItemCheckedStateChanged(null, position, id,
!mNotesListAdapter.isSelectedItem(position));
}
return;
}
// 根据当前所处的状态mState来进行不同的操作处理
switch (mState) {
case NOTE_LIST:
// 如果数据项类型是文件夹Notes.TYPE_FOLDER或者系统类型Notes.TYPE_SYSTEM
if (item.getType() == Notes.TYPE_FOLDER || item.getType() == Notes.TYPE_SYSTEM) {
// 调用openFolder方法打开对应的文件夹具体功能由openFolder方法实现
openFolder(item);
} else if (item.getType() == Notes.TYPE_NOTE) {
// 调用openNode方法打开对应的笔记节点具体功能由openNode方法实现
openNode(item);
} else {
// 如果数据项类型不符合预期,则记录错误日志
Log.e(TAG, "Wrong note type in NOTE_LIST");
}
break;
case SUB_FOLDER:
case CALL_RECORD_FOLDER:
// 如果数据项类型是笔记Notes.TYPE_NOTE
if (item.getType() == Notes.TYPE_NOTE) {
// 调用openNode方法打开对应的笔记节点具体功能由openNode方法实现
openNode(item);
} else {
// 如果数据项类型不符合预期,则记录错误日志
Log.e(TAG, "Wrong note type in SUB_FOLDER");
}
break;
default:
break;
}
}
}
}
// 用于启动查询目标文件夹的方法
private void startQueryDestinationFolders() {
// 构建查询条件字符串SQL语句中的WHERE子句部分用于筛选符合特定条件的文件夹记录
String selection = NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>? AND " + NoteColumns.ID + "<>?";
// 根据当前所处的编辑状态mState来进一步调整查询条件如果处于NOTE_LIST状态则使用原查询条件否则添加额外的条件与根文件夹相关的条件
selection = (mState == ListEditState.NOTE_LIST)? selection :
"(" + selection + ") OR (" + NoteColumns.ID + "=" + Notes.ID_ROOT_FOLDER + ")";
// 通过后台查询处理器mBackgroundQueryHandler启动一个查询操作传入相关参数包括查询令牌用于标识查询任务、查询结果回调这里传入null可能后续有其他处理方式、查询的内容URI指向笔记相关的内容提供器的URI、查询投影指定要查询返回的列信息、构建好的查询条件字符串、查询条件的参数值数组对应查询条件中?占位符的值)以及排序方式(按照修改日期降序排列)
mBackgroundQueryHandler.startQuery(FOLDER_LIST_QUERY_TOKEN,
null,
Notes.CONTENT_NOTE_URI,
FoldersListAdapter.PROJECTION,
selection,
new String[] {
String.valueOf(Notes.TYPE_FOLDER),
String.valueOf(Notes.ID_TRASH_FOLER),
String.valueOf(mCurrentFolderId)
},
NoteColumns.MODIFIED_DATE + " DESC");
}
// 当列表项被长按(长按点击)时触发的方法
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
// 如果被长按的视图是NotesListItem类型的实例
if (view instanceof NotesListItem) {
// 获取该视图对应的笔记数据项NoteItemData并赋值给mFocusNoteDataItem用于后续操作比如上下文菜单等操作可能会用到
mFocusNoteDataItem = ((NotesListItem) view).getItemData();
// 如果数据项类型是笔记Notes.TYPE_NOTE并且笔记列表适配器mNotesListAdapter不处于选择模式
if (mFocusNoteDataItem.getType() == Notes.TYPE_NOTE &&!mNotesListAdapter.isInChoiceMode()) {
// 尝试启动一个操作模式例如上下文操作模式等如果启动成功返回不为null
if (mNotesListView.startActionMode(mModeCallBack)!= null) {
// 调用回调接口mModeCallBack的方法通知该项的选中状态变为已选中传入相关参数这里传入的参数表示视图为null可能在某些情况下不需要具体视图引用、位置、数据项ID、选中状态为true
mModeCallBack.onItemCheckedStateChanged(null, position, id, true);
// 触发触觉反馈(长按震动等反馈),使用系统默认的长按震动反馈常量
mNotesListView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
} else {
// 如果启动操作模式失败,则记录错误日志
Log.e(TAG, "startActionMode fails");
}
} else if (mFocusNoteDataItem.getType() == Notes.TYPE_FOLDER) {
// 如果数据项类型是文件夹则为笔记列表视图mNotesListView设置上下文菜单创建监听器为之前定义的mFolderOnCreateContextMenuListener这样长按文件夹时可以弹出相应的上下文菜单
mNotesListView.setOnCreateContextMenuListener(mFolderOnCreateContextMenuListener);
}
}
// 返回false可能表示长按操作未完全消费该事件具体行为可能取决于调用该方法的上层逻辑
return false;
}
}