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.selection.ViewAutoScroller;
     28 import com.android.documentsui.selection.ViewAutoScroller.ScrollHost;
     29 import com.android.documentsui.selection.ViewAutoScroller.ScrollerCallbacks;
     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.ScrollerCallbacks scrollCallbacks) {
     70 
     71         mDragHandler = dragHandler;
     72         mHeight = heightSupplier;
     73         mIsScrollView = isScrollView;
     74         mCanScrollUp = scrollUpSupplier;
     75         mCanScrollDown = scrollDownSupplier;
     76 
     77         ScrollHost scrollHost = new ScrollHost() {
     78             @Override
     79             public Point getCurrentPosition() {
     80                 return mCurrentPosition;
     81             }
     82 
     83             @Override
     84             public int getViewHeight() {
     85                 return mHeight.getAsInt();
     86             }
     87 
     88             @Override
     89             public boolean isActive() {
     90                 return mDragHappening;
     91             }
     92         };
     93 
     94         mDragScroller = new ViewAutoScroller(scrollHost, scrollCallbacks);
     95     }
     96 
     97     static DragHoverListener create(
     98             ItemDragListener<? extends DragHost> dragHandler,
     99             View scrollView) {
    100 
    101         ScrollerCallbacks scrollCallbacks = new ScrollerCallbacks() {
    102             @Override
    103             public void scrollBy(int dy) {
    104                 scrollView.scrollBy(0, dy);
    105             }
    106 
    107             @Override
    108             public void runAtNextFrame(Runnable r) {
    109                 scrollView.postOnAnimation(r);
    110 
    111             }
    112 
    113             @Override
    114             public void removeCallback(Runnable r) {
    115                 scrollView.removeCallbacks(r);
    116             }
    117         };
    118 
    119         DragHoverListener listener = new DragHoverListener(
    120                 dragHandler,
    121                 scrollView::getHeight,
    122                 (view) -> (scrollView == view),
    123                 () -> scrollView.canScrollVertically(-1),
    124                 () -> scrollView.canScrollVertically(1),
    125                 scrollCallbacks);
    126 
    127         return listener;
    128     }
    129 
    130     @Override
    131     public boolean onDrag(View v, DragEvent event) {
    132         switch (event.getAction()) {
    133             case DragEvent.ACTION_DRAG_STARTED:
    134                 mDragHappening = true;
    135                 break;
    136             case DragEvent.ACTION_DRAG_ENDED:
    137                 mDragHappening = false;
    138                 break;
    139             case DragEvent.ACTION_DRAG_LOCATION:
    140                 handleLocationEvent(v, event.getX(), event.getY());
    141                 break;
    142             default:
    143                 break;
    144         }
    145 
    146         // Always forward events to the drag handler for item highlight, spring load, etc.
    147         return mDragHandler.onDrag(v, event);
    148     }
    149 
    150     private boolean handleLocationEvent(View v, float x, float y) {
    151         mCurrentPosition = transformToScrollViewCoordinate(v, x, y);
    152         if (insideDragZone()) {
    153             mDragScroller.run();
    154             return true;
    155         }
    156         return false;
    157     }
    158 
    159     private Point transformToScrollViewCoordinate(View v, float x, float y) {
    160         // Check if v is the RecyclerView itself. If not we need to transform the coordinate to
    161         // relative to the RecyclerView because we need to test the scroll zone in the coordinate
    162         // relative to the RecyclerView; if yes we don't need to transform coordinates.
    163         final boolean isScrollView = mIsScrollView.test(v);
    164         final float offsetX = isScrollView ? 0 : v.getX();
    165         final float offsetY = isScrollView ? 0 : v.getY();
    166         return new Point(Math.round(offsetX + x), Math.round(offsetY + y));
    167     }
    168 
    169     private boolean insideDragZone() {
    170         if (mCurrentPosition == null) {
    171             return false;
    172         }
    173 
    174         float topBottomRegionHeight = mHeight.getAsInt()
    175                 * ViewAutoScroller.TOP_BOTTOM_THRESHOLD_RATIO;
    176         boolean shouldScrollUp = mCurrentPosition.y < topBottomRegionHeight
    177                 && mCanScrollUp.getAsBoolean();
    178         boolean shouldScrollDown = mCurrentPosition.y > mHeight.getAsInt() - topBottomRegionHeight
    179                 && mCanScrollDown.getAsBoolean();
    180         return shouldScrollUp || shouldScrollDown;
    181     }
    182 }