package com.lcodecore.tkrefreshlayout.processor; import android.animation.Animator; import android.animation.Animator.AnimatorListener; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.animation.DecelerateInterpolator; import com.lcodecore.tkrefreshlayout.TwinklingRefreshLayout; import com.lcodecore.tkrefreshlayout.utils.LogUtil; import com.lcodecore.tkrefreshlayout.utils.ScrollingUtil; import java.util.LinkedList; import static android.view.View.GONE; import static android.view.View.VISIBLE; /** * Created by lcodecore on 2016/11/26. */ public class AnimProcessor implements IAnimRefresh, IAnimOverScroll { private TwinklingRefreshLayout.CoContext cp; private static final float animFraction = 1f; //动画的变化率 private DecelerateInterpolator decelerateInterpolator; public AnimProcessor(TwinklingRefreshLayout.CoContext coProcessor) { this.cp = coProcessor; decelerateInterpolator = new DecelerateInterpolator(8); } private boolean scrollHeadLocked = false; private boolean scrollBottomLocked = false; public void scrollHeadByMove(float moveY) { float offsetY = decelerateInterpolator.getInterpolation(moveY / cp.getMaxHeadHeight() / 2) * moveY / 2; //禁止下拉刷新时下拉不显示头部 if (cp.isPureScrollModeOn() || (!cp.enableRefresh() && !cp.isOverScrollTopShow())) { if (cp.getHeader().getVisibility() != GONE) { cp.getHeader().setVisibility(GONE); } } else { if (cp.getHeader().getVisibility() != VISIBLE) { cp.getHeader().setVisibility(VISIBLE); } } if (scrollHeadLocked && cp.isEnableKeepIView()) { cp.getHeader().setTranslationY(offsetY - cp.getHeader().getLayoutParams().height); } else { cp.getHeader().setTranslationY(0); cp.getHeader().getLayoutParams().height = (int) Math.abs(offsetY); cp.getHeader().requestLayout(); cp.onPullingDown(offsetY); } if (!cp.isOpenFloatRefresh()) { cp.getTargetView().setTranslationY(offsetY); translateExHead((int) offsetY); } } public void scrollBottomByMove(float moveY) { float offsetY = decelerateInterpolator.getInterpolation(moveY / cp.getMaxBottomHeight() / 2) * moveY / 2; if (cp.isPureScrollModeOn() || (!cp.enableLoadmore() && !cp.isOverScrollBottomShow())) { if (cp.getFooter().getVisibility() != GONE) { cp.getFooter().setVisibility(GONE); } } else { if (cp.getFooter().getVisibility() != VISIBLE) { cp.getFooter().setVisibility(VISIBLE); } } if (scrollBottomLocked && cp.isEnableKeepIView()) { cp.getFooter().setTranslationY(cp.getFooter().getLayoutParams().height - offsetY); } else { cp.getFooter().setTranslationY(0); cp.getFooter().getLayoutParams().height = (int) Math.abs(offsetY); cp.getFooter().requestLayout(); cp.onPullingUp(-offsetY); } cp.getTargetView().setTranslationY(-offsetY); } public void dealPullDownRelease() { if (!cp.isPureScrollModeOn() && cp.enableRefresh() && getVisibleHeadHeight() >= cp.getHeadHeight() - cp.getTouchSlop()) { animHeadToRefresh(); } else { animHeadBack(false); } } public void dealPullUpRelease() { if (!cp.isPureScrollModeOn() && cp.enableLoadmore() && getVisibleFootHeight() >= cp.getBottomHeight() - cp.getTouchSlop()) { animBottomToLoad(); } else { animBottomBack(false); } } private int getVisibleHeadHeight() { LogUtil.i("header translationY:" + cp.getHeader().getTranslationY() + ",Visible head height:" + (cp.getHeader().getLayoutParams().height + cp.getHeader().getTranslationY())); return (int) (cp.getHeader().getLayoutParams().height + cp.getHeader().getTranslationY()); } private int getVisibleFootHeight() { LogUtil.i("footer translationY:" + cp.getFooter().getTranslationY() + ""); return (int) (cp.getFooter().getLayoutParams().height - cp.getFooter().getTranslationY()); } private void transHeader(float offsetY) { cp.getHeader().setTranslationY(offsetY - cp.getHeader().getLayoutParams().height); } private void transFooter(float offsetY) { cp.getFooter().setTranslationY(cp.getFooter().getLayoutParams().height - offsetY); } private boolean isAnimHeadToRefresh = false; /** * 1.满足进入刷新的条件或者主动刷新时,把Head位移到刷新位置(当前位置 ~ HeadHeight) */ public void animHeadToRefresh() { LogUtil.i("animHeadToRefresh:"); isAnimHeadToRefresh = true; animLayoutByTime(getVisibleHeadHeight(), cp.getHeadHeight(), animHeadUpListener, new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { isAnimHeadToRefresh = false; if (cp.getHeader().getVisibility() != VISIBLE) { cp.getHeader().setVisibility(VISIBLE); } cp.setRefreshVisible(true); if (cp.isEnableKeepIView()) { if (!scrollHeadLocked) { cp.setRefreshing(true); cp.onRefresh(); scrollHeadLocked = true; } } else { cp.setRefreshing(true); cp.onRefresh(); } } }); } private boolean isAnimHeadBack = false; /** * 2.动画结束或不满足进入刷新状态的条件,收起头部(当前位置 ~ 0) */ public void animHeadBack(final boolean isFinishRefresh) { LogUtil.i("animHeadBack:finishRefresh?->" + isFinishRefresh); isAnimHeadBack = true; if (isFinishRefresh && scrollHeadLocked && cp.isEnableKeepIView()) { cp.setPrepareFinishRefresh(true); } animLayoutByTime(getVisibleHeadHeight(), 0, animHeadUpListener, new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { isAnimHeadBack = false; cp.setRefreshVisible(false); if (isFinishRefresh && scrollHeadLocked && cp.isEnableKeepIView()) { cp.getHeader().getLayoutParams().height = 0; cp.getHeader().requestLayout(); cp.getHeader().setTranslationY(0); scrollHeadLocked = false; cp.setRefreshing(false); cp.resetHeaderView(); } } }); } private boolean isAnimBottomToLoad = false; /** * 3.满足进入加载更多的条件或者主动加载更多时,把Footer移到加载更多位置(当前位置 ~ BottomHeight) */ public void animBottomToLoad() { LogUtil.i("animBottomToLoad"); isAnimBottomToLoad = true; animLayoutByTime(getVisibleFootHeight(), cp.getBottomHeight(), animBottomUpListener, new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { isAnimBottomToLoad = false; if (cp.getFooter().getVisibility() != VISIBLE) { cp.getFooter().setVisibility(VISIBLE); } cp.setLoadVisible(true); if (cp.isEnableKeepIView()) { if (!scrollBottomLocked) { cp.setLoadingMore(true); cp.onLoadMore(); scrollBottomLocked = true; } } else { cp.setLoadingMore(true); cp.onLoadMore(); } } }); } private boolean isAnimBottomBack = false; /** * 4.加载更多完成或者不满足进入加载更多模式的条件时,收起尾部(当前位置 ~ 0) */ public void animBottomBack(final boolean isFinishLoading) { LogUtil.i("animBottomBack:finishLoading?->" + isFinishLoading); isAnimBottomBack = true; if (isFinishLoading && scrollBottomLocked && cp.isEnableKeepIView()) { cp.setPrepareFinishLoadMore(true); } animLayoutByTime(getVisibleFootHeight(), 0, new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { int height = (int) animation.getAnimatedValue(); //列表中加载到内容时滚动List if (!ScrollingUtil.isViewToBottom(cp.getTargetView(), cp.getTouchSlop())) { int dy = getVisibleFootHeight() - height; //可以让TargetView滚动dy高度,但这样两个方向上滚动感觉画面闪烁,改为dy/2是为了消除闪烁 if (dy > 0) { if (cp.getTargetView() instanceof RecyclerView) ScrollingUtil.scrollAViewBy(cp.getTargetView(), dy); else ScrollingUtil.scrollAViewBy(cp.getTargetView(), dy / 2); } } //decorate the AnimatorUpdateListener animBottomUpListener.onAnimationUpdate(animation); } }, new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { isAnimBottomBack = false; cp.setLoadVisible(false); if (isFinishLoading) { if (scrollBottomLocked && cp.isEnableKeepIView()) { cp.getFooter().getLayoutParams().height = 0; cp.getFooter().requestLayout(); cp.getFooter().setTranslationY(0); scrollBottomLocked = false; cp.resetBottomView(); cp.setLoadingMore(false); } } } }); } private boolean isAnimHeadHide = false; /** * 5.当刷新处于可见状态,向上滑动屏幕时,隐藏刷新控件 * * @param vy 手指向上滑动速度 */ public void animHeadHideByVy(int vy) { if (isAnimHeadHide) return; isAnimHeadHide = true; LogUtil.i("animHeadHideByVy:vy->" + vy); vy = Math.abs(vy); if (vy < 5000) vy = 8000; animLayoutByTime(getVisibleHeadHeight(), 0, 5 * Math.abs(getVisibleHeadHeight() * 1000 / vy), animHeadUpListener, new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { isAnimHeadHide = false; cp.setRefreshVisible(false); if (!cp.isEnableKeepIView()) { cp.setRefreshing(false); cp.onRefreshCanceled(); cp.resetHeaderView(); } } }); } private boolean isAnimBottomHide = false; /** * 6.当加载更多处于可见状态时,向下滑动屏幕,隐藏加载更多控件 * * @param vy 手指向下滑动的速度 */ public void animBottomHideByVy(int vy) { LogUtil.i("animBottomHideByVy:vy->" + vy); if (isAnimBottomHide) return; isAnimBottomHide = true; vy = Math.abs(vy); if (vy < 5000) vy = 8000; animLayoutByTime(getVisibleFootHeight(), 0, 5 * getVisibleFootHeight() * 1000 / vy, animBottomUpListener, new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { isAnimBottomHide = false; cp.setLoadVisible(false); if (!cp.isEnableKeepIView()) { cp.setLoadingMore(false); cp.onLoadmoreCanceled(); cp.resetBottomView(); } } }); } private boolean isAnimOsTop = false; private boolean isOverScrollTopLocked = false; /** * 7.执行顶部越界 To executive cross-border springback at the top. * 越界高度height ∝ vy/computeTimes,此处采用的模型是 height=A*(vy + B)/computeTimes * * @param vy 满足越界条件的手指滑动速度 the finger sliding speed on the screen. * @param computeTimes 从满足条件到滚动到顶部总共计算的次数 Calculation times from sliding to top. */ public void animOverScrollTop(float vy, int computeTimes) { LogUtil.i("animOverScrollTop:vy->" + vy + ",computeTimes->" + computeTimes); if (isOverScrollTopLocked) return; isOverScrollTopLocked = true; isAnimOsTop = true; cp.setStatePTD(); int oh = (int) Math.abs(vy / computeTimes / 2); final int overHeight = oh > cp.getOsHeight() ? cp.getOsHeight() : oh; final int time = overHeight <= 50 ? 115 : (int) (0.3 * overHeight + 100); animLayoutByTime(getVisibleHeadHeight(), overHeight, time, overScrollTopUpListener, new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (scrollHeadLocked && cp.isEnableKeepIView() && cp.showRefreshingWhenOverScroll()) { animHeadToRefresh(); isAnimOsTop = false; isOverScrollTopLocked = false; return; } animLayoutByTime(overHeight, 0, 2 * time, overScrollTopUpListener, new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { isAnimOsTop = false; isOverScrollTopLocked = false; } }); } }); } private boolean isAnimOsBottom = false; private boolean isOverScrollBottomLocked = false; /** * 8.执行底部越界 * * @param vy 满足越界条件的手指滑动速度 * @param computeTimes 从满足条件到滚动到顶部总共计算的次数 */ public void animOverScrollBottom(float vy, int computeTimes) { LogUtil.i("animOverScrollBottom:vy->" + vy + ",computeTimes->" + computeTimes); if (isOverScrollBottomLocked) return; cp.setStatePBU(); int oh = (int) Math.abs(vy / computeTimes / 2); final int overHeight = oh > cp.getOsHeight() ? cp.getOsHeight() : oh; final int time = overHeight <= 50 ? 115 : (int) (0.3 * overHeight + 100); if (!scrollBottomLocked && cp.autoLoadMore()) { cp.startLoadMore(); } else { isOverScrollBottomLocked = true; isAnimOsBottom = true; animLayoutByTime(0, overHeight, time, overScrollBottomUpListener, new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (scrollBottomLocked && cp.isEnableKeepIView() && cp.showLoadingWhenOverScroll()) { animBottomToLoad(); isAnimOsBottom = false; isOverScrollBottomLocked = false; return; } animLayoutByTime(overHeight, 0, 2 * time, overScrollBottomUpListener, new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { isAnimOsBottom = false; isOverScrollBottomLocked = false; } }); } }); } } private AnimatorUpdateListener animHeadUpListener = new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { int height = (int) animation.getAnimatedValue(); if (scrollHeadLocked && cp.isEnableKeepIView()) { transHeader(height); } else { cp.getHeader().getLayoutParams().height = height; cp.getHeader().requestLayout(); cp.getHeader().setTranslationY(0); cp.onPullDownReleasing(height); } if (!cp.isOpenFloatRefresh()) { cp.getTargetView().setTranslationY(height); translateExHead(height); } } }; private AnimatorUpdateListener animBottomUpListener = new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { int height = (int) animation.getAnimatedValue(); if (scrollBottomLocked && cp.isEnableKeepIView()) { transFooter(height); } else { cp.getFooter().getLayoutParams().height = height; cp.getFooter().requestLayout(); cp.getFooter().setTranslationY(0); cp.onPullUpReleasing(height); } cp.getTargetView().setTranslationY(-height); } }; private AnimatorUpdateListener overScrollTopUpListener = new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { int height = (int) animation.getAnimatedValue(); if (cp.isOverScrollTopShow()) { if (cp.getHeader().getVisibility() != VISIBLE) { cp.getHeader().setVisibility(VISIBLE); } } else { if (cp.getHeader().getVisibility() != GONE) { cp.getHeader().setVisibility(GONE); } } if (scrollHeadLocked && cp.isEnableKeepIView()) { transHeader(height); } else { cp.getHeader().setTranslationY(0); cp.getHeader().getLayoutParams().height = height; cp.getHeader().requestLayout(); cp.onPullDownReleasing(height); } cp.getTargetView().setTranslationY(height); translateExHead(height); } }; private AnimatorUpdateListener overScrollBottomUpListener = new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { int height = (int) animation.getAnimatedValue(); if (cp.isOverScrollBottomShow()) { if (cp.getFooter().getVisibility() != VISIBLE) { cp.getFooter().setVisibility(VISIBLE); } } else { if (cp.getFooter().getVisibility() != GONE) { cp.getFooter().setVisibility(GONE); } } if (scrollBottomLocked && cp.isEnableKeepIView()) { transFooter(height); } else { cp.getFooter().getLayoutParams().height = height; cp.getFooter().requestLayout(); cp.getFooter().setTranslationY(0); cp.onPullUpReleasing(height); } cp.getTargetView().setTranslationY(-height); } }; private void translateExHead(int offsetY) { if (!cp.isExHeadLocked()) cp.getExHead().setTranslationY(offsetY); } public void animLayoutByTime(int start, int end, long time, AnimatorUpdateListener listener, AnimatorListener animatorListener) { ValueAnimator va = ValueAnimator.ofInt(start, end); va.setInterpolator(new DecelerateInterpolator()); va.addUpdateListener(listener); va.addListener(animatorListener); va.setDuration(time); va.start(); // offerToQueue(va); } public void animLayoutByTime(int start, int end, long time, AnimatorUpdateListener listener) { ValueAnimator va = ValueAnimator.ofInt(start, end); va.setInterpolator(new DecelerateInterpolator()); va.addUpdateListener(listener); va.setDuration(time); va.start(); // offerToQueue(va); } public void animLayoutByTime(int start, int end, AnimatorUpdateListener listener, AnimatorListener animatorListener) { ValueAnimator va = ValueAnimator.ofInt(start, end); va.setInterpolator(new DecelerateInterpolator()); va.addUpdateListener(listener); va.addListener(animatorListener); va.setDuration((int) (Math.abs(start - end) * animFraction)); va.start(); // offerToQueue(va); } //just for test. private void offerToQueue(Animator animator) { if (animator == null) return; if (animQueue == null) { animQueue = new LinkedList<>(); } animQueue.offer(animator); System.out.println("Current Animators:" + animQueue.size()); animator.addListener(new AnimatorListenerAdapter() { long startTime = 0; @Override public void onAnimationStart(Animator animation) { startTime = System.currentTimeMillis(); } @Override public void onAnimationEnd(Animator animation) { animQueue.poll(); if (animQueue.size() > 0) { animQueue.getFirst().start(); } System.out.println("Anim end:start time->" + startTime + ",elapsed time->" + (System.currentTimeMillis() - startTime)); } }); if (animQueue.size() == 1) { animator.start(); } } private LinkedList<Animator> animQueue; }