Home | History | Annotate | Download | only in dirlist
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.documentsui.dirlist;
     18 
     19 import android.graphics.Point;
     20 import android.support.annotation.VisibleForTesting;
     21 import android.view.DragEvent;
     22 import android.view.View;
     23 import android.view.View.OnDragListener;
     24 
     25 import com.android.documentsui.ItemDragListener;
     26 import com.android.documentsui.ItemDragListener.DragHost;
     27 import com.android.documentsui.ui.ViewAutoScroller;
     28 import com.android.documentsui.ui.ViewAutoScroller.ScrollActionDelegate;
     29 import com.android.documentsui.ui.ViewAutoScroller.ScrollDistanceDelegate;
     30 
     31 import java.util.function.BooleanSupplier;
     32 import java.util.function.IntSupplier;
     33 import java.util.function.Predicate;
     34 
     35 import javax.annotation.Nullable;
     36 
     37 /**
     38  * This class acts as a middle-man handler for potential auto-scrolling before passing the dragEvent
     39  * onto {@link DirectoryDragListener}.
     40  */
     41 class DragHoverListener implements OnDragListener {
     42 
     43     private final ItemDragListener<? extends DragHost> mDragHandler;
     44     private final IntSupplier mHeight;
     45     private final BooleanSupplier mCanScrollUp;
     46     private final BooleanSupplier mCanScrollDown;
     47     private final Runnable mDragScroller;
     48 
     49     /**
     50      * Predicate to tests whether it's the scroll view ({@link DirectoryFragment#mRecView}) itself.
     51      *
     52      * {@link DragHoverListener} is used for both {@link DirectoryFragment#mRecView} and its
     53      * children. When we decide whether it's in the scroll zone we need to obtain the coordinate
     54      * relative to {@link DirectoryFragment#mRecView} so we need to transform the coordinate if the
     55      * view that gets drag and drop events is a child of {@link DirectoryFragment#mRecView}.
     56      */
     57     private final Predicate<View> mIsScrollView;
     58 
     59     private boolean mDragHappening;
     60     private @Nullable Point mCurrentPosition;
     61 
     62     @VisibleForTesting
     63     DragHoverListener(
     64             ItemDragListener<? extends DragHost> dragHandler,
     65             IntSupplier heightSupplier,
     66             Predicate<View> isScrollView,
     67             BooleanSupplier scrollUpSupplier,
     68             BooleanSupplier scrollDownSupplier,
     69             ViewAutoScroller.ScrollActionDelegate actionDelegate) {
     70         mDragHandler = dragHandler;
     71         mHeight = heightSupplier;
     72         mIsScrollView = isScrollView;
     73         mCanScrollUp = scrollUpSupplier;
     74         mCanScrollDown = scrollDownSupplier;
     75 
     76         ScrollDistanceDelegate distanceDelegate = new ScrollDistanceDelegate() {
     77             @Override
     78             public Point getCurrentPosition() {
     79                 return mCurrentPosition;
     80             }
     81 
     82             @Override
     83             public int getViewHeight() {
     84                 return mHeight.getAsInt();
     85             }
     86 
     87             @Override
     88             public boolean isActive() {
     89                 return mDragHappening;
     90             }
     91         };
     92 
     93         mDragScroller = new ViewAutoScroller(distanceDelegate, actionDelegate);
     94     }
     95 
     96     static DragHoverListener create(
     97             ItemDragListener<? extends DragHost> dragHandler,
     98             View scrollView) {
     99         ScrollActionDelegate actionDelegate = new ScrollActionDelegate() {
    100             @Override
    101             public void scrollBy(int dy) {
    102                 scrollView.scrollBy(0, dy);
    103             }
    104 
    105             @Override
    106             public void runAtNextFrame(Runnable r) {
    107                 scrollView.postOnAnimation(r);
    108 
    109             }
    110 
    111             @Override
    112             public void removeCallback(Runnable r) {
    113                 scrollView.removeCallbacks(r);
    114             }
    115         };
    116         DragHoverListener listener = new DragHoverListener(
    117                 dragHandler,
    118                 scrollView::getHeight,
    119                 (view) -> (scrollView == view),
    120                 () -> scrollView.canScrollVertically(-1),
    121                 () -> scrollView.canScrollVertically(1),
    122                 actionDelegate);
    123         return listener;
    124     }
    125 
    126     @Override
    127     public boolean onDrag(View v, DragEvent event) {
    128         switch (event.getAction()) {
    129             case DragEvent.ACTION_DRAG_STARTED:
    130                 mDragHappening = true;
    131                 break;
    132             case DragEvent.ACTION_DRAG_ENDED:
    133                 mDragHappening = false;
    134                 break;
    135             case DragEvent.ACTION_DRAG_LOCATION:
    136                 handleLocationEvent(v, event.getX(), event.getY());
    137                 break;
    138             default:
    139                 break;
    140         }
    141 
    142         // Always forward events to the drag handler for item highlight, spring load, etc.
    143         return mDragHandler.onDrag(v, event);
    144     }
    145 
    146     private boolean handleLocationEvent(View v, float x, float y) {
    147         mCurrentPosition = transformToScrollViewCoordinate(v, x, y);
    148         if (insideDragZone()) {
    149             mDragScroller.run();
    150             return true;
    151         }
    152         return false;
    153     }
    154 
    155     private Point transformToScrollViewCoordinate(View v, float x, float y) {
    156         // Check if v is the RecyclerView itself. If not we need to transform the coordinate to
    157         // relative to the RecyclerView because we need to test the scroll zone in the coordinate
    158         // relative to the RecyclerView; if yes we don't need to transform coordinates.
    159         final boolean isScrollView = mIsScrollView.test(v);
    160         final float offsetX = isScrollView ? 0 : v.getX();
    161         final float offsetY = isScrollView ? 0 : v.getY();
    162         return new Point(Math.round(offsetX + x), Math.round(offsetY + y));
    163     }
    164 
    165     private boolean insideDragZone() {
    166         if (mCurrentPosition == null) {
    167             return false;
    168         }
    169 
    170         float topBottomRegionHeight = mHeight.getAsInt()
    171                 * ViewAutoScroller.TOP_BOTTOM_THRESHOLD_RATIO;
    172         boolean shouldScrollUp = mCurrentPosition.y < topBottomRegionHeight
    173                 && mCanScrollUp.getAsBoolean();
    174         boolean shouldScrollDown = mCurrentPosition.y > mHeight.getAsInt() - topBottomRegionHeight
    175                 && mCanScrollDown.getAsBoolean();
    176         return shouldScrollUp || shouldScrollDown;
    177     }
    178 }