Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d85bc507 authored by Keisuke Kuroyanagi's avatar Keisuke Kuroyanagi
Browse files

Make scroll bar mouse draggable.

This is for Views that have special mouse dragging handling. e.g.
TextView invokes text selection on mouse dragging, so it cannot be
scrolled by mouse dragging.
This provides such Views or ViewGroups a last resort to scroll as we
don't assume that all devices have touch panel, touch pad, or mouse
wheel.

Bug: 20016455

Change-Id: I68a13258a50b5e4ea681b2576da6000a0bb3fa65
parent 1ee3e370
Loading
Loading
Loading
Loading
+259 −59
Original line number Diff line number Diff line
@@ -98,13 +98,13 @@ import android.view.inputmethod.InputMethodManager;
import android.widget.Checkable;
import android.widget.FrameLayout;
import android.widget.ScrollBarDrawable;
import static android.os.Build.VERSION_CODES.*;
import static java.lang.Math.max;
import com.android.internal.R;
import com.android.internal.util.Predicate;
import com.android.internal.view.menu.MenuBuilder;
import com.android.internal.widget.ScrollBarUtils;
import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
@@ -5110,6 +5110,88 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
        return mVerticalScrollbarPosition;
    }
    boolean isOnScrollbar(float x, float y) {
        if (mScrollCache == null) {
            return false;
        }
        x += getScrollX();
        y += getScrollY();
        if (isVerticalScrollBarEnabled() && !isVerticalScrollBarHidden()) {
            final Rect bounds = mScrollCache.mScrollBarBounds;
            getVerticalScrollBarBounds(bounds);
            if (bounds.contains((int)x, (int)y)) {
                return true;
            }
        }
        if (isHorizontalScrollBarEnabled()) {
            final Rect bounds = mScrollCache.mScrollBarBounds;
            getHorizontalScrollBarBounds(bounds);
            if (bounds.contains((int)x, (int)y)) {
                return true;
            }
        }
        return false;
    }
    boolean isOnScrollbarThumb(float x, float y) {
        return isOnVerticalScrollbarThumb(x, y) || isOnHorizontalScrollbarThumb(x, y);
    }
    private boolean isOnVerticalScrollbarThumb(float x, float y) {
        if (mScrollCache == null) {
            return false;
        }
        if (isVerticalScrollBarEnabled() && !isVerticalScrollBarHidden()) {
            x += getScrollX();
            y += getScrollY();
            final Rect bounds = mScrollCache.mScrollBarBounds;
            getVerticalScrollBarBounds(bounds);
            final int range = computeVerticalScrollRange();
            final int offset = computeVerticalScrollOffset();
            final int extent = computeVerticalScrollExtent();
            final int thumbLength = ScrollBarUtils.getThumbLength(bounds.height(), bounds.width(),
                    extent, range);
            final int thumbOffset = ScrollBarUtils.getThumbOffset(bounds.height(), thumbLength,
                    extent, range, offset);
            final int thumbTop = bounds.top + thumbOffset;
            if (x >= bounds.left && x <= bounds.right && y >= thumbTop
                    && y <= thumbTop + thumbLength) {
                return true;
            }
        }
        return false;
    }
    private boolean isOnHorizontalScrollbarThumb(float x, float y) {
        if (mScrollCache == null) {
            return false;
        }
        if (isHorizontalScrollBarEnabled()) {
            x += getScrollX();
            y += getScrollY();
            final Rect bounds = mScrollCache.mScrollBarBounds;
            getHorizontalScrollBarBounds(bounds);
            final int range = computeHorizontalScrollRange();
            final int offset = computeHorizontalScrollOffset();
            final int extent = computeHorizontalScrollExtent();
            final int thumbLength = ScrollBarUtils.getThumbLength(bounds.width(), bounds.height(),
                    extent, range);
            final int thumbOffset = ScrollBarUtils.getThumbOffset(bounds.width(), thumbLength,
                    extent, range, offset);
            final int thumbLeft = bounds.left + thumbOffset;
            if (x >= thumbLeft && x <= thumbLeft + thumbLength && y >= bounds.top
                    && y <= bounds.bottom) {
                return true;
            }
        }
        return false;
    }
    boolean isDraggingScrollBar() {
        return mScrollCache != null
                && mScrollCache.mScrollBarDraggingState != ScrollabilityCache.NOT_DRAGGING;
    }
    /**
     * Sets the state of all scroll indicators.
     * <p>
@@ -9586,6 +9668,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
        }
        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
@@ -10450,6 +10535,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
            }
        }
        if ((action == MotionEvent.ACTION_HOVER_ENTER || action == MotionEvent.ACTION_HOVER_MOVE)
                && event.isFromSource(InputDevice.SOURCE_MOUSE)
                && isOnScrollbar(event.getX(), event.getY())) {
            awakenScrollBars();
        }
        if (isHoverable()) {
            switch (action) {
                case MotionEvent.ACTION_HOVER_ENTER:
@@ -10552,6 +10642,110 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
    public void onHoverChanged(boolean hovered) {
    }
    /**
     * Handles scroll bar dragging by mouse input.
     *
     * @hide
     * @param event The motion event.
     *
     * @return true if the event was handled as a scroll bar dragging, false otherwise.
     */
    protected boolean handleScrollBarDragging(MotionEvent event) {
        if (mScrollCache == null) {
            return false;
        }
        final float x = event.getX();
        final float y = event.getY();
        final int action = event.getAction();
        if ((mScrollCache.mScrollBarDraggingState == ScrollabilityCache.NOT_DRAGGING
                && action != MotionEvent.ACTION_DOWN)
                    || !event.isFromSource(InputDevice.SOURCE_MOUSE)
                    || !event.isButtonPressed(MotionEvent.BUTTON_PRIMARY)) {
            mScrollCache.mScrollBarDraggingState = ScrollabilityCache.NOT_DRAGGING;
            return false;
        }
        switch (action) {
            case MotionEvent.ACTION_MOVE:
                if (mScrollCache.mScrollBarDraggingState == ScrollabilityCache.NOT_DRAGGING) {
                    return false;
                }
                if (mScrollCache.mScrollBarDraggingState
                        == ScrollabilityCache.DRAGGING_VERTICAL_SCROLL_BAR) {
                    final Rect bounds = mScrollCache.mScrollBarBounds;
                    getVerticalScrollBarBounds(bounds);
                    final int range = computeVerticalScrollRange();
                    final int offset = computeVerticalScrollOffset();
                    final int extent = computeVerticalScrollExtent();
                    final int thumbLength = ScrollBarUtils.getThumbLength(
                            bounds.height(), bounds.width(), extent, range);
                    final int thumbOffset = ScrollBarUtils.getThumbOffset(
                            bounds.height(), thumbLength, extent, range, offset);
                    final float diff = y - mScrollCache.mScrollBarDraggingPos;
                    final float maxThumbOffset = bounds.height() - thumbLength;
                    final float newThumbOffset =
                            Math.min(Math.max(thumbOffset + diff, 0.0f), maxThumbOffset);
                    final int height = getHeight();
                    if (Math.round(newThumbOffset) != thumbOffset && maxThumbOffset > 0
                            && height > 0 && extent > 0) {
                        final int newY = Math.round((range - extent)
                                / ((float)extent / height) * (newThumbOffset / maxThumbOffset));
                        if (newY != getScrollY()) {
                            mScrollCache.mScrollBarDraggingPos = y;
                            setScrollY(newY);
                        }
                    }
                    return true;
                }
                if (mScrollCache.mScrollBarDraggingState
                        == ScrollabilityCache.DRAGGING_HORIZONTAL_SCROLL_BAR) {
                    final Rect bounds = mScrollCache.mScrollBarBounds;
                    getHorizontalScrollBarBounds(bounds);
                    final int range = computeHorizontalScrollRange();
                    final int offset = computeHorizontalScrollOffset();
                    final int extent = computeHorizontalScrollExtent();
                    final int thumbLength = ScrollBarUtils.getThumbLength(
                            bounds.width(), bounds.height(), extent, range);
                    final int thumbOffset = ScrollBarUtils.getThumbOffset(
                            bounds.width(), thumbLength, extent, range, offset);
                    final float diff = x - mScrollCache.mScrollBarDraggingPos;
                    final float maxThumbOffset = bounds.width() - thumbLength;
                    final float newThumbOffset =
                            Math.min(Math.max(thumbOffset + diff, 0.0f), maxThumbOffset);
                    final int width = getWidth();
                    if (Math.round(newThumbOffset) != thumbOffset && maxThumbOffset > 0
                            && width > 0 && extent > 0) {
                        final int newX = Math.round((range - extent)
                                / ((float)extent / width) * (newThumbOffset / maxThumbOffset));
                        if (newX != getScrollX()) {
                            mScrollCache.mScrollBarDraggingPos = x;
                            setScrollX(newX);
                        }
                    }
                    return true;
                }
            case MotionEvent.ACTION_DOWN:
                if (isOnVerticalScrollbarThumb(x, y)) {
                    mScrollCache.mScrollBarDraggingState =
                            ScrollabilityCache.DRAGGING_VERTICAL_SCROLL_BAR;
                    mScrollCache.mScrollBarDraggingPos = y;
                    return true;
                }
                if (isOnHorizontalScrollbarThumb(x, y)) {
                    mScrollCache.mScrollBarDraggingState =
                            ScrollabilityCache.DRAGGING_HORIZONTAL_SCROLL_BAR;
                    mScrollCache.mScrollBarDraggingPos = x;
                    return true;
                }
        }
        mScrollCache.mScrollBarDraggingState = ScrollabilityCache.NOT_DRAGGING;
        return false;
    }
    /**
     * Implement this method to handle touch screen motion events.
     * <p>
@@ -10585,7 +10779,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
                    || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                    || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
        }
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
@@ -14146,6 +14339,45 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
        }
    }
    private void getHorizontalScrollBarBounds(Rect bounds) {
        final int inside = (mViewFlags & SCROLLBARS_OUTSIDE_MASK) == 0 ? ~0 : 0;
        final boolean drawVerticalScrollBar = isVerticalScrollBarEnabled()
                && !isVerticalScrollBarHidden();
        final int size = getHorizontalScrollbarHeight();
        final int verticalScrollBarGap = drawVerticalScrollBar ?
                getVerticalScrollbarWidth() : 0;
        final int width = mRight - mLeft;
        final int height = mBottom - mTop;
        bounds.top = mScrollY + height - size - (mUserPaddingBottom & inside);
        bounds.left = mScrollX + (mPaddingLeft & inside);
        bounds.right = mScrollX + width - (mUserPaddingRight & inside) - verticalScrollBarGap;
        bounds.bottom = bounds.top + size;
    }
    private void getVerticalScrollBarBounds(Rect bounds) {
        final int inside = (mViewFlags & SCROLLBARS_OUTSIDE_MASK) == 0 ? ~0 : 0;
        final int size = getVerticalScrollbarWidth();
        int verticalScrollbarPosition = mVerticalScrollbarPosition;
        if (verticalScrollbarPosition == SCROLLBAR_POSITION_DEFAULT) {
            verticalScrollbarPosition = isLayoutRtl() ?
                    SCROLLBAR_POSITION_LEFT : SCROLLBAR_POSITION_RIGHT;
        }
        final int width = mRight - mLeft;
        final int height = mBottom - mTop;
        switch (verticalScrollbarPosition) {
            default:
            case SCROLLBAR_POSITION_RIGHT:
                bounds.left = mScrollX + width - size - (mUserPaddingRight & inside);
                break;
            case SCROLLBAR_POSITION_LEFT:
                bounds.left = mScrollX + (mUserPaddingLeft & inside);
                break;
        }
        bounds.top = mScrollY + (mPaddingTop & inside);
        bounds.right = bounds.left + size;
        bounds.bottom = mScrollY + height - (mUserPaddingBottom & inside);
    }
    /**
     * <p>Request the drawing of the horizontal and the vertical scrollbar. The
     * scrollbars are painted only if they have been awakened first.</p>
@@ -14193,80 +14425,36 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
                cache.scrollBar.mutate().setAlpha(255);
            }
            final int viewFlags = mViewFlags;
            final boolean drawHorizontalScrollBar =
                (viewFlags & SCROLLBARS_HORIZONTAL) == SCROLLBARS_HORIZONTAL;
            final boolean drawVerticalScrollBar =
                (viewFlags & SCROLLBARS_VERTICAL) == SCROLLBARS_VERTICAL
            final boolean drawHorizontalScrollBar = isHorizontalScrollBarEnabled();
            final boolean drawVerticalScrollBar = isVerticalScrollBarEnabled()
                    && !isVerticalScrollBarHidden();
            if (drawVerticalScrollBar || drawHorizontalScrollBar) {
                final int width = mRight - mLeft;
                final int height = mBottom - mTop;
                final ScrollBarDrawable scrollBar = cache.scrollBar;
                final int scrollX = mScrollX;
                final int scrollY = mScrollY;
                final int inside = (viewFlags & SCROLLBARS_OUTSIDE_MASK) == 0 ? ~0 : 0;
                int left;
                int top;
                int right;
                int bottom;
                if (drawHorizontalScrollBar) {
                    int size = scrollBar.getSize(false);
                    if (size <= 0) {
                        size = cache.scrollBarSize;
                    }
                    scrollBar.setParameters(computeHorizontalScrollRange(),
                                            computeHorizontalScrollOffset(),
                                            computeHorizontalScrollExtent(), false);
                    final int verticalScrollBarGap = drawVerticalScrollBar ?
                            getVerticalScrollbarWidth() : 0;
                    top = scrollY + height - size - (mUserPaddingBottom & inside);
                    left = scrollX + (mPaddingLeft & inside);
                    right = scrollX + width - (mUserPaddingRight & inside) - verticalScrollBarGap;
                    bottom = top + size;
                    onDrawHorizontalScrollBar(canvas, scrollBar, left, top, right, bottom);
                    final Rect bounds = cache.mScrollBarBounds;
                    getHorizontalScrollBarBounds(bounds);
                    onDrawHorizontalScrollBar(canvas, scrollBar, bounds.left, bounds.top,
                            bounds.right, bounds.bottom);
                    if (invalidate) {
                        invalidate(left, top, right, bottom);
                        invalidate(bounds);
                    }
                }
                if (drawVerticalScrollBar) {
                    int size = scrollBar.getSize(true);
                    if (size <= 0) {
                        size = cache.scrollBarSize;
                    }
                    scrollBar.setParameters(computeVerticalScrollRange(),
                                            computeVerticalScrollOffset(),
                                            computeVerticalScrollExtent(), true);
                    int verticalScrollbarPosition = mVerticalScrollbarPosition;
                    if (verticalScrollbarPosition == SCROLLBAR_POSITION_DEFAULT) {
                        verticalScrollbarPosition = isLayoutRtl() ?
                                SCROLLBAR_POSITION_LEFT : SCROLLBAR_POSITION_RIGHT;
                    }
                    switch (verticalScrollbarPosition) {
                        default:
                        case SCROLLBAR_POSITION_RIGHT:
                            left = scrollX + width - size - (mUserPaddingRight & inside);
                            break;
                        case SCROLLBAR_POSITION_LEFT:
                            left = scrollX + (mUserPaddingLeft & inside);
                            break;
                    }
                    top = scrollY + (mPaddingTop & inside);
                    right = left + size;
                    bottom = scrollY + height - (mUserPaddingBottom & inside);
                    onDrawVerticalScrollBar(canvas, scrollBar, left, top, right, bottom);
                    final Rect bounds = cache.mScrollBarBounds;
                    getVerticalScrollBarBounds(bounds);
                    onDrawVerticalScrollBar(canvas, scrollBar, bounds.left, bounds.top,
                            bounds.right, bounds.bottom);
                    if (invalidate) {
                        invalidate(left, top, right, bottom);
                        invalidate(bounds);
                    }
                }
            }
@@ -21201,6 +21389,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
     * @see PointerIcon
     */
    public PointerIcon getPointerIcon(MotionEvent event, float x, float y) {
        if (isDraggingScrollBar() || isOnScrollbarThumb(x, y)) {
            return PointerIcon.getSystemIcon(mContext, PointerIcon.STYLE_ARROW);
        }
        return mPointerIcon;
    }
