Enable drag & drop -> Springload for breadcrumb.
Bug: 29519481
Change-Id: I54d98c48c08153c22b2f01c7728187927c4fe1a2
(cherry picked from commit 81d903bc083807c6048ac9ce1d1d3ca18b6643a3)
diff --git a/packages/DocumentsUI/res/drawable/breadcrumb_item_background.xml b/packages/DocumentsUI/res/drawable/breadcrumb_item_background.xml
new file mode 100644
index 0000000..c4bc77b
--- /dev/null
+++ b/packages/DocumentsUI/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/packages/DocumentsUI/res/layout/fixed_layout.xml b/packages/DocumentsUI/res/layout/fixed_layout.xml
index 2ea9366..6c65437 100644
--- a/packages/DocumentsUI/res/layout/fixed_layout.xml
+++ b/packages/DocumentsUI/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/packages/DocumentsUI/res/layout/navigation_breadcrumb_item.xml b/packages/DocumentsUI/res/layout/navigation_breadcrumb_item.xml
index ab9d3b2..b45d25d 100644
--- a/packages/DocumentsUI/res/layout/navigation_breadcrumb_item.xml
+++ b/packages/DocumentsUI/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/packages/DocumentsUI/res/values/attrs.xml b/packages/DocumentsUI/res/values/attrs.xml
index b48c52f..27906f6 100644
--- a/packages/DocumentsUI/res/values/attrs.xml
+++ b/packages/DocumentsUI/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/packages/DocumentsUI/res/values/colors.xml b/packages/DocumentsUI/res/values/colors.xml
index cf0643d..0acaeb8 100644
--- a/packages/DocumentsUI/res/values/colors.xml
+++ b/packages/DocumentsUI/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/packages/DocumentsUI/res/values/dimens.xml b/packages/DocumentsUI/res/values/dimens.xml
index cad26e2..5f1b349 100644
--- a/packages/DocumentsUI/res/values/dimens.xml
+++ b/packages/DocumentsUI/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/packages/DocumentsUI/src/com/android/documentsui/DragOverTextView.java b/packages/DocumentsUI/src/com/android/documentsui/DragOverTextView.java
new file mode 100644
index 0000000..e9fc2a0
--- /dev/null
+++ b/packages/DocumentsUI/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/packages/DocumentsUI/src/com/android/documentsui/HorizontalBreadcrumb.java b/packages/DocumentsUI/src/com/android/documentsui/HorizontalBreadcrumb.java
index 9f6b79b..e473479 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/HorizontalBreadcrumb.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/HorizontalBreadcrumb.java
@@ -26,7 +26,6 @@
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 @@
/**
* 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 @@
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 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 @@
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 @@
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 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/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryDragListener.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryDragListener.java
index 40ee14d..0860f4c 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryDragListener.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryDragListener.java
@@ -31,16 +31,11 @@
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/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index afdcdf1..f96341a 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -499,6 +499,26 @@
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 @@
activity.setRootsDrawerOpen(false);
}
- void deleteDragClipFile() {
+ private void deleteDragClipFile() {
mClipper.deleteDragClip();
}