diff options
-rw-r--r-- | res/drawable/breadcrumb_item_background.xml | 42 | ||||
-rw-r--r-- | res/layout/fixed_layout.xml | 1 | ||||
-rw-r--r-- | res/layout/navigation_breadcrumb_item.xml | 9 | ||||
-rw-r--r-- | res/values/attrs.xml | 2 | ||||
-rw-r--r-- | res/values/colors.xml | 1 | ||||
-rw-r--r-- | res/values/dimens.xml | 2 | ||||
-rw-r--r-- | src/com/android/documentsui/DragOverTextView.java | 52 | ||||
-rw-r--r-- | src/com/android/documentsui/HorizontalBreadcrumb.java | 93 | ||||
-rw-r--r-- | src/com/android/documentsui/dirlist/DirectoryDragListener.java | 13 | ||||
-rw-r--r-- | src/com/android/documentsui/dirlist/DirectoryFragment.java | 22 |
10 files changed, 213 insertions, 24 deletions
diff --git a/res/drawable/breadcrumb_item_background.xml b/res/drawable/breadcrumb_item_background.xml new file mode 100644 index 000000000..c4bc77b3b --- /dev/null +++ b/res/drawable/breadcrumb_item_background.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<ripple + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res/com.android.documentsui" + android:color="?attr/colorControlHighlight"> + <item + android:id="@android:id/mask" + android:drawable="@android:color/white"/> + + <item> + <selector> + <item + app:state_highlighted="true" + android:drawable="@color/item_breadcrumb_background_hovered"/> + <item + app:state_highlighted="false" + android:drawable="@android:color/transparent"> + <corners + android:topLeftRadius="2dp" + android:topRightRadius="2dp" + android:bottomLeftRadius="2dp" + android:bottomRightRadius="2dp" + /> + </item> + </selector> + </item> +</ripple>
\ No newline at end of file diff --git a/res/layout/fixed_layout.xml b/res/layout/fixed_layout.xml index 2ea936641..6c6543780 100644 --- a/res/layout/fixed_layout.xml +++ b/res/layout/fixed_layout.xml @@ -41,6 +41,7 @@ <com.android.documentsui.HorizontalBreadcrumb android:id="@+id/breadcrumb" + android:layout_marginRight="20dp" android:layout_width="match_parent" android:layout_height="wrap_content" /> diff --git a/res/layout/navigation_breadcrumb_item.xml b/res/layout/navigation_breadcrumb_item.xml index ab9d3b26d..b45d25d49 100644 --- a/res/layout/navigation_breadcrumb_item.xml +++ b/res/layout/navigation_breadcrumb_item.xml @@ -28,17 +28,20 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" - android:layout_height="match_parent" + android:layout_height="@dimen/breadcrumb_item_height" android:layout_alignParentTop="true" android:gravity="center_vertical" android:orientation="horizontal"> - <TextView + <com.android.documentsui.DragOverTextView android:id="@+id/breadcrumb_text" android:layout_width="wrap_content" android:layout_height="match_parent" android:gravity="center_vertical" - android:textAppearance="@android:style/TextAppearance.Material.Widget.ActionBar.Title" /> + android:paddingRight="@dimen/breadcrumb_item_padding" + android:paddingLeft="@dimen/breadcrumb_item_padding" + android:textAppearance="@android:style/TextAppearance.Material.Widget.ActionBar.Title" + android:background="@drawable/breadcrumb_item_background" /> <ImageView android:id="@+id/breadcrumb_arrow" diff --git a/res/values/attrs.xml b/res/values/attrs.xml index b48c52f4c..27906f608 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -18,7 +18,7 @@ <attr name="colorActionMode" format="color"/> </declare-styleable> - <declare-styleable name="RootItemView"> + <declare-styleable name="HighlightedItemView"> <attr name="state_highlighted" format="boolean"/> </declare-styleable> </resources> diff --git a/res/values/colors.xml b/res/values/colors.xml index cf0643d8c..0acaeb836 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -43,5 +43,6 @@ <!-- TODO: Would be nice to move this to a color-set, but not sure how to support animation --> <color name="item_doc_background">#fffafafa</color> <color name="item_doc_background_selected">#ffe0f2f1</color> + <color name="item_breadcrumb_background_hovered">#1affffff</color> </resources> diff --git a/res/values/dimens.xml b/res/values/dimens.xml index cad26e250..5f1b349d5 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -33,6 +33,8 @@ <dimen name="grid_padding_vert">4dp</dimen> <dimen name="list_item_height">72dp</dimen> <dimen name="list_item_padding">16dp</dimen> + <dimen name="breadcrumb_item_padding">8dp</dimen> + <dimen name="breadcrumb_item_height">36dp</dimen> <dimen name="list_divider_inset">72dp</dimen> <dimen name="dir_elevation">8dp</dimen> <dimen name="drag_shadow_size">120dp</dimen> diff --git a/src/com/android/documentsui/DragOverTextView.java b/src/com/android/documentsui/DragOverTextView.java new file mode 100644 index 000000000..e9fc2a0fe --- /dev/null +++ b/src/com/android/documentsui/DragOverTextView.java @@ -0,0 +1,52 @@ +/* + * 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; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View.OnDragListener; +import android.widget.TextView; + +/** + * An {@link TextView} that uses drawable states to distinct between normal and highlighted states. + */ + +public final class DragOverTextView extends TextView { + private static final int[] STATE_HIGHLIGHTED = {R.attr.state_highlighted}; + + private boolean mHighlighted = false; + + public DragOverTextView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected int[] onCreateDrawableState(int extraSpace) { + final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); + + if (mHighlighted) { + mergeDrawableStates(drawableState, STATE_HIGHLIGHTED); + } + + return drawableState; + } + + public void setHighlight(boolean highlight) { + mHighlighted = highlight; + refreshDrawableState(); + } +} diff --git a/src/com/android/documentsui/HorizontalBreadcrumb.java b/src/com/android/documentsui/HorizontalBreadcrumb.java index 9f6b79b68..e47347918 100644 --- a/src/com/android/documentsui/HorizontalBreadcrumb.java +++ b/src/com/android/documentsui/HorizontalBreadcrumb.java @@ -26,7 +26,6 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; -import android.widget.TextView; import com.android.documentsui.NavigationViewManager.Breadcrumb; import com.android.documentsui.NavigationViewManager.Environment; @@ -38,7 +37,10 @@ import java.util.function.Consumer; /** * Horizontal implementation of breadcrumb used for tablet / desktop device layouts */ -public final class HorizontalBreadcrumb extends RecyclerView implements Breadcrumb { +public final class HorizontalBreadcrumb extends RecyclerView + implements Breadcrumb, ItemDragListener.DragHost { + + private static final int USER_NO_SCROLL_OFFSET_THRESHOLD = 5; private LinearLayoutManager mLayoutManager; private BreadcrumbAdapter mAdapter; @@ -64,7 +66,8 @@ public final class HorizontalBreadcrumb extends RecyclerView implements Breadcru mListener = listener; mLayoutManager = new LinearLayoutManager( getContext(), LinearLayoutManager.HORIZONTAL, false); - mAdapter = new BreadcrumbAdapter(state, env); + mAdapter = new BreadcrumbAdapter( + state, env, new ItemDragListener<>(this)); setLayoutManager(mLayoutManager); addOnItemTouchListener(new ClickListener(getContext(), this::onSingleTapUp)); @@ -74,18 +77,62 @@ public final class HorizontalBreadcrumb extends RecyclerView implements Breadcru public void show(boolean visibility) { if (visibility) { setVisibility(VISIBLE); - setAdapter(mAdapter); - mLayoutManager.scrollToPosition(mAdapter.getItemCount() - 1); + boolean shouldScroll = !hasUserDefineScrollOffset(); + if (getAdapter() == null) { + setAdapter(mAdapter); + } else { + int currentItemCount = mAdapter.getItemCount(); + int lastItemCount = mAdapter.getLastItemSize(); + if (currentItemCount > lastItemCount) { + mAdapter.notifyItemRangeInserted(lastItemCount, + currentItemCount - lastItemCount); + mAdapter.notifyItemChanged(lastItemCount - 1); + } else if (currentItemCount < lastItemCount) { + mAdapter.notifyItemRangeRemoved(currentItemCount, + lastItemCount - currentItemCount); + mAdapter.notifyItemChanged(currentItemCount - 1); + } + } + if (shouldScroll) { + mLayoutManager.scrollToPosition(mAdapter.getItemCount() - 1); + } } else { setVisibility(GONE); setAdapter(null); } + mAdapter.updateLastItemSize(); + } + + private boolean hasUserDefineScrollOffset() { + final int maxOffset = computeHorizontalScrollRange() - computeHorizontalScrollExtent(); + return (maxOffset - computeHorizontalScrollOffset() > USER_NO_SCROLL_OFFSET_THRESHOLD); } @Override public void postUpdate() { } + @Override + public void runOnUiThread(Runnable runnable) { + post(runnable); + } + + @Override + public void setDropTargetHighlight(View v, boolean highlight) { + RecyclerView.ViewHolder vh = getChildViewHolder(v); + if (vh instanceof BreadcrumbHolder) { + ((BreadcrumbHolder) vh).setHighlighted(highlight); + } + } + + @Override + public void onViewHovered(View v) { + int pos = getChildAdapterPosition(v); + if (pos != mAdapter.getItemCount() - 1) { + mListener.accept(pos); + } + } + private void onSingleTapUp(MotionEvent e) { View itemView = findChildViewUnder(e.getX(), e.getY()); int pos = getChildAdapterPosition(itemView); @@ -97,12 +144,19 @@ public final class HorizontalBreadcrumb extends RecyclerView implements Breadcru private static final class BreadcrumbAdapter extends RecyclerView.Adapter<BreadcrumbHolder> { - private Environment mEnv; - private com.android.documentsui.State mState; + private final Environment mEnv; + private final com.android.documentsui.State mState; + private final OnDragListener mDragListener; + // We keep the old item size so the breadcrumb will only re-render views that are necessary + private int mLastItemSize; - public BreadcrumbAdapter(com.android.documentsui.State state, Environment env) { + public BreadcrumbAdapter(com.android.documentsui.State state, + Environment env, + OnDragListener dragListener) { mState = state; mEnv = env; + mDragListener = dragListener; + mLastItemSize = mState.stack.size(); } @Override @@ -125,7 +179,10 @@ public final class HorizontalBreadcrumb extends RecyclerView implements Breadcru if (position == getItemCount() - 1) { holder.arrow.setVisibility(View.GONE); + } else { + holder.arrow.setVisibility(View.VISIBLE); } + holder.itemView.setOnDragListener(mDragListener); } private DocumentInfo getItem(int position) { @@ -136,18 +193,34 @@ public final class HorizontalBreadcrumb extends RecyclerView implements Breadcru public int getItemCount() { return mState.stack.size(); } + + public int getLastItemSize() { + return mLastItemSize; + } + + public void updateLastItemSize() { + mLastItemSize = mState.stack.size(); + } } private static class BreadcrumbHolder extends RecyclerView.ViewHolder { - protected TextView title; + protected DragOverTextView title; protected ImageView arrow; public BreadcrumbHolder(View itemView) { super(itemView); - title = (TextView) itemView.findViewById(R.id.breadcrumb_text); + title = (DragOverTextView) itemView.findViewById(R.id.breadcrumb_text); arrow = (ImageView) itemView.findViewById(R.id.breadcrumb_arrow); } + + /** + * Highlights the associated item view. + * @param highlighted + */ + public void setHighlighted(boolean highlighted) { + title.setHighlight(highlighted); + } } private static final class ClickListener extends GestureDetector diff --git a/src/com/android/documentsui/dirlist/DirectoryDragListener.java b/src/com/android/documentsui/dirlist/DirectoryDragListener.java index 40ee14db2..0860f4ca1 100644 --- a/src/com/android/documentsui/dirlist/DirectoryDragListener.java +++ b/src/com/android/documentsui/dirlist/DirectoryDragListener.java @@ -31,16 +31,11 @@ class DirectoryDragListener extends ItemDragListener<DirectoryFragment> { public boolean onDrag(View v, DragEvent event) { final boolean result = super.onDrag(v, event); - if (event.getAction() == DragEvent.ACTION_DRAG_ENDED) { + if (event.getAction() == DragEvent.ACTION_DRAG_STARTED) { + mDragHost.dragStarted(); + } else if (event.getAction() == DragEvent.ACTION_DRAG_ENDED) { // getResult() is true if drag was accepted - if (event.getResult()) { - mDragHost.clearSelection(); - } else { - // When drag starts we might write a new clip file to disk. - // No drop event happens, remove clip file here. This may be called multiple times, - // but it should be OK because deletion is idempotent and cheap. - mDragHost.deleteDragClipFile(); - } + mDragHost.dragStopped(event.getResult()); } return result; diff --git a/src/com/android/documentsui/dirlist/DirectoryFragment.java b/src/com/android/documentsui/dirlist/DirectoryFragment.java index afdcdf1cb..f96341a24 100644 --- a/src/com/android/documentsui/dirlist/DirectoryFragment.java +++ b/src/com/android/documentsui/dirlist/DirectoryFragment.java @@ -499,6 +499,26 @@ public class DirectoryFragment extends Fragment state.dirState.put(mStateKey, container); } + void dragStarted() { + // When files are selected for dragging, ActionMode is started. This obscures the breadcrumb + // with an ActionBar. In order to make drag and drop to the breadcrumb possible, we first + // end ActionMode so the breadcrumb is visible to the user. + if (mActionMode != null) { + mActionMode.finish(); + } + } + + void dragStopped(boolean result) { + if (result) { + clearSelection(); + } else { + // When drag starts we might write a new clip file to disk. + // No drop event happens, remove clip file here. This may be called multiple times, + // but it should be OK because deletion is idempotent and cheap. + deleteDragClipFile(); + } + } + public void onDisplayStateChanged() { updateDisplayState(); } @@ -1273,7 +1293,7 @@ public class DirectoryFragment extends Fragment activity.setRootsDrawerOpen(false); } - void deleteDragClipFile() { + private void deleteDragClipFile() { mClipper.deleteDragClip(); } |