package com.adafruit.bluefruit.le.connect.ui.utils;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.FrameLayout;
import android.widget.OverScroller;
// from: https://github.com/devunwired/custom-touch-examples/blob/master/TouchExamples/src/com/examples/customtouch/widget/TwoDimensionScrollView.java
public class TwoDimensionScrollView extends FrameLayout {
//Fling components
private OverScroller mScroller;
private VelocityTracker mVelocityTracker;
/* Positions of the last motion event */
private float mLastTouchX, mLastTouchY;
/* Drag threshold */
private int mTouchSlop;
/* Fling Velocity */
private int mMaximumVelocity, mMinimumVelocity;
/* Drag Lock */
private boolean mDragging = false;
public TwoDimensionScrollView(Context context) {
super(context);
init(context);
}
public TwoDimensionScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public TwoDimensionScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
private void init(Context context) {
mScroller = new OverScroller(context);
mVelocityTracker = VelocityTracker.obtain();
//Get system constants for touch thresholds
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
mMaximumVelocity = ViewConfiguration.get(context).getScaledMaximumFlingVelocity();
mMinimumVelocity = ViewConfiguration.get(context).getScaledMinimumFlingVelocity();
}
/*
* Override the measureChild... implementations to guarantee that the child view
* gets measured to be as large as it wants to be. The default implementation will
* force some children to be only as large as this view.
*/
@Override
protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
int childWidthMeasureSpec;
int childHeightMeasureSpec;
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
@Override
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
lp.leftMargin + lp.rightMargin, MeasureSpec.UNSPECIFIED);
final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
lp.topMargin + lp.bottomMargin, MeasureSpec.UNSPECIFIED);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
// This is called at drawing time by ViewGroup. We use
// this method to keep the fling animation going through
// to completion.
int oldX = getScrollX();
int oldY = getScrollY();
int x = mScroller.getCurrX();
int y = mScroller.getCurrY();
if (getChildCount() > 0) {
View child = getChildAt(0);
x = clamp(x, getWidth() - getPaddingRight() - getPaddingLeft(), child.getWidth());
y = clamp(y, getHeight() - getPaddingBottom() - getPaddingTop(), child.getHeight());
if (x != oldX || y != oldY) {
scrollTo(x, y);
}
}
// Keep on drawing until the animation has finished.
postInvalidate();
}
}
//Override scrollTo to do bounds checks on any scrolling request
@Override
public void scrollTo(int x, int y) {
// we rely on the fact the View.scrollBy calls scrollTo.
if (getChildCount() > 0) {
View child = getChildAt(0);
x = clamp(x, getWidth() - getPaddingRight() - getPaddingLeft(), child.getWidth());
y = clamp(y, getHeight() - getPaddingBottom() - getPaddingTop(), child.getHeight());
if (x != getScrollX() || y != getScrollY()) {
super.scrollTo(x, y);
}
}
}
/*
* Utility method to initialize the Scroller and start redrawing
*/
public void fling(int velocityX, int velocityY) {
if (getChildCount() > 0) {
int height = getHeight() - getPaddingBottom() - getPaddingTop();
int width = getWidth() - getPaddingLeft() - getPaddingRight();
int bottom = getChildAt(0).getHeight();
int right = getChildAt(0).getWidth();
mScroller.fling(getScrollX(), getScrollY(), velocityX, velocityY,
0, Math.max(0, right - width),
0, Math.max(0, bottom - height));
invalidate();
}
}
/*
* Utility method to assist in doing bounds checking
*/
private int clamp(int n, int my, int child) {
if (my >= child || n < 0) {
/* my >= child is this case:
* |--------------- me ---------------|
* |------ child ------|
* or
* |--------------- me ---------------|
* |------ child ------|
* or
* |--------------- me ---------------|
* |------ child ------|
*
* n < 0 is this case:
* |------ me ------|
* |-------- child --------|
* |-- mScrollX --|
*/
return 0;
}
if ((my + n) > child) {
/* this case:
* |------ me ------|
* |------ child ------|
* |-- mScrollX --|
*/
return child - my;
}
return n;
}
/*
* Monitor touch events passed down to the children and
* intercept as soon as it is determined we are dragging. This
* allows child views to still receive touch events if they are
* interactive (i.e. Buttons)
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//Stop any flinging in progress
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
//Reset the velocity tracker
mVelocityTracker.clear();
mVelocityTracker.addMovement(event);
//Save the initial touch point
mLastTouchX = event.getX();
mLastTouchY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
final float x = event.getX();
final float y = event.getY();
final int yDiff = (int) Math.abs(y - mLastTouchY);
final int xDiff = (int) Math.abs(x - mLastTouchX);
//Verify that either difference is enough to be a drag
if (yDiff > mTouchSlop || xDiff > mTouchSlop) {
mDragging = true;
mVelocityTracker.addMovement(event);
//Start capturing events ourselves
return true;
}
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
mDragging = false;
mVelocityTracker.clear();
break;
}
return super.onInterceptTouchEvent(event);
}
/*
* Feed all touch events we receive to the detector for
* processing.
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
mVelocityTracker.addMovement(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// We've already stored the initial point,
// but if we got here a child view didn't capture
// the event, so we need to.
return true;
case MotionEvent.ACTION_MOVE:
final float x = event.getX();
final float y = event.getY();
float deltaY = mLastTouchY - y;
float deltaX = mLastTouchX - x;
//Check for slop on direct events
if (!mDragging && (Math.abs(deltaY) > mTouchSlop || Math.abs(deltaX) > mTouchSlop)) {
mDragging = true;
}
if (mDragging) {
//Scroll the view
scrollBy((int) deltaX, (int) deltaY);
//Update the last touch event
mLastTouchX = x;
mLastTouchY = y;
}
break;
case MotionEvent.ACTION_CANCEL:
mDragging = false;
//Stop any flinging in progress
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
break;
case MotionEvent.ACTION_UP:
mDragging = false;
// Compute the current velocity and start a fling if it is above
// the minimum threshold.
mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int velocityX = (int) mVelocityTracker.getXVelocity();
int velocityY = (int) mVelocityTracker.getYVelocity();
if (Math.abs(velocityX) > mMinimumVelocity || Math.abs(velocityY) > mMinimumVelocity) {
fling(-velocityX, -velocityY);
}
break;
}
return super.onTouchEvent(event);
}
}