| /* |
| * 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.graphics.Point; |
| import androidx.annotation.VisibleForTesting; |
| import android.view.DragEvent; |
| import android.view.View; |
| import android.view.View.OnDragListener; |
| import android.widget.AbsListView; |
| |
| import com.android.documentsui.ItemDragListener.DragHost; |
| import com.android.documentsui.ViewAutoScroller.ScrollHost; |
| import com.android.documentsui.ViewAutoScroller.ScrollerCallbacks; |
| |
| import java.util.function.BooleanSupplier; |
| import java.util.function.IntConsumer; |
| import java.util.function.IntSupplier; |
| import java.util.function.Predicate; |
| |
| import javax.annotation.Nullable; |
| |
| /** |
| * This class acts as a middle-man handler for potential auto-scrolling before passing the dragEvent |
| * onto {@link ItemDragListener}. |
| */ |
| public class DragHoverListener implements OnDragListener { |
| |
| private final ItemDragListener<? extends DragHost> mDragHandler; |
| private final IntSupplier mHeight; |
| private final BooleanSupplier mCanScrollUp; |
| private final BooleanSupplier mCanScrollDown; |
| private final Runnable mDragScroller; |
| |
| /** |
| * Predicate to tests whether it's the scroll view itself. |
| * |
| * {@link DragHoverListener} is used for both the scroll view and its children. |
| * When we decide whether it's in the scroll zone we need to obtain the coordinate |
| * relative to container view so we need to transform the coordinate if the view |
| * that gets drag and drop events is a child of scroll view. |
| */ |
| private final Predicate<View> mIsScrollView; |
| |
| private boolean mDragHappening; |
| private @Nullable Point mCurrentPosition; |
| |
| @VisibleForTesting |
| DragHoverListener( |
| ItemDragListener<? extends DragHost> dragHandler, |
| IntSupplier heightSupplier, |
| Predicate<View> isScrollView, |
| BooleanSupplier scrollUpSupplier, |
| BooleanSupplier scrollDownSupplier, |
| ViewAutoScroller.ScrollerCallbacks scrollCallbacks) { |
| |
| mDragHandler = dragHandler; |
| mHeight = heightSupplier; |
| mIsScrollView = isScrollView; |
| mCanScrollUp = scrollUpSupplier; |
| mCanScrollDown = scrollDownSupplier; |
| |
| ScrollHost scrollHost = new ScrollHost() { |
| @Override |
| public Point getCurrentPosition() { |
| return mCurrentPosition; |
| } |
| |
| @Override |
| public int getViewHeight() { |
| return mHeight.getAsInt(); |
| } |
| |
| @Override |
| public boolean isActive() { |
| return mDragHappening; |
| } |
| }; |
| |
| mDragScroller = new ViewAutoScroller(scrollHost, scrollCallbacks); |
| } |
| |
| public static DragHoverListener create( |
| ItemDragListener<? extends DragHost> dragHandler, |
| AbsListView scrollView) { |
| return create(dragHandler, scrollView, scrollView::scrollListBy); |
| } |
| |
| public static DragHoverListener create( |
| ItemDragListener<? extends DragHost> dragHandler, |
| View scrollView) { |
| return create( |
| dragHandler, |
| scrollView, |
| (int dy) -> { |
| scrollView.scrollBy(0, dy); |
| }); |
| } |
| |
| static DragHoverListener create( |
| ItemDragListener<? extends DragHost> dragHandler, |
| View scrollView, |
| IntConsumer scroller) { |
| |
| ScrollerCallbacks scrollCallbacks = new ScrollerCallbacks() { |
| @Override |
| public void scrollBy(int dy) { |
| scroller.accept(dy); |
| } |
| |
| @Override |
| public void runAtNextFrame(Runnable r) { |
| scrollView.postOnAnimation(r); |
| } |
| |
| @Override |
| public void removeCallback(Runnable r) { |
| scrollView.removeCallbacks(r); |
| } |
| }; |
| |
| return new DragHoverListener( |
| dragHandler, |
| scrollView::getHeight, |
| (view) -> (scrollView == view), |
| () -> scrollView.canScrollVertically(-1), |
| () -> scrollView.canScrollVertically(1), |
| scrollCallbacks); |
| } |
| |
| @Override |
| public boolean onDrag(View v, DragEvent event) { |
| switch (event.getAction()) { |
| case DragEvent.ACTION_DRAG_STARTED: |
| mDragHappening = true; |
| break; |
| case DragEvent.ACTION_DRAG_ENDED: |
| mDragHappening = false; |
| break; |
| case DragEvent.ACTION_DRAG_LOCATION: |
| handleLocationEvent(v, event.getX(), event.getY()); |
| break; |
| default: |
| break; |
| } |
| |
| // Always forward events to the drag handler for item highlight, spring load, etc. |
| return mDragHandler.onDrag(v, event); |
| } |
| |
| private boolean handleLocationEvent(View v, float x, float y) { |
| mCurrentPosition = transformToScrollViewCoordinate(v, x, y); |
| if (insideDragZone()) { |
| mDragScroller.run(); |
| return true; |
| } |
| return false; |
| } |
| |
| private Point transformToScrollViewCoordinate(View v, float x, float y) { |
| // Check if v is the scroll view itself. If not we need to transform the coordinate to |
| // relative to the scroll view because we need to test the scroll zone in the coordinate |
| // relative to the scroll view; if yes we don't need to transform coordinates. |
| final boolean isScrollView = mIsScrollView.test(v); |
| final float offsetX = isScrollView ? 0 : v.getX(); |
| final float offsetY = isScrollView ? 0 : v.getY(); |
| return new Point(Math.round(offsetX + x), Math.round(offsetY + y)); |
| } |
| |
| private boolean insideDragZone() { |
| if (mCurrentPosition == null) { |
| return false; |
| } |
| |
| float topBottomRegionHeight = mHeight.getAsInt() |
| * ViewAutoScroller.TOP_BOTTOM_THRESHOLD_RATIO; |
| boolean shouldScrollUp = mCurrentPosition.y < topBottomRegionHeight |
| && mCanScrollUp.getAsBoolean(); |
| boolean shouldScrollDown = mCurrentPosition.y > mHeight.getAsInt() - topBottomRegionHeight |
| && mCanScrollDown.getAsBoolean(); |
| return shouldScrollUp || shouldScrollDown; |
| } |
| } |