com.github.shareme.gwsmaterialuikit.library.advancerv.touchguard.RecyclerViewTouchActionGuardManager.java Source code

Java tutorial

Introduction

Here is the source code for com.github.shareme.gwsmaterialuikit.library.advancerv.touchguard.RecyclerViewTouchActionGuardManager.java

Source

/*
 *    Copyright (C) 2015 Haruki Hasegawa
 *    Modifications Copyright(C) 2016 Fred Grott(GrottWorkShop)
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package com.github.shareme.gwsmaterialuikit.library.advancerv.touchguard;

import android.support.annotation.NonNull;
import android.support.v4.view.MotionEventCompat;
import android.support.v7.widget.RecyclerView;
import android.view.MotionEvent;
import android.view.ViewConfiguration;

import timber.log.Timber;

/**
 * Hooks touch events to avoid unexpected scrolling.
 */
@SuppressWarnings("unused")
public class RecyclerViewTouchActionGuardManager {
    private static final String TAG = "ARVTouchActionGuardMgr";

    private static final boolean LOCAL_LOGV = false;
    private static final boolean LOCAL_LOGD = false;

    private RecyclerView.OnItemTouchListener mInternalUseOnItemTouchListener;
    private RecyclerView mRecyclerView;
    private boolean mGuarding;
    private int mInitialTouchY;
    private int mLastTouchY;
    private int mTouchSlop;
    private boolean mEnabled;
    private boolean mInterceptScrollingWhileAnimationRunning;

    /**
     * Constructor.
     */
    public RecyclerViewTouchActionGuardManager() {
        mInternalUseOnItemTouchListener = new RecyclerView.OnItemTouchListener() {
            @Override
            public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
                return RecyclerViewTouchActionGuardManager.this.onInterceptTouchEvent(rv, e);
            }

            @Override
            public void onTouchEvent(RecyclerView rv, MotionEvent e) {
                RecyclerViewTouchActionGuardManager.this.onTouchEvent(rv, e);
            }

            @Override
            public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
            }
        };
    }

    /**
     * Indicates this manager instance has released or not.
     *
     * @return True if this manager instance has released
     */
    public boolean isReleased() {
        return (mInternalUseOnItemTouchListener == null);
    }

    /**
     * Attaches {@link RecyclerView} instance.
     *
     * @param rv The {@link RecyclerView} instance
     */
    public void attachRecyclerView(@NonNull RecyclerView rv) {
        if (isReleased()) {
            throw new IllegalStateException("Accessing released object");
        }

        if (mRecyclerView != null) {
            throw new IllegalStateException("RecyclerView instance has already been set");
        }

        mRecyclerView = rv;
        mRecyclerView.addOnItemTouchListener(mInternalUseOnItemTouchListener);

        mTouchSlop = ViewConfiguration.get(rv.getContext()).getScaledTouchSlop();
    }

    /**
     * Detach the {@link RecyclerView} instance and release internal field references.
     *
     * This method should be called in order to avoid memory leaks.
     */
    public void release() {
        if (mRecyclerView != null && mInternalUseOnItemTouchListener != null) {
            mRecyclerView.removeOnItemTouchListener(mInternalUseOnItemTouchListener);
        }
        mInternalUseOnItemTouchListener = null;
        mRecyclerView = null;
    }

    /*package*/ boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
        if (!mEnabled) {
            return false;
        }

        final int action = MotionEventCompat.getActionMasked(e);

        if (LOCAL_LOGV) {
            Timber.v("onInterceptTouchEvent() action = " + action);
        }

        switch (action) {
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
            handleActionUpOrCancel();
            break;

        case MotionEvent.ACTION_DOWN:
            handleActionDown(e);
            break;

        case MotionEvent.ACTION_MOVE:
            if (handleActionMove(rv, e)) {
                return true;
            }
            break;
        }

        return false;
    }

    /*package*/ void onTouchEvent(RecyclerView rv, MotionEvent e) {
        if (!mEnabled) {
            return;
        }

        final int action = MotionEventCompat.getActionMasked(e);

        if (LOCAL_LOGV) {
            Timber.v("onTouchEvent() action = " + action);
        }

        switch (action) {
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
            handleActionUpOrCancel();
            break;
        }
    }

    private boolean handleActionMove(RecyclerView rv, MotionEvent e) {
        if (!mGuarding) {
            mLastTouchY = (int) (e.getY() + 0.5f);

            final int distance = mLastTouchY - mInitialTouchY;

            if (mInterceptScrollingWhileAnimationRunning && (Math.abs(distance) > mTouchSlop)
                    && isAnimationRunning(rv)) {
                // intercept vertical move touch events while animation is running
                mGuarding = true;
            }
        }

        return mGuarding;
    }

    private static boolean isAnimationRunning(RecyclerView rv) {
        final RecyclerView.ItemAnimator itemAnimator = rv.getItemAnimator();
        return (itemAnimator != null) && (itemAnimator.isRunning());
    }

    private void handleActionUpOrCancel() {
        mGuarding = false;
        mInitialTouchY = 0;
        mLastTouchY = 0;
    }

    private void handleActionDown(MotionEvent e) {
        mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f);
        mGuarding = false;
    }

    /**
     * Sets whether to use touch guard feature. If set false, all touch event interceptions will be disabled.
     *
     * @param enabled enabled / disabled
     */
    public void setEnabled(boolean enabled) {
        if (mEnabled == enabled) {
            return;
        }
        mEnabled = enabled;

        if (!mEnabled) {
            handleActionUpOrCancel();
        }
    }

    /**
     * Checks whether the touch guard feature is enabled.
     *
     * @return True for currently touch guard feature is enabled, otherwise false
     */
    public boolean isEnabled() {
        return mEnabled;
    }

    /**
     * Sets whether to use interception of "vertical scroll while animation running".
     *
     * @param enabled enabled / disabled
     */
    public void setInterceptVerticalScrollingWhileAnimationRunning(boolean enabled) {
        mInterceptScrollingWhileAnimationRunning = enabled;
    }

    /**
     * Checks whether the interception of "vertical scroll while animation running" is enabled.
     *
     * @return enabled / disabled
     */
    public boolean isInterceptScrollingWhileAnimationRunning() {
        return mInterceptScrollingWhileAnimationRunning;
    }
}