diff --git a/java/net/micode/notes/ui/NotesListActivity.java b/java/net/micode/notes/ui/NotesListActivity.java index e843aec..9f1e47c 100644 --- a/java/net/micode/notes/ui/NotesListActivity.java +++ b/java/net/micode/notes/ui/NotesListActivity.java @@ -14,6 +14,11 @@ * limitations under the License. */ +/** + * 笔记列表主活动 + * 负责展示笔记和文件夹列表,处理用户交互操作如创建、删除、移动笔记等 + * 支持文件夹导航、批量操作和上下文菜单功能 + */ package net.micode.notes.ui; import android.app.Activity; @@ -78,96 +83,110 @@ 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; - - private static final int FOLDER_LIST_QUERY_TOKEN = 1; - + private static final int FOLDER_LIST_QUERY_TOKEN = 1; + + // 上下文菜单项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 static final String PREFERENCE_ADD_INTRODUCTION = "net.micode.notes.introduction"; - + + // 列表编辑状态枚举 private enum ListEditState { - NOTE_LIST, SUB_FOLDER, CALL_RECORD_FOLDER + NOTE_LIST, // 主笔记列表状态 + SUB_FOLDER, // 子文件夹状态 + CALL_RECORD_FOLDER // 通话记录文件夹状态 }; - - private ListEditState mState; - - private BackgroundQueryHandler mBackgroundQueryHandler; - - private NotesListAdapter mNotesListAdapter; - - private ListView mNotesListView; - - private Button mAddNewNote; - - private boolean mDispatch; - - private int mOriginY; - - private int mDispatchY; - - private TextView mTitleBar; - - private long mCurrentFolderId; - - private ContentResolver mContentResolver; - - private ModeCallback mModeCallBack; - - private static final String TAG = "NotesListActivity"; - - public static final int NOTES_LISTVIEW_SCROLL_RATE = 30; - - private NoteItemData mFocusNoteDataItem; - + + // 类成员变量 + private ListEditState mState; // 当前列表状态 + private BackgroundQueryHandler mBackgroundQueryHandler; // 后台查询处理器 + private NotesListAdapter mNotesListAdapter; // 笔记列表适配器 + private ListView mNotesListView; // 笔记列表视图 + private Button mAddNewNote; // 新建笔记按钮 + private boolean mDispatch; // 事件分发标志 + private int mOriginY; // 触摸事件原始Y坐标 + private int mDispatchY; // 事件分发Y坐标 + private TextView mTitleBar; // 标题栏 + private long mCurrentFolderId; // 当前文件夹ID + private ContentResolver mContentResolver; // 内容解析器 + private ModeCallback mModeCallBack; // 模式回调 + private static final String TAG = "NotesListActivity"; // 日志标签 + private static final int NOTES_LISTVIEW_SCROLL_RATE = 30; // 列表滚动速率 + private NoteItemData mFocusNoteDataItem; // 聚焦的笔记数据项 + + // 查询选择条件 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)"; - + + // 活动请求码 private final static int REQUEST_CODE_OPEN_NODE = 102; - private final static int REQUEST_CODE_NEW_NODE = 103; - + private final static int REQUEST_CODE_NEW_NODE = 103; + + /** + * 活动创建时调用 + * 初始化资源、设置布局和事件监听 + * + * @param savedInstanceState 保存的活动状态 + */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.note_list); initResources(); - /** - * Insert an introduction when user firstly use this application - */ + // 首次使用应用时插入介绍笔记 setAppInfoFromRawRes(); } + /** + * 处理活动结果 + * + * @param requestCode 请求码 + * @param resultCode 结果码 + * @param data 返回数据 + */ @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); } } + /** + * 从资源文件读取应用介绍并创建介绍笔记 + */ 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); + // 打开资源文件 + in = getResources().openRawResource(R.raw.introduction); if (in != null) { + // 读取资源文件内容 InputStreamReader isr = new InputStreamReader(in); BufferedReader br = new BufferedReader(isr); - char [] buf = new char[1024]; + char[] buf = new char[1024]; int len = 0; while ((len = br.read(buf)) > 0) { sb.append(buf, 0, len); @@ -180,49 +199,65 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt e.printStackTrace(); return; } finally { - if(in != null) { + // 关闭输入流 + if (in != null) { try { in.close(); } catch (IOException e) { - // TODO Auto-generated catch block 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; } } } + /** + * 活动开始时调用 + * 启动异步笔记列表查询 + */ @Override protected void onStart() { super.onStart(); startAsyncNotesListQuery(); } + /** + * 初始化资源和视图 + */ private void initResources() { mContentResolver = this.getContentResolver(); mBackgroundQueryHandler = new BackgroundQueryHandler(this.getContentResolver()); mCurrentFolderId = Notes.ID_ROOT_FOLDER; + + // 初始化列表视图 mNotesListView = (ListView) findViewById(R.id.notes_list); mNotesListView.addFooterView(LayoutInflater.from(this).inflate(R.layout.note_list_footer, null), null, false); mNotesListView.setOnItemClickListener(new OnListItemClickListener()); mNotesListView.setOnItemLongClickListener(this); + + // 初始化列表适配器 mNotesListAdapter = new NotesListAdapter(this); mNotesListView.setAdapter(mNotesListAdapter); + + // 初始化新建笔记按钮 mAddNewNote = (Button) findViewById(R.id.btn_new_note); mAddNewNote.setOnClickListener(this); mAddNewNote.setOnTouchListener(new NewNoteOnTouchListener()); + + // 初始化其他状态变量 mDispatch = false; mDispatchY = 0; mOriginY = 0; @@ -231,184 +266,11 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt mModeCallBack = new ModeCallback(); } - private class ModeCallback implements ListView.MultiChoiceModeListener, OnMenuItemClickListener { - private DropdownMenu mDropDownMenu; - private ActionMode mActionMode; - private MenuItem mMoveMenu; - - public boolean onCreateActionMode(ActionMode mode, Menu menu) { - getMenuInflater().inflate(R.menu.note_list_options, menu); - menu.findItem(R.id.delete).setOnMenuItemClickListener(this); - 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); - } - mActionMode = mode; - mNotesListAdapter.setChoiceMode(true); - mNotesListView.setLongClickable(false); - mAddNewNote.setVisibility(View.GONE); - - View customView = LayoutInflater.from(NotesListActivity.this).inflate( - R.layout.note_list_dropdown_menu, null); - 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(); - // 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); - } else { - item.setChecked(false); - item.setTitle(R.string.menu_select_all); - } - } - } - - 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; - } - - public void onDestroyActionMode(ActionMode mode) { - mNotesListAdapter.setChoiceMode(false); - mNotesListView.setLongClickable(true); - mAddNewNote.setVisibility(View.VISIBLE); - } - - public void finishActionMode() { - mActionMode.finish(); - } - - public void onItemCheckedStateChanged(ActionMode mode, int position, long id, - boolean checked) { - mNotesListAdapter.setCheckedItem(position, checked); - updateMenu(); - } - - public boolean onMenuItemClick(MenuItem item) { - 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 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(); - } - }); - builder.setNegativeButton(android.R.string.cancel, null); - builder.show(); - break; - case R.id.move: - startQueryDestinationFolders(); - break; - default: - return false; - } - 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(); - } - /** - * 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; - 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; - } - - }; - + /** + * 启动异步笔记列表查询 + */ private void startAsyncNotesListQuery() { + // 根据当前文件夹ID设置查询条件 String selection = (mCurrentFolderId == Notes.ID_ROOT_FOLDER) ? ROOT_FOLDER_SELECTION : NORMAL_SELECTION; mBackgroundQueryHandler.startQuery(FOLDER_NOTE_LIST_QUERY_TOKEN, null, @@ -417,6 +279,10 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt }, NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC"); } + /** + * 后台查询处理器 + * 处理异步查询结果 + */ private final class BackgroundQueryHandler extends AsyncQueryHandler { public BackgroundQueryHandler(ContentResolver contentResolver) { super(contentResolver); @@ -426,10 +292,12 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt 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"); @@ -441,13 +309,18 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } + /** + * 显示文件夹列表菜单 + * + * @param cursor 文件夹查询结果游标 + */ 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( @@ -462,6 +335,9 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt builder.show(); } + /** + * 创建新笔记 + */ private void createNewNote() { Intent intent = new Intent(this, NoteEditActivity.class); intent.setAction(Intent.ACTION_INSERT_OR_EDIT); @@ -469,20 +345,23 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt this.startActivityForResult(intent, REQUEST_CODE_NEW_NODE); } + /** + * 批量删除笔记 + */ private void batchDelete() { new AsyncTask>() { protected HashSet doInBackground(Void... unused) { + // 获取选中的小部件 HashSet widgets = mNotesListAdapter.getSelectedWidget(); if (!isSyncMode()) { - // if not synced, delete notes directly + // 非同步模式下直接删除 if (DataUtils.batchDeleteNotes(mContentResolver, mNotesListAdapter .getSelectedItemIds())) { } else { Log.e(TAG, "Delete notes error, should not happens"); } } else { - // in sync mode, we'll move the deleted note into the trash - // folder + // 同步模式下移到回收站 if (!DataUtils.batchMoveToFolder(mContentResolver, mNotesListAdapter .getSelectedItemIds(), Notes.ID_TRASH_FOLER)) { Log.e(TAG, "Move notes to trash folder error, should not happens"); @@ -494,6 +373,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt @Override protected void onPostExecute(HashSet widgets) { if (widgets != null) { + // 更新相关小部件 for (AppWidgetAttribute widget : widgets) { if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID && widget.widgetType != Notes.TYPE_WIDGET_INVALIDE) { @@ -506,6 +386,11 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt }.execute(); } + /** + * 删除文件夹 + * + * @param folderId 要删除的文件夹ID + */ private void deleteFolder(long folderId) { if (folderId == Notes.ID_ROOT_FOLDER) { Log.e(TAG, "Wrong folder id, should not happen " + folderId); @@ -514,16 +399,18 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt HashSet ids = new HashSet(); ids.add(folderId); + // 获取文件夹关联的小部件 HashSet widgets = DataUtils.getFolderNoteWidget(mContentResolver, folderId); if (!isSyncMode()) { - // if not synced, delete folder directly + // 非同步模式下直接删除 DataUtils.batchDeleteNotes(mContentResolver, ids); } else { - // in sync mode, we'll move the deleted folder into the trash folder + // 同步模式下移到回收站 DataUtils.batchMoveToFolder(mContentResolver, ids, Notes.ID_TRASH_FOLER); } if (widgets != null) { + // 更新相关小部件 for (AppWidgetAttribute widget : widgets) { if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID && widget.widgetType != Notes.TYPE_WIDGET_INVALIDE) { @@ -533,6 +420,11 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } + /** + * 打开笔记 + * + * @param data 要打开的笔记数据 + */ private void openNode(NoteItemData data) { Intent intent = new Intent(this, NoteEditActivity.class); intent.setAction(Intent.ACTION_VIEW); @@ -540,6 +432,11 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt this.startActivityForResult(intent, REQUEST_CODE_OPEN_NODE); } + /** + * 打开文件夹 + * + * @param data 要打开的文件夹数据 + */ private void openFolder(NoteItemData data) { mCurrentFolderId = data.getId(); startAsyncNotesListQuery(); @@ -549,6 +446,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } else { mState = ListEditState.SUB_FOLDER; } + // 更新标题栏 if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { mTitleBar.setText(R.string.call_record_folder_name); } else { @@ -557,6 +455,11 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt mTitleBar.setVisibility(View.VISIBLE); } + /** + * 视图点击事件处理 + * + * @param v 被点击的视图 + */ public void onClick(View v) { switch (v.getId()) { case R.id.btn_new_note: @@ -567,6 +470,9 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } + /** + * 显示软键盘 + */ private void showSoftInput() { InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); if (inputMethodManager != null) { @@ -574,11 +480,21 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } + /** + * 隐藏软键盘 + * + * @param view 用于获取窗口令牌的视图 + */ private void hideSoftInput(View view) { InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0); } + /** + * 显示创建或修改文件夹对话框 + * + * @param create 是否为创建新文件夹 + */ private void showCreateOrModifyFolderDialog(final boolean create) { final AlertDialog.Builder builder = new AlertDialog.Builder(this); View view = LayoutInflater.from(this).inflate(R.layout.dialog_edit_text, null); @@ -605,7 +521,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt }); final Dialog dialog = builder.setView(view).show(); - final Button positive = (Button)dialog.findViewById(android.R.id.button1); + final Button positive = (Button) dialog.findViewById(android.R.id.button1); positive.setOnClickListener(new OnClickListener() { public void onClick(View v) { hideSoftInput(etName); @@ -618,6 +534,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } if (!create) { if (!TextUtils.isEmpty(name)) { + // 修改文件夹名称 ContentValues values = new ContentValues(); values.put(NoteColumns.SNIPPET, name); values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); @@ -628,6 +545,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt }); } } else if (!TextUtils.isEmpty(name)) { + // 创建新文件夹 ContentValues values = new ContentValues(); values.put(NoteColumns.SNIPPET, name); values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); @@ -637,17 +555,12 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } }); + // 根据输入框内容启用或禁用确定按钮 if (TextUtils.isEmpty(etName.getText())) { positive.setEnabled(false); } - /** - * When the name edit text is null, disable the positive button - */ etName.addTextChangedListener(new TextWatcher() { - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - // TODO Auto-generated method stub - - } + public void beforeTextChanged(CharSequence s, int start, int count, int after) {} public void onTextChanged(CharSequence s, int start, int before, int count) { if (TextUtils.isEmpty(etName.getText())) { @@ -657,23 +570,25 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } - public void afterTextChanged(Editable s) { - // TODO Auto-generated method stub - - } + public void afterTextChanged(Editable s) {} }); } + /** + * 返回键处理 + */ @Override public void onBackPressed() { switch (mState) { case SUB_FOLDER: + // 返回主文件夹 mCurrentFolderId = Notes.ID_ROOT_FOLDER; mState = ListEditState.NOTE_LIST; startAsyncNotesListQuery(); mTitleBar.setVisibility(View.GONE); break; case CALL_RECORD_FOLDER: + // 返回主文件夹 mCurrentFolderId = Notes.ID_ROOT_FOLDER; mState = ListEditState.NOTE_LIST; mAddNewNote.setVisibility(View.VISIBLE); @@ -688,6 +603,12 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } + /** + * 更新小部件 + * + * @param appWidgetId 小部件ID + * @param appWidgetType 小部件类型 + */ private void updateWidget(int appWidgetId, int appWidgetType) { Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); if (appWidgetType == Notes.TYPE_WIDGET_2X) { @@ -707,36 +628,324 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt setResult(RESULT_OK, intent); } - private final OnCreateContextMenuListener mFolderOnCreateContextMenuListener = new OnCreateContextMenuListener() { - public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { - if (mFocusNoteDataItem != null) { - menu.setHeaderTitle(mFocusNoteDataItem.getSnippet()); - menu.add(0, MENU_FOLDER_VIEW, 0, R.string.menu_folder_view); - menu.add(0, MENU_FOLDER_DELETE, 0, R.string.menu_folder_delete); - menu.add(0, MENU_FOLDER_CHANGE_NAME, 0, R.string.menu_folder_change_name); + /** + * 模式回调类 + * 处理列表的多选模式 + */ + private class ModeCallback implements ListView.MultiChoiceModeListener, OnMenuItemClickListener { + private DropdownMenu mDropDownMenu; + private ActionMode mActionMode; + private MenuItem mMoveMenu; + + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + // 充气菜单 + getMenuInflater().inflate(R.menu.note_list_options, menu); + menu.findItem(R.id.delete).setOnMenuItemClickListener(this); + 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); } - } - }; + mActionMode = mode; + + // 设置列表为多选模式 + mNotesListAdapter.setChoiceMode(true); + mNotesListView.setLongClickable(false); + mAddNewNote.setVisibility(View.GONE); - @Override - public void onContextMenuClosed(Menu menu) { - if (mNotesListView != null) { - mNotesListView.setOnCreateContextMenuListener(null); + // 设置自定义模式视图 + View customView = LayoutInflater.from(NotesListActivity.this).inflate( + R.layout.note_list_dropdown_menu, null); + 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; } - super.onContextMenuClosed(menu); - } - @Override - public boolean onContextItemSelected(MenuItem item) { - if (mFocusNoteDataItem == null) { - Log.e(TAG, "The long click data item is null"); - return false; - } - switch (item.getItemId()) { - case MENU_FOLDER_VIEW: - openFolder(mFocusNoteDataItem); - break; + /** + * 更新菜单状态 + */ + 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); + } + } + } + + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + return false; + } + + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + return false; + } + + public void onDestroyActionMode(ActionMode mode) { + // 退出多选模式 + mNotesListAdapter.setChoiceMode(false); + mNotesListView.setLongClickable(true); + mAddNewNote.setVisibility(View.VISIBLE); + } + + public void finishActionMode() { + mActionMode.finish(); + } + + public void onItemCheckedStateChanged(ActionMode mode, int position, long id, + boolean checked) { + mNotesListAdapter.setCheckedItem(position, checked); + updateMenu(); + } + + public boolean onMenuItemClick(MenuItem item) { + 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 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(); + } + }); + builder.setNegativeButton(android.R.string.cancel, null); + builder.show(); + break; + case R.id.move: + // 启动目标文件夹查询 + startQueryDestinationFolders(); + break; + default: + return false; + } + 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(); + + // 减去标题栏高度(如果处于子文件夹状态) + if (mState == ListEditState.SUB_FOLDER) { + eventY -= mTitleBar.getHeight(); + start -= mTitleBar.getHeight(); + } + + // 处理按钮透明区域的触摸事件分发 + 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; + } + } + + /** + * 列表项点击监听器 + */ + private class OnListItemClickListener implements OnItemClickListener { + public void onItemClick(AdapterView parent, View view, int position, long id) { + if (view instanceof NotesListItem) { + NoteItemData item = ((NotesListItem) view).getItemData(); + if (mNotesListAdapter.isInChoiceMode()) { + if (item.getType() == Notes.TYPE_NOTE) { + position = position - mNotesListView.getHeaderViewsCount(); + mModeCallBack.onItemCheckedStateChanged(null, position, id, + !mNotesListAdapter.isSelectedItem(position)); + } + return; + } + + // 根据当前状态处理点击事件 + switch (mState) { + case NOTE_LIST: + if (item.getType() == Notes.TYPE_FOLDER + || item.getType() == Notes.TYPE_SYSTEM) { + openFolder(item); + } else if (item.getType() == Notes.TYPE_NOTE) { + openNode(item); + } else { + Log.e(TAG, "Wrong note type in NOTE_LIST"); + } + break; + case SUB_FOLDER: + case CALL_RECORD_FOLDER: + if (item.getType() == Notes.TYPE_NOTE) { + openNode(item); + } else { + Log.e(TAG, "Wrong note type in SUB_FOLDER"); + } + break; + default: + break; + } + } + } + } + + /** + * 启动目标文件夹查询 + */ + private void startQueryDestinationFolders() { + String selection = NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>? AND " + NoteColumns.ID + "<>?"; + selection = (mState == ListEditState.NOTE_LIST) ? selection: + "(" + selection + ") OR (" + NoteColumns.ID + "=" + Notes.ID_ROOT_FOLDER + ")"; + + 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"); + } + + /** + * 列表项长按事件处理 + * + * @param parent 父适配器视图 + * @param view 被长按的视图 + * @param position 位置 + * @param id 项ID + * @return 是否处理了长按事件 + */ + public boolean onItemLongClick(AdapterView parent, View view, int position, long id) { + if (view instanceof NotesListItem) { + mFocusNoteDataItem = ((NotesListItem) view).getItemData(); + if (mFocusNoteDataItem.getType() == Notes.TYPE_NOTE && !mNotesListAdapter.isInChoiceMode()) { + if (mNotesListView.startActionMode(mModeCallBack) != null) { + 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.setOnCreateContextMenuListener(mFolderOnCreateContextMenuListener); + } + } + return false; + } + + /** + * 上下文菜单创建监听器 + */ + private final OnCreateContextMenuListener mFolderOnCreateContextMenuListener = new OnCreateContextMenuListener() { + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { + if (mFocusNoteDataItem != null) { + menu.setHeaderTitle(mFocusNoteDataItem.getSnippet()); + menu.add(0, MENU_FOLDER_VIEW, 0, R.string.menu_folder_view); + menu.add(0, MENU_FOLDER_DELETE, 0, R.string.menu_folder_delete); + menu.add(0, MENU_FOLDER_CHANGE_NAME, 0, R.string.menu_folder_change_name); + } + } + }; + + /** + * 上下文菜单关闭处理 + * + * @param menu 关闭的菜单 + */ + @Override + public void onContextMenuClosed(Menu menu) { + if (mNotesListView != null) { + mNotesListView.setOnCreateContextMenuListener(null); + } + super.onContextMenuClosed(menu); + } + + /** + * 上下文菜单项选择处理 + * + * @param item 选中的菜单项 + * @return 是否处理了选择事件 + */ + @Override + public boolean onContextItemSelected(MenuItem item) { + if (mFocusNoteDataItem == null) { + Log.e(TAG, "The long click data item is null"); + return false; + } + switch (item.getItemId()) { + case MENU_FOLDER_VIEW: + openFolder(mFocusNoteDataItem); + break; case MENU_FOLDER_DELETE: + // 显示删除文件夹确认对话框 AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(getString(R.string.alert_title_delete)); builder.setIcon(android.R.drawable.ic_dialog_alert); @@ -756,16 +965,21 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt default: break; } - return true; } + /** + * 准备选项菜单 + * + * @param menu 要准备的菜单 + * @return 是否成功准备菜单 + */ @Override public boolean onPrepareOptionsMenu(Menu menu) { menu.clear(); if (mState == ListEditState.NOTE_LIST) { getMenuInflater().inflate(R.menu.note_list, menu); - // set sync or sync_cancel + // 设置同步菜单项标题 menu.findItem(R.id.menu_sync).setTitle( GTaskSyncService.isSyncing() ? R.string.menu_sync_cancel : R.string.menu_sync); } else if (mState == ListEditState.SUB_FOLDER) { @@ -778,18 +992,25 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt return true; } + /** + * 选项菜单项选择处理 + * + * @param item 选中的菜单项 + * @return 是否处理了选择事件 + */ @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { - case R.id.menu_new_folder: { + case R.id.menu_new_folder: + // 显示创建文件夹对话框 showCreateOrModifyFolderDialog(true); break; - } - case R.id.menu_export_text: { + case R.id.menu_export_text: + // 导出笔记为文本 exportNoteToText(); break; - } - case R.id.menu_sync: { + case R.id.menu_sync: + // 处理同步操作 if (isSyncMode()) { if (TextUtils.equals(item.getTitle(), getString(R.string.menu_sync))) { GTaskSyncService.startSync(this); @@ -800,16 +1021,16 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt startPreferenceActivity(); } break; - } - case R.id.menu_setting: { + case R.id.menu_setting: + // 启动设置活动 startPreferenceActivity(); break; - } - case R.id.menu_new_note: { + case R.id.menu_new_note: + // 创建新笔记 createNewNote(); break; - } case R.id.menu_search: + // 启动搜索 onSearchRequested(); break; default: @@ -818,16 +1039,23 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt return true; } + /** + * 处理搜索请求 + * + * @return 是否处理了搜索请求 + */ @Override public boolean onSearchRequested() { - startSearch(null, false, null /* appData */, false); + startSearch(null, false, null, false); return true; } + /** + * 导出笔记为文本文件 + */ private void exportNoteToText() { final BackupUtils backup = BackupUtils.getInstance(NotesListActivity.this); new AsyncTask() { - @Override protected Integer doInBackground(Void... unused) { return backup.exportToText(); @@ -835,6 +1063,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt @Override protected void onPostExecute(Integer result) { + // 根据导出结果显示相应提示 if (result == BackupUtils.STATE_SD_CARD_UNMOUONTED) { AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); builder.setTitle(NotesListActivity.this @@ -862,93 +1091,24 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt builder.show(); } } - }.execute(); } + /** + * 检查是否为同步模式 + * + * @return 是否为同步模式 + */ private boolean isSyncMode() { return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0; } + /** + * 启动偏好设置活动 + */ private void startPreferenceActivity() { Activity from = getParent() != null ? getParent() : this; Intent intent = new Intent(from, NotesPreferenceActivity.class); from.startActivityIfNeeded(intent, -1); } - - private class OnListItemClickListener implements OnItemClickListener { - - public void onItemClick(AdapterView parent, View view, int position, long id) { - if (view instanceof NotesListItem) { - NoteItemData item = ((NotesListItem) view).getItemData(); - if (mNotesListAdapter.isInChoiceMode()) { - if (item.getType() == Notes.TYPE_NOTE) { - position = position - mNotesListView.getHeaderViewsCount(); - mModeCallBack.onItemCheckedStateChanged(null, position, id, - !mNotesListAdapter.isSelectedItem(position)); - } - return; - } - - switch (mState) { - case NOTE_LIST: - if (item.getType() == Notes.TYPE_FOLDER - || item.getType() == Notes.TYPE_SYSTEM) { - openFolder(item); - } else if (item.getType() == Notes.TYPE_NOTE) { - openNode(item); - } else { - Log.e(TAG, "Wrong note type in NOTE_LIST"); - } - break; - case SUB_FOLDER: - case CALL_RECORD_FOLDER: - if (item.getType() == Notes.TYPE_NOTE) { - openNode(item); - } else { - Log.e(TAG, "Wrong note type in SUB_FOLDER"); - } - break; - default: - break; - } - } - } - - } - - private void startQueryDestinationFolders() { - String selection = NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>? AND " + NoteColumns.ID + "<>?"; - selection = (mState == ListEditState.NOTE_LIST) ? selection: - "(" + selection + ") OR (" + NoteColumns.ID + "=" + Notes.ID_ROOT_FOLDER + ")"; - - 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) { - if (view instanceof NotesListItem) { - mFocusNoteDataItem = ((NotesListItem) view).getItemData(); - if (mFocusNoteDataItem.getType() == Notes.TYPE_NOTE && !mNotesListAdapter.isInChoiceMode()) { - if (mNotesListView.startActionMode(mModeCallBack) != null) { - 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.setOnCreateContextMenuListener(mFolderOnCreateContextMenuListener); - } - } - return false; - } -} +} \ No newline at end of file