@@ -22457,6 +22648,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
        private int mLastColor;
        public final Rect mScrollBarBounds = new Rect();
        public static final int NOT_DRAGGING = 0;
        public static final int DRAGGING_VERTICAL_SCROLL_BAR = 1;
        public static final int DRAGGING_HORIZONTAL_SCROLL_BAR = 2;
        public int mScrollBarDraggingState = NOT_DRAGGING;
        public float mScrollBarDraggingPos = 0;
        public ScrollabilityCache(ViewConfiguration configuration, View host) {
            fadingEdgeLength = configuration.getScaledFadingEdgeLength();
            scrollBarSize = configuration.getScaledScrollBarSize();
+20 −1
Original line number Diff line number Diff line
@@ -59,6 +59,7 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;

/**
@@ -1717,6 +1718,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager

    @Override
    public PointerIcon getPointerIcon(MotionEvent event, float x, float y) {
        if (isOnScrollbarThumb(x, y) || isDraggingScrollBar()) {
            return PointerIcon.getSystemIcon(mContext, PointerIcon.STYLE_ARROW);
        }
        // Check what the child under the pointer says about the pointer.
        final int childrenCount = mChildrenCount;
        if (childrenCount != 0) {
@@ -2027,7 +2031,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
     * hover exit event in {@link #onHoverEvent} and then the hovered child will
     * receive a hover enter event.
     * </p><p>
     * The default implementation always returns false.
     * The default implementation handles mouse hover on the scroll bars.
     * </p>
     *
     * @param event The motion event that describes the hover.
@@ -2035,6 +2039,15 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
     * and prevent its children from receiving it.
     */
    public boolean onInterceptHoverEvent(MotionEvent event) {
        if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
            final int action = event.getAction();
            final float x = event.getX();
            final float y = event.getY();
            if ((action == MotionEvent.ACTION_HOVER_MOVE
                    || action == MotionEvent.ACTION_HOVER_ENTER) && isOnScrollbar(x, y)) {
                return true;
            }
        }
        return false;
    }

