package cy.agorise.crystalwallet.util; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.support.annotation.IdRes; import android.support.annotation.Nullable; import android.support.v4.view.MotionEventCompat; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.StaggeredGridLayoutManager; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import android.widget.ListView; import android.widget.ScrollView; /** * Found this class on the internet and did some changes do adjust it to our * needs but I still need to figure out some stuff to obtain the exact desired * animation */ public class BounceTouchListener implements View.OnTouchListener { private static final long DEFAULT_ANIMATION_TIME = 600L; private boolean downCalled = false; private OnTranslateListener onTranslateListener; private View mMainView; private View mContent; private float mDownY; private boolean mSwipingDown; private boolean mSwipingUp; private Interpolator mInterpolator = new DecelerateInterpolator(3f); private boolean swipeUpEnabled = true; private int mActivePointerId = -99; private float mLastTouchY = -99; private int mMaxAbsTranslation = -99; private BounceTouchListener(View mainView, int contentResId, @Nullable OnTranslateListener listener) { mMainView = mainView; mContent = (contentResId == -1) ? mMainView : mMainView.findViewById(contentResId); onTranslateListener = listener; } /** * Creates a new BounceTouchListener * * @param mainScrollableView The main view that this touch listener is attached to * @param onTranslateListener To perform action on translation, can be null if not needed * @return A new BounceTouchListener attached to the given scrollable view */ public static BounceTouchListener create(View mainScrollableView, @Nullable OnTranslateListener onTranslateListener) { return create(mainScrollableView, -1, onTranslateListener); } /** * Creates a new BounceTouchListener * * @param mainView The main view that this touch listener is attached to * @param contentResId Resource Id of the scrollable view * @param onTranslateListener To perform action on translation, can be null if not needed * @return A new BounceTouchListener attached to the given scrollable view */ public static BounceTouchListener create(View mainView, @IdRes int contentResId, @Nullable OnTranslateListener onTranslateListener) { return new BounceTouchListener(mainView, contentResId, onTranslateListener); } @Override public boolean onTouch(View view, MotionEvent motionEvent) { final int action = MotionEventCompat.getActionMasked(motionEvent); switch (action) { case MotionEvent.ACTION_DOWN: { onDownMotionEvent(motionEvent); view.onTouchEvent(motionEvent); downCalled = true; if (mContent.getTranslationY() == 0) { return false; } } case MotionEvent.ACTION_MOVE: { if (mActivePointerId == -99) { onDownMotionEvent(motionEvent); downCalled = true; } final int pointerIndex = MotionEventCompat.findPointerIndex(motionEvent, mActivePointerId); final float y = MotionEventCompat.getY(motionEvent, pointerIndex); if (!hasHitTop() && !hasHitBottom() || !downCalled) { if (!downCalled) { downCalled = true; } mDownY = y; view.onTouchEvent(motionEvent); return false; } float deltaY = y - mDownY; if (Math.abs(deltaY) > 0 && hasHitTop() && deltaY > 0) { mSwipingDown = true; sendCancelEventToView(view, motionEvent); } if (swipeUpEnabled) { if (Math.abs(deltaY) > 0 && hasHitBottom() && deltaY < 0) { mSwipingUp = true; sendCancelEventToView(view, motionEvent); } } if (mSwipingDown || mSwipingUp) { if ((deltaY <= 0 && mSwipingDown) || (deltaY >= 0 && mSwipingUp)) { mDownY = 0; mSwipingDown = false; mSwipingUp = false; downCalled = false; MotionEvent downEvent = MotionEvent.obtain(motionEvent); downEvent.setAction(MotionEvent.ACTION_DOWN | (MotionEventCompat.getActionIndex(motionEvent) << MotionEventCompat.ACTION_POINTER_INDEX_SHIFT)); view.onTouchEvent(downEvent); break; } int translation = (int) ((deltaY / Math.abs(deltaY)) * Math.pow(Math.abs(deltaY), .8f)); if (mMaxAbsTranslation > 0) { if (translation < 0) { translation = Math.max(-mMaxAbsTranslation, translation); } else { translation = Math.min(mMaxAbsTranslation, translation); } } mContent.setTranslationY(translation); if (onTranslateListener != null) onTranslateListener.onTranslate(mContent.getTranslationY()); return true; } break; } case MotionEvent.ACTION_UP: { mActivePointerId = -99; // cancel mContent.animate() .setInterpolator(mInterpolator) .translationY(0) .setDuration(DEFAULT_ANIMATION_TIME) .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { ((ValueAnimator) animation).addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { if (onTranslateListener != null) { onTranslateListener.onTranslate(mContent.getTranslationY()); } } }); super.onAnimationStart(animation); } }); mDownY = 0; mSwipingDown = false; mSwipingUp = false; downCalled = false; break; } case MotionEvent.ACTION_CANCEL: { mActivePointerId = -99; break; } case MotionEvent.ACTION_POINTER_UP: { final int pointerIndex = MotionEventCompat.getActionIndex(motionEvent); final int pointerId = MotionEventCompat.getPointerId(motionEvent, pointerIndex); if (pointerId == mActivePointerId) { final int newPointerIndex = pointerIndex == 0 ? 1 : 0; mLastTouchY = MotionEventCompat.getY(motionEvent, newPointerIndex); mActivePointerId = MotionEventCompat.getPointerId(motionEvent, newPointerIndex); if (mContent.getTranslationY() > 0) { mDownY = mLastTouchY - (int) Math.pow(mContent.getTranslationY(), 10f / 8f); mContent.animate().cancel(); } else if (mContent.getTranslationY() < 0) { mDownY = mLastTouchY + (int) Math.pow(-mContent.getTranslationY(), 10f / 8f); mContent.animate().cancel(); } } break; } } return false; } private void sendCancelEventToView(View view, MotionEvent motionEvent) { ((ViewGroup) view).requestDisallowInterceptTouchEvent(true); MotionEvent cancelEvent = MotionEvent.obtain(motionEvent); cancelEvent.setAction(MotionEvent.ACTION_CANCEL | (MotionEventCompat.getActionIndex(motionEvent) << MotionEventCompat.ACTION_POINTER_INDEX_SHIFT)); view.onTouchEvent(cancelEvent); } private void onDownMotionEvent(MotionEvent motionEvent) { final int pointerIndex = MotionEventCompat.getActionIndex(motionEvent); mLastTouchY = MotionEventCompat.getY(motionEvent, pointerIndex); mActivePointerId = MotionEventCompat.getPointerId(motionEvent, 0); if (mContent.getTranslationY() > 0) { mDownY = mLastTouchY - (int) Math.pow(mContent.getTranslationY(), 10f / 8f); mContent.animate().cancel(); } else if (mContent.getTranslationY() < 0) { mDownY = mLastTouchY + (int) Math.pow(-mContent.getTranslationY(), 10f / 8f); mContent.animate().cancel(); } else { mDownY = mLastTouchY; } } private boolean hasHitBottom() { if (mMainView instanceof ScrollView) { ScrollView scrollView = (ScrollView) mMainView; View view = scrollView.getChildAt(scrollView.getChildCount() - 1); int diff = (view.getBottom() - (scrollView.getHeight() + scrollView.getScrollY()));// Calculate the scrolldiff return diff == 0; } else if (mMainView instanceof ListView) { ListView listView = (ListView) mMainView; if (listView.getAdapter() != null) { if (listView.getAdapter().getCount() > 0) { return listView.getLastVisiblePosition() == listView.getAdapter().getCount() - 1 && listView.getChildAt(listView.getChildCount() - 1).getBottom() <= listView.getHeight(); } } } else if (mMainView instanceof RecyclerView) { RecyclerView recyclerView = (RecyclerView) mMainView; if (recyclerView.getAdapter() != null && recyclerView.getLayoutManager() != null) { RecyclerView.Adapter adapter = recyclerView.getAdapter(); if (adapter.getItemCount() > 0) { RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); if (layoutManager instanceof LinearLayoutManager) { LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager; return linearLayoutManager.findLastCompletelyVisibleItemPosition() == adapter.getItemCount() - 1; } else if (layoutManager instanceof StaggeredGridLayoutManager) { StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager; int[] checks = staggeredGridLayoutManager.findLastCompletelyVisibleItemPositions(null); for (int check : checks) { if (check == adapter.getItemCount() - 1) return true; } } } } } return false; } private boolean hasHitTop() { if (mMainView instanceof ScrollView) { ScrollView scrollView = (ScrollView) mMainView; return scrollView.getScrollY() == 0; } else if (mMainView instanceof ListView) { ListView listView = (ListView) mMainView; if (listView.getAdapter() != null) { if (listView.getAdapter().getCount() > 0) { return listView.getFirstVisiblePosition() == 0 && listView.getChildAt(0).getTop() >= 0; } } } else if (mMainView instanceof RecyclerView) { RecyclerView recyclerView = (RecyclerView) mMainView; if (recyclerView.getAdapter() != null && recyclerView.getLayoutManager() != null) { RecyclerView.Adapter adapter = recyclerView.getAdapter(); if (adapter.getItemCount() > 0) { RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); if (layoutManager instanceof LinearLayoutManager) { LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager; return linearLayoutManager.findFirstCompletelyVisibleItemPosition() == 0; } else if (layoutManager instanceof StaggeredGridLayoutManager) { StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager; int[] checks = staggeredGridLayoutManager.findFirstCompletelyVisibleItemPositions(null); for (int check : checks) { if (check == 0) return true; } } } } } return false; } public void setMaxAbsTranslation(int maxAbsTranslation) { this.mMaxAbsTranslation = maxAbsTranslation; } public interface OnTranslateListener { void onTranslate(float translation); } }