|
|
|
@ -78,130 +78,195 @@ import java.io.InputStream;
|
|
|
|
|
import java.io.InputStreamReader;
|
|
|
|
|
import java.util.HashSet;
|
|
|
|
|
|
|
|
|
|
public class NotesListActivity extends Activity implements OnClickListener, OnItemLongClickListener {
|
|
|
|
|
private static final int FOLDER_NOTE_LIST_QUERY_TOKEN = 0;
|
|
|
|
|
//导入所需的类
|
|
|
|
|
import android.app.Activity;
|
|
|
|
|
import android.content.ContentResolver;
|
|
|
|
|
import android.content.Context;
|
|
|
|
|
import android.database.Cursor;
|
|
|
|
|
import android.net.Uri;
|
|
|
|
|
import android.os.Bundle;
|
|
|
|
|
import android.view.View;
|
|
|
|
|
import android.widget.AdapterView;
|
|
|
|
|
import android.widget.Button;
|
|
|
|
|
import android.widget.ListView;
|
|
|
|
|
import android.widget.TextView;
|
|
|
|
|
|
|
|
|
|
private static final int FOLDER_LIST_QUERY_TOKEN = 1;
|
|
|
|
|
//导入自定义的NoteItemData类,该类可能用于存储笔记数据
|
|
|
|
|
import net.micode.notes.NoteItemData;
|
|
|
|
|
|
|
|
|
|
private static final int MENU_FOLDER_DELETE = 0;
|
|
|
|
|
//导入自定义的Notes类,该类可能包含一些常量,如笔记类型等
|
|
|
|
|
import net.micode.notes.Notes;
|
|
|
|
|
|
|
|
|
|
private static final int MENU_FOLDER_VIEW = 1;
|
|
|
|
|
//导入自定义的NoteColumns类,该类可能包含一些与笔记相关的列名
|
|
|
|
|
import net.micode.notes.NoteColumns;
|
|
|
|
|
|
|
|
|
|
private static final int MENU_FOLDER_CHANGE_NAME = 2;
|
|
|
|
|
//导入自定义的ModeCallback接口,该接口可能用于回调某种模式的状态或结果
|
|
|
|
|
import net.micode.notes.ModeCallback;
|
|
|
|
|
|
|
|
|
|
private static final String PREFERENCE_ADD_INTRODUCTION = "net.micode.notes.introduction";
|
|
|
|
|
//定义一个用于记录笔记列表状态的枚举类ListEditState
|
|
|
|
|
public class NotesListActivity extends Activity implements OnClickListener, OnItemLongClickListener {
|
|
|
|
|
// 定义查询令牌,用于查询笔记列表和文件夹列表
|
|
|
|
|
private static final int FOLDER_NOTE_LIST_QUERY_TOKEN = 0;
|
|
|
|
|
private static final int FOLDER_LIST_QUERY_TOKEN = 1;
|
|
|
|
|
|
|
|
|
|
private enum ListEditState {
|
|
|
|
|
NOTE_LIST, SUB_FOLDER, CALL_RECORD_FOLDER
|
|
|
|
|
};
|
|
|
|
|
// 定义菜单项的ID,用于删除文件夹、查看文件夹和更改文件夹名称的操作
|
|
|
|
|
private static final int MENU_FOLDER_DELETE = 0;
|
|
|
|
|
private static final int MENU_FOLDER_VIEW = 1;
|
|
|
|
|
private static final int MENU_FOLDER_CHANGE_NAME = 2;
|
|
|
|
|
|
|
|
|
|
private ListEditState mState;
|
|
|
|
|
// 定义一个用于存储应用程序偏好的键
|
|
|
|
|
private static final String PREFERENCE_ADD_INTRODUCTION = "net.micode.notes.introduction";
|
|
|
|
|
|
|
|
|
|
private BackgroundQueryHandler mBackgroundQueryHandler;
|
|
|
|
|
// 定义一个枚举类型,用于记录笔记列表的状态(如普通笔记列表、子文件夹、通话记录文件夹)
|
|
|
|
|
private enum ListEditState {
|
|
|
|
|
NOTE_LIST, SUB_FOLDER, CALL_RECORD_FOLDER
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
private NotesListAdapter mNotesListAdapter;
|
|
|
|
|
// 记录当前笔记列表的状态
|
|
|
|
|
private ListEditState mState;
|
|
|
|
|
|
|
|
|
|
private ListView mNotesListView;
|
|
|
|
|
// 用于在后台执行查询的处理器
|
|
|
|
|
private BackgroundQueryHandler mBackgroundQueryHandler;
|
|
|
|
|
|
|
|
|
|
private Button mAddNewNote;
|
|
|
|
|
// 用于显示笔记列表的适配器
|
|
|
|
|
private NotesListAdapter mNotesListAdapter;
|
|
|
|
|
|
|
|
|
|
private boolean mDispatch;
|
|
|
|
|
// 用于显示笔记列表的ListView组件
|
|
|
|
|
private ListView mNotesListView;
|
|
|
|
|
|
|
|
|
|
private int mOriginY;
|
|
|
|
|
// 添加新笔记的按钮组件
|
|
|
|
|
private Button mAddNewNote;
|
|
|
|
|
|
|
|
|
|
private int mDispatchY;
|
|
|
|
|
// 一个标志,指示是否正在分发事件(可能是用于防止重复点击)
|
|
|
|
|
private boolean mDispatch;
|
|
|
|
|
|
|
|
|
|
private TextView mTitleBar;
|
|
|
|
|
// 记录事件的原始Y坐标和分发的Y坐标(可能是用于处理滚动事件)
|
|
|
|
|
private int mOriginY;
|
|
|
|
|
private int mDispatchY;
|
|
|
|
|
|
|
|
|
|
private long mCurrentFolderId;
|
|
|
|
|
// 标题栏的TextView组件(可能是用于显示当前选中的文件夹名称)
|
|
|
|
|
private TextView mTitleBar;
|
|
|
|
|
|
|
|
|
|
private ContentResolver mContentResolver;
|
|
|
|
|
// 当前选中的文件夹ID(可能是用于筛选要显示的笔记)
|
|
|
|
|
private long mCurrentFolderId;
|
|
|
|
|
|
|
|
|
|
private ModeCallback mModeCallBack;
|
|
|
|
|
// Android的内容解析器,用于访问数据库中的笔记数据
|
|
|
|
|
private ContentResolver mContentResolver;
|
|
|
|
|
|
|
|
|
|
private static final String TAG = "NotesListActivity";
|
|
|
|
|
// 一个回调接口,可能用于在更改模式时接收回调(例如,更改文件夹名称后的回调)
|
|
|
|
|
private ModeCallback mModeCallBack;
|
|
|
|
|
|
|
|
|
|
public static final int NOTES_LISTVIEW_SCROLL_RATE = 30;
|
|
|
|
|
// 定义一个用于日志记录的标签(可能是用于调试或日志记录)
|
|
|
|
|
private static final String TAG = "NotesListActivity";
|
|
|
|
|
|
|
|
|
|
private NoteItemData mFocusNoteDataItem;
|
|
|
|
|
// 定义一个常量,用于控制笔记列表视图的滚动速度(可能是用于滚动动画效果)
|
|
|
|
|
public static final int NOTES_LISTVIEW_SCROLL_RATE = 30;
|
|
|
|
|
|
|
|
|
|
private static final String NORMAL_SELECTION = NoteColumns.PARENT_ID + "=?";
|
|
|
|
|
// 当前选中的笔记项的数据对象(可能是用于显示选中的笔记项的详细信息)
|
|
|
|
|
private NoteItemData mFocusNoteDataItem;
|
|
|
|
|
|
|
|
|
|
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)";
|
|
|
|
|
// 定义查询笔记时使用的筛选条件(可能是用于筛选特定类型的笔记或子文件夹)
|
|
|
|
|
|
|
|
|
|
private final static int REQUEST_CODE_OPEN_NODE = 102;
|
|
|
|
|
private final static int REQUEST_CODE_NEW_NODE = 103;
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected void onCreate(Bundle savedInstanceState) {
|
|
|
|
|
super.onCreate(savedInstanceState);
|
|
|
|
|
setContentView(R.layout.note_list);
|
|
|
|
|
initResources();
|
|
|
|
|
//定义一个常量,表示普通选择条件的查询字符串
|
|
|
|
|
private static final String NORMAL_SELECTION = NoteColumns.PARENT_ID + "=?";
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Insert an introduction when user firstly use this application
|
|
|
|
|
*/
|
|
|
|
|
setAppInfoFromRawRes();
|
|
|
|
|
}
|
|
|
|
|
//定义一个常量,表示根文件夹的选择条件的查询字符串
|
|
|
|
|
private static final String ROOT_FOLDER_SELECTION = "(" + NoteColumns.TYPE + "<>" + Notes.TYPE_SYSTEM + ")";
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
|
|
|
|
if (resultCode == RESULT_OK
|
|
|
|
|
&& (requestCode == REQUEST_CODE_OPEN_NODE || requestCode == REQUEST_CODE_NEW_NODE)) {
|
|
|
|
|
mNotesListAdapter.changeCursor(null);
|
|
|
|
|
} else {
|
|
|
|
|
super.onActivityResult(requestCode, resultCode, data);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
//重写Activity的onCreate方法
|
|
|
|
|
@Override
|
|
|
|
|
protected void onCreate(Bundle savedInstanceState) {
|
|
|
|
|
super.onCreate(savedInstanceState); // 调用父类的onCreate方法
|
|
|
|
|
setContentView(R.layout.note_list); // 设置布局为note_list.xml
|
|
|
|
|
initResources(); // 初始化资源(可能是初始化一些UI组件或数据)
|
|
|
|
|
|
|
|
|
|
private void setAppInfoFromRawRes() {
|
|
|
|
|
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
|
|
|
|
|
if (!sp.getBoolean(PREFERENCE_ADD_INTRODUCTION, false)) {
|
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
|
InputStream in = null;
|
|
|
|
|
try {
|
|
|
|
|
in = getResources().openRawResource(R.raw.introduction);
|
|
|
|
|
if (in != null) {
|
|
|
|
|
InputStreamReader isr = new InputStreamReader(in);
|
|
|
|
|
BufferedReader br = new BufferedReader(isr);
|
|
|
|
|
char [] buf = new char[1024];
|
|
|
|
|
int len = 0;
|
|
|
|
|
while ((len = br.read(buf)) > 0) {
|
|
|
|
|
sb.append(buf, 0, len);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
Log.e(TAG, "Read introduction file error");
|
|
|
|
|
return;
|
|
|
|
|
// 当用户第一次使用此应用程序时,插入介绍信息
|
|
|
|
|
setAppInfoFromRawRes();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//重写Activity的onActivityResult方法
|
|
|
|
|
@Override
|
|
|
|
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
|
|
|
|
if (resultCode == RESULT_OK // 如果返回的结果码是RESULT_OK(表示操作成功)
|
|
|
|
|
&& (requestCode == REQUEST_CODE_OPEN_NODE || requestCode == REQUEST_CODE_NEW_NODE)) { // 并且请求码是打开节点或新建节点的请求码
|
|
|
|
|
mNotesListAdapter.changeCursor(null); // 改变适配器的数据源为空,可能是为了清空列表
|
|
|
|
|
} else {
|
|
|
|
|
super.onActivityResult(requestCode, resultCode, data); // 否则,调用父类的onActivityResult方法
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 从应用的资源文件中获取应用程序信息
|
|
|
|
|
*/
|
|
|
|
|
private void setAppInfoFromRawRes() {
|
|
|
|
|
// 获取默认的SharedPreferences对象,用于存储应用的一些偏好设置
|
|
|
|
|
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
|
|
|
|
|
|
|
|
|
|
// 检查SharedPreferences中是否已经设置了介绍信息,如果没有,则继续下面的操作
|
|
|
|
|
if (!sp.getBoolean(PREFERENCE_ADD_INTRODUCTION, false)) {
|
|
|
|
|
// 创建一个StringBuilder对象,用于拼接读取到的文件内容
|
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
|
|
|
|
|
|
// 尝试从资源文件中打开"introduction"文件
|
|
|
|
|
InputStream in = null;
|
|
|
|
|
try {
|
|
|
|
|
in = getResources().openRawResource(R.raw.introduction);
|
|
|
|
|
|
|
|
|
|
// 如果文件成功打开
|
|
|
|
|
if (in != null) {
|
|
|
|
|
// 创建一个InputStreamReader对象,用于将字节流转换为字符流
|
|
|
|
|
InputStreamReader isr = new InputStreamReader(in);
|
|
|
|
|
|
|
|
|
|
// 创建一个BufferedReader对象,用于按行读取字符流
|
|
|
|
|
BufferedReader br = new BufferedReader(isr);
|
|
|
|
|
|
|
|
|
|
// 创建一个字符数组,用于缓存读取到的文件内容
|
|
|
|
|
char [] buf = new char[1024];
|
|
|
|
|
int len = 0;
|
|
|
|
|
|
|
|
|
|
// 按行读取文件内容,并添加到StringBuilder中
|
|
|
|
|
while ((len = br.read(buf)) > 0) {
|
|
|
|
|
sb.append(buf, 0, len);
|
|
|
|
|
}
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
} else {
|
|
|
|
|
// 如果文件打开失败,打印错误日志,并结束函数执行
|
|
|
|
|
Log.e(TAG, "Read introduction file error");
|
|
|
|
|
return;
|
|
|
|
|
} finally {
|
|
|
|
|
if(in != null) {
|
|
|
|
|
try {
|
|
|
|
|
in.close();
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
|
// TODO Auto-generated catch block
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
|
// 如果在读取文件过程中出现异常,打印异常堆栈信息,并结束函数执行
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
return;
|
|
|
|
|
} finally {
|
|
|
|
|
// 无论是否出现异常,都尝试关闭已打开的输入流
|
|
|
|
|
if(in != null) {
|
|
|
|
|
try {
|
|
|
|
|
in.close();
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
|
// 如果关闭输入流失败,打印异常堆栈信息
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
WorkingNote note = WorkingNote.createEmptyNote(this, Notes.ID_ROOT_FOLDER,
|
|
|
|
|
AppWidgetManager.INVALID_APPWIDGET_ID, Notes.TYPE_WIDGET_INVALIDE,
|
|
|
|
|
ResourceParser.RED);
|
|
|
|
|
note.setWorkingText(sb.toString());
|
|
|
|
|
if (note.saveNote()) {
|
|
|
|
|
sp.edit().putBoolean(PREFERENCE_ADD_INTRODUCTION, true).commit();
|
|
|
|
|
} else {
|
|
|
|
|
Log.e(TAG, "Save introduction note error");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// 创建一个新的工作笔记(WorkingNote)对象,并设置其内容为读取到的文件内容
|
|
|
|
|
WorkingNote note = WorkingNote.createEmptyNote(this, Notes.ID_ROOT_FOLDER,
|
|
|
|
|
AppWidgetManager.INVALID_APPWIDGET_ID, Notes.TYPE_WIDGET_INVALIDE,
|
|
|
|
|
ResourceParser.RED);
|
|
|
|
|
note.setWorkingText(sb.toString());
|
|
|
|
|
|
|
|
|
|
// 如果成功保存工作笔记,则将偏好设置中的介绍信息设置为已设置,并提交更改
|
|
|
|
|
if (note.saveNote()) {
|
|
|
|
|
sp.edit().putBoolean(PREFERENCE_ADD_INTRODUCTION, true).commit();
|
|
|
|
|
} else {
|
|
|
|
|
// 如果保存工作笔记失败,打印错误日志,并结束函数执行
|
|
|
|
|
Log.e(TAG, "Save introduction note error");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected void onStart() {
|
|
|
|
@ -209,44 +274,86 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
|
|
|
|
|
startAsyncNotesListQuery();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 初始化资源
|
|
|
|
|
*/
|
|
|
|
|
private void initResources() {
|
|
|
|
|
// 获取ContentResolver对象,用于访问应用程序中的内容提供者
|
|
|
|
|
mContentResolver = this.getContentResolver();
|
|
|
|
|
|
|
|
|
|
// 创建一个后台查询处理器,用于在后台处理数据库查询操作
|
|
|
|
|
mBackgroundQueryHandler = new BackgroundQueryHandler(this.getContentResolver());
|
|
|
|
|
|
|
|
|
|
// 设置当前文件夹ID为根文件夹ID
|
|
|
|
|
mCurrentFolderId = Notes.ID_ROOT_FOLDER;
|
|
|
|
|
|
|
|
|
|
// 获取ListView对象,用于显示笔记列表
|
|
|
|
|
mNotesListView = (ListView) findViewById(R.id.notes_list);
|
|
|
|
|
|
|
|
|
|
// 向ListView添加一个底部视图,可能是用于显示一些额外的信息或控件
|
|
|
|
|
mNotesListView.addFooterView(LayoutInflater.from(this).inflate(R.layout.note_list_footer, null),
|
|
|
|
|
null, false);
|
|
|
|
|
|
|
|
|
|
// 设置ListView的Item点击监听器为OnListItemClickListener对象
|
|
|
|
|
mNotesListView.setOnItemClickListener(new OnListItemClickListener());
|
|
|
|
|
|
|
|
|
|
// 设置ListView的Item长按监听器为当前类对象(this),即实现了OnItemLongClickListener接口
|
|
|
|
|
mNotesListView.setOnItemLongClickListener(this);
|
|
|
|
|
|
|
|
|
|
// 创建一个笔记列表适配器,用于将数据与视图进行绑定
|
|
|
|
|
mNotesListAdapter = new NotesListAdapter(this);
|
|
|
|
|
|
|
|
|
|
// 将适配器设置到ListView上,以显示数据
|
|
|
|
|
mNotesListView.setAdapter(mNotesListAdapter);
|
|
|
|
|
|
|
|
|
|
// 获取一个按钮对象,可能是用于添加新笔记的按钮
|
|
|
|
|
mAddNewNote = (Button) findViewById(R.id.btn_new_note);
|
|
|
|
|
|
|
|
|
|
// 设置按钮的点击监听器为当前类对象(this),即实现了OnClickListener接口
|
|
|
|
|
mAddNewNote.setOnClickListener(this);
|
|
|
|
|
|
|
|
|
|
// 设置按钮的触摸监听器为NewNoteOnTouchListener对象,用于处理触摸事件
|
|
|
|
|
mAddNewNote.setOnTouchListener(new NewNoteOnTouchListener());
|
|
|
|
|
|
|
|
|
|
// 初始化一些变量,如dispatch(可能是用于处理拖动手势的变量)等
|
|
|
|
|
mDispatch = false;
|
|
|
|
|
mDispatchY = 0;
|
|
|
|
|
mOriginY = 0;
|
|
|
|
|
|
|
|
|
|
// 获取一个TextView对象,可能是标题栏的一部分
|
|
|
|
|
mTitleBar = (TextView) findViewById(R.id.tv_title_bar);
|
|
|
|
|
|
|
|
|
|
// 设置当前的状态为笔记列表状态(ListEditState.NOTE_LIST)
|
|
|
|
|
mState = ListEditState.NOTE_LIST;
|
|
|
|
|
|
|
|
|
|
// 创建一个回调模式对象,用于处理不同的菜单选项和动作模式等交互事件
|
|
|
|
|
mModeCallBack = new ModeCallback();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* ModeCallback内部类,实现了ListView.MultiChoiceModeListener和OnMenuItemClickListener接口。
|
|
|
|
|
* 用于处理列表的多选模式和菜单项点击事件。
|
|
|
|
|
*/
|
|
|
|
|
private class ModeCallback implements ListView.MultiChoiceModeListener, OnMenuItemClickListener {
|
|
|
|
|
// 创建下拉菜单对象和ActionMode对象,用于处理多选模式下的菜单和动作模式等交互事件。
|
|
|
|
|
private DropdownMenu mDropDownMenu;
|
|
|
|
|
private ActionMode mActionMode;
|
|
|
|
|
private MenuItem mMoveMenu;
|
|
|
|
|
private MenuItem mMoveMenu; // 菜单项移动操作项
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 当ActionMode被创建时调用。用于初始化ActionMode的菜单。
|
|
|
|
|
* @param mode ActionMode对象,用于处理多选模式下的交互事件。
|
|
|
|
|
* @param menu 菜单对象,用于显示菜单项。
|
|
|
|
|
* @return 是否成功创建ActionMode。如果成功创建返回true,否则返回false。
|
|
|
|
|
*/
|
|
|
|
|
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
|
|
|
|
|
// 使用指定的菜单项初始化ActionMode的菜单。这里是从R.menu.note_list_options加载菜单项。
|
|
|
|
|
getMenuInflater().inflate(R.menu.note_list_options, menu);
|
|
|
|
|
// 在菜单中查找“删除”菜单项,并将其点击事件设置为当前类对象(this)。
|
|
|
|
|
menu.findItem(R.id.delete).setOnMenuItemClickListener(this);
|
|
|
|
|
// 获取“移动”菜单项并保存到mMoveMenu变量中。如果“移动”菜单项存在的话。
|
|
|
|
|
mMoveMenu = menu.findItem(R.id.move);
|
|
|
|
|
if (mFocusNoteDataItem.getParentId() == Notes.ID_CALL_RECORD_FOLDER
|
|
|
|
|
|| DataUtils.getUserFolderCount(mContentResolver) == 0) {
|
|
|
|
|
mMoveMenu.setVisible(false);
|
|
|
|
|
} else {
|
|
|
|
|
mMoveMenu.setVisible(true);
|
|
|
|
|
mMoveMenu.setOnMenuItemClickListener(this);
|
|
|
|
|
}
|
|
|
|
|
// 检查焦点笔记数据项的父ID是否为“调用记录文件夹”ID或用户文件夹数量是否为0。如果是的话,则隐藏“移动”菜单项。否则显示“移动”
|
|
|
|
|
mActionMode = mode;
|
|
|
|
|
mNotesListAdapter.setChoiceMode(true);
|
|
|
|
|
mNotesListView.setLongClickable(false);
|
|
|
|
@ -269,198 +376,244 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 更新菜单
|
|
|
|
|
*/
|
|
|
|
|
private void updateMenu() {
|
|
|
|
|
// 获取已选择的数量
|
|
|
|
|
int selectedCount = mNotesListAdapter.getSelectedCount();
|
|
|
|
|
// Update dropdown menu
|
|
|
|
|
|
|
|
|
|
// 更新下拉菜单的标题
|
|
|
|
|
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);
|
|
|
|
|
item.setTitle(R.string.menu_deselect_all); // 设置标题为“取消全选”
|
|
|
|
|
} else {
|
|
|
|
|
item.setChecked(false);
|
|
|
|
|
item.setTitle(R.string.menu_select_all);
|
|
|
|
|
item.setTitle(R.string.menu_select_all); // 设置标题为“全选”
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 在准备ActionMode时返回是否成功准备
|
|
|
|
|
*/
|
|
|
|
|
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
|
|
|
|
|
// TODO Auto-generated method stub
|
|
|
|
|
// TODO: 需要实现该方法来返回是否成功准备ActionMode
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 处理ActionMode的菜单项点击事件
|
|
|
|
|
*/
|
|
|
|
|
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
|
|
|
|
|
// TODO Auto-generated method stub
|
|
|
|
|
// TODO: 需要实现该方法来处理菜单项点击事件
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 当ActionMode销毁时执行的操作
|
|
|
|
|
*/
|
|
|
|
|
public void onDestroyActionMode(ActionMode mode) {
|
|
|
|
|
// 取消选择模式,使列表项可长按,并显示添加新笔记的按钮
|
|
|
|
|
mNotesListAdapter.setChoiceMode(false);
|
|
|
|
|
mNotesListView.setLongClickable(true);
|
|
|
|
|
mAddNewNote.setVisibility(View.VISIBLE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 结束ActionMode的方法
|
|
|
|
|
*/
|
|
|
|
|
public void finishActionMode() {
|
|
|
|
|
mActionMode.finish();
|
|
|
|
|
mActionMode.finish(); // 结束ActionMode
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void onItemCheckedStateChanged(ActionMode mode, int position, long id,
|
|
|
|
|
boolean checked) {
|
|
|
|
|
/**
|
|
|
|
|
* 当列表项的选中状态改变时调用此方法
|
|
|
|
|
*/
|
|
|
|
|
public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
|
|
|
|
|
// 更新列表项的选中状态,并调用updateMenu()更新菜单状态
|
|
|
|
|
mNotesListAdapter.setCheckedItem(position, checked);
|
|
|
|
|
updateMenu();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 处理菜单项点击事件
|
|
|
|
|
*
|
|
|
|
|
* @param item 被点击的菜单项
|
|
|
|
|
* @return 如果事件被成功处理,返回true;否则返回false
|
|
|
|
|
*/
|
|
|
|
|
public boolean onMenuItemClick(MenuItem item) {
|
|
|
|
|
// 如果没有任何项目被选中
|
|
|
|
|
if (mNotesListAdapter.getSelectedCount() == 0) {
|
|
|
|
|
// 显示一个Toast提示没有选中的项目
|
|
|
|
|
Toast.makeText(NotesListActivity.this, getString(R.string.menu_select_none),
|
|
|
|
|
Toast.LENGTH_SHORT).show();
|
|
|
|
|
// 返回true表示事件被成功处理
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 根据菜单项的ID进行不同的操作
|
|
|
|
|
switch (item.getItemId()) {
|
|
|
|
|
case R.id.delete:
|
|
|
|
|
// 创建一个删除确认对话框
|
|
|
|
|
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.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,
|
|
|
|
|
mNotesListAdapter.getSelectedCount())); // 设置对话框消息,带已选中的项目数量
|
|
|
|
|
builder.setPositiveButton(android.R.string.ok, // 设置确定按钮的文本
|
|
|
|
|
new DialogInterface.OnClickListener() {
|
|
|
|
|
public void onClick(DialogInterface dialog,
|
|
|
|
|
int which) {
|
|
|
|
|
// 点击确定按钮后执行批量删除操作
|
|
|
|
|
batchDelete();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
builder.setNegativeButton(android.R.string.cancel, null);
|
|
|
|
|
builder.show();
|
|
|
|
|
break;
|
|
|
|
|
builder.setNegativeButton(android.R.string.cancel, null); // 设置取消按钮的文本为“取消”,不执行任何操作
|
|
|
|
|
builder.show(); // 显示对话框
|
|
|
|
|
break; // 结束case R.id.delete分支
|
|
|
|
|
case R.id.move:
|
|
|
|
|
// 开始查询目标文件夹,用于移动操作
|
|
|
|
|
startQueryDestinationFolders();
|
|
|
|
|
break;
|
|
|
|
|
break; // 结束case R.id.move分支
|
|
|
|
|
default:
|
|
|
|
|
// 如果不是上述两种情况,返回false表示事件未被成功处理
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
// 无论哪种情况,最后都返回true表示事件被成功处理
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
|
|
|
|
|
// 定义一个私有内部类 NewNoteOnTouchListener,实现 OnTouchListener 接口
|
|
|
|
|
private class NewNoteOnTouchListener implements OnTouchListener {
|
|
|
|
|
|
|
|
|
|
// 实现 onTouch 方法,接收一个 View 和一个 MotionEvent 对象
|
|
|
|
|
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;
|
|
|
|
|
// 获取触摸事件在屏幕上的Y坐标
|
|
|
|
|
int eventY = start + (int) event.getY();
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 减去标题栏的高度
|
|
|
|
|
*/
|
|
|
|
|
if (mState == ListEditState.SUB_FOLDER) {
|
|
|
|
|
// 如果当前状态是子文件夹状态,减去标题栏的高度
|
|
|
|
|
eventY -= mTitleBar.getHeight();
|
|
|
|
|
// 更新新笔记视图在屏幕上的起始位置
|
|
|
|
|
start -= mTitleBar.getHeight();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果触摸事件的Y坐标小于某个条件,执行以下操作
|
|
|
|
|
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))) {
|
|
|
|
|
// 保存触摸事件的Y坐标为起始位置
|
|
|
|
|
mOriginY = (int) event.getY();
|
|
|
|
|
// 更新新笔记视图在屏幕上的起始位置为事件Y坐标
|
|
|
|
|
mDispatchY = eventY;
|
|
|
|
|
// 重置触摸事件的位置为新的X和Y坐标
|
|
|
|
|
event.setLocation(event.getX(), mDispatchY);
|
|
|
|
|
// 设置一个标志位表示需要分发触摸事件给列表视图
|
|
|
|
|
mDispatch = true;
|
|
|
|
|
// 分发触摸事件给列表视图并返回结果
|
|
|
|
|
return mNotesListView.dispatchTouchEvent(event);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break; // 结束触摸事件的动作是 ACTION_DOWN 的分支
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* 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+94(Unit: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.
|
|
|
|
|
*/
|
|
|
|
|
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;
|
|
|
|
|
case MotionEvent.ACTION_MOVE: {
|
|
|
|
|
// 如果设置了需要分发触摸事件的标志位,执行以下操作
|
|
|
|
|
if (mDispatch) {
|
|
|
|
|
// 更新新笔记视图在屏幕上的起始位置为触摸事件的Y坐标减去起始位置的Y坐标加上起始位置的Y坐标(这里可能有一个错误)
|
|
|
|
|
mDispatchY += (int) event.getY() - mOriginY;
|
|
|
|
|
// 重置触摸事件的位置为新的X和Y坐标(这里可能有一个错误)
|
|
|
|
|
event.setLocation(event.getX(), mDispatchY);
|
|
|
|
|
mDispatch = true;
|
|
|
|
|
// 分发触摸事件给列表视图并返回结果(这里可能有一个错误)
|
|
|
|
|
return mNotesListView.dispatchTouchEvent(event);
|
|
|
|
|
}
|
|
|
|
|
break; // 结束触摸事件的动作是 ACTION_MOVE 的分支(这里可能有一个错误)
|
|
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
} // 结束 switch 语句(这里可能有一个错误)
|
|
|
|
|
return false; // 如果不满足任何条件,返回 false(这里可能有一个错误)
|
|
|
|
|
} // 结束 onTouch 方法(这里可能有一个错误)
|
|
|
|
|
} // 结束 NewNoteOnTouchListener 类(这里可能有一个错误)
|
|
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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");
|
|
|
|
|
}
|
|
|
|
|
// 启动异步笔记列表查询
|
|
|
|
|
private void startAsyncNotesListQuery() {
|
|
|
|
|
// 根据当前文件夹ID选择查询条件
|
|
|
|
|
String selection = (mCurrentFolderId == Notes.ID_ROOT_FOLDER) ? ROOT_FOLDER_SELECTION
|
|
|
|
|
: NORMAL_SELECTION;
|
|
|
|
|
|
|
|
|
|
private final class BackgroundQueryHandler extends AsyncQueryHandler {
|
|
|
|
|
public BackgroundQueryHandler(ContentResolver contentResolver) {
|
|
|
|
|
super(contentResolver);
|
|
|
|
|
// 启动异步查询,获取笔记列表
|
|
|
|
|
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");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
|
|
|
|
|
switch (token) {
|
|
|
|
|
case FOLDER_NOTE_LIST_QUERY_TOKEN:
|
|
|
|
|
mNotesListAdapter.changeCursor(cursor);
|
|
|
|
|
break;
|
|
|
|
|
case FOLDER_LIST_QUERY_TOKEN:
|
|
|
|
|
if (cursor != null && cursor.getCount() > 0) {
|
|
|
|
|
showFolderListMenu(cursor);
|
|
|
|
|
} else {
|
|
|
|
|
Log.e(TAG, "Query folder failed");
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
return;
|
|
|
|
|
// 背景查询处理器,继承自AsyncQueryHandler
|
|
|
|
|
private final class BackgroundQueryHandler extends AsyncQueryHandler {
|
|
|
|
|
public BackgroundQueryHandler(ContentResolver contentResolver) {
|
|
|
|
|
super(contentResolver);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
// 当查询完成时的回调方法
|
|
|
|
|
@Override
|
|
|
|
|
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
|
|
|
|
|
switch (token) {
|
|
|
|
|
case FOLDER_NOTE_LIST_QUERY_TOKEN: // 如果token是笔记列表查询令牌
|
|
|
|
|
// 更改笔记列表适配器的游标
|
|
|
|
|
mNotesListAdapter.changeCursor(cursor);
|
|
|
|
|
break;
|
|
|
|
|
case FOLDER_LIST_QUERY_TOKEN: // 如果token是文件夹列表查询令牌
|
|
|
|
|
if (cursor != null && cursor.getCount() > 0) {
|
|
|
|
|
// 显示文件夹列表菜单
|
|
|
|
|
showFolderListMenu(cursor);
|
|
|
|
|
} else {
|
|
|
|
|
Log.e(TAG, "Query folder failed"); // 否则,记录错误日志
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
return; // 其他情况直接返回
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
builder.show();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 显示文件夹列表菜单的方法
|
|
|
|
|
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() { // 设置适配器并添加点击监听器
|
|
|
|
|
// 这里应该是显示文件夹列表的点击事件处理代码,但您没有提供完整代码。
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void createNewNote() {
|
|
|
|
|
Intent intent = new Intent(this, NoteEditActivity.class);
|
|
|
|
|