diff options
| -rw-r--r-- | packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java | 163 | ||||
| -rw-r--r-- | packages/DocumentsUI/src/com/android/documentsui/dirlist/FocusManager.java | 207 |
2 files changed, 216 insertions, 154 deletions
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java index 7fe881e38340..174984cec8ed 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java @@ -151,6 +151,7 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi private MultiSelectManager mSelectionManager; private Model.UpdateListener mModelUpdateListener = new ModelUpdateListener(); private ItemEventListener mItemEventListener = new ItemEventListener(); + private FocusManager mFocusManager; private IconHelper mIconHelper; @@ -262,6 +263,8 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi mSelectionManager.addCallback(selectionListener); + mFocusManager = new FocusManager(mRecView, mSelectionManager); + mModel = new Model(); mModel.addUpdateListener(mAdapter); mModel.addUpdateListener(mModelUpdateListener); @@ -1249,165 +1252,17 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi return false; } - boolean handled = false; - if (Events.isNavigationKeyCode(keyCode)) { - // Find the target item and focus it. - int endPos = findTargetPosition(doc.itemView, keyCode); - - if (endPos != RecyclerView.NO_POSITION) { - focusItem(endPos); - - // Handle any necessary adjustments to selection. - boolean extendSelection = event.isShiftPressed(); - if (extendSelection) { - int startPos = doc.getAdapterPosition(); - mSelectionManager.selectRange(startPos, endPos); - } - handled = true; - } - } else { - // Handle enter key events - if (keyCode == KeyEvent.KEYCODE_ENTER) { - handled = onActivate(doc); - } - } - - return handled; - } - - /** - * Finds the destination position where the focus should land for a given navigation event. - * - * @param view The view that received the event. - * @param keyCode The key code for the event. - * @return The adapter position of the destination item. Could be RecyclerView.NO_POSITION. - */ - private int findTargetPosition(View view, int keyCode) { - switch (keyCode) { - case KeyEvent.KEYCODE_MOVE_HOME: - return 0; - case KeyEvent.KEYCODE_MOVE_END: - return mAdapter.getItemCount() - 1; - case KeyEvent.KEYCODE_PAGE_UP: - case KeyEvent.KEYCODE_PAGE_DOWN: - return findTargetPositionByPage(view, keyCode); - } - - // Find a navigation target based on the arrow key that the user pressed. - int searchDir = -1; - switch (keyCode) { - case KeyEvent.KEYCODE_DPAD_UP: - searchDir = View.FOCUS_UP; - break; - case KeyEvent.KEYCODE_DPAD_DOWN: - searchDir = View.FOCUS_DOWN; - break; - case KeyEvent.KEYCODE_DPAD_LEFT: - searchDir = View.FOCUS_LEFT; - break; - case KeyEvent.KEYCODE_DPAD_RIGHT: - searchDir = View.FOCUS_RIGHT; - break; - } - - if (searchDir != -1) { - View targetView = view.focusSearch(searchDir); - // TargetView can be null, for example, if the user pressed <down> at the bottom - // of the list. - if (targetView != null) { - // Ignore navigation targets that aren't items in the RecyclerView. - if (targetView.getParent() == mRecView) { - return mRecView.getChildAdapterPosition(targetView); - } - } - } - - return RecyclerView.NO_POSITION; - } - - /** - * Given a PgUp/PgDn event and the current view, find the position of the target view. - * This returns: - * <li>The position of the topmost (or bottom-most) visible item, if the current item is not - * the top- or bottom-most visible item. - * <li>The position of an item that is one page's worth of items up (or down) if the current - * item is the top- or bottom-most visible item. - * <li>The first (or last) item, if paging up (or down) would go past those limits. - * @param view The view that received the key event. - * @param keyCode Must be KEYCODE_PAGE_UP or KEYCODE_PAGE_DOWN. - * @return The adapter position of the target item. - */ - private int findTargetPositionByPage(View view, int keyCode) { - int first = mLayout.findFirstVisibleItemPosition(); - int last = mLayout.findLastVisibleItemPosition(); - int current = mRecView.getChildAdapterPosition(view); - int pageSize = last - first + 1; - - if (keyCode == KeyEvent.KEYCODE_PAGE_UP) { - if (current > first) { - // If the current item isn't the first item, target the first item. - return first; - } else { - // If the current item is the first item, target the item one page up. - int target = current - pageSize; - return target < 0 ? 0 : target; - } + if (mFocusManager.handleKey(doc, keyCode, event)) { + return true; } - if (keyCode == KeyEvent.KEYCODE_PAGE_DOWN) { - if (current < last) { - // If the current item isn't the last item, target the last item. - return last; - } else { - // If the current item is the last item, target the item one page down. - int target = current + pageSize; - int max = mAdapter.getItemCount() - 1; - return target < max ? target : max; - } + // Handle enter key events + if (keyCode == KeyEvent.KEYCODE_ENTER) { + return onActivate(doc); } - throw new IllegalArgumentException("Unsupported keyCode: " + keyCode); - } - - /** - * Requests focus for the item in the given adapter position, scrolling the RecyclerView if - * necessary. - * - * @param pos - */ - public void focusItem(final int pos) { - // If the item is already in view, focus it; otherwise, scroll to it and focus it. - RecyclerView.ViewHolder vh = mRecView.findViewHolderForAdapterPosition(pos); - if (vh != null) { - vh.itemView.requestFocus(); - } else { - mRecView.smoothScrollToPosition(pos); - // Set a one-time listener to request focus when the scroll has completed. - mRecView.addOnScrollListener( - new RecyclerView.OnScrollListener() { - @Override - public void onScrollStateChanged (RecyclerView view, int newState) { - if (newState == RecyclerView.SCROLL_STATE_IDLE) { - // When scrolling stops, find the item and focus it. - RecyclerView.ViewHolder vh = - view.findViewHolderForAdapterPosition(pos); - if (vh != null) { - vh.itemView.requestFocus(); - } else { - // This might happen in weird corner cases, e.g. if the user is - // scrolling while a delete operation is in progress. In that - // case, just don't attempt to focus the missing item. - Log.w( - TAG, "Unable to focus position " + pos + " after a scroll"); - } - view.removeOnScrollListener(this); - } - } - }); - } + return false; } - - } private final class ModelUpdateListener implements Model.UpdateListener { diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/FocusManager.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FocusManager.java new file mode 100644 index 000000000000..86b9146ba48c --- /dev/null +++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FocusManager.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.documentsui.dirlist; + +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.util.Log; +import android.view.KeyEvent; +import android.view.View; + +import com.android.documentsui.Events; + +/** + * A class that handles navigation and focus within the DirectoryFragment. + */ +class FocusManager { + private static final String TAG = "FocusManager"; + + private RecyclerView mView; + private RecyclerView.Adapter<?> mAdapter; + private LinearLayoutManager mLayout; + private MultiSelectManager mSelectionManager; + + public FocusManager(RecyclerView view, MultiSelectManager selectionManager) { + mView = view; + mAdapter = view.getAdapter(); + mLayout = (LinearLayoutManager) view.getLayoutManager(); + mSelectionManager = selectionManager; + } + + /** + * Handles navigation (setting focus, adjusting selection if needed) arising from incoming key + * events. + * + * @param doc The DocumentHolder receiving the key event. + * @param keyCode + * @param event + * @return Whether the event was handled. + */ + public boolean handleKey(DocumentHolder doc, int keyCode, KeyEvent event) { + boolean handled = false; + if (Events.isNavigationKeyCode(keyCode)) { + // Find the target item and focus it. + int endPos = findTargetPosition(doc.itemView, keyCode, event); + + if (endPos != RecyclerView.NO_POSITION) { + focusItem(endPos); + + // Handle any necessary adjustments to selection. + boolean extendSelection = event.isShiftPressed(); + if (extendSelection) { + int startPos = doc.getAdapterPosition(); + mSelectionManager.selectRange(startPos, endPos); + } + handled = true; + } + } + return handled; + } + + /** + * Finds the destination position where the focus should land for a given navigation event. + * + * @param view The view that received the event. + * @param keyCode The key code for the event. + * @param event + * @return The adapter position of the destination item. Could be RecyclerView.NO_POSITION. + */ + private int findTargetPosition(View view, int keyCode, KeyEvent event) { + switch (keyCode) { + case KeyEvent.KEYCODE_MOVE_HOME: + return 0; + case KeyEvent.KEYCODE_MOVE_END: + return mAdapter.getItemCount() - 1; + case KeyEvent.KEYCODE_PAGE_UP: + case KeyEvent.KEYCODE_PAGE_DOWN: + return findPagedTargetPosition(view, keyCode, event); + } + + // Find a navigation target based on the arrow key that the user pressed. + int searchDir = -1; + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_UP: + searchDir = View.FOCUS_UP; + break; + case KeyEvent.KEYCODE_DPAD_DOWN: + searchDir = View.FOCUS_DOWN; + break; + case KeyEvent.KEYCODE_DPAD_LEFT: + searchDir = View.FOCUS_LEFT; + break; + case KeyEvent.KEYCODE_DPAD_RIGHT: + searchDir = View.FOCUS_RIGHT; + break; + } + + if (searchDir != -1) { + View targetView = view.focusSearch(searchDir); + // TargetView can be null, for example, if the user pressed <down> at the bottom + // of the list. + if (targetView != null) { + // Ignore navigation targets that aren't items in the RecyclerView. + if (targetView.getParent() == mView) { + return mView.getChildAdapterPosition(targetView); + } + } + } + + return RecyclerView.NO_POSITION; + } + + /** + * Given a PgUp/PgDn event and the current view, find the position of the target view. + * This returns: + * <li>The position of the topmost (or bottom-most) visible item, if the current item is not + * the top- or bottom-most visible item. + * <li>The position of an item that is one page's worth of items up (or down) if the current + * item is the top- or bottom-most visible item. + * <li>The first (or last) item, if paging up (or down) would go past those limits. + * @param view The view that received the key event. + * @param keyCode Must be KEYCODE_PAGE_UP or KEYCODE_PAGE_DOWN. + * @param event + * @return The adapter position of the target item. + */ + private int findPagedTargetPosition(View view, int keyCode, KeyEvent event) { + int first = mLayout.findFirstVisibleItemPosition(); + int last = mLayout.findLastVisibleItemPosition(); + int current = mView.getChildAdapterPosition(view); + int pageSize = last - first + 1; + + if (keyCode == KeyEvent.KEYCODE_PAGE_UP) { + if (current > first) { + // If the current item isn't the first item, target the first item. + return first; + } else { + // If the current item is the first item, target the item one page up. + int target = current - pageSize; + return target < 0 ? 0 : target; + } + } + + if (keyCode == KeyEvent.KEYCODE_PAGE_DOWN) { + if (current < last) { + // If the current item isn't the last item, target the last item. + return last; + } else { + // If the current item is the last item, target the item one page down. + int target = current + pageSize; + int max = mAdapter.getItemCount() - 1; + return target < max ? target : max; + } + } + + throw new IllegalArgumentException("Unsupported keyCode: " + keyCode); + } + + /** + * Requests focus for the item in the given adapter position, scrolling the RecyclerView if + * necessary. + * + * @param pos + */ + private void focusItem(final int pos) { + // If the item is already in view, focus it; otherwise, scroll to it and focus it. + RecyclerView.ViewHolder vh = mView.findViewHolderForAdapterPosition(pos); + if (vh != null) { + vh.itemView.requestFocus(); + } else { + mView.smoothScrollToPosition(pos); + // Set a one-time listener to request focus when the scroll has completed. + mView.addOnScrollListener( + new RecyclerView.OnScrollListener() { + @Override + public void onScrollStateChanged(RecyclerView view, int newState) { + if (newState == RecyclerView.SCROLL_STATE_IDLE) { + // When scrolling stops, find the item and focus it. + RecyclerView.ViewHolder vh = + view.findViewHolderForAdapterPosition(pos); + if (vh != null) { + vh.itemView.requestFocus(); + } else { + // This might happen in weird corner cases, e.g. if the user is + // scrolling while a delete operation is in progress. In that + // case, just don't attempt to focus the missing item. + Log.w(TAG, "Unable to focus position " + pos + " after scroll"); + } + view.removeOnScrollListener(this); + } + } + }); + } + } +} |