Android Open Source - Billy Activity Swipe Dismiss Listener






From Project

Back to project page Billy.

License

The source code is released under:

GNU General Public License

If you think the Android project Billy listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

package com.vibin.billy.swipeable;
/*from ww w . j a  v a2s .c  o  m*/
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.os.Handler;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewPropertyAnimator;

/**
 * Created by Saketme on 2/13/14.
 * Gesturelistener for closing an activity by swiping it to the left or right.
 */
public final class ActivitySwipeDismissListener extends SwipeListener
        implements View.OnTouchListener {

    // debug
    private String TAG = ActivitySwipeDismissListener.class.getSimpleName();

    // Cached stuff and system-wide constant values
    private ViewGroup mRootView;
    private float mDismissSlopPercent;

    // Transient properties
    private float mDownX;
    private float mDownTranslationX;                    // translationX of mRootView when ACTION_DOWN is received
    private boolean mSwiping;
    private boolean mFirstAnimation;
    private boolean mSwipeDisabled;
    private VelocityTracker mVelocityTracker;
    private final ViewPropertyAnimator mActivityAnimation;
    private ObjectAnimator mDrawerAnimator;             // for animating the activity's closing and opening

    // for ensuring that the user did not swipe back upwards at the last moment
    private float mLastRawX = -1;
    private boolean mSwipingToRight = false;

    // activity states
    public static final int STATE_DISMISSED = 0;
    public static final int STATE_NORMAL = 1;
    public static final int STATE_DRAGGING = 2;
    private int mActivityState = STATE_NORMAL;

    // gesture
    //private SwipeDirection getSwipeDirection() = SwipeDirection.RIGHT;

    // flags
    //private boolean mOngoingAnimation = false;

    /**
     * The callback interface used by {@link ActivitySwipeDismissListener}
     * to inform its client about a successful dismissal of the activity
     */
    public interface SwipeListener {

        /**
         * Called when the user has dismissed the activity
         */
        void onDismiss();

        /**
         * Called when the user is swiping the activity
         */
        void onSlide(float slideOffset);

    }

    private SwipeListener mListener;

    /**
     * Constructs a new swipe-to-dismiss touch listener for the given activity
     *
     * @param rootView           Root view of the activity which should be dismissable.
     * @param dismissSlopPercent Percentage of width after which the activity can be dismissed.
     *                           For example, a 0.5f slop would allow the activity to dismissed
     */
    public ActivitySwipeDismissListener(Context context, ViewGroup rootView, ViewConfiguration vc,
                                        float dismissSlopPercent, SwipeListener dismissCallback) {
        super(context, vc);

        //Log.i(TAG, "ActivitySwipeListener()");

        mDismissSlopPercent = dismissSlopPercent;
        mListener = dismissCallback;

        mRootView = rootView;
        mActivityAnimation = rootView.animate();
        rootView.setOnTouchListener(this);

    }

/* GETTERS & SETTERS */

    @Override
    public long getMaximumAnimDuration() {
        return 1400;    // ms
    }

    /**
     * Sets the directions in which the activity can be swiped to delete.
     * By default this is set to {@link SwipeDirection#RIGHT}.
     *
     * @param direction The direction to limit the swipe to.
     */
    public void setSwipeDirection(SwipeDirection direction) {
        super.setSwipeDirection(direction);
        mSwipingToRight = getSwipeDirection() == SwipeDirection.RIGHT;
    }

    /**
     * Enable/disable swipe.
     */
    public void setSwipeDisabled(boolean disabled) {
        this.mSwipeDisabled = disabled;
    }

    public boolean isActivityDismissed() {
        return mActivityState == STATE_DISMISSED;
    }

    public boolean isActivityBeingDragged() {
        return mActivityState == STATE_DRAGGING;
    }

    public boolean isActivityInNormalState() {
        return mActivityState == STATE_NORMAL;
    }

    public int getActivityState() {
        return mActivityState;
    }

/* TOUCH */

    @Override
    public boolean onTouch(View view, MotionEvent motionEvent) {

        if (mSwipeDisabled) return false;

        switch (motionEvent.getActionMasked()) {
            case MotionEvent.ACTION_DOWN: {

                mDownX = motionEvent.getRawX();
                mDownTranslationX = mRootView.getTranslationX();

                mVelocityTracker = VelocityTracker.obtain();
                mVelocityTracker.addMovement(motionEvent);

                // stop any ongoing animation as soon as the user places his finger
                stopOngoingAnimation();

                return true;
            }

            case MotionEvent.ACTION_UP:

                if (mVelocityTracker == null) {
                    break;
                }

                // distance moved
                float deltaX = motionEvent.getRawX() - mDownX;
                float deltaXAbs = Math.abs(deltaX);
                boolean openDrawerRight = deltaX > 0;

                // velocity of movement
                mVelocityTracker.addMovement(motionEvent);
                mVelocityTracker.computeCurrentVelocity(1000);
                float velocityXAbs = Math.abs(mVelocityTracker.getXVelocity());

                // in case the user, at the last moment, dropped his decision of
                // dismissing this activity
                boolean dismiss = mSwipingToRight == openDrawerRight
                        && (deltaXAbs > mRootView.getWidth() * mDismissSlopPercent
                        && mSwiping || MIN_FLING_VEL * 2 <= velocityXAbs
                        && velocityXAbs <= MAX_FLING_VEL && mSwiping
                        && deltaXAbs > FLING_DISTANCE);

                /*
                LONGER VERSION:

                if(mSwipingToRight != openDrawerRight){
                    dismiss = false;
                    //Log.w(TAG, "STOPP!");

                }else{

                    // dismiss it only if the view was being swiped
                    if (deltaXAbs > mRootView.getWidth() * mDismissSlopPercent && mSwiping) {

                        dismiss = true;

                        //Log.i(TAG, "Should be dismissed. dismissRight: " + dismissRight);

                    }

                    // or, flinged
                    else if (MIN_FLING_VEL *2 <= velocityXAbs && velocityXAbs <= MAX_FLING_VEL
                            && mSwiping && deltaXAbs > FLING_DISTANCE) {


                        // velocityYAbs*2 < velocityXAbs will be true because we've already
                        // verifying it in SwipeDismissViewGroup's onInterceptTouchEvent()

                    dismiss = true;

                    //Log.i(TAG, "Flinged. Right: " + dismissRight);

                    }

                }

                */

                // animation time
                if (dismiss)    // animate dismissal
                    animateActivityDismissal(deltaX);

                else            // reset positions
                    animateResettingOfActivity();

            case MotionEvent.ACTION_CANCEL:
                mVelocityTracker = null;
                mDownX = 0;
                mSwiping = false;
                mDownTranslationX = 0;
                manageLayers(mRootView, false);
                break;

            case MotionEvent.ACTION_MOVE: {

                if (mVelocityTracker == null) break;

                mVelocityTracker.addMovement(motionEvent);
                float x = motionEvent.getRawX();
                float deltaMoveX = x - mDownX;

                // no need to check against mSlop or diagonal movements. We're already doing that
                // while intercepting touch
                mSwiping = true;

                // for ensuring that the user was indeed swiping down before dismissing
                mSwipingToRight = !(motionEvent.getRawX() < mLastRawX && mLastRawX != -1);

                /*
                * LONGER VERSION:

                if (motionEvent.getRawX() < mLastRawX && mLastRawX != -1)
                    mSwipingToRight = false;
                else
                    mSwipingToRight = true;

                */

                // update after every few pixels movement
                if (Math.abs(motionEvent.getRawX() - mLastRawX) > DIRECTION_CHANGE_DISTANCE) {
                    mLastRawX = motionEvent.getRawX();
                }

                if (mSwiping) {

                    // set state
                    mActivityState = STATE_DRAGGING;

                    manageLayers(mRootView, true);
                    view.setTranslationX(translateWithinBounds(deltaMoveX));
                    manageLayers(mRootView, false);

                    // and callback
                    sendSlidingCallback();

                    return true;
                }

                break;
            }
        }
        return false;
    }

/* ANIMATION */

    private void animateActivityDismissal(float deltaX) {

        // this flag prevents the onAnimationEnd method in our listener to
        // be called twice. It usually happens whena a row is cleared
        // in the GridView
        mFirstAnimation = true;

        float distance = mRootView.getWidth() - mRootView.getTranslationX();
        long dynamicDuration = getDynamicDurationTime(mRootView, distance);
        boolean dismissRight = getSwipeDirection() == SwipeDirection.RIGHT;

        // for dismissing a view, the direction of fling/swipe should match the set
        // swipe direction
        if (deltaX > 0 != dismissRight) {
            return;
        }

        mDrawerAnimator = ObjectAnimator.ofFloat(
                mRootView,
                "translationX",
                mRootView.getTranslationX(),
                dismissRight ? mRootView.getWidth() : -mRootView.getWidth()
        );
        mDrawerAnimator.setStartDelay(0);
        mDrawerAnimator.setInterpolator(AnimationUtils.EASE_ACCELERATE_DEACELERATE_INTERPOLATOR);
//        mDrawerAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
        mDrawerAnimator.setDuration(dynamicDuration);
        mDrawerAnimator.addListener(new AnimatorListenerAdapter() {

            @Override
            public void onAnimationStart(Animator animation) {
                super.onAnimationStart(animation);

                /**
                 * Changing layer type to hardware for better animation.
                 * See doc of {@link SwipeListener#manageLayers(android.view.View, boolean)}
                 * for more info
                 * */
                manageLayers(mRootView, true);

                // set state
                mActivityState = STATE_DRAGGING;

            }

            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);

                // change layer type back to NONE when this animation is complete
                manageLayers(mRootView, false);

                // set state
                mActivityState = STATE_DISMISSED;

            }

        });
        mDrawerAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                sendSlidingCallback();
            }
        });
        mDrawerAnimator.start();

        // Android takes some time to close an Activity after finish() has been called.
        // As a workaround, we're calling finish beforehand so that this animation
        // and finish() happen around the same time.
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                //Log.i(TAG, "ACTION_UP -> onAnimationEnd");
                sendDismissCallback();
            }
        }, (long) (dynamicDuration * 0.6));

    }

    private void animateResettingOfActivity() {

        long dynamicDuration = getDynamicDurationTime(mRootView, mRootView.getTranslationX());

        mDrawerAnimator = ObjectAnimator.ofFloat(
                mRootView,
                "translationX",
                mRootView.getTranslationX(),
                0f
        );
        mDrawerAnimator.setStartDelay(0);
        mDrawerAnimator.setInterpolator(AnimationUtils.EASE_ACCELERATE_DEACELERATE_INTERPOLATOR);
//        mDrawerAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
        mDrawerAnimator.setDuration(dynamicDuration);
        mDrawerAnimator.addListener(new AnimatorListenerAdapter() {

            @Override
            public void onAnimationStart(Animator animation) {
                super.onAnimationStart(animation);

                /**
                 * Changing layer type to hardware for better animation. See doc of
                 * {@link ActivitySwipeDismissListener#manageLayers(mRootView, boolean)}
                 * for more info.
                 * */
                manageLayers(mRootView, true);

            }

            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);

                // change layer type back to NONE when this animation is complete
                manageLayers(mRootView, false);

                // set state
                mActivityState = STATE_NORMAL;

            }

        });
        mDrawerAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                sendSlidingCallback();
            }
        });
        mDrawerAnimator.start();

    }

    // immediately stops any on-going animation
    private void stopOngoingAnimation() {
        if (mDrawerAnimator != null) {
            mDrawerAnimator.removeAllUpdateListeners();
            mDrawerAnimator.cancel();
        }
    }

    public float translateWithinBounds(float deltaX) {

        float targetX = mDownTranslationX + deltaX;

        //Log.i(TAG, "Activity deltaX: " + deltaX + ", targetX: " + targetX);

        if (getSwipeDirection() == SwipeDirection.RIGHT) {
            if (targetX < 0)
                targetX = 0;
        } else if (getSwipeDirection() == SwipeDirection.LEFT) {
            if (targetX > 0)
                targetX = 0;
        }

        return targetX;
    }

