Java tutorial
/* * MIT License * * Copyright (c) 2017 Alibaba Group * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.tmall.wireless.tangram.ext; import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.content.Context; import android.os.Build; import android.support.v4.view.GestureDetectorCompat; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; import com.alibaba.android.vlayout.VirtualLayoutManager; import com.tmall.ultraviewpager.UltraViewPager; import com.tmall.wireless.tangram.core.adapter.GroupBasicAdapter; import com.tmall.wireless.tangram.dataparser.concrete.Card; import com.tmall.wireless.tangram.structure.card.SwipeCard; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; /** * Intercept touch event of RecyclerView binded to Tangram * <li>Steps: </li> * <li>Record the current cards in visible area of screen when recyclerView's state changes to idle. Start intercepting gesture event if there's a {@link SwipeCard}</li> * <li>During gesture interception, detect the horizontal and vertical swipe gesture and invoke callback</li> * Created by mikeafc on 17/2/16. */ public class SwipeItemTouchListener implements RecyclerView.OnItemTouchListener { private static final String TAG = "TangramEngine"; private static final int ANIMATE_DURATION = 150; private static final int SWIPING_HOR = 1; private static final int SWIPING_VER = 2; private static final int SWIPING_NONE = -1; private GestureDetectorCompat mSwipeGestureDector; private List<View> mChildList; private float mDistanceX; private float mDistanceY; private MotionEvent lastMotionEvent; private Card currCard; private int currCardIdx; private int swipeType = SWIPING_NONE; private GroupBasicAdapter mGroupBasicAdapter; private VirtualLayoutManager layoutManager; private RecyclerView recyclerView; private WeakReference<SwipeCard> mSwipeCardRef; private PullFromEndListener pullFromEndListener; private int mActionEdge = 0; public SwipeItemTouchListener(Context context, GroupBasicAdapter groupBasicAdapter, RecyclerView recyclerView) { this.mGroupBasicAdapter = groupBasicAdapter; this.recyclerView = recyclerView; this.recyclerView.addOnScrollListener(scrollListener); this.layoutManager = (VirtualLayoutManager) recyclerView.getLayoutManager(); mSwipeGestureDector = new GestureDetectorCompat(context, new SwipeGestureListener()); mChildList = new ArrayList<>(); } public void setActionEdge(int actionEdge) { this.mActionEdge = actionEdge; } public void setPullFromEndListener(PullFromEndListener listener) { pullFromEndListener = listener; } private boolean isReadyToPullFromEnd() { return pullFromEndListener != null && pullFromEndListener.isReadyToPull(); } private boolean isSwiping() { return swipeType != SWIPING_NONE; } @Override public boolean onInterceptTouchEvent(RecyclerView recyclerView, MotionEvent motionEvent) { if ((recyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) || !isAttachedToWindow(recyclerView) || !hasAdapter(recyclerView)) { return false; } if (findFixedChildViewUnder(motionEvent) != null) { return false; } if (findScrollableChildViewUnder(motionEvent) != null) { return false; } mSwipeGestureDector.onTouchEvent(motionEvent); return isSwiping(); } @Override public void onTouchEvent(RecyclerView recyclerView, MotionEvent motionEvent) { mSwipeGestureDector.onTouchEvent(motionEvent); if (motionEvent.getAction() == MotionEvent.ACTION_UP || motionEvent.getAction() == MotionEvent.ACTION_CANCEL) { final boolean reachActionEdge = swipeType == SWIPING_HOR && (Math.abs(mDistanceX) > (mActionEdge > 0 ? mActionEdge : recyclerView.getWidth() / 3)); boolean reachTabEdge = false; if (mSwipeCardRef != null && mSwipeCardRef.get() != null && swipeType == SWIPING_HOR) { SwipeCard swipeCard = mSwipeCardRef.get(); if (swipeCard.getCurrentIndex() == 0 && mDistanceX > 0 || (swipeCard.getCurrentIndex() == swipeCard.getTotalPage() - 1) && mDistanceX < 0) { reachTabEdge = true; } } int direction = 1; if (swipeType == SWIPING_HOR) { direction = mDistanceX > 0 ? 1 : -1; } else if (swipeType == SWIPING_VER) { direction = mDistanceY > 0 ? 1 : -1; } resetViews(recyclerView, swipeType, reachActionEdge && !reachTabEdge, direction); } } @Override public void onRequestDisallowInterceptTouchEvent(boolean b) { } private void resetViews(RecyclerView recyclerView, final int swipingType, final boolean reachActionEdge, final int direction) { int contentWidth = recyclerView.getWidth(); AnimatorSet animatorSet = new AnimatorSet(); List<Animator> list = new ArrayList<>(); String translation = "translationX"; if (swipingType == SWIPING_VER) { translation = "translationY"; } for (View view : mChildList) { ObjectAnimator animator; if (reachActionEdge) { animator = ObjectAnimator.ofFloat(view, translation, contentWidth * direction) .setDuration(ANIMATE_DURATION); animator.setInterpolator(new AccelerateInterpolator()); } else { animator = ObjectAnimator.ofFloat(view, translation, 0).setDuration(ANIMATE_DURATION); animator.setInterpolator(new DecelerateInterpolator()); } list.add(animator); } animatorSet.playTogether(list); animatorSet.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { if (swipingType == SWIPING_HOR && reachActionEdge) { if (mSwipeCardRef != null && mSwipeCardRef.get() != null) { SwipeCard swipeCard = mSwipeCardRef.get(); swipeCard.switchTo(swipeCard.getCurrentIndex() - direction); } } mChildList.clear(); } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); animatorSet.start(); if (swipingType == SWIPING_VER) { if (pullFromEndListener != null) { if (mDistanceY < 0 && (mDistanceY < -pullFromEndListener.getPullEdge())) { pullFromEndListener.onAction(); } else { pullFromEndListener.onReset(); } } } swipeType = SWIPING_NONE; } private VirtualLayoutManager getLayoutManager() { return layoutManager; } private View findFixedChildViewUnder(MotionEvent event) { final int x = (int) event.getX(); final int y = (int) event.getY(); final List<View> fixedViews = getLayoutManager().getFixedViews(); int count = fixedViews.size(); for (int i = count - 1; i >= 0; --i) { View child = fixedViews.get(i); float translationX = child.getTranslationX(); float translationY = child.getTranslationY(); if (x >= (float) child.getLeft() + translationX && x <= (float) child.getRight() + translationX && y >= (float) child.getTop() + translationY && y <= (float) child.getBottom() + translationY) { return child; } } return null; } private View findScrollableChildViewUnder(MotionEvent event) { final int x = (int) event.getX(); final int y = (int) event.getY(); final int first = getLayoutManager().findFirstVisibleItemPosition(); final int last = getLayoutManager().findLastVisibleItemPosition(); for (int i = 0; i <= last - first; i++) { View child = getLayoutManager().getChildAt(i); if (child instanceof ViewGroup) { float translationX = child.getTranslationX(); float translationY = child.getTranslationY(); if (x >= (float) child.getLeft() + translationX && x <= (float) child.getRight() + translationX && y >= (float) child.getTop() + translationY && y <= (float) child.getBottom() + translationY) { if (findCanScrollView(child) != null) { return child; } } } } return null; } private View findCanScrollView(View v) { if (v instanceof ViewGroup) { ViewGroup target = (ViewGroup) v; if ((target instanceof UltraViewPager || target instanceof RecyclerView) && target.getVisibility() == View.VISIBLE) { return target; } else { for (int i = 0; i < target.getChildCount(); ++i) { View view = findCanScrollView(target.getChildAt(i)); if (view != null) { return view; } } return null; } } else { return null; } } private static boolean isAttachedToWindow(RecyclerView hostView) { if (Build.VERSION.SDK_INT >= 19) { return hostView.isAttachedToWindow(); } else { return (hostView.getHandler() != null); } } private static boolean hasAdapter(RecyclerView hostView) { return (hostView.getAdapter() != null); } private RecyclerView.OnScrollListener scrollListener = new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { if (RecyclerView.SCROLL_STATE_IDLE == newState && recyclerView != null && lastMotionEvent != null) { View childView = recyclerView.findChildViewUnder(lastMotionEvent.getX(), lastMotionEvent.getY()); if (childView != null) { int position = layoutManager.getPosition(childView); currCardIdx = mGroupBasicAdapter.findCardIdxFor(position); List<Card> groups = mGroupBasicAdapter.getGroups(); if (currCardIdx >= groups.size() || currCardIdx < 0) { Log.e(TAG, "onScroll: group size >= cardIdx"); return; } currCard = groups.get(currCardIdx); } } } @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); } }; private class SwipeGestureListener extends GestureDetector.SimpleOnGestureListener { @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { if (e1 == null || e2 == null) return false; lastMotionEvent = e2; mDistanceX = e2.getX() - e1.getX(); mDistanceY = e2.getY() - e1.getY(); Log.e(TAG, "onScroll: distanceX " + mDistanceX + ", distanceY " + mDistanceY); if (recyclerView != null && currCard instanceof SwipeCard) { SwipeCard swipeCard = (SwipeCard) currCard; mSwipeCardRef = new WeakReference<SwipeCard>(swipeCard); if (!isSwiping()) { if (Math.abs(distanceX) > Math.abs(distanceY)) { swipeType = SWIPING_HOR; Log.e(TAG, "onScroll: hor"); } else if (pullFromEndListener != null && Math.abs(distanceX) < Math.abs(distanceY) && mDistanceY < 0 && isReadyToPullFromEnd()) { swipeType = SWIPING_VER; Log.e(TAG, "onScroll: ver"); } else { Log.e(TAG, "onScroll: none"); return false; } } if (swipeType == SWIPING_HOR) { for (int i = 0; i < recyclerView.getChildCount(); i++) { View child = recyclerView.getChildAt(i); int cidx = mGroupBasicAdapter.findCardIdxFor(layoutManager.getPosition(child)); if (cidx == currCardIdx) { if (!mChildList.contains(child)) mChildList.add(child); final int sign = mDistanceX > 0 ? 1 : -1; child.setTranslationX((float) (sign * 10f * Math.sqrt(Math.abs(mDistanceX)))); Log.e(TAG, "onScroll: translationX"); } } } else if (swipeType == SWIPING_VER && mDistanceY < 0) { for (int i = 0; i < recyclerView.getChildCount(); i++) { View child = recyclerView.getChildAt(i); int cidx = mGroupBasicAdapter.findCardIdxFor(layoutManager.getPosition(child)); if (cidx == currCardIdx) { if (!mChildList.contains(child)) mChildList.add(child); final int sign = mDistanceY > 0 ? 1 : -1; if (mDistanceY < -pullFromEndListener.getPullEdge()) { pullFromEndListener.onReleaseToAction(mDistanceX, mDistanceY); } else { pullFromEndListener.onPull(mDistanceX, mDistanceY); } child.setTranslationY((float) (sign * 10f * Math.sqrt(Math.abs(mDistanceY)))); Log.e(TAG, "onScroll: translationY"); } } } else { Log.e(TAG, "onScroll: no hanlding"); return false; } return true; } return false; } } }