Android UI How to - Track user gesture velocity








The following code shows how to Track user gesture velocity.

Code revised from
Android Recipes:A Problem-Solution Approach
http://www.apress.com/9781430234135
ISBN13: 978-1-4302-3413-5

Example

//  w  ww  .ja  v a2s. com

package com.java2s.myapplication3.app;

import android.app.Activity;
import android.os.Bundle;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
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.Scroller;

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        PanScrollView scrollView = new PanScrollView(this);

        LinearLayout layout = new LinearLayout(this);
        layout.setOrientation(LinearLayout.VERTICAL);
        for(int i=0; i < 5; i++) {
            ImageView iv = new ImageButton(this);
            iv.setImageResource(R.drawable.ic_launcher);
            layout.addView(iv, new LinearLayout.LayoutParams(1000, 500));
        }

        scrollView.addView(layout);
        setContentView(scrollView);
    }
}

class PanScrollView extends FrameLayout {

    // Fling components
    private Scroller 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 PanScrollView(Context context) {
        super(context);
        init(context);
    }

    public PanScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public PanScrollView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context);
    }

    private void init(Context context) {
        mScroller = new Scroller(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);
            }
        }
    }

    /*
     * 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);
    }

    /*
     * 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;
    }
}
null