diff options
6 files changed, 157 insertions, 35 deletions
diff --git a/packages/DocumentsUI/res/color/item_doc_list_background_activated.xml b/packages/DocumentsUI/res/color/item_doc_list_background_activated.xml index 90e2b7e653c6..7d7a110864d9 100644 --- a/packages/DocumentsUI/res/color/item_doc_list_background_activated.xml +++ b/packages/DocumentsUI/res/color/item_doc_list_background_activated.xml @@ -16,10 +16,6 @@ <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item - android:state_focused="true" - android:color="@color/platform_blue_a200" - android:alpha="0.1" /> - <item android:state_activated="true" android:color="?android:attr/colorAccent" android:alpha="0.1" /> diff --git a/packages/DocumentsUI/res/layout-sw720dp-land/item_doc_list.xml b/packages/DocumentsUI/res/layout-sw720dp-land/item_doc_list.xml index d1243200dd5d..231e110fa09d 100644 --- a/packages/DocumentsUI/res/layout-sw720dp-land/item_doc_list.xml +++ b/packages/DocumentsUI/res/layout-sw720dp-land/item_doc_list.xml @@ -14,10 +14,16 @@ limitations under the License. --> -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" +<com.android.documentsui.ListItem xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="@drawable/item_doc_list_background"> + android:background="@drawable/item_doc_list_background" + android:orientation="horizontal"> + + <View + android:id="@+id/focus_indicator" + android:layout_width="4dp" + android:layout_height="match_parent" /> <LinearLayout android:layout_width="match_parent" @@ -121,4 +127,4 @@ </LinearLayout> -</FrameLayout> +</com.android.documentsui.ListItem> diff --git a/packages/DocumentsUI/res/layout/item_doc_list.xml b/packages/DocumentsUI/res/layout/item_doc_list.xml index c576669d8958..085df352bb4a 100644 --- a/packages/DocumentsUI/res/layout/item_doc_list.xml +++ b/packages/DocumentsUI/res/layout/item_doc_list.xml @@ -14,10 +14,16 @@ limitations under the License. --> -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" +<com.android.documentsui.DocListItem xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="@drawable/item_doc_list_background"> + android:background="@drawable/item_doc_list_background" + android:orientation="horizontal"> + + <View + android:id="@+id/focus_indicator" + android:layout_width="4dp" + android:layout_height="match_parent" /> <LinearLayout android:layout_width="match_parent" @@ -131,4 +137,4 @@ </LinearLayout> -</FrameLayout> +</com.android.documentsui.DocListItem> diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java index 766268983ccc..a317ef2ea431 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java @@ -68,6 +68,7 @@ import android.support.v7.widget.RecyclerView.LayoutManager; import android.support.v7.widget.RecyclerView.OnItemTouchListener; import android.support.v7.widget.RecyclerView.RecyclerListener; import android.support.v7.widget.RecyclerView.ViewHolder; +import android.support.v7.widget.SimpleItemAnimator; import android.text.TextUtils; import android.text.format.DateUtils; import android.text.format.Formatter; @@ -79,12 +80,12 @@ import android.util.TypedValue; import android.view.ActionMode; import android.view.DragEvent; import android.view.GestureDetector; +import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; -import android.view.View.OnLayoutChangeListener; import android.view.ViewGroup; import android.view.ViewParent; import android.widget.ImageView; @@ -134,6 +135,7 @@ public class DirectoryFragment extends Fragment { private Model mModel; private MultiSelectManager mSelectionManager; private Model.UpdateListener mModelUpdateListener = new ModelUpdateListener(); + private ItemClickListener mItemClickListener = new ItemClickListener(); private View mEmptyView; private RecyclerView mRecView; @@ -238,7 +240,7 @@ public class DirectoryFragment extends Fragment { // TODO: Rather than update columns on layout changes, push this // code (or something like it) into GridLayoutManager. mRecView.addOnLayoutChangeListener( - new OnLayoutChangeListener() { + new View.OnLayoutChangeListener() { @Override public void onLayoutChange( @@ -251,6 +253,9 @@ public class DirectoryFragment extends Fragment { } }); + // TODO: Restore transition animations. See b/24802917. + ((SimpleItemAnimator) mRecView.getItemAnimator()).setSupportsChangeAnimations(false); + // TODO: Add a divider between views (which might use RecyclerView.ItemDecoration). if (DEBUG_ENABLE_DND) { setupDragAndDropOnDirectoryView(mRecView); @@ -450,7 +455,10 @@ public class DirectoryFragment extends Fragment { } private boolean onSingleTapUp(MotionEvent e) { - if (Events.isTouchEvent(e) && mSelectionManager.getSelection().isEmpty()) { + // Only respond to touch events. Single-click mouse events are selection events and are + // handled by the selection manager. Tap events that occur while the selection manager is + // active are also selection events. + if (Events.isTouchEvent(e) && !mSelectionManager.hasSelection()) { int position = getEventAdapterPosition(e); if (position != RecyclerView.NO_POSITION) { return handleViewItem(position); @@ -825,7 +833,7 @@ public class DirectoryFragment extends Fragment { Snackbars.makeSnackbar(activity, message, Snackbar.LENGTH_LONG) .setAction( R.string.undo, - new android.view.View.OnClickListener() { + new View.OnClickListener() { @Override public void onClick(View view) {} }) @@ -889,10 +897,16 @@ public class DirectoryFragment extends Fragment { // Provide a reference to the views for each data item // Complex data items may need more than one view per item, and // you provide access to all the views for a data item in a view holder - private static final class DocumentHolder extends RecyclerView.ViewHolder { + private static final class DocumentHolder + extends RecyclerView.ViewHolder + implements View.OnKeyListener + { // each data item is just a string in this case public View view; public String docId; // The stable document id. + private ClickListener mClickListener; + private View.OnKeyListener mKeyListener; + public DocumentHolder(View view) { super(view); this.view = view; @@ -900,6 +914,38 @@ public class DirectoryFragment extends Fragment { // So we set it here. Note that touch mode focus is a separate issue - see // View.setFocusableInTouchMode and View.isInTouchMode for more info. this.view.setFocusable(true); + this.view.setOnKeyListener(this); + } + + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + // Intercept enter key-up events, and treat them as clicks. Forward other events. + if (event.getAction() == KeyEvent.ACTION_UP && + keyCode == KeyEvent.KEYCODE_ENTER) { + if (mClickListener != null) { + mClickListener.onClick(this); + } + return true; + } else if (mKeyListener != null) { + return mKeyListener.onKey(v, keyCode, event); + } + return false; + } + + public void addClickListener(ClickListener listener) { + // Just handle one for now; switch to a list if necessary. + checkState(mClickListener == null); + mClickListener = listener; + } + + public void addOnKeyListener(View.OnKeyListener listener) { + // Just handle one for now; switch to a list if necessary. + checkState(mKeyListener == null); + mKeyListener = listener; + } + + interface ClickListener { + public void onClick(DocumentHolder doc); } } @@ -952,10 +998,11 @@ public class DirectoryFragment extends Fragment { default: throw new IllegalStateException("Unsupported layout mode."); } - // Key event bubbling doesn't work properly, so instead of setting one key listener on - // the RecyclerView, we have to set it on each Item. See b/24865023. - item.setOnKeyListener(mSelectionManager); - return new DocumentHolder(item); + + DocumentHolder holder = new DocumentHolder(item); + holder.addClickListener(mItemClickListener); + holder.addOnKeyListener(mSelectionManager); + return holder; } @Override @@ -1957,6 +2004,18 @@ public class DirectoryFragment extends Fragment { } } + private class ItemClickListener implements DocumentHolder.ClickListener { + @Override + public void onClick(DocumentHolder doc) { + final int position = doc.getAdapterPosition(); + if (mSelectionManager.hasSelection()) { + mSelectionManager.toggleSelection(position); + } else { + handleViewItem(position); + } + } + } + private class ModelUpdateListener extends Model.UpdateListener { @Override public void onModelUpdate(Model model) { diff --git a/packages/DocumentsUI/src/com/android/documentsui/ListItem.java b/packages/DocumentsUI/src/com/android/documentsui/ListItem.java new file mode 100644 index 000000000000..5c40f1b20d74 --- /dev/null +++ b/packages/DocumentsUI/src/com/android/documentsui/ListItem.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2015 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; + +import android.content.Context; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.View; +import android.widget.LinearLayout; + +/** + * Layout for a single item in List mode. This class overrides the default focus listener in order + * to light up a focus indicator when it is focused. + */ +public class ListItem extends LinearLayout +{ + public ListItem(Context context) { + super(context); + } + + public ListItem(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { + View indicator = findViewById(R.id.focus_indicator); + if (gainFocus) { + TypedValue color = new TypedValue(); + getContext().getTheme().resolveAttribute(android.R.attr.colorAccent, color, true); + indicator.setBackgroundColor(color.data); + } else { + indicator.setBackgroundColor(android.R.color.transparent); + } + super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); + } +} diff --git a/packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java b/packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java index 87c037dcd738..b9c4da11ca09 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java +++ b/packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java @@ -183,6 +183,10 @@ public final class MultiSelectManager implements View.OnKeyListener { mCallbacks.add(callback); } + public boolean hasSelection() { + return !mSelection.isEmpty(); + } + /** * Returns a Selection object that provides a live view * on the current selection. @@ -217,7 +221,7 @@ public final class MultiSelectManager implements View.OnKeyListener { */ @VisibleForTesting public boolean setItemSelected(int position, boolean selected) { - if (mSingleSelect && !mSelection.isEmpty()) { + if (mSingleSelect && hasSelection()) { clearSelectionQuietly(); } return setItemsSelected(position, 1, selected); @@ -263,7 +267,7 @@ public final class MultiSelectManager implements View.OnKeyListener { private void clearSelectionQuietly() { mRanger = null; - if (mSelection.isEmpty()) { + if (!hasSelection()) { return; } if (mIntermediateSelection == null) { @@ -292,7 +296,7 @@ public final class MultiSelectManager implements View.OnKeyListener { @VisibleForTesting boolean onSingleTapUp(InputEvent input) { if (DEBUG) Log.d(TAG, "Processing tap event."); - if (mSelection.isEmpty()) { + if (!hasSelection()) { // if this is a mouse click on an item, start selection mode. // TODO: && input.isPrimaryButtonPressed(), but it is returning false. if (input.isOverItem() && input.isMouseEvent()) { @@ -335,7 +339,7 @@ public final class MultiSelectManager implements View.OnKeyListener { * * @param position */ - private void toggleSelection(int position) { + void toggleSelection(int position) { // Position may be special "no position" during certain // transitional phases. If so, skip handling of the event. if (position == RecyclerView.NO_POSITION) { @@ -351,7 +355,7 @@ public final class MultiSelectManager implements View.OnKeyListener { if (!canSelect) { return; } - if (mSingleSelect && !mSelection.isEmpty()) { + if (mSingleSelect && hasSelection()) { clearSelectionQuietly(); } @@ -398,7 +402,7 @@ public final class MultiSelectManager implements View.OnKeyListener { if (selected) { boolean canSelect = notifyBeforeItemStateChange(i, true); if (canSelect) { - if (mSingleSelect && !mSelection.isEmpty()) { + if (mSingleSelect && hasSelection()) { clearSelectionQuietly(); } selectAndNotify(i); @@ -1959,7 +1963,11 @@ public final class MultiSelectManager implements View.OnKeyListener { } if (searchDir != -1) { View targetView = view.focusSearch(searchDir); - target = mEnvironment.getAdapterPositionForChildView(targetView); + // TargetView can be null, for example, if the user pressed <down> at the bottom of + // the list. + if (targetView != null) { + target = mEnvironment.getAdapterPositionForChildView(targetView); + } } } @@ -1972,18 +1980,13 @@ public final class MultiSelectManager implements View.OnKeyListener { mEnvironment.focusItem(target); if (event.isShiftPressed()) { - if (mSelection.isEmpty()) { + if (!hasSelection()) { // If there is no selection, start a selection when the user presses shift-arrow. toggleSelection(mEnvironment.getAdapterPositionForChildView(view)); - } else { - // Deal with b/24802917 (selected items can't be focused) by adjusting the - // selection sorted the focused item isn't in the selection. - target -= Integer.signum(target - mRanger.mBegin); - mRanger.snapSelection(target); } - } else if (!event.isShiftPressed() && !mSelection.isEmpty()) { - // If there is a selection, clear it if the user presses arrow with no shift. - clearSelection(); + + mRanger.snapSelection(target); + notifySelectionChanged(); } return true; |