package zone.com.zrefreshlayout; import android.content.Context; import android.os.Handler; import android.os.Looper; import android.support.annotation.NonNull; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.view.animation.LinearInterpolator; import android.widget.FrameLayout; import com.nineoldandroids.animation.Animator; import com.nineoldandroids.animation.ValueAnimator; import zone.com.zanimate.value.ValueAnimatorProxy; import zone.com.zrefreshlayout.footer.LoadFooter; import zone.com.zrefreshlayout.header.SinaRefreshHeader; import zone.com.zrefreshlayout.loadmore.LoadMoreController; import zone.com.zrefreshlayout.loadmore.LoadMoreOtherListener; import zone.com.zrefreshlayout.utils.ScrollingUtil; import zone.com.zrefreshlayout.utils.SimpleAnimatorListener; import static zone.com.zrefreshlayout.utils.LogUtils.log; /** * Created by fuzhipeng on 2017/1/9. */ //自动刷新;--> 直接到 头部位置 并且是 刷新状态 //demo listView,GridView recycler imageView scrollerView,WebView //先弄上啦加载 最后是否固定头部 已经固定底部 下拉头部高度 仅头部使用 //阻力下拉 //全局切换 头与脚,全局配置初始化 //自定义头部 与 底部; 新浪 google支持, wave映射图 //ReadMe,wiki 名字改动 public class ZRefreshLayout extends FrameLayout { private final Handler mHandler = new Handler(Looper.getMainLooper()); static Config config; static final int REST = 0;//等待状态-休息; static final int PULL = 1;//下拉 static final int REFRESH_ABLE = 2;//松开刷新 条件--->滑动超过头部高度 static final int REFRESHING = 3;//刷新中 static final int COMPLETE = 4;//松开刷新 static final int LOADMORE_ING = 5;//加载中 static final int AUTO_PULL = 6;//自动刷新 int state = REST; private int mTouchSlop;//滑动有效的最小距离 private View headerView, footerView, content; private IHeaderView mIHeaderView; private IFooterView mIFooterView; private boolean isPinContent;//头部是否固定 private IScroll mIScroll;//滚动策略 private PullListener mPullListener; private RefreshAbleListener mRefreshAbleListener; private LoadMoreListener mLoadMoreListener; private IResistance mIResistance;//阻力策略 AnimateBack mAnimateBack = AnimateBack.None; private boolean isCanLoadMore = true; private boolean isCanRefresh = true; //ms 设置最长时间 自动恢复; 默认时间config里的配置 -1关闭延迟功能 private int delay_millis_auto_complete; public ZRefreshLayout(Context context) { this(context, null); } public ZRefreshLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public ZRefreshLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); initDelayAutoComplete(); initPinContent(); initHeaderView(); initFooterView(); initResistance(); initListener(); } private void initDelayAutoComplete() { if (config != null) delay_millis_auto_complete = config.delay_millis_auto_complete; } private void initListener() { if (config != null && config.mPullListener != null) mPullListener = config.mPullListener; if (config != null && config.mLoadMoreListener != null) mLoadMoreListener = config.mLoadMoreListener; } private void initResistance() { if (config != null && config.resistance != null) mIResistance = config.resistance.clone_(); } private void initPinContent() { if (config != null && config.isPinContent) { this.isPinContent = config.isPinContent; mIScroll = new Scroll_Pin(); } else mIScroll = new Scroll_PinNot(); } private void initHeaderView() { if (mIHeaderView != null)//个人配置 headerView = mIHeaderView.getView(this); else { if (config != null && config.headerView != null) { //全局配置 mIHeaderView = config.headerView.clone_(); if (mIHeaderView != null) headerView = mIHeaderView.getView(this); } //全局配置clone为空的时候 if (mIHeaderView == null) { //都为空 默认新浪 mIHeaderView = new SinaRefreshHeader(); headerView = mIHeaderView.getView(this); } } } private void initFooterView() { if (mIFooterView != null)//个人配置 footerView = mIFooterView.getView(this); else { if (config != null && config.footerView != null) { //全局配置 mIFooterView = config.footerView.clone_(); if (mIFooterView != null) footerView = mIFooterView.getView(this); } //全局配置clone为空的时候 if (footerView == null) { //都为空 默认新浪 mIFooterView = new LoadFooter(); footerView = mIFooterView.getView(this); } } } @Override protected void onFinishInflate() { super.onFinishInflate(); addView(headerView); addView(footerView); content = getChildAt(0); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); LayoutParams headerViewLp = (LayoutParams) headerView.getLayoutParams(); //headerView 仅仅支持 bottomMargin headerView.layout((getWidth() - headerView.getWidth()) / 2, -headerView.getHeight() - headerViewLp.bottomMargin, (getWidth() + headerView.getWidth()) / 2, -headerViewLp.bottomMargin); if (mLoadMoreListener != null) { log("bottom - footerView.getHeight():" + (bottom - footerView.getHeight()) + "\t bottom:" + bottom); footerView.layout(0, getHeight() - footerView.getHeight(), getWidth(), getHeight()); } } private float mTouchX, mTouchY; //防止down事件被吃 就是刷新完事 手指不离开继续下滑的问题; boolean haveDownEvent = false; @Override public boolean onInterceptTouchEvent(MotionEvent event) { if (state == REST) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mTouchX = event.getX(); mTouchY = event.getY(); log("down!事件"); haveDownEvent = true; break; case MotionEvent.ACTION_MOVE: if (!haveDownEvent) return super.onInterceptTouchEvent(event); float dx = event.getX() - mTouchX; float dy = event.getY() - mTouchY; log("mTouchY :" + mTouchY + "___ event.getY():" + event.getY()); log("dy :" + dy + "___ mTouchSlop:" + mTouchSlop); // log(String.format("下拉截断判断:%b\tdy>mTouchSlop:%b \tMath.abs(dx) <= Math.abs(dy):%b\t !ScrollingUtil.canChildScrollUp(content):%b", // dy > mTouchSlop && Math.abs(dx) <= Math.abs(dy)&& !ScrollingUtil.canChildScrollUp(content) // ,dy>mTouchSlop,Math.abs(dx) <= Math.abs(dy),!ScrollingUtil.canChildScrollUp(content))); if (isCanRefresh && dy > mTouchSlop && Math.abs(dx) <= Math.abs(dy) && !ScrollingUtil.canChildScrollUp(content)) { //滑动允许最大角度为45度 log("下拉截断!"); return true; } // log(String.format("上拉截断判断:%b\t!isInterceptInnerLoadMore:%b\t" + // "isCanLoadMore:%b\tmLoadMoreListener!=null:%b\tdy < 0:%b \tMath.abs(dy) > mTouchSlop:%b\t Math.abs(dx) <= Math.abs(dy):%b\t !ScrollingUtil.canChildScrollUp(content):%b", // !isInterceptInnerLoadMore&&isCanLoadMore&&mLoadMoreListener != null && dy < 0 // && Math.abs(dy) > mTouchSlop // && Math.abs(dx) <= Math.abs(dy) // && !ScrollingUtil.canChildScrollDown(content), // !isInterceptInnerLoadMore,isCanLoadMore&&mLoadMoreListener != null, // mLoadMoreListener != null,dy < 0,Math.abs(dy) > mTouchSlop, // Math.abs(dx) <= Math.abs(dy),!ScrollingUtil.canChildScrollUp(content))); if (!isInterceptInnerLoadMore && isCanLoadMore && mLoadMoreListener != null && dy < 0 && Math.abs(dy) > mTouchSlop && Math.abs(dx) <= Math.abs(dy) && !ScrollingUtil.canChildScrollDown(content)) { log("上啦截断!"); return true; } break; default: break; } } return super.onInterceptTouchEvent(event); } private int direction = 0; private final static int LOAD_UP = 1; private final static int PULL_DOWN = 2; @Override public boolean onTouchEvent(MotionEvent event) {//被拦截状态下 不是刷新就是 加载更多 if (state == AUTO_PULL) return super.onTouchEvent(event); switch (event.getAction()) { case MotionEvent.ACTION_MOVE: float dy = event.getY() - mTouchY; if (dy > 0) direction = PULL_DOWN; else direction = LOAD_UP; if (mIResistance != null)//dy 需要映射不然 真实的移动 和 MotionEvent移动对不上了 也没法和getRefreshAbleHeight()[真实的]比较了 dy = mIResistance.getOffSetYMapValue(getRefreshAbleHeight(), (int) dy); if (isCanRefresh &&state == REST && direction == PULL_DOWN) { state = PULL; log("PULL!"); } if (!isInterceptInnerLoadMore && haveDownEvent && isCanLoadMore && mLoadMoreListener != null && Math.abs(dy) > mTouchSlop && state == REST && direction == LOAD_UP && mIScroll.getScrollY() == 0) { log("dy:" + dy + "_ mTouchY:" + mTouchY + "__event.getY():" + event.getY()); loadMore(); } if (direction == PULL_DOWN && state == PULL && Math.abs(dy) >= getRefreshAbleHeight()) { state = REFRESH_ABLE; log("Math.abs(dy):" + Math.abs(dy) + "___getRefreshAbleHeight():" + getRefreshAbleHeight()); log("REFRESH_ABLE!"); mIHeaderView.refreshAble(true); if (mRefreshAbleListener != null) mRefreshAbleListener.refreshAble(true); } if (state == REFRESH_ABLE && Math.abs(dy) < getRefreshAbleHeight()) { state = PULL; log("Math.abs(dy):" + Math.abs(dy) + "___getRefreshAbleHeight():" + getRefreshAbleHeight()); log("PULL!"); mIHeaderView.refreshAble(false); if (mRefreshAbleListener != null) mRefreshAbleListener.refreshAble(false); } if (state == PULL || state == REFRESH_ABLE) { //防止别的状态 可以滚动 mIScroll.scrollTo_((int) (event.getY() - mTouchY)); } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: if (state == PULL) { //回弹 mAnimateBack = AnimateBack.DisRefreshAble_Back; mIScroll.smoothScrollTo_(0); log("回弹!"); } if (state == REFRESH_ABLE) { //刷新 mAnimateBack = AnimateBack.RefreshAble_Back; mIScroll.smoothScrollTo_(getRefreshAbleHeight()); } break; default: break; } return true; } void loadMore() { state = LOADMORE_ING; if (!isDelegate&&mIFooterView != null) { mIFooterView.onStart(this, footerView.getHeight()); } } void notityLoadMoreListener() { state = LOADMORE_ING; log("LOADMORE_ING!"); delayAutoComplete(); mLoadMoreListener.loadMore(this); } private void notityRefresh() { state = REFRESHING; log("释放回弹 将要刷新"); //等待回调 刷新完成 if (mPullListener != null) mPullListener.refresh(this); if (mIHeaderView != null) mIHeaderView.onRefreshing(getRefreshAbleHeight(), false); } Runnable runableAutoComplete = new Runnable() { @Override public void run() { if (ZRefreshLayout.this != null && ZRefreshLayout.this.isRefreshOrLoadMore()) { log("延迟结束刷新/加载!"); ZRefreshLayout.this.refresh2LoadMoreComplete(); } } }; public void setDelayAutoCompleteTime(int delayAutoComplete) { this.delay_millis_auto_complete = delayAutoComplete; } public int getDelayAutoCompleteTime() { return delay_millis_auto_complete; } private void delayAutoComplete() { if (delay_millis_auto_complete != -1){ log("add延迟完成!"); mHandler.postDelayed(runableAutoComplete, delay_millis_auto_complete); } } private void removeDelayAutoComplete() { if (delay_millis_auto_complete != -1){ log("remove延迟完成!"); mHandler.removeCallbacks(runableAutoComplete); } } int heightToRefresh; //因为头部设置更有效 所以就暴露了 通过AUtils类暴露接口 void setHeaderHeightToRefresh(int heightToRefresh) { this.heightToRefresh = heightToRefresh; } private int getRefreshAbleHeight() { return heightToRefresh != 0 ? heightToRefresh : headerView.getHeight(); } public void autoRefresh(boolean haveAnimate) { if (state == REST) { //等待回调 刷新完成 state = AUTO_PULL; log("AUTO_PULL!"); delayAutoComplete(); if (mPullListener != null) mPullListener.refresh(this); if (mIHeaderView != null) { mIHeaderView.onRefreshing(getRefreshAbleHeight(), true); } if (!haveAnimate) mIScroll.scrollTo_(getRefreshAbleHeight(), true); else { mAnimateBack = AnimateBack.Auto_Refresh; mIScroll.smoothScrollTo_(getRefreshAbleHeight()); } } } public void refreshComplete() { if (!isRefresh()) { log("非刷新状态下 refreshComplete"); return; } removeDelayAutoComplete(); mAnimateBack = AnimateBack.Complete_Back; mIScroll.smoothScrollTo_(0); } public void loadMoreComplete() { if (!isLoadMore()) { log("非加载状态下 loadMoreComplete"); return; } removeDelayAutoComplete(); if (!isDelegate&&mIFooterView != null) mIFooterView.onComplete(this); } void notifyLoadMoreCompleteListener() { mLoadMoreListener.loadMoreAnimationComplete(this); state = REST; haveDownEvent = false; } public LoadMoreListener getLoadMoreListener() { return mLoadMoreListener; } boolean isInterceptInnerLoadMore; boolean isDelegate; LoadMoreOtherListener outterLoadMoreListener; public void setLoadMoreListener(LoadMoreListener mLoadMoreListener) { setLoadMoreListener(false,mLoadMoreListener); } /** * @param isDelegate 委托加载更多,在外边处理 * 外部监听请调用此方法{@link AUtils#notityLoadMoreListener(ZRefreshLayout)} * @param mLoadMoreListener */ public void setLoadMoreListener(boolean isDelegate, LoadMoreListener mLoadMoreListener) { this.mLoadMoreListener = mLoadMoreListener; this.isDelegate=isDelegate; if (!isDelegate){ outterLoadMoreListener = LoadMoreController.addLoadMoreListener(content, this); isInterceptInnerLoadMore = outterLoadMoreListener == null ? false : true; }else isInterceptInnerLoadMore=true; } public boolean isCanLoadMore() { return isCanLoadMore && mLoadMoreListener != null; } public boolean isLoadMore() { return state == LOADMORE_ING; } public boolean isRefreshOrLoadMore() { return isLoadMore() || isRefresh(); } public void refresh2LoadMoreComplete() { if (isRefresh()) refreshComplete(); if (isLoadMore()) loadMoreComplete(); } public boolean isRefresh() { return state == REFRESHING || state == AUTO_PULL; } //必须设置加载更多监听才能 加载更多 public void setCanLoadMore(boolean canLoadMore) { isCanLoadMore = canLoadMore; if (outterLoadMoreListener == null) return; if (canLoadMore) { if (!outterLoadMoreListener.haveListener()) outterLoadMoreListener.addListener(content, this); } else outterLoadMoreListener.removeListener(content); } public boolean isCanRefresh() { return isCanRefresh; } public void setCanRefresh(boolean canRefresh) { isCanRefresh = canRefresh; } public PullListener getPullListener() { return mPullListener; } public void setPullListener(PullListener mPullListener) { this.mPullListener = mPullListener; } public IHeaderView getIHeaderView() { return mIHeaderView; } public void setIHeaderView(@NonNull IHeaderView mIHeaderView) { this.mIHeaderView = mIHeaderView; removeView(headerView); headerView = mIHeaderView.getView(this); addView(headerView); } public IFooterView getIFooterView() { return mIFooterView; } public void setIFooterView(@NonNull IFooterView mIFooterView) { this.mIFooterView = mIFooterView; removeView(footerView); footerView = mIFooterView.getView(this); addView(footerView); } public void setPinContent(boolean isPinContent) { this.isPinContent = isPinContent; if (state == REST) if (this.isPinContent) mIScroll = new Scroll_Pin(); else mIScroll = new Scroll_PinNot(); } public boolean isPinContent() { return isPinContent; } public IResistance getIResistance() { return mIResistance; } public void setIResistance(IResistance mIResistance) { this.mIResistance = mIResistance; } //------------------------------------------- 滚动策略 ------------------------------------------------ public abstract class IScroll { private static final int DEFAULT_DURATION = 250; private ValueAnimatorProxy valueAnimator = ValueAnimatorProxy.ofInt(0, 100) .setDuration(DEFAULT_DURATION) .setInterpolator(new LinearInterpolator()) .addListener(new SimpleAnimatorListener() { @Override public void onAnimationEnd(Animator animation) { log("onAnimationEnd"); if (mAnimateBack != AnimateBack.Auto_Refresh) refreshCompeleStateToRest(); //和上边顺序不要不调换! if (mAnimateBack == AnimateBack.RefreshAble_Back) notityRefresh(); } }) .addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { Integer offset = (Integer) animation.getAnimatedValue(); if (mIHeaderView != null) mIHeaderView.animateBack(mAnimateBack, 1F * Math.abs(getScrollY()) / getRefreshAbleHeight(), getRefreshAbleHeight(), isPinContent); scrollTo(offset, true); } }); void refreshCompeleStateToRest() { if (state == REFRESHING || state == PULL || state == AUTO_PULL) { log("刷新结束, 回滚到0"); if (mPullListener != null && state == REFRESHING) { mPullListener.refreshAnimationComplete(ZRefreshLayout.this); } state = COMPLETE; log("refreshAnimationComplete"); if (mIHeaderView != null) mIHeaderView.onComplete(); state = REST; mAnimateBack = AnimateBack.None; haveDownEvent = false; } } private void smoothScrollTo_(int fy) { if (mIHeaderView.interceptAnimateBack(mAnimateBack, mIScroll)) return; valueAnimator.setIntValues(getScrollY(), fy); valueAnimator.start(); } void smoothScrollTo_NotIntercept(int fy) { valueAnimator.setIntValues(getScrollY(), fy); valueAnimator.start(); } private void scrollTo_(int fy) { scrollTo(fy > 0 ? fy : 0, false); } private void scrollTo_(int fy, boolean isAnimate) { scrollTo(fy > 0 ? fy : 0, isAnimate); } /** * @param fy * @param isAnimate 是动画的话就不做滚动映射了 */ protected abstract void scrollTo(int fy, boolean isAnimate); protected abstract int getScrollY(); } public class Scroll_Pin extends IScroll { @Override protected void scrollTo(int fy, boolean isAnimate) { if (mIResistance != null && !isAnimate) fy = mIResistance.getOffSetYMapValue(getRefreshAbleHeight(), fy); if (mIHeaderView != null && !isAnimate) mIHeaderView.onPullingDown(1F * Math.abs(fy) / getRefreshAbleHeight(), getRefreshAbleHeight()); headerView.setTranslationY(fy); } @Override protected int getScrollY() { int scrolly = (int) headerView.getTranslationY(); return scrolly; } } public class Scroll_PinNot extends IScroll { @Override protected void scrollTo(int fy, boolean isAnimate) { if (mIResistance != null && !isAnimate) fy = mIResistance.getOffSetYMapValue(getRefreshAbleHeight(), fy); if (mIHeaderView != null && !isAnimate) mIHeaderView.onPullingDown(1F * Math.abs(fy) / getRefreshAbleHeight(), getRefreshAbleHeight()); ZRefreshLayout.this.scrollTo(0, -fy); } @Override protected int getScrollY() { int scrolly = -ZRefreshLayout.this.getScrollY(); return scrolly; } } public RefreshAbleListener getRefreshAbleListener() { return mRefreshAbleListener; } public void setRefreshAbleListener(RefreshAbleListener mRefreshAbleListener) { this.mRefreshAbleListener = mRefreshAbleListener; } //-------------------------内部接口---------------------------------------------------- public interface PullListener { void refresh(ZRefreshLayout zRefreshLayout); void refreshAnimationComplete(ZRefreshLayout zRefreshLayout); } public interface RefreshAbleListener { void refreshAble(boolean refreshAble); } public interface LoadMoreListener { void loadMore(ZRefreshLayout zRefreshLayout); void loadMoreAnimationComplete(ZRefreshLayout zRefreshLayout); } }