package com.team.zhuoke.ui.refreshview;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.support.annotation.LayoutRes;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.animation.LinearInterpolator;
import android.widget.AbsListView.OnScrollListener;
import android.widget.LinearLayout;
import android.widget.Scroller;
import com.team.zhuoke.R;
import com.team.zhuoke.ui.refreshview.callback.IFooterCallBack;
import com.team.zhuoke.ui.refreshview.callback.IHeaderCallBack;
import com.team.zhuoke.ui.refreshview.listener.OnBottomLoadMoreTime;
import com.team.zhuoke.ui.refreshview.listener.OnTopRefreshTime;
import com.team.zhuoke.ui.refreshview.utils.Utils;
import com.team.zhuoke.utils.L;
import java.util.Calendar;
import java.util.concurrent.CopyOnWriteArrayList;
public class XRefreshView extends LinearLayout {
// -- header view
private View mHeaderView;
private int mHeaderViewHeight; // header view's height
/**
* 最初的滚动位置.第一次布局时滚动header的高度的距离
*/
protected int mInitScrollY = 0;
private int mLastY = -1; // save event y
private int mLastX = -1; // save event x
private boolean mEnablePullRefresh = true;
public boolean mPullRefreshing = false; // is refreashing.
private float OFFSET_RADIO = 1.8f; // support iOS like pull
private XRefreshViewListener mRefreshViewListener;
// -- footer view
private View mFooterView;
private boolean mEnablePullLoad;
public boolean mPullLoading;
/**
* 默认不自动刷新
*/
private boolean autoRefresh = false;
/**
* 默认自动加载更多
*/
private boolean autoLoadMore = true;
private int mFootHeight;
/**
* 被刷新的view
*/
private XRefreshContentView mContentView;
private boolean isHeightMatchParent = true;
private boolean isWidthMatchParent = true;
private int mInitialMotionY;
private int mTouchSlop;
private XRefreshHolder mHolder;
private MotionEvent mLastMoveEvent;
private boolean mHasSendCancelEvent = false;
private boolean mHasSendDownEvent = false;
private Scroller mScroller;
private boolean mMoveForHorizontal = false;
private boolean isForHorizontalMove = false;
private boolean mCanMoveHeaderWhenDisablePullRefresh = true;
private boolean mCanMoveFooterWhenDisablePullLoadMore = true;
private boolean mIsIntercept = false;
private IHeaderCallBack mHeaderCallBack;
private IFooterCallBack mFooterCallBack;
/**
* 当刷新完成以后,headerview和footerview被固定的时间,在这个时间以后headerview才会回弹
*/
private int mPinnedTime;
private XRefreshViewState mState = null;
/**
* 当已无更多数据时候,需把这个变量设为true
*/
private boolean mHasLoadComplete = false;
/**
* 在刷新的时候是否可以移动contentView
*/
private boolean mIsPinnedContentWhenRefreshing = false;
private boolean enableReleaseToLoadMore = true;
/**
* 在Recyclerview滑倒最底部的时候,是否允许Recyclerview继续往上滑动
*/
private boolean enableRecyclerViewPullUp = true;
/**
* 当Recyclerview加载完成的时候,不允许界面被上拉
*/
private boolean enablePullUp = true;
/**
* 布局是否准备好了,准备好以后才能进行自动刷新这种操作
*/
private boolean mLayoutReady = false;
private boolean mNeedToRefresh = false;
public XRefreshView(Context context) {
this(context, null);
}
public XRefreshView(Context context, AttributeSet attrs) {
super(context, attrs);
setClickable(true);
setLongClickable(true);
mContentView = new XRefreshContentView();
mHolder = new XRefreshHolder();
mScroller = new Scroller(getContext(), new LinearInterpolator());
initWithContext(context, attrs);
setOrientation(VERTICAL);
}
/**
* 设置顶部刷新时机
*
* @param topListener
*/
public void setOnTopRefreshTime(OnTopRefreshTime topListener) {
mContentView.setOnTopRefreshTime(topListener);
}
public void setOnBottomLoadMoreTime(OnBottomLoadMoreTime bottomListener) {
mContentView.setOnBottomLoadMoreTime(bottomListener);
}
/**
* 如果被刷新的view是RecyclerView,那么footerView将被添加到adapter中,不会添加到这里
*
* @return
*/
public boolean needAddFooterView() {
return !mContentView.isRecyclerView();
}
/**
* pass true if need use for Horizontal move, or false
*
* @param isForHorizontalMove default false
*/
public void setMoveForHorizontal(boolean isForHorizontalMove) {
this.isForHorizontalMove = isForHorizontalMove;
}
/**
* 设置静默加载更多,旨在提供被刷新的view滚动到底部的监听,自动静默加载更多
*/
public void setSilenceLoadMore() {
mContentView.setSilenceLoadMore(true);
setPullLoadEnable(false);
}
/**
* 当切换layoutManager时,需调用此方法
*/
public void notifyLayoutManagerChanged() {
mContentView.setScrollListener();
mContentView.notifyDatasetChanged();
}
private void initWithContext(Context context, AttributeSet attrs) {
// 根据属性设置参数
if (attrs != null) {
TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
R.styleable.XRefreshView, 0, 0);
try {
isHeightMatchParent = a.getBoolean(
R.styleable.XRefreshView_isHeightMatchParent, true);
isWidthMatchParent = a.getBoolean(
R.styleable.XRefreshView_isHeightMatchParent, true);
autoRefresh = a.getBoolean(
R.styleable.XRefreshView_autoRefresh, false);
autoLoadMore = a.getBoolean(
R.styleable.XRefreshView_autoLoadMore, true);
} catch (Exception e) {
e.printStackTrace();
} finally {
a.recycle();
}
}
addHeaderView();
this.getViewTreeObserver().addOnGlobalLayoutListener(
new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
mLayoutReady = true;
if (autoRefresh || mNeedToRefresh) {
startRefresh();
}
setHeadMoveLargestDistence(mHeadMoveDistence);
attachContentView();
addFooterView();
if (waitForShowEmptyView == 1) {
enableEmptyView(true);
waitForShowEmptyView = 0;
}
// 移除视图树监听器
removeViewTreeObserver(this);
}
});
mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
}
private void addHeaderView() {
if (mHeaderView == null) {
mHeaderView = new XRefreshViewHeader(getContext());
}
dealAddHeaderView();
}
private void dealAddHeaderView() {
if (indexOfChild(mHeaderView) == -1) {
Utils.removeViewFromParent(mHeaderView);
addView(mHeaderView, 0);
mHeaderCallBack = (IHeaderCallBack) mHeaderView;
setRefreshTime();
checkPullRefreshEnable();
}
}
private void dealAddFooterView() {
if (indexOfChild(mFooterView) == -1) {
if (needAddFooterView()) {
Utils.removeViewFromParent(mFooterView);
try {
addView(mFooterView, 2);
} catch (IndexOutOfBoundsException e) {
new RuntimeException("XRefreshView is allowed to have one and only one child");
}
}
mFooterCallBack = (IFooterCallBack) mFooterView;
checkPullLoadEnable();
}
}
private void attachContentView() {
mContentView.setContentView(XRefreshView.this.getChildAt(1));
mContentView.setContainer(autoLoadMore ? this : null);
mContentView.setContentViewLayoutParams(isHeightMatchParent, isWidthMatchParent);
mContentView.setHolder(mHolder);
mContentView.setParent(this);
mContentView.setScrollListener();
}
private void addFooterView() {
if (mFooterView == null) {
mFooterView = new XRefreshViewFooter(getContext());
}
dealAddFooterView();
}
@SuppressWarnings("deprecation")
@SuppressLint("NewApi")
public void removeViewTreeObserver(OnGlobalLayoutListener listener) {
if (Build.VERSION.SDK_INT < VERSION_CODES.JELLY_BEAN) {
getViewTreeObserver().removeGlobalOnLayoutListener(listener);
} else {
getViewTreeObserver().removeOnGlobalLayoutListener(listener);
}
}
private int mHeaderGap;
public void setHeaderGap(int headerGap) {
mHeaderGap = headerGap;
}
private void getHeaderHeight() {
if (mHeaderCallBack != null) {
mHeaderViewHeight = mHeaderCallBack.getHeaderHeight();
}
}
private void getFooterHeight() {
if (mFooterCallBack != null) {
mFootHeight = mFooterCallBack.getFooterHeight();
}
// if (mFooterView != null) {
// mFootHeight = mFooterView.getMeasuredHeight();
// }
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
int childCount = getChildCount();
int finalHeight = 0;
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (child.getVisibility() != View.GONE) {
final int paddingLeft = getPaddingLeft();
final int paddingRight = getPaddingRight();
final int paddingTop = getPaddingTop();
final int paddingBottom = getPaddingBottom();
LayoutParams lp = (LayoutParams) child.getLayoutParams();
int childWidthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth() - lp.leftMargin - lp.rightMargin - paddingLeft - paddingRight, MeasureSpec.EXACTLY);
// int childWidthSpec = getChildMeasureSpec(widthMeasureSpec,
// paddingLeft + paddingRight, getMeasuredWidth()-lp.leftMargin - lp.rightMargin);
int childHeightSpec = getChildMeasureSpec(heightMeasureSpec,
paddingTop + paddingBottom + lp.topMargin + lp.bottomMargin, lp.height);
child.measure(childWidthSpec, childHeightSpec);
finalHeight += child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
}
}
setMeasuredDimension(width, finalHeight);
getHeaderHeight();
getFooterHeight();
}
@Override
protected void onLayout(boolean changed, int l, int t2, int r, int b) {
// super.onLayout(changed, l, t2, r, b);
// if(mHolder.mOffsetY!=0)return;
L.d("onLayout mHolder.mOffsetY=" + mHolder.mOffsetY);
int childCount = getChildCount();
int top = getPaddingTop() + mHolder.mOffsetY;
int adHeight = 0;
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
LayoutParams margins = (LayoutParams) child.getLayoutParams();
int topMargin = margins.topMargin;
int bottomMargin = margins.bottomMargin;
int leftMargin = margins.leftMargin;
int rightMargin = margins.rightMargin;
l = leftMargin + getPaddingLeft();
top += topMargin;
r = child.getMeasuredWidth();
if (child.getVisibility() != View.GONE) {
if (i == 0) {
adHeight = child.getMeasuredHeight() - mHeaderViewHeight;
child.layout(l, top - mHeaderViewHeight, l + r, top + adHeight);
top += adHeight;
} else if (i == 1) {
int childHeight = child.getMeasuredHeight() - adHeight;
int bottom = childHeight + top;
child.layout(l, top, l + r, bottom);
top += childHeight + bottomMargin;
} else {
int bottom = child.getMeasuredHeight() + top;
child.layout(l, top, l + r, bottom);
top += child.getMeasuredHeight();
}
}
}
}
private boolean isIntercepted = false;
private int mHeadMoveDistence;
private final CopyOnWriteArrayList<TouchLifeCycle> mTouchLifeCycles = new CopyOnWriteArrayList<>();
interface TouchLifeCycle {
void onTouch(MotionEvent event);
}
public void addTouchLifeCycle(TouchLifeCycle lifeCycle) {
mTouchLifeCycles.add(lifeCycle);
}
public void removeTouchLifeCycle(TouchLifeCycle lifeCycle) {
if (lifeCycle == null) {
return;
}
if (mTouchLifeCycles.contains(lifeCycle)) {
mTouchLifeCycles.remove(lifeCycle);
}
}
private void updateTouchAction(MotionEvent event) {
for (TouchLifeCycle lifeCycle : mTouchLifeCycles) {
if (lifeCycle != null) {
lifeCycle.onTouch(event);
}
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
int deltaY = 0;
int deltaX = 0;
updateTouchAction(ev);
switch (action) {
case MotionEvent.ACTION_DOWN:
mHasSendCancelEvent = false;
mHasSendDownEvent = false;
mLastY = (int) ev.getRawY();
mLastX = (int) ev.getRawX();
mInitialMotionY = mLastY;
break;
case MotionEvent.ACTION_MOVE:
mLastMoveEvent = ev;
if (/*!enablePullUp ||*/ mStopingRefresh || !isEnabled() || mIsIntercept) {
return super.dispatchTouchEvent(ev);
}
if ((mPullLoading || mPullRefreshing) && mIsPinnedContentWhenRefreshing) {
sendCancelEvent();
return true;
}
int currentY = (int) ev.getRawY();
int currentX = (int) ev.getRawX();
deltaY = currentY - mLastY;
deltaX = currentX - mLastX;
mLastY = currentY;
mLastX = currentX;
// intercept the MotionEvent only when user is not scrolling
if (!isIntercepted) {
if (Math.abs(currentY - mInitialMotionY) >= mTouchSlop) {
isIntercepted = true;
} else {
return super.dispatchTouchEvent(ev);
}
}
if (isForHorizontalMove && !mMoveForHorizontal && Math.abs(deltaX) > mTouchSlop
&& Math.abs(deltaX) > Math.abs(deltaY)) {
if (mHolder.mOffsetY == 0) {
mMoveForHorizontal = true;
}
}
if (mMoveForHorizontal) {
return super.dispatchTouchEvent(ev);
}
L.d("isTop=" + mContentView.isTop() + ";isBottom=" + mContentView.isBottom());
if (deltaY > 0 && mHolder.mOffsetY <= mHeadMoveDistence || deltaY < 0) {
deltaY = (int) (deltaY / OFFSET_RADIO);
} else {
return super.dispatchTouchEvent(ev);
}
if (!mPullLoading && !mReleaseToLoadMore && mContentView.isTop() && (deltaY > 0 || (deltaY < 0 && mHolder.hasHeaderPullDown()))) {
sendCancelEvent();
updateHeaderHeight(currentY, deltaY);
} else if (!mPullRefreshing && mContentView.isBottom()
&& (deltaY < 0 || deltaY > 0 && mHolder.hasFooterPullUp())) {
sendCancelEvent();
updateFooterHeight(deltaY);
} else if (deltaY != 0 && (mContentView.isTop() && !mHolder.hasHeaderPullDown()
|| mContentView.isBottom() && !mHolder.hasFooterPullUp())) {
if (mReleaseToLoadMore) {
releaseToLoadMore(false);
}
if (Math.abs(deltaY) > 0)
sendDownEvent();
}
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
// if (mHolder.mOffsetY != 0 && mRefreshViewListener != null
// && !mPullRefreshing && !mPullLoading) {
// mRefreshViewListener.onRelease(mHolder.mOffsetY);
// }
if (mHolder.hasHeaderPullDown()) {
if (mEnablePullRefresh && !mStopingRefresh && !mPullRefreshing && mHolder.mOffsetY > mHeaderViewHeight) {
mPullRefreshing = true;
mHeaderCallBack.onStateRefreshing();
mState = XRefreshViewState.STATE_REFRESHING;
if (mRefreshViewListener != null) {
mRefreshViewListener.onRefresh();
}
}
resetHeaderHeight();
} else if (mHolder.hasFooterPullUp()) {
if (!mStopingRefresh) {
if (mEnablePullLoad && !isEmptyViewShowing() && needAddFooterView() && !mHasLoadComplete) {
invokeLoadMore();
} else {
int offset = 0 - mHolder.mOffsetY;
startScroll(offset, Utils.computeScrollVerticalDuration(offset, getHeight()));
}
}
}
mLastY = -1; // reset
mLastX = -1;
mInitialMotionY = 0;
isIntercepted = false;
mMoveForHorizontal = false;
break;
}
return super.dispatchTouchEvent(ev);
}
public XRefreshContentView getContentView() {
return mContentView;
}
public boolean invokeLoadMore() {
if (mEnablePullLoad && !isEmptyViewShowing() && !mPullRefreshing && !mStopingRefresh && !mHasLoadComplete) {
int offset = 0 - mHolder.mOffsetY - mFootHeight;
if (offset != 0) {
startScroll(offset, Utils.computeScrollVerticalDuration(offset, getHeight()));
}
startLoadMore();
return true;
}
return false;
}
public void notifyLoadMore() {
if (needAddFooterView()) {
startLoadMore();
} else {
mContentView.notifyRecyclerViewLoadMore();
}
}
/**
* if child need the touch event,pass true
*/
public void disallowInterceptTouchEvent(boolean isIntercept) {
mIsIntercept = isIntercept;
}
private void sendCancelEvent() {
if (!mHasSendCancelEvent) {
L.d("sendCancelEvent");
setRefreshTime();
mHasSendCancelEvent = true;
mHasSendDownEvent = false;
MotionEvent last = mLastMoveEvent;
MotionEvent e = MotionEvent.obtain(
last.getDownTime(),
last.getEventTime()
+ ViewConfiguration.getLongPressTimeout(),
MotionEvent.ACTION_CANCEL, last.getX(), last.getY(),
last.getMetaState());
dispatchTouchEventSupper(e);
}
}
/**
* header可下拉的最大距离
*
* @param headMoveDistence
*/
public void setHeadMoveLargestDistence(int headMoveDistence) {
if (headMoveDistence <= 0) {
int ScreenHeight = Utils.getScreenSize(getContext()).y;
mHeadMoveDistence = ScreenHeight / 3;
} else {
mHeadMoveDistence = headMoveDistence;
}
mHeadMoveDistence = mHeadMoveDistence <= mHeaderViewHeight ? mHeaderViewHeight + 1 : mHeadMoveDistence;
}
private void sendDownEvent() {
if (!mHasSendDownEvent) {
L.d("sendDownEvent");
mHasSendCancelEvent = false;
mHasSendDownEvent = true;
isIntercepted = false;
final MotionEvent last = mLastMoveEvent;
if (last == null)
return;
MotionEvent e = MotionEvent.obtain(last.getDownTime(),
last.getEventTime(), MotionEvent.ACTION_DOWN, last.getX(),
last.getY(), last.getMetaState());
dispatchTouchEventSupper(e);
}
}
public boolean dispatchTouchEventSupper(MotionEvent e) {
return super.dispatchTouchEvent(e);
}
/**
* enable or disable pull up load more feature.
*
* @param enable
*/
public void setPullLoadEnable(boolean enable) {
mEnablePullLoad = enable;
if (needAddFooterView()) {
checkPullLoadEnable();
} else {
mContentView.setEnablePullLoad(enable);
}
}
public boolean getPullLoadEnable() {
return mEnablePullLoad;
}
public boolean getPullRefreshEnable() {
return mEnablePullRefresh;
}
/**
* enable or disable pull down refresh feature.
*
* @param enable
*/
public void setPullRefreshEnable(boolean enable) {
mEnablePullRefresh = enable;
checkPullRefreshEnable();
}
private void checkPullRefreshEnable() {
if (mHeaderCallBack == null) {
return;
}
if (!mEnablePullRefresh) {
mHeaderCallBack.hide();
} else {
mHeaderCallBack.show();
}
}
private void checkPullLoadEnable() {
if (mFooterCallBack == null) {
return;
}
if (!mEnablePullLoad) {
mFooterCallBack.show(false);
} else {
mPullLoading = false;
mFooterCallBack.show(true);
mFooterCallBack.onStateRefreshing();
}
}
private void startLoadMore() {
if (!mPullLoading) {
mFooterCallBack.onStateRefreshing();
mPullLoading = true;
if (mRefreshViewListener != null) {
mRefreshViewListener.onLoadMore(false);
}
}
}
/**
* 如果第可变参数不为空,则代表是自动刷新
*
* @param currentY
* @param deltaY
* @param during
*/
private void updateHeaderHeight(int currentY, int deltaY, int... during) {
boolean isAutoRefresh = during != null && during.length > 0;
if (isAutoRefresh) {
mHeaderCallBack.onStateRefreshing();
startScroll(deltaY, during[0]);
} else {
if (mHolder.isOverHeader(deltaY)) {
deltaY = -mHolder.mOffsetY;
}
if (mEnablePullRefresh || mCanMoveHeaderWhenDisablePullRefresh) {
moveView(deltaY);
}
if (mEnablePullRefresh && !mPullRefreshing) {
if (mHolder.mOffsetY > mHeaderViewHeight) {
if (mState != XRefreshViewState.STATE_READY) {
mHeaderCallBack.onStateReady();
mState = XRefreshViewState.STATE_READY;
}
} else {
if (mState != XRefreshViewState.STATE_NORMAL) {
mHeaderCallBack.onStateNormal();
mState = XRefreshViewState.STATE_NORMAL;
}
}
}
}
}
/**
* 设置在下拉刷新被禁用的情况下,是否允许界面被下拉
*
* @param moveHeadWhenDisablePullRefresh 默认是true
*/
public void setMoveHeadWhenDisablePullRefresh(boolean moveHeadWhenDisablePullRefresh) {
mCanMoveHeaderWhenDisablePullRefresh = moveHeadWhenDisablePullRefresh;
}
/**
* 设置在上拉加载被禁用的情况下,是否允许界面被上拉
*
* @param moveFootWhenDisablePullLoadMore 默认为true
*/
public void setMoveFootWhenDisablePullLoadMore(boolean moveFootWhenDisablePullLoadMore) {
mCanMoveFooterWhenDisablePullLoadMore = moveFootWhenDisablePullLoadMore;
}
private boolean mReleaseToLoadMore = false;
private boolean mEnablePullUpWhenLoadCompleted = true;
private boolean canReleaseToLoadMore() {
return enableReleaseToLoadMore && mEnablePullLoad && mContentView != null && !mContentView.hasLoadCompleted() && !mContentView.isLoading();
}
private void releaseToLoadMore(boolean loadMore) {
mReleaseToLoadMore = loadMore;
mContentView.releaseToLoadMore(mReleaseToLoadMore);
}
private void updateFooterHeight(int deltaY) {
if (mEnablePullLoad) {
if (needAddFooterView()) {
if (isEmptyViewShowing()) {
if (mFooterCallBack.isShowing()) {
mFooterCallBack.show(false);
}
} else {
if (mState != XRefreshViewState.STATE_LOADING) {
mFooterCallBack.onStateRefreshing();
mState = XRefreshViewState.STATE_LOADING;
}
}
} else if (canReleaseToLoadMore()) {
releaseToLoadMore(mHolder.mOffsetY != 0);
}
}
if (needAddFooterView() || enableRecyclerViewPullUp) {
if (mEnablePullUpWhenLoadCompleted || !mContentView.hasLoadCompleted()) {
if (mContentView.hasLoadCompleted() && needAddFooterView() && mFooterCallBack != null && mFooterCallBack.isShowing()) {
mFooterCallBack.show(false);
}
/* if (!needAddFooterView() && mContentView.getState() != XRefreshViewState.STATE_COMPLETE && autoLoadMore) {
//当时是recyclerview,自动加载更多,并且没有加载完全的时候,不让Recyclerview上拉
} else */
if (mEnablePullLoad || mCanMoveFooterWhenDisablePullLoadMore) {
moveView(deltaY);
}
}
}
}
/**
* 设置是否自动刷新,默认不自动刷新
*
* @param autoRefresh true则自动刷新
*/
public void setAutoRefresh(boolean autoRefresh) {
this.autoRefresh = autoRefresh;
}
/**
* 设置是否自动加载更多,默认是
*
* @param autoLoadMore true则自动刷新
*/
public void setAutoLoadMore(boolean autoLoadMore) {
this.autoLoadMore = autoLoadMore;
if (mContentView != null) {
mContentView.setContainer(autoLoadMore ? this : null);
}
if (autoLoadMore) {
setPullLoadEnable(true);
}
}
public void startRefresh() {
if (!mEnablePullRefresh || mHolder.mOffsetY != 0 || mContentView.isLoading() || mPullRefreshing || !isEnabled()) {
return;
}
if (mLayoutReady) {
mNeedToRefresh = false;
updateHeaderHeight(0, mHeaderViewHeight, 0);
mPullRefreshing = true;
if (mRefreshViewListener != null) {
mRefreshViewListener.onRefresh();
}
mContentView.scrollToTop();
} else {
mNeedToRefresh = true;
}
}
/**
* reset header view's height.
*/
private void resetHeaderHeight() {
float height = mHolder.mOffsetY;
// refreshing and header isn't shown fully. do nothing.
if (mPullRefreshing && (height <= mHeaderViewHeight || height == 0)) {
return;
}
int offsetY;
if (mPullRefreshing) {
offsetY = mHeaderViewHeight - mHolder.mOffsetY;
startScroll(offsetY, Utils.computeScrollVerticalDuration(offsetY, getHeight()));
} else {
offsetY = 0 - mHolder.mOffsetY;
startScroll(offsetY, Utils.computeScrollVerticalDuration(offsetY, getHeight()));
}
L.d("resetHeaderHeight offsetY=" + offsetY);
}
public void moveView(int deltaY) {
mHolder.move(deltaY);
mHeaderView.offsetTopAndBottom(deltaY);
mContentView.offsetTopAndBottom(deltaY);
if (needAddFooterView()) {
mFooterView.offsetTopAndBottom(deltaY);
}
ViewCompat.postInvalidateOnAnimation(this);
if (mRefreshViewListener != null && (mContentView.isTop() || mPullRefreshing)) {
double headerMovePercent = 1.0 * mHolder.mOffsetY / mHeaderViewHeight;
// headerMovePercent = headerMovePercent > 1 ? 1 : headerMovePercent;
mRefreshViewListener.onHeaderMove(headerMovePercent, mHolder.mOffsetY);
mHeaderCallBack.onHeaderMove(headerMovePercent, mHolder.mOffsetY, deltaY);
}
}
private boolean mStopingRefresh = false;
/**
* stop refresh, reset header view.
*/
public void stopRefresh() {
stopRefresh(true);
}
/**
* stop refresh, reset header view.
*/
public void stopRefresh(boolean success) {
L.d("stopRefresh mPullRefreshing=" + mPullRefreshing);
if (mPullRefreshing == true) {
mStopingRefresh = true;
mHeaderCallBack.onStateFinish(success);
mState = XRefreshViewState.STATE_COMPLETE;
postDelayed(new Runnable() {
@Override
public void run() {
mPullRefreshing = false;
if (mStopingRefresh) {
resetHeaderHeight();
}
lastRefreshTime = Calendar.getInstance().getTimeInMillis();
}
}, mPinnedTime);
}
}
private long lastRefreshTime = -1;
/**
* 恢复上次刷新的时间
*
* @param lastRefreshTime
*/
public void restoreLastRefreshTime(long lastRefreshTime) {
this.lastRefreshTime = lastRefreshTime;
}
/**
* 在停止刷新的时候调用,记录这次刷新的时间,用于下次刷新的时候显示
*
* @return
*/
public long getLastRefreshTime() {
return lastRefreshTime;
}
/**
* 设置并显示上次刷新的时间
*/
private void setRefreshTime() {
if (lastRefreshTime <= 0) {
return;
}
mHeaderCallBack.setRefreshTime(lastRefreshTime);
}
/**
* stop load more, reset footer view.
*/
public void stopLoadMore() {
stopLoadMore(true);
}
/**
* stop load more, reset footer view.
*
* @param hideFooter hide footerview if true
*/
public void stopLoadMore(final boolean hideFooter) {
if (needAddFooterView()) {
if (mPullLoading == true) {
mStopingRefresh = true;
mState = XRefreshViewState.STATE_COMPLETE;
mFooterCallBack.onStateFinish(hideFooter);
if (mPinnedTime >= 1000) {// 在加载更多完成以后,只有mPinnedTime大于1s才生效,不然效果不好
postDelayed(new Runnable() {
@Override
public void run() {
endLoadMore(hideFooter);
}
}, mPinnedTime);
} else {
endLoadMore(hideFooter);
}
}
}
mContentView.stopLoading(hideFooter);
}
protected void resetLayout() {
enablePullUp(false);
if (mHolder.mOffsetY != 0 && !mStopingRefresh) {
startScroll(-mHolder.mOffsetY, Utils.computeScrollVerticalDuration(mHolder.mOffsetY, getHeight()));
}
}
protected void enablePullUp(boolean enablePullUp) {
this.enablePullUp = enablePullUp;
}
/**
* 此方法当没有更多数据时调用,不要与stopLoadMore()同时调用
*
* @param hasComplete
*/
public void setLoadComplete(boolean hasComplete) {
mHasLoadComplete = hasComplete;
if (needAddFooterView()) {
stopLoadMore();
if (!hasComplete && mEnablePullLoad && mFooterCallBack != null) {
mFooterCallBack.onStateRefreshing();
// mFooterCallBack.show(true);
}
}
mContentView.setLoadComplete(hasComplete);
}
public boolean hasLoadCompleted() {
return mHasLoadComplete;
}
private void endLoadMore(boolean hideFooter) {
mPullLoading = false;
startScroll(-mHolder.mOffsetY, 0);
// mFooterCallBack.onStateRefreshing();
if (mHasLoadComplete && hideFooter) {
mFooterCallBack.show(false);
}
}
/**
* @param offsetY 滑动偏移量,负数向上滑,正数反之
* @param duration 滑动持续时间
*/
public void startScroll(int offsetY, int duration) {
mScroller.startScroll(0, mHolder.mOffsetY, 0, offsetY, duration);
post(mRunnable);
}
private Runnable mRunnable = new Runnable() {
@Override
public void run() {
if (mScroller.computeScrollOffset()) {
int lastScrollY = mHolder.mOffsetY;
int currentY = mScroller.getCurrY();
int offsetY = currentY - lastScrollY;
lastScrollY = currentY;
moveView(offsetY);
int[] location = new int[2];
mHeaderView.getLocationInWindow(location);
L.d("currentY=" + currentY + ";mHolder.mOffsetY=" + mHolder.mOffsetY);
if (enableReleaseToLoadMore && mHolder.mOffsetY == 0 && mReleaseToLoadMore && mContentView != null && mContentView.isBottom()) {
mReleaseToLoadMore = false;
mContentView.startLoadMore(false, null, null);
}
post(this);
} else {
int currentY = mScroller.getCurrY();
if (mHolder.mOffsetY == 0) {
enablePullUp(true);
mStopingRefresh = false;
} else {
//有时scroller已经停止了,但是却没有回到应该在的位置,执行下面的方法恢复
if (mStopingRefresh && !mPullLoading && !mPullRefreshing) {
startScroll(-currentY, Utils.computeScrollVerticalDuration(currentY, getHeight()));
}
}
}
}
};
/**
* 设置Abslistview的滚动监听事件
*
* @param scrollListener
*/
public void setOnAbsListViewScrollListener(OnScrollListener scrollListener) {
mContentView.setOnAbsListViewScrollListener(scrollListener);
}
private View mEmptyView;
private View mTempTarget;
public void setEmptyView(View emptyView) {
Utils.removeViewFromParent(emptyView);
mEmptyView = emptyView;
addEmptyViewLayoutParams();
}
private void addEmptyViewLayoutParams() {
if (mEmptyView == null) {
return;
}
LayoutParams layoutparams = generateDefaultLayoutParams();
layoutparams.height = LayoutParams.MATCH_PARENT;
layoutparams.width = LayoutParams.MATCH_PARENT;
mEmptyView.setLayoutParams(layoutparams);
}
public void setEmptyView(@LayoutRes int emptyView) {
String resourceTypeName = getContext().getResources().getResourceTypeName(emptyView);
if (!resourceTypeName.contains("layout")) {
throw new RuntimeException(getContext().getResources().getResourceName(emptyView) + " is a illegal layoutid , please check your layout id first !");
}
setEmptyView(LayoutInflater.from(getContext()).inflate(emptyView, this, false));
}
private int waitForShowEmptyView = 0;
public void enableEmptyView(boolean enable) {
if (!mLayoutReady) {
waitForShowEmptyView = enable ? 1 : 2;
return;
}
View contentView = getChildAt(1);
if (enable) {
if (mEmptyView != null && contentView != mEmptyView) {
mTempTarget = getChildAt(1);
swapContentView(mEmptyView);
}
} else {
if (mTempTarget != null && contentView == mEmptyView) {
swapContentView(mTempTarget);
}
}
}
public boolean isEmptyViewShowing() {
if (mEmptyView != null && getChildCount() >= 2) {
View child = getChildAt(1);
return child == mEmptyView;
}
return false;
}
public View getEmptyView() {
return mEmptyView;
}
private void swapContentView(View newContentView) {
removeViewAt(1);
addView(newContentView, 1);
mContentView.setContentView(newContentView);
mContentView.scrollToTop();
}
/**
* 设置Recylerview的滚动监听事件
*/
public void setOnRecyclerViewScrollListener(
RecyclerView.OnScrollListener scrollListener) {
mContentView.setOnRecyclerViewScrollListener(scrollListener);
}
/**
* 设置静默加载时提前加载的item个数
*
* @param count
*/
public void setPreLoadCount(int count) {
mContentView.setPreLoadCount(count);
}
public void setXRefreshViewListener(XRefreshViewListener l) {
mRefreshViewListener = l;
mContentView.setXRefreshViewListener(l);
}
/**
* 是否开启Recyclerview的松开加载更多功能,默认开启
*/
public void enableReleaseToLoadMore(boolean enable) {
this.enableReleaseToLoadMore = enable;
}
/**
* 设置在数据加载完成以后,是否可以向上继续拉被刷新的view,默认为true
*
* @param enable
*/
public void enablePullUpWhenLoadCompleted(boolean enable) {
mEnablePullUpWhenLoadCompleted = enable;
}
/**
* 设置在被刷新的view滑倒最底部的时候,是否允许被刷新的view继续往上滑动,默认是true
*/
public void enableRecyclerViewPullUp(boolean enable) {
enableRecyclerViewPullUp = enable;
}
public void setFooterCallBack(IFooterCallBack footerCallBack) {
mFooterCallBack = footerCallBack;
}
/**
* 设置阻尼系数,建议使用默认的
*
* @param ratio 默认 1.8
*/
public void setDampingRatio(float ratio) {
OFFSET_RADIO = ratio;
}
/**
* 设置当下拉刷新完成以后,headerview和footerview被固定的时间
* 注:考虑到ui效果,只有时间大于1s的时候,footerview被固定的效果才会生效
*
* @param pinnedTime
*/
public void setPinnedTime(int pinnedTime) {
mPinnedTime = pinnedTime;
mContentView.setPinnedTime(pinnedTime);
}
/**
* 设置Recyclerview是否在数据加载完成以后隐藏footerview
*
* @param hide true则隐藏footerview,false则反之,默认隐藏
*/
public void setHideFooterWhenComplete(boolean hide) {
mContentView.setHideFooterWhenComplete(hide);
}
/**
* 设置在刷新的时候是否可以移动contentView
*
* @param isPinned true 固定不移动 反之,可以移动
*/
public void setPinnedContent(boolean isPinned) {
mIsPinnedContentWhenRefreshing = isPinned;
}
/**
* 设置自定义headerView
*
* @param headerView headerView必须要实现 IHeaderCallBack接口
*/
public void setCustomHeaderView(View headerView) {
if (headerView instanceof IHeaderCallBack) {
if (mHeaderView != null) {
removeView(mHeaderView);
}
mHeaderView = headerView;
dealAddHeaderView();
} else {
throw new RuntimeException("headerView must be implementes IHeaderCallBack!");
}
}
/**
* 设置自定义footerView
*
* @param footerView footerView必须要实现 IFooterCallBack接口
*/
public void setCustomFooterView(View footerView) {
if (footerView instanceof IFooterCallBack) {
if (mFooterView != null) {
removeView(mFooterView);
}
mFooterView = footerView;
dealAddFooterView();
} else {
throw new RuntimeException(
"footerView must be implementes IFooterCallBack!");
}
}
/**
* implements this interface to get refresh/load more event.
*/
public interface XRefreshViewListener {
public void onRefresh();
/**
* @param isSilence 是不是静默加载,静默加载即不显示footerview,自动监听滚动到底部并触发此回调
*/
public void onLoadMore(boolean isSilence);
/**
* 用户手指释放的监听回调
*
* @param direction >0: 下拉释放,<0:上拉释放 注:暂时没有使用这个方法
*/
public void onRelease(float direction);
/**
* 获取headerview显示的高度与headerview高度的比例
*
* @param headerMovePercent 移动距离和headerview高度的比例
* @param offsetY headerview移动的距离
*/
public void onHeaderMove(double headerMovePercent, int offsetY);
}
public static class SimpleXRefreshListener implements XRefreshViewListener {
@Override
public void onRefresh() {
}
@Override
public void onLoadMore(boolean isSilence) {
}
@Override
public void onRelease(float direction) {
}
@Override
public void onHeaderMove(double offset, int offsetY) {
}
}
}