/* CALLBACKS */

    private void sendDismissCallback() {
        // Fire the dismiss callback when the activity has been successfully swiped

        // dismiss this item
        mListener.onDismiss();
    }

    private void sendSlidingCallback() {

        int width = mRootView.getWidth();

        if (mListener == null || width == 0) return;

        float translationXAbs = Math.abs(mRootView.getTranslationX());
        float slideOffset = (translationXAbs / width);

        //Log.i(TAG, "slideOffset: " + slideOffset);

        //slideOffset = Math.min(1f, Math.max(0f, slideOffset));

        //Log.i(TAG, "Slide offset: " + slideOffset);
        mListener.onSlide(slideOffset);

    }

}




Java Source Code List

com.vibin.billy.BillyApplication.java
com.vibin.billy.BillyItem.java
com.vibin.billy.BitmapLruCache.java
com.vibin.billy.ChangelogDialog.java
com.vibin.billy.CustomBaseAdapter.java
com.vibin.billy.CustomDatabaseAdapter.java
com.vibin.billy.CustomFragmentAdapter.java
com.vibin.billy.CustomListPreference.java
com.vibin.billy.CustomShareActionProvider.java
com.vibin.billy.CustomStringRequest.java
com.vibin.billy.DetailView.java
com.vibin.billy.LicensesFragment.java
com.vibin.billy.MainActivity.java
com.vibin.billy.MediaControl.java
com.vibin.billy.NotifyingScrollView.java
com.vibin.billy.PPlayerService.java
com.vibin.billy.PlayerService.java
com.vibin.billy.ProcessingTask.java
com.vibin.billy.ReorderedListPreference.java
com.vibin.billy.Settings.java
com.vibin.billy.SongsFragment.java
com.vibin.billy.SwingBottomInAnimationAdapter.java
com.vibin.billy.draglistview.DynamicListView.java
com.vibin.billy.draglistview.StableArrayAdapter.java
com.vibin.billy.swipeable.ActivitySwipeDismissListener.java
com.vibin.billy.swipeable.AnimationUtils.java
com.vibin.billy.swipeable.SwipeDismissViewGroup.java
com.vibin.billy.swipeable.SwipeListener.java
com.vibin.billy.swipeable.SwipeableActivity.java
com.vibin.billy.swipeable.WindowDimens.java
com.vibin.billy.swipeable.WindowUtils.java
org.videolan.libvlc.AudioOutput.java
org.videolan.libvlc.EventHandler.java
org.videolan.libvlc.HWDecoderUtil.java
org.videolan.libvlc.IVideoPlayer.java
org.videolan.libvlc.LibVLC.java
org.videolan.libvlc.LibVlcException.java
org.videolan.libvlc.LibVlcUtil.java
org.videolan.libvlc.MediaList.java
org.videolan.libvlc.Media.java
org.videolan.libvlc.TrackInfo.java