@@ -2781,6 +2794,12 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
     * messages will be delivered here.
     */
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
                && ev.getAction() == MotionEvent.ACTION_DOWN
                && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
                && isOnScrollbarThumb(ev.getX(), ev.getY())) {
            return true;
        }
        return false;
    }

+7 −0
Original line number Diff line number Diff line
@@ -3669,6 +3669,13 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
        }
    }

    /** @hide */
    @Override
    protected boolean handleScrollBarDragging(MotionEvent event) {
        // Doesn't support normal scroll bar dragging. Use FastScroller.
        return false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (!isEnabled()) {
+4 −0
Original line number Diff line number Diff line
@@ -454,6 +454,10 @@ public class HorizontalScrollView extends FrameLayout {
            return true;
        }

        if (super.onInterceptTouchEvent(ev)) {
            return true;
        }

        switch (action & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_MOVE: {
                /*
+9 −15
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package android.widget;

import com.android.internal.widget.ScrollBarUtils;

import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.PixelFormat;
@@ -135,23 +137,15 @@ public class ScrollBarDrawable extends Drawable implements Drawable.Callback {
        }

        if (drawThumb) {
            final int size = vertical ? r.height() : r.width();
            final int scrollBarLength = vertical ? r.height() : r.width();
            final int thickness = vertical ? r.width() : r.height();
            final int minLength = thickness * 2;

            // Avoid the tiny thumb.
            int length = Math.round((float) size * extent / range);
            if (length < minLength) {
                length = minLength;
            }

            // Avoid the too-big thumb.
            int offset = Math.round((float) (size - length) * mOffset / (range - extent));
            if (offset > size - length) {
                offset = size - length;
            }
            final int thumbLength =
                    ScrollBarUtils.getThumbLength(scrollBarLength, thickness, extent, range);
            final int thumbOffset =
                    ScrollBarUtils.getThumbOffset(scrollBarLength, thumbLength, extent, range,
                            mOffset);

            drawThumb(canvas, r, offset, length, vertical);
            drawThumb(canvas, r, thumbOffset, thumbLength, vertical);
        }
    }

Loading