Allow multiple range selections using the shift key.
- Introduce an API on MultiSelectManager for starting/ending range
selections.
- Navigation with the shift key pressed extends the current range
selection (or starts a new one, if one isn't in progress).
- Navigation without the shift key pressed will end the current range
selection.
BUG=27124371
Change-Id: Ieddf3ee816812bf5210463536fe63179ef1809ad
(cherry picked from commit 09792ef150)
This commit is contained in:
@@ -101,7 +101,6 @@ import com.android.documentsui.model.RootInfo;
|
||||
import com.android.documentsui.services.FileOperationService;
|
||||
import com.android.documentsui.services.FileOperationService.OpType;
|
||||
import com.android.documentsui.services.FileOperations;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
@@ -264,7 +263,7 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi
|
||||
mSelectionManager.addCallback(selectionListener);
|
||||
|
||||
// Make sure this is done after the RecyclerView is set up.
|
||||
mFocusManager = new FocusManager(mRecView, mSelectionManager);
|
||||
mFocusManager = new FocusManager(mRecView);
|
||||
|
||||
mModel = new Model();
|
||||
mModel.addUpdateListener(mAdapter);
|
||||
@@ -1262,6 +1261,18 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi
|
||||
}
|
||||
|
||||
if (mFocusManager.handleKey(doc, keyCode, event)) {
|
||||
// Handle range selection adjustments. Extending the selection will adjust the
|
||||
// bounds of the in-progress range selection. Each time an unshifted navigation
|
||||
// event is received, the range selection is restarted.
|
||||
if (shouldExtendSelection(event)) {
|
||||
if (!mSelectionManager.isRangeSelectionActive()) {
|
||||
// Start a range selection if one isn't active
|
||||
mSelectionManager.startRangeSelection(doc.getAdapterPosition());
|
||||
}
|
||||
mSelectionManager.snapRangeSelection(mFocusManager.getFocusPosition());
|
||||
} else {
|
||||
mSelectionManager.endRangeSelection();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1272,6 +1283,11 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean shouldExtendSelection(KeyEvent event) {
|
||||
return Events.isNavigationKeyCode(event.getKeyCode()) &&
|
||||
event.isShiftPressed();
|
||||
}
|
||||
}
|
||||
|
||||
private final class ModelUpdateListener implements Model.UpdateListener {
|
||||
|
||||
@@ -33,15 +33,13 @@ class FocusManager implements View.OnFocusChangeListener {
|
||||
private RecyclerView mView;
|
||||
private RecyclerView.Adapter<?> mAdapter;
|
||||
private LinearLayoutManager mLayout;
|
||||
private MultiSelectManager mSelectionManager;
|
||||
|
||||
private int mLastFocusPosition = RecyclerView.NO_POSITION;
|
||||
|
||||
public FocusManager(RecyclerView view, MultiSelectManager selectionManager) {
|
||||
public FocusManager(RecyclerView view) {
|
||||
mView = view;
|
||||
mAdapter = view.getAdapter();
|
||||
mLayout = (LinearLayoutManager) view.getLayoutManager();
|
||||
mSelectionManager = selectionManager;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,13 +58,6 @@ class FocusManager implements View.OnFocusChangeListener {
|
||||
|
||||
if (endPos != RecyclerView.NO_POSITION) {
|
||||
focusItem(endPos);
|
||||
boolean extendSelection = event.isShiftPressed();
|
||||
|
||||
// Handle any necessary adjustments to selection.
|
||||
if (extendSelection) {
|
||||
int startPos = doc.getAdapterPosition();
|
||||
mSelectionManager.selectRange(startPos, endPos);
|
||||
}
|
||||
}
|
||||
// Swallow all navigation keystrokes. Otherwise they go to the app's global
|
||||
// key-handler, which will route them back to the DF and cause focus to be reset.
|
||||
@@ -96,6 +87,13 @@ class FocusManager implements View.OnFocusChangeListener {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The adapter position of the last focused item.
|
||||
*/
|
||||
public int getFocusPosition() {
|
||||
return mLastFocusPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the destination position where the focus should land for a given navigation event.
|
||||
*
|
||||
|
||||
@@ -370,39 +370,41 @@ public final class MultiSelectManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a range selection event.
|
||||
* <li> If the MSM is currently in single-select mode, only the last item in the range will
|
||||
* actually be selected.
|
||||
* <li>If a range selection is not already active, one will be started, and the given range of
|
||||
* items will be selected. The given startPos becomes the anchor for the range selection.
|
||||
* <li>If a range selection is already active, the anchor is not changed. The range is extended
|
||||
* from its current anchor to endPos.
|
||||
* Starts a range selection. If a range selection is already active, this will start a new range
|
||||
* selection (which will reset the range anchor).
|
||||
*
|
||||
* @param startPos
|
||||
* @param endPos
|
||||
* @param pos The anchor position for the selection range.
|
||||
*/
|
||||
public void selectRange(int startPos, int endPos) {
|
||||
// In single-select mode, just select the last item in the range.
|
||||
if (mSingleSelect) {
|
||||
attemptSelect(mAdapter.getModelId(endPos));
|
||||
return;
|
||||
}
|
||||
void startRangeSelection(int pos) {
|
||||
attemptSelect(mAdapter.getModelId(pos));
|
||||
setSelectionRangeBegin(pos);
|
||||
}
|
||||
|
||||
// In regular (i.e. multi-select) mode
|
||||
if (!isRangeSelectionActive()) {
|
||||
// If a range selection isn't active, start one up
|
||||
attemptSelect(mAdapter.getModelId(startPos));
|
||||
setSelectionRangeBegin(startPos);
|
||||
}
|
||||
// Extend the range selection
|
||||
mRanger.snapSelection(endPos);
|
||||
/**
|
||||
* Sets the end point for the current range selection, started by a call to
|
||||
* {@link #startRangeSelection(int)}. This function should only be called when a range selection
|
||||
* is active (see {@link #isRangeSelectionActive()}. Items in the range [anchor, end] will be
|
||||
* selected.
|
||||
*
|
||||
* @param pos The new end position for the selection range.
|
||||
*/
|
||||
void snapRangeSelection(int pos) {
|
||||
checkNotNull(mRanger);
|
||||
mRanger.snapSelection(pos);
|
||||
notifySelectionChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops an in-progress range selection.
|
||||
*/
|
||||
void endRangeSelection() {
|
||||
mRanger = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Whether or not there is a current range selection active.
|
||||
*/
|
||||
private boolean isRangeSelectionActive() {
|
||||
boolean isRangeSelectionActive() {
|
||||
return mRanger != null;
|
||||
}
|
||||
|
||||
|
||||
@@ -189,6 +189,54 @@ public class MultiSelectManagerTest extends AndroidTestCase {
|
||||
assertSelection(items.get(20));
|
||||
}
|
||||
|
||||
public void testRangeSelection() {
|
||||
mManager.startRangeSelection(15);
|
||||
mManager.snapRangeSelection(19);
|
||||
assertRangeSelection(15, 19);
|
||||
}
|
||||
|
||||
public void testRangeSelection_snapExpand() {
|
||||
mManager.startRangeSelection(15);
|
||||
mManager.snapRangeSelection(19);
|
||||
mManager.snapRangeSelection(27);
|
||||
assertRangeSelection(15, 27);
|
||||
}
|
||||
|
||||
public void testRangeSelection_snapContract() {
|
||||
mManager.startRangeSelection(15);
|
||||
mManager.snapRangeSelection(27);
|
||||
mManager.snapRangeSelection(19);
|
||||
assertRangeSelection(15, 19);
|
||||
}
|
||||
|
||||
public void testRangeSelection_snapInvert() {
|
||||
mManager.startRangeSelection(15);
|
||||
mManager.snapRangeSelection(27);
|
||||
mManager.snapRangeSelection(3);
|
||||
assertRangeSelection(3, 15);
|
||||
}
|
||||
|
||||
public void testRangeSelection_multiple() {
|
||||
mManager.startRangeSelection(15);
|
||||
mManager.snapRangeSelection(27);
|
||||
mManager.endRangeSelection();
|
||||
mManager.startRangeSelection(42);
|
||||
mManager.snapRangeSelection(57);
|
||||
assertSelectionSize(29);
|
||||
assertRangeSelected(15, 27);
|
||||
assertRangeSelected(42, 57);
|
||||
|
||||
}
|
||||
|
||||
public void testRangeSelection_singleSelect() {
|
||||
mManager = new MultiSelectManager(mEnv, mAdapter, MultiSelectManager.MODE_SINGLE, null);
|
||||
mManager.addCallback(mCallback);
|
||||
mManager.startRangeSelection(11);
|
||||
mManager.snapRangeSelection(19);
|
||||
assertSelectionSize(1);
|
||||
assertSelection(items.get(19));
|
||||
}
|
||||
|
||||
public void testProvisionalSelection() {
|
||||
Selection s = mManager.getSelection();
|
||||
assertSelection();
|
||||
|
||||
Reference in New Issue
Block a user