/* * Copyright (C) 2017 Jakub Ksiezniak * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ package com.github.jksiezni.xpra.views; import android.content.Context; import android.support.v4.view.GestureDetectorCompat; import android.support.v4.view.ScaleGestureDetectorCompat; import android.util.AttributeSet; import android.util.Log; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.ScaleGestureDetector; import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewParent; import android.widget.FrameLayout; import android.widget.OverScroller; /** * Scrolling view. */ public class XYScrollView extends FrameLayout { private static final String TAG = XYScrollView.class.getSimpleName(); private final GestureDetectorCompat gestureDetector; private final ScaleGestureDetector scaleGestureDetector; private boolean mIsBeingDragged; private VelocityTracker mVelocityTracker; private OverScroller mScroller; private int mActivePointerId; private int mLastMotionX; private int mLastMotionY; private int mTouchSlop; public XYScrollView(Context context) { super(context); gestureDetector = new GestureDetectorCompat(context, gestureListener); scaleGestureDetector = new ScaleGestureDetector(context, scaleGestureListener); init(); } public XYScrollView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public XYScrollView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); gestureDetector = new GestureDetectorCompat(context, gestureListener); scaleGestureDetector = new ScaleGestureDetector(context, scaleGestureListener); init(); } private void init() { ScaleGestureDetectorCompat.setQuickScaleEnabled(scaleGestureDetector, true); final ViewConfiguration configuration = ViewConfiguration.get(getContext()); mTouchSlop = configuration.getScaledTouchSlop(); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { /* * Shortcut the most recurring case: the user is in the dragging * state and he is moving his finger. We want to intercept this * motion. */ final int action = ev.getAction(); if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) { return true; } /* * Don't try to intercept touch if we can't scroll anyway. */ if (getScrollY() == 0 && !canScrollVertically(1) && getScrollX() == 0 && !canScrollHorizontally(1)) { return true; } switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_MOVE: { /* * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check * whether the user has moved far enough from his original down touch. */ /* * Locally do absolute value. mLastMotionY is set to the y value * of the down event. */ final int activePointerId = mActivePointerId; if (activePointerId == MotionEvent.INVALID_POINTER_ID) { // If we don't have a valid id, the touch down wasn't on content. break; } final int pointerIndex = ev.findPointerIndex(activePointerId); if (pointerIndex == -1) { Log.e(TAG, "Invalid pointerId=" + activePointerId + " in onInterceptTouchEvent"); break; } final int y = (int) ev.getY(pointerIndex); final int yDiff = Math.abs(y - mLastMotionY); if (yDiff > mTouchSlop) { mIsBeingDragged = true; mLastMotionY = y; initOrResetVelocityTracker(); mVelocityTracker.addMovement(ev); final ViewParent parent = getParent(); if (parent != null) { parent.requestDisallowInterceptTouchEvent(true); } } break; } case MotionEvent.ACTION_DOWN: { final int x = (int) ev.getX(); final int y = (int) ev.getY(); if (!inChild(x, y)) { mIsBeingDragged = false; recycleVelocityTracker(); break; } /* * Remember location of down touch. * ACTION_DOWN always refers to pointer index 0. */ mLastMotionX = x; mLastMotionY = y; mActivePointerId = ev.getPointerId(0); initOrResetVelocityTracker(); mVelocityTracker.addMovement(ev); /* * If being flinged and user touches the screen, initiate drag; * otherwise don't. mScroller.isFinished should be false when * being flinged. */ mIsBeingDragged = !mScroller.isFinished(); //startNestedScroll(SCROLL_AXIS_VERTICAL | SCROLL_AXIS_HORIZONTAL); break; } case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: /* Release the drag */ mIsBeingDragged = false; mActivePointerId = MotionEvent.INVALID_POINTER_ID ; recycleVelocityTracker(); // if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange())) { // postInvalidateOnAnimation(); // } //stopNestedScroll(); break; case MotionEvent.ACTION_POINTER_UP: onSecondaryPointerUp(ev); break; } /* * The only time we want to intercept motion events is if we are in the * drag mode. */ return mIsBeingDragged; } private void onSecondaryPointerUp(MotionEvent ev) { final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; final int pointerId = ev.getPointerId(pointerIndex); if (pointerId == mActivePointerId) { // This was our active pointer going up. Choose a new // active pointer and adjust accordingly. // TODO: Make this decision more intelligent. final int newPointerIndex = pointerIndex == 0 ? 1 : 0; mLastMotionY = (int) ev.getY(newPointerIndex); mActivePointerId = ev.getPointerId(newPointerIndex); if (mVelocityTracker != null) { mVelocityTracker.clear(); } } } @Override public boolean onTouchEvent(MotionEvent event) { return gestureDetector.onTouchEvent(event) || scaleGestureDetector.onTouchEvent(event) || super.onTouchEvent(event); } public View getChildView() { return getChildAt(0); } private boolean inChild(int x, int y) { if (getChildCount() > 0) { final View child = getChildAt(0); final int scrollY = child.getScrollY(); return !(y < child.getTop() - scrollY || y >= child.getBottom() - scrollY || x < child.getLeft() || x >= child.getRight()); } return false; } private void initOrResetVelocityTracker() { if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } else { mVelocityTracker.clear(); } } private void recycleVelocityTracker() { if (mVelocityTracker != null) { mVelocityTracker.recycle(); mVelocityTracker = null; } } private final GestureDetector.SimpleOnGestureListener gestureListener = new GestureDetector.SimpleOnGestureListener() { @Override public boolean onDown(MotionEvent e) { return super.onDown(e); } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { Log.i(TAG, "distx: " + distanceX + ", distY: " + distanceY); scrollBy((int)distanceX, (int)distanceY); return super.onScroll(e1, e2, distanceX, distanceY); } }; private final ScaleGestureDetector.SimpleOnScaleGestureListener scaleGestureListener = new ScaleGestureDetector.SimpleOnScaleGestureListener() { float originScaleX; float originScaleY; @Override public boolean onScaleBegin(ScaleGestureDetector detector) { if (getChildView() == null) { return false; } originScaleX = getChildView().getScaleX(); originScaleY = getChildView().getScaleY(); return true; } @Override public boolean onScale(ScaleGestureDetector detector) { Log.i("ads", "onScale(): " + detector.getScaleFactor()); float scaleFactor = detector.getScaleFactor(); getChildView().setScaleX(originScaleX * scaleFactor); getChildView().setScaleY(originScaleY * scaleFactor); return false; } }; }