Loading res/values/dimens.xml +2 −0 Original line number Diff line number Diff line Loading @@ -46,4 +46,6 @@ <dimen name="drag_shadow_width">160dp</dimen> <dimen name="drag_shadow_height">48dp</dimen> <dimen name="autoscroll_edge_height">32dp</dimen> </resources> src/com/android/documentsui/Shared.java +0 −5 Original line number Diff line number Diff line Loading @@ -21,7 +21,6 @@ import android.app.AlertDialog; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; import android.net.Uri; import android.os.Looper; import android.provider.DocumentsContract; import android.text.TextUtils; Loading @@ -30,10 +29,6 @@ import android.text.format.Time; import android.util.Log; import android.view.WindowManager; import com.android.documentsui.model.DocumentInfo; import com.android.documentsui.model.RootInfo; import java.io.FileNotFoundException; import java.text.Collator; import java.util.ArrayList; import java.util.List; Loading src/com/android/documentsui/dirlist/BandController.java +29 −120 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.documentsui.dirlist; import static com.android.documentsui.Shared.DEBUG; import static com.android.documentsui.dirlist.ModelBackedDocumentsAdapter.ITEM_TYPE_DIRECTORY; import static com.android.documentsui.dirlist.ModelBackedDocumentsAdapter.ITEM_TYPE_DOCUMENT; import static com.android.documentsui.dirlist.ViewAutoScroller.NOT_SET; import android.graphics.Point; import android.graphics.Rect; Loading @@ -39,6 +40,8 @@ import com.android.documentsui.Events.InputEvent; import com.android.documentsui.Events.MotionInputEvent; import com.android.documentsui.R; import com.android.documentsui.dirlist.MultiSelectManager.Selection; import com.android.documentsui.dirlist.ViewAutoScroller.ScrollActionDelegate; import com.android.documentsui.dirlist.ViewAutoScroller.ScrollDistanceDelegate; import java.util.ArrayList; import java.util.Collections; Loading @@ -54,15 +57,14 @@ import java.util.Set; */ public class BandController extends RecyclerView.OnScrollListener { private static final int NOT_SET = -1; private static final String TAG = "BandController"; private static final int AUTOSCROLL_EDGE_HEIGHT = 1; private final Runnable mModelBuilder; private final SelectionEnvironment mEnvironment; private final DocumentsAdapter mAdapter; private final MultiSelectManager mSelectionManager; private final Runnable mViewScroller = new ViewScroller(); private final Runnable mViewScroller; private final GridModel.OnSelectionChangedListener mGridListener; @Nullable private Rect mBounds; Loading @@ -70,9 +72,6 @@ public class BandController extends RecyclerView.OnScrollListener { @Nullable private Point mOrigin; @Nullable private BandController.GridModel mModel; // The time at which the current band selection-induced scroll began. If no scroll is in // progress, the value is NOT_SET. private long mScrollStartTime = NOT_SET; private Selection mSelection; public BandController( Loading Loading @@ -114,6 +113,25 @@ public class BandController extends RecyclerView.OnScrollListener { mSelectionManager = selectionManager; mEnvironment.addOnScrollListener(this); mViewScroller = new ViewAutoScroller( AUTOSCROLL_EDGE_HEIGHT, new ScrollDistanceDelegate() { @Override public Point getCurrentPosition() { return mCurrentPosition; } @Override public int getViewHeight() { return mEnvironment.getHeight(); } @Override public boolean isActive() { return BandController.this.isActive(); } }, env); mAdapter.registerAdapterDataObserver( new RecyclerView.AdapterDataObserver() { Loading Loading @@ -173,6 +191,10 @@ public class BandController extends RecyclerView.OnScrollListener { }; } private boolean isActive() { return mModel != null; } void bindSelection(Selection selection) { mSelection = selection; } Loading Loading @@ -212,10 +234,6 @@ public class BandController extends RecyclerView.OnScrollListener { return isActive(); } private boolean isActive() { return mModel != null; } /** * Handle a change in layout by cleaning up and getting rid of the old model and creating * a new model which will track the new layout. Loading Loading @@ -336,112 +354,6 @@ public class BandController extends RecyclerView.OnScrollListener { return mSelectionManager.notifyBeforeItemStateChange(id, nextState); } private class ViewScroller implements Runnable { /** * The number of milliseconds of scrolling at which scroll speed continues to increase. * At first, the scroll starts slowly; then, the rate of scrolling increases until it * reaches its maximum value at after this many milliseconds. */ private static final long SCROLL_ACCELERATION_LIMIT_TIME_MS = 2000; @Override public void run() { // Compute the number of pixels the pointer's y-coordinate is past the view. // Negative values mean the pointer is at or before the top of the view, and // positive values mean that the pointer is at or after the bottom of the view. Note // that one additional pixel is added here so that the view still scrolls when the // pointer is exactly at the top or bottom. int pixelsPastView = 0; if (mCurrentPosition.y <= 0) { pixelsPastView = mCurrentPosition.y - 1; } else if (mCurrentPosition.y >= mEnvironment.getHeight() - 1) { pixelsPastView = mCurrentPosition.y - mEnvironment.getHeight() + 1; } if (!isActive() || pixelsPastView == 0) { // If band selection is inactive, or if it is active but not at the edge of the // view, no scrolling is necessary. mScrollStartTime = NOT_SET; return; } if (mScrollStartTime == NOT_SET) { // If the pointer was previously not at the edge of the view but now is, set the // start time for the scroll. mScrollStartTime = System.currentTimeMillis(); } // Compute the number of pixels to scroll, and scroll that many pixels. final int numPixels = computeScrollDistance( pixelsPastView, System.currentTimeMillis() - mScrollStartTime); mEnvironment.scrollBy(numPixels); mEnvironment.removeCallback(mViewScroller); mEnvironment.runAtNextFrame(this); } /** * Computes the number of pixels to scroll based on how far the pointer is past the end * of the view and how long it has been there. Roughly based on ItemTouchHelper's * algorithm for computing the number of pixels to scroll when an item is dragged to the * end of a {@link RecyclerView}. * @param pixelsPastView * @param scrollDuration * @return */ private int computeScrollDistance(int pixelsPastView, long scrollDuration) { final int maxScrollStep = mEnvironment.getHeight(); final int direction = (int) Math.signum(pixelsPastView); final int absPastView = Math.abs(pixelsPastView); // Calculate the ratio of how far out of the view the pointer currently resides to // the entire height of the view. final float outOfBoundsRatio = Math.min( 1.0f, (float) absPastView / mEnvironment.getHeight()); // Interpolate this ratio and use it to compute the maximum scroll that should be // possible for this step. final float cappedScrollStep = direction * maxScrollStep * smoothOutOfBoundsRatio(outOfBoundsRatio); // Likewise, calculate the ratio of the time spent in the scroll to the limit. final float timeRatio = Math.min( 1.0f, (float) scrollDuration / SCROLL_ACCELERATION_LIMIT_TIME_MS); // Interpolate this ratio and use it to compute the final number of pixels to // scroll. final int numPixels = (int) (cappedScrollStep * smoothTimeRatio(timeRatio)); // If the final number of pixels to scroll ends up being 0, the view should still // scroll at least one pixel. return numPixels != 0 ? numPixels : direction; } /** * Interpolates the given out of bounds ratio on a curve which starts at (0,0) and ends * at (1,1) and quickly approaches 1 near the start of that interval. This ensures that * drags that are at the edge or barely past the edge of the view still cause sufficient * scrolling. The equation y=(x-1)^5+1 is used, but this could also be tweaked if * needed. * @param ratio A ratio which is in the range [0, 1]. * @return A "smoothed" value, also in the range [0, 1]. */ private float smoothOutOfBoundsRatio(float ratio) { return (float) Math.pow(ratio - 1.0f, 5) + 1.0f; } /** * Interpolates the given time ratio on a curve which starts at (0,0) and ends at (1,1) * and stays close to 0 for most input values except those very close to 1. This ensures * that scrolls start out very slowly but speed up drastically after the scroll has been * in progress close to SCROLL_ACCELERATION_LIMIT_TIME_MS. The equation y=x^5 is used, * but this could also be tweaked if needed. * @param ratio A ratio which is in the range [0, 1]. * @return A "smoothed" value, also in the range [0, 1]. */ private float smoothTimeRatio(float ratio) { return (float) Math.pow(ratio, 5); } }; @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (!isActive()) { Loading Loading @@ -1110,16 +1022,13 @@ public class BandController extends RecyclerView.OnScrollListener { * Provides functionality for BandController. Exists primarily to tests that are * fully isolated from RecyclerView. */ interface SelectionEnvironment { interface SelectionEnvironment extends ScrollActionDelegate { void showBand(Rect rect); void hideBand(); void addOnScrollListener(RecyclerView.OnScrollListener listener); void removeOnScrollListener(RecyclerView.OnScrollListener listener); void scrollBy(int dy); int getHeight(); void invalidateView(); void runAtNextFrame(Runnable r); void removeCallback(Runnable r); Point createAbsolutePoint(Point relativePoint); Rect getAbsoluteRectForChildViewAt(int index); int getAdapterPositionAt(int index); Loading src/com/android/documentsui/dirlist/DirectoryFragment.java +4 −3 Original line number Diff line number Diff line Loading @@ -74,7 +74,6 @@ import android.widget.Toolbar; import com.android.documentsui.BaseActivity; import com.android.documentsui.DirectoryLoader; import com.android.documentsui.DirectoryResult; import com.android.documentsui.clipping.DocumentClipper; import com.android.documentsui.DocumentsActivity; import com.android.documentsui.DocumentsApplication; import com.android.documentsui.Events.InputEvent; Loading @@ -93,6 +92,7 @@ import com.android.documentsui.Shared; import com.android.documentsui.Snackbars; import com.android.documentsui.State; import com.android.documentsui.State.ViewMode; import com.android.documentsui.clipping.DocumentClipper; import com.android.documentsui.clipping.UrisSupplier; import com.android.documentsui.dirlist.MultiSelectManager.Selection; import com.android.documentsui.dirlist.UserInputHandler.DocumentDetails; Loading Loading @@ -183,7 +183,7 @@ public class DirectoryFragment extends Fragment private @Nullable BandController mBandController; private @Nullable ActionMode mActionMode; private DirectoryDragListener mOnDragListener; private DragScrollListener mOnDragListener; private MenuManager mMenuManager; @Override Loading @@ -210,7 +210,8 @@ public class DirectoryFragment extends Fragment mRecView.setItemAnimator(new DirectoryItemAnimator(getActivity())); mOnDragListener = new DirectoryDragListener(this); mOnDragListener = DragScrollListener.create( getActivity(), new DirectoryDragListener(this), mRecView); // Make the recycler and the empty views responsive to drop events. mRecView.setOnDragListener(mOnDragListener); Loading src/com/android/documentsui/dirlist/DragScrollListener.java 0 → 100644 +167 −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.content.Context; import android.graphics.Point; import android.view.DragEvent; import android.view.View; import android.view.View.OnDragListener; import com.android.documentsui.ItemDragListener; import com.android.documentsui.ItemDragListener.DragHost; import com.android.documentsui.dirlist.ViewAutoScroller.ScrollActionDelegate; import com.android.documentsui.dirlist.ViewAutoScroller.ScrollDistanceDelegate; import com.android.documentsui.R; import java.util.function.BooleanSupplier; import java.util.function.IntSupplier; import javax.annotation.Nullable; /** * This class acts as a middle-man handler for potential auto-scrolling before passing the dragEvent * onto {@link DirectoryDragListener}. */ class DragScrollListener implements OnDragListener { private final ItemDragListener<? extends DragHost> mDragHandler; private final IntSupplier mHeight; private final BooleanSupplier mCanScrollUp; private final BooleanSupplier mCanScrollDown; private final int mAutoScrollEdgeHeight; private final Runnable mDragScroller; private boolean mDragHappening; private @Nullable Point mCurrentPosition; private DragScrollListener( Context context, ItemDragListener<? extends DragHost> dragHandler, IntSupplier heightSupplier, BooleanSupplier scrollUpSupplier, BooleanSupplier scrollDownSupplier, ViewAutoScroller.ScrollActionDelegate actionDelegate) { mDragHandler = dragHandler; mAutoScrollEdgeHeight = (int) context.getResources() .getDimension(R.dimen.autoscroll_edge_height); mHeight = heightSupplier; mCanScrollUp = scrollUpSupplier; mCanScrollDown = scrollDownSupplier; ScrollDistanceDelegate distanceDelegate = new ScrollDistanceDelegate() { @Override public Point getCurrentPosition() { return mCurrentPosition; } @Override public int getViewHeight() { return mHeight.getAsInt(); } @Override public boolean isActive() { return mDragHappening; } }; mDragScroller = new ViewAutoScroller( mAutoScrollEdgeHeight, distanceDelegate, actionDelegate); } static DragScrollListener create( Context context, ItemDragListener<? extends DragHost> dragHandler, View scrollView) { ScrollActionDelegate actionDelegate = new ScrollActionDelegate() { @Override public void scrollBy(int dy) { scrollView.scrollBy(0, dy); } @Override public void runAtNextFrame(Runnable r) { scrollView.postOnAnimation(r); } @Override public void removeCallback(Runnable r) { scrollView.removeCallbacks(r); } }; DragScrollListener listener = new DragScrollListener( context, dragHandler, scrollView::getHeight, () -> { return scrollView.canScrollVertically(-1); }, () -> { return scrollView.canScrollVertically(1); }, actionDelegate); return listener; } @Override public boolean onDrag(View v, DragEvent event) { boolean handled = false; switch (event.getAction()) { case DragEvent.ACTION_DRAG_STARTED: mDragHappening = true; break; case DragEvent.ACTION_DRAG_ENDED: mDragHappening = false; break; case DragEvent.ACTION_DRAG_ENTERED: handled = insideDragZone(); break; case DragEvent.ACTION_DRAG_LOCATION: handled = handleLocationEvent(v, event.getX(), event.getY()); break; default: break; } if (!handled) { handled = mDragHandler.onDrag(v, event); } return handled; } private boolean handleLocationEvent(View v, float x, float y) { mCurrentPosition = new Point(Math.round(v.getX() + x), Math.round(v.getY() + y)); if (insideDragZone()) { mDragScroller.run(); return true; } return false; } private boolean insideDragZone() { if (mCurrentPosition == null) { return false; } boolean shouldScrollUp = mCurrentPosition.y < mAutoScrollEdgeHeight && mCanScrollUp.getAsBoolean(); boolean shouldScrollDown = mCurrentPosition.y > mHeight.getAsInt() - mAutoScrollEdgeHeight && mCanScrollDown.getAsBoolean(); return shouldScrollUp || shouldScrollDown; } } No newline at end of file Loading
res/values/dimens.xml +2 −0 Original line number Diff line number Diff line Loading @@ -46,4 +46,6 @@ <dimen name="drag_shadow_width">160dp</dimen> <dimen name="drag_shadow_height">48dp</dimen> <dimen name="autoscroll_edge_height">32dp</dimen> </resources>
src/com/android/documentsui/Shared.java +0 −5 Original line number Diff line number Diff line Loading @@ -21,7 +21,6 @@ import android.app.AlertDialog; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; import android.net.Uri; import android.os.Looper; import android.provider.DocumentsContract; import android.text.TextUtils; Loading @@ -30,10 +29,6 @@ import android.text.format.Time; import android.util.Log; import android.view.WindowManager; import com.android.documentsui.model.DocumentInfo; import com.android.documentsui.model.RootInfo; import java.io.FileNotFoundException; import java.text.Collator; import java.util.ArrayList; import java.util.List; Loading
src/com/android/documentsui/dirlist/BandController.java +29 −120 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.documentsui.dirlist; import static com.android.documentsui.Shared.DEBUG; import static com.android.documentsui.dirlist.ModelBackedDocumentsAdapter.ITEM_TYPE_DIRECTORY; import static com.android.documentsui.dirlist.ModelBackedDocumentsAdapter.ITEM_TYPE_DOCUMENT; import static com.android.documentsui.dirlist.ViewAutoScroller.NOT_SET; import android.graphics.Point; import android.graphics.Rect; Loading @@ -39,6 +40,8 @@ import com.android.documentsui.Events.InputEvent; import com.android.documentsui.Events.MotionInputEvent; import com.android.documentsui.R; import com.android.documentsui.dirlist.MultiSelectManager.Selection; import com.android.documentsui.dirlist.ViewAutoScroller.ScrollActionDelegate; import com.android.documentsui.dirlist.ViewAutoScroller.ScrollDistanceDelegate; import java.util.ArrayList; import java.util.Collections; Loading @@ -54,15 +57,14 @@ import java.util.Set; */ public class BandController extends RecyclerView.OnScrollListener { private static final int NOT_SET = -1; private static final String TAG = "BandController"; private static final int AUTOSCROLL_EDGE_HEIGHT = 1; private final Runnable mModelBuilder; private final SelectionEnvironment mEnvironment; private final DocumentsAdapter mAdapter; private final MultiSelectManager mSelectionManager; private final Runnable mViewScroller = new ViewScroller(); private final Runnable mViewScroller; private final GridModel.OnSelectionChangedListener mGridListener; @Nullable private Rect mBounds; Loading @@ -70,9 +72,6 @@ public class BandController extends RecyclerView.OnScrollListener { @Nullable private Point mOrigin; @Nullable private BandController.GridModel mModel; // The time at which the current band selection-induced scroll began. If no scroll is in // progress, the value is NOT_SET. private long mScrollStartTime = NOT_SET; private Selection mSelection; public BandController( Loading Loading @@ -114,6 +113,25 @@ public class BandController extends RecyclerView.OnScrollListener { mSelectionManager = selectionManager; mEnvironment.addOnScrollListener(this); mViewScroller = new ViewAutoScroller( AUTOSCROLL_EDGE_HEIGHT, new ScrollDistanceDelegate() { @Override public Point getCurrentPosition() { return mCurrentPosition; } @Override public int getViewHeight() { return mEnvironment.getHeight(); } @Override public boolean isActive() { return BandController.this.isActive(); } }, env); mAdapter.registerAdapterDataObserver( new RecyclerView.AdapterDataObserver() { Loading Loading @@ -173,6 +191,10 @@ public class BandController extends RecyclerView.OnScrollListener { }; } private boolean isActive() { return mModel != null; } void bindSelection(Selection selection) { mSelection = selection; } Loading Loading @@ -212,10 +234,6 @@ public class BandController extends RecyclerView.OnScrollListener { return isActive(); } private boolean isActive() { return mModel != null; } /** * Handle a change in layout by cleaning up and getting rid of the old model and creating * a new model which will track the new layout. Loading Loading @@ -336,112 +354,6 @@ public class BandController extends RecyclerView.OnScrollListener { return mSelectionManager.notifyBeforeItemStateChange(id, nextState); } private class ViewScroller implements Runnable { /** * The number of milliseconds of scrolling at which scroll speed continues to increase. * At first, the scroll starts slowly; then, the rate of scrolling increases until it * reaches its maximum value at after this many milliseconds. */ private static final long SCROLL_ACCELERATION_LIMIT_TIME_MS = 2000; @Override public void run() { // Compute the number of pixels the pointer's y-coordinate is past the view. // Negative values mean the pointer is at or before the top of the view, and // positive values mean that the pointer is at or after the bottom of the view. Note // that one additional pixel is added here so that the view still scrolls when the // pointer is exactly at the top or bottom. int pixelsPastView = 0; if (mCurrentPosition.y <= 0) { pixelsPastView = mCurrentPosition.y - 1; } else if (mCurrentPosition.y >= mEnvironment.getHeight() - 1) { pixelsPastView = mCurrentPosition.y - mEnvironment.getHeight() + 1; } if (!isActive() || pixelsPastView == 0) { // If band selection is inactive, or if it is active but not at the edge of the // view, no scrolling is necessary. mScrollStartTime = NOT_SET; return; } if (mScrollStartTime == NOT_SET) { // If the pointer was previously not at the edge of the view but now is, set the // start time for the scroll. mScrollStartTime = System.currentTimeMillis(); } // Compute the number of pixels to scroll, and scroll that many pixels. final int numPixels = computeScrollDistance( pixelsPastView, System.currentTimeMillis() - mScrollStartTime); mEnvironment.scrollBy(numPixels); mEnvironment.removeCallback(mViewScroller); mEnvironment.runAtNextFrame(this); } /** * Computes the number of pixels to scroll based on how far the pointer is past the end * of the view and how long it has been there. Roughly based on ItemTouchHelper's * algorithm for computing the number of pixels to scroll when an item is dragged to the * end of a {@link RecyclerView}. * @param pixelsPastView * @param scrollDuration * @return */ private int computeScrollDistance(int pixelsPastView, long scrollDuration) { final int maxScrollStep = mEnvironment.getHeight(); final int direction = (int) Math.signum(pixelsPastView); final int absPastView = Math.abs(pixelsPastView); // Calculate the ratio of how far out of the view the pointer currently resides to // the entire height of the view. final float outOfBoundsRatio = Math.min( 1.0f, (float) absPastView / mEnvironment.getHeight()); // Interpolate this ratio and use it to compute the maximum scroll that should be // possible for this step. final float cappedScrollStep = direction * maxScrollStep * smoothOutOfBoundsRatio(outOfBoundsRatio); // Likewise, calculate the ratio of the time spent in the scroll to the limit. final float timeRatio = Math.min( 1.0f, (float) scrollDuration / SCROLL_ACCELERATION_LIMIT_TIME_MS); // Interpolate this ratio and use it to compute the final number of pixels to // scroll. final int numPixels = (int) (cappedScrollStep * smoothTimeRatio(timeRatio)); // If the final number of pixels to scroll ends up being 0, the view should still // scroll at least one pixel. return numPixels != 0 ? numPixels : direction; } /** * Interpolates the given out of bounds ratio on a curve which starts at (0,0) and ends * at (1,1) and quickly approaches 1 near the start of that interval. This ensures that * drags that are at the edge or barely past the edge of the view still cause sufficient * scrolling. The equation y=(x-1)^5+1 is used, but this could also be tweaked if * needed. * @param ratio A ratio which is in the range [0, 1]. * @return A "smoothed" value, also in the range [0, 1]. */ private float smoothOutOfBoundsRatio(float ratio) { return (float) Math.pow(ratio - 1.0f, 5) + 1.0f; } /** * Interpolates the given time ratio on a curve which starts at (0,0) and ends at (1,1) * and stays close to 0 for most input values except those very close to 1. This ensures * that scrolls start out very slowly but speed up drastically after the scroll has been * in progress close to SCROLL_ACCELERATION_LIMIT_TIME_MS. The equation y=x^5 is used, * but this could also be tweaked if needed. * @param ratio A ratio which is in the range [0, 1]. * @return A "smoothed" value, also in the range [0, 1]. */ private float smoothTimeRatio(float ratio) { return (float) Math.pow(ratio, 5); } }; @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (!isActive()) { Loading Loading @@ -1110,16 +1022,13 @@ public class BandController extends RecyclerView.OnScrollListener { * Provides functionality for BandController. Exists primarily to tests that are * fully isolated from RecyclerView. */ interface SelectionEnvironment { interface SelectionEnvironment extends ScrollActionDelegate { void showBand(Rect rect); void hideBand(); void addOnScrollListener(RecyclerView.OnScrollListener listener); void removeOnScrollListener(RecyclerView.OnScrollListener listener); void scrollBy(int dy); int getHeight(); void invalidateView(); void runAtNextFrame(Runnable r); void removeCallback(Runnable r); Point createAbsolutePoint(Point relativePoint); Rect getAbsoluteRectForChildViewAt(int index); int getAdapterPositionAt(int index); Loading
src/com/android/documentsui/dirlist/DirectoryFragment.java +4 −3 Original line number Diff line number Diff line Loading @@ -74,7 +74,6 @@ import android.widget.Toolbar; import com.android.documentsui.BaseActivity; import com.android.documentsui.DirectoryLoader; import com.android.documentsui.DirectoryResult; import com.android.documentsui.clipping.DocumentClipper; import com.android.documentsui.DocumentsActivity; import com.android.documentsui.DocumentsApplication; import com.android.documentsui.Events.InputEvent; Loading @@ -93,6 +92,7 @@ import com.android.documentsui.Shared; import com.android.documentsui.Snackbars; import com.android.documentsui.State; import com.android.documentsui.State.ViewMode; import com.android.documentsui.clipping.DocumentClipper; import com.android.documentsui.clipping.UrisSupplier; import com.android.documentsui.dirlist.MultiSelectManager.Selection; import com.android.documentsui.dirlist.UserInputHandler.DocumentDetails; Loading Loading @@ -183,7 +183,7 @@ public class DirectoryFragment extends Fragment private @Nullable BandController mBandController; private @Nullable ActionMode mActionMode; private DirectoryDragListener mOnDragListener; private DragScrollListener mOnDragListener; private MenuManager mMenuManager; @Override Loading @@ -210,7 +210,8 @@ public class DirectoryFragment extends Fragment mRecView.setItemAnimator(new DirectoryItemAnimator(getActivity())); mOnDragListener = new DirectoryDragListener(this); mOnDragListener = DragScrollListener.create( getActivity(), new DirectoryDragListener(this), mRecView); // Make the recycler and the empty views responsive to drop events. mRecView.setOnDragListener(mOnDragListener); Loading
src/com/android/documentsui/dirlist/DragScrollListener.java 0 → 100644 +167 −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.content.Context; import android.graphics.Point; import android.view.DragEvent; import android.view.View; import android.view.View.OnDragListener; import com.android.documentsui.ItemDragListener; import com.android.documentsui.ItemDragListener.DragHost; import com.android.documentsui.dirlist.ViewAutoScroller.ScrollActionDelegate; import com.android.documentsui.dirlist.ViewAutoScroller.ScrollDistanceDelegate; import com.android.documentsui.R; import java.util.function.BooleanSupplier; import java.util.function.IntSupplier; import javax.annotation.Nullable; /** * This class acts as a middle-man handler for potential auto-scrolling before passing the dragEvent * onto {@link DirectoryDragListener}. */ class DragScrollListener implements OnDragListener { private final ItemDragListener<? extends DragHost> mDragHandler; private final IntSupplier mHeight; private final BooleanSupplier mCanScrollUp; private final BooleanSupplier mCanScrollDown; private final int mAutoScrollEdgeHeight; private final Runnable mDragScroller; private boolean mDragHappening; private @Nullable Point mCurrentPosition; private DragScrollListener( Context context, ItemDragListener<? extends DragHost> dragHandler, IntSupplier heightSupplier, BooleanSupplier scrollUpSupplier, BooleanSupplier scrollDownSupplier, ViewAutoScroller.ScrollActionDelegate actionDelegate) { mDragHandler = dragHandler; mAutoScrollEdgeHeight = (int) context.getResources() .getDimension(R.dimen.autoscroll_edge_height); mHeight = heightSupplier; mCanScrollUp = scrollUpSupplier; mCanScrollDown = scrollDownSupplier; ScrollDistanceDelegate distanceDelegate = new ScrollDistanceDelegate() { @Override public Point getCurrentPosition() { return mCurrentPosition; } @Override public int getViewHeight() { return mHeight.getAsInt(); } @Override public boolean isActive() { return mDragHappening; } }; mDragScroller = new ViewAutoScroller( mAutoScrollEdgeHeight, distanceDelegate, actionDelegate); } static DragScrollListener create( Context context, ItemDragListener<? extends DragHost> dragHandler, View scrollView) { ScrollActionDelegate actionDelegate = new ScrollActionDelegate() { @Override public void scrollBy(int dy) { scrollView.scrollBy(0, dy); } @Override public void runAtNextFrame(Runnable r) { scrollView.postOnAnimation(r); } @Override public void removeCallback(Runnable r) { scrollView.removeCallbacks(r); } }; DragScrollListener listener = new DragScrollListener( context, dragHandler, scrollView::getHeight, () -> { return scrollView.canScrollVertically(-1); }, () -> { return scrollView.canScrollVertically(1); }, actionDelegate); return listener; } @Override public boolean onDrag(View v, DragEvent event) { boolean handled = false; switch (event.getAction()) { case DragEvent.ACTION_DRAG_STARTED: mDragHappening = true; break; case DragEvent.ACTION_DRAG_ENDED: mDragHappening = false; break; case DragEvent.ACTION_DRAG_ENTERED: handled = insideDragZone(); break; case DragEvent.ACTION_DRAG_LOCATION: handled = handleLocationEvent(v, event.getX(), event.getY()); break; default: break; } if (!handled) { handled = mDragHandler.onDrag(v, event); } return handled; } private boolean handleLocationEvent(View v, float x, float y) { mCurrentPosition = new Point(Math.round(v.getX() + x), Math.round(v.getY() + y)); if (insideDragZone()) { mDragScroller.run(); return true; } return false; } private boolean insideDragZone() { if (mCurrentPosition == null) { return false; } boolean shouldScrollUp = mCurrentPosition.y < mAutoScrollEdgeHeight && mCanScrollUp.getAsBoolean(); boolean shouldScrollDown = mCurrentPosition.y > mHeight.getAsInt() - mAutoScrollEdgeHeight && mCanScrollDown.getAsBoolean(); return shouldScrollUp || shouldScrollDown; } } No newline at end of file