Loading packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java +9 −154 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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; if (mFocusManager.handleKey(doc, keyCode, event)) { return 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; return onActivate(doc); } /** * 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 (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 */ 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 { Loading packages/DocumentsUI/src/com/android/documentsui/dirlist/FocusManager.java 0 → 100644 +207 −0 Original line number Diff line number Diff line /* * 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); } } }); } } } Loading
packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java +9 −154 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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; if (mFocusManager.handleKey(doc, keyCode, event)) { return 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; return onActivate(doc); } /** * 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 (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 */ 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 { Loading
packages/DocumentsUI/src/com/android/documentsui/dirlist/FocusManager.java 0 → 100644 +207 −0 Original line number Diff line number Diff line /* * 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); } } }); } } }