package com.lcodecore.tkrefreshlayout; import android.content.Context; import android.content.res.TypedArray; import android.support.v4.view.MotionEventCompat; import android.support.v4.view.NestedScrollingChild; import android.support.v4.view.NestedScrollingChildHelper; import android.support.v4.view.ViewCompat; import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.ViewParent; import android.widget.FrameLayout; import android.widget.RelativeLayout; import com.lcodecore.tkrefreshlayout.footer.BallPulseView; import com.lcodecore.tkrefreshlayout.header.GoogleDotView; import com.lcodecore.tkrefreshlayout.processor.AnimProcessor; import com.lcodecore.tkrefreshlayout.processor.IDecorator; import com.lcodecore.tkrefreshlayout.processor.OverScrollDecorator; import com.lcodecore.tkrefreshlayout.processor.RefreshProcessor; import com.lcodecore.tkrefreshlayout.utils.DensityUtil; import java.lang.reflect.Constructor; import static android.support.v4.widget.ViewDragHelper.INVALID_POINTER; /** * Created by lcodecore on 16/3/2. */ public class TwinklingRefreshLayout extends RelativeLayout implements PullListener, NestedScrollingChild { //波浪的高度,最大扩展高度 protected float mMaxHeadHeight; protected float mMaxBottomHeight; //头部的高度 protected float mHeadHeight; //允许的越界回弹的高度 protected float mOverScrollHeight; //子控件 private View mChildView; //头部layout protected FrameLayout mHeadLayout; //整个头部 private FrameLayout mExtraHeadLayout; //附加顶部高度 private int mExHeadHeight = 0; private IHeaderView mHeadView; private IBottomView mBottomView; //设置的默认的header/footer class的完整包名+类名 private static String HEADER_CLASS_NAME = ""; private static String FOOTER_CLASS_NAME = ""; //底部高度 private float mBottomHeight; //底部layout private FrameLayout mBottomLayout; //是否刷新视图可见 protected boolean isRefreshVisible = false; //是否加载更多视图可见 protected boolean isLoadingVisible = false; //是否处于刷新状态(和isRefreshVisible区别为是否开启了enableKeepHeadWhenRefresh) protected boolean isRefreshing = false; protected boolean isLoadingMore = false; //是否需要加载更多,默认需要 protected boolean enableLoadmore = true; //是否需要下拉刷新,默认需要 protected boolean enableRefresh = true; //是否在越界回弹的时候显示下拉图标 protected boolean isOverScrollTopShow = true; //是否在越界回弹的时候显示上拉图标 protected boolean isOverScrollBottomShow = true; //是否隐藏刷新控件,开启越界回弹模式(开启之后刷新控件将隐藏) protected boolean isPureScrollModeOn = false; //是否自动加载更多 protected boolean autoLoadMore = false; //是否开启悬浮刷新模式 protected boolean floatRefresh = false; //是否允许进入越界回弹模式 protected boolean enableOverScroll = true; //是否在刷新或者加载更多后保持状态 protected boolean enableKeepIView = true; //是否在越界且处于刷新时直接显示顶部 protected boolean showRefreshingWhenOverScroll = true; //是否在越界且处于加载更多时直接显示底部 protected boolean showLoadingWhenOverScroll = true; private CoContext cp; private final int mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); //设置手势动作的监听器 private PullListener pullListener = this; private final NestedScrollingChildHelper mChildHelper; public TwinklingRefreshLayout(Context context) { this(context, null, 0); } public TwinklingRefreshLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public TwinklingRefreshLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TwinklingRefreshLayout, defStyleAttr, 0); try { mMaxHeadHeight = a.getDimensionPixelSize(R.styleable.TwinklingRefreshLayout_tr_max_head_height, (int) DensityUtil.dp2px(context, 120)); mHeadHeight = a.getDimensionPixelSize(R.styleable.TwinklingRefreshLayout_tr_head_height, (int) DensityUtil.dp2px(context, 80)); mMaxBottomHeight = a.getDimensionPixelSize(R.styleable.TwinklingRefreshLayout_tr_max_bottom_height, (int) DensityUtil.dp2px(context, 120)); mBottomHeight = a.getDimensionPixelSize(R.styleable.TwinklingRefreshLayout_tr_bottom_height, (int) DensityUtil.dp2px(context, 60)); mOverScrollHeight = a.getDimensionPixelSize(R.styleable.TwinklingRefreshLayout_tr_overscroll_height, (int) mHeadHeight); enableRefresh = a.getBoolean(R.styleable.TwinklingRefreshLayout_tr_enable_refresh, true); enableLoadmore = a.getBoolean(R.styleable.TwinklingRefreshLayout_tr_enable_loadmore, true); isPureScrollModeOn = a.getBoolean(R.styleable.TwinklingRefreshLayout_tr_pureScrollMode_on, false); isOverScrollTopShow = a.getBoolean(R.styleable.TwinklingRefreshLayout_tr_overscroll_top_show, true); isOverScrollBottomShow = a.getBoolean(R.styleable.TwinklingRefreshLayout_tr_overscroll_bottom_show, true); enableOverScroll = a.getBoolean(R.styleable.TwinklingRefreshLayout_tr_enable_overscroll, true); floatRefresh = a.getBoolean(R.styleable.TwinklingRefreshLayout_tr_floatRefresh, false); autoLoadMore = a.getBoolean(R.styleable.TwinklingRefreshLayout_tr_autoLoadMore, false); enableKeepIView = a.getBoolean(R.styleable.TwinklingRefreshLayout_tr_enable_keepIView, true); showRefreshingWhenOverScroll = a.getBoolean(R.styleable.TwinklingRefreshLayout_tr_showRefreshingWhenOverScroll, true); showLoadingWhenOverScroll = a.getBoolean(R.styleable.TwinklingRefreshLayout_tr_showLoadingWhenOverScroll, true); } finally { a.recycle(); } cp = new CoContext(); addHeader(); addFooter(); setFloatRefresh(floatRefresh); setAutoLoadMore(autoLoadMore); setEnableRefresh(enableRefresh); setEnableLoadmore(enableLoadmore); mChildHelper = new NestedScrollingChildHelper(this); setNestedScrollingEnabled(true); } private void addHeader() { FrameLayout headViewLayout = new FrameLayout(getContext()); LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, 0); layoutParams.addRule(ALIGN_PARENT_TOP); FrameLayout extraHeadLayout = new FrameLayout(getContext()); extraHeadLayout.setId(R.id.ex_header); LayoutParams layoutParams2 = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); this.addView(extraHeadLayout, layoutParams2); this.addView(headViewLayout, layoutParams); mExtraHeadLayout = extraHeadLayout; mHeadLayout = headViewLayout; if (mHeadView == null) { if (!TextUtils.isEmpty(HEADER_CLASS_NAME)) { try { Class headClazz = Class.forName(HEADER_CLASS_NAME); Constructor ct = headClazz.getDeclaredConstructor(Context.class); setHeaderView((IHeaderView) ct.newInstance(getContext())); } catch (Exception e) { Log.e("TwinklingRefreshLayout:", "setDefaultHeader classname=" + e.getMessage()); setHeaderView(new GoogleDotView(getContext())); } } else { setHeaderView(new GoogleDotView(getContext())); } } } private void addFooter() { FrameLayout bottomViewLayout = new FrameLayout(getContext()); LayoutParams layoutParams2 = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0); layoutParams2.addRule(ALIGN_PARENT_BOTTOM); bottomViewLayout.setLayoutParams(layoutParams2); mBottomLayout = bottomViewLayout; this.addView(mBottomLayout); if (mBottomView == null) { if (!TextUtils.isEmpty(FOOTER_CLASS_NAME)) { try { Class clazz = Class.forName(FOOTER_CLASS_NAME); Constructor ct = clazz.getDeclaredConstructor(Context.class); setBottomView((IBottomView) ct.newInstance(getContext())); } catch (Exception e) { Log.e("TwinklingRefreshLayout:", "setDefaultFooter classname=" + e.getMessage()); setBottomView(new BallPulseView(getContext())); } } else { setBottomView(new BallPulseView(getContext())); } } } @Override protected void onFinishInflate() { super.onFinishInflate(); //获得子控件 //onAttachedToWindow方法中mChildView始终是第0个child,把header、footer放到构造函数中,mChildView最后被inflate mChildView = getChildAt(3); cp.init(); decorator = new OverScrollDecorator(cp, new RefreshProcessor(cp)); initGestureDetector(); } private IDecorator decorator; private OnGestureListener listener; private void initGestureDetector() { listener = new OnGestureListener() { @Override public void onDown(MotionEvent ev) { decorator.onFingerDown(ev); } @Override public void onScroll(MotionEvent downEvent, MotionEvent currentEvent, float distanceX, float distanceY) { decorator.onFingerScroll(downEvent, currentEvent, distanceX, distanceY, vx, vy); } @Override public void onUp(MotionEvent ev, boolean isFling) { decorator.onFingerUp(ev, isFling); } @Override public void onFling(MotionEvent downEvent, MotionEvent upEvent, float velocityX, float velocityY) { decorator.onFingerFling(downEvent, upEvent, velocityX, velocityY); } }; } //VelocityX,VelocityY private float vx, vy; private VelocityTracker mVelocityTracker; private float mLastFocusX; private float mLastFocusY; private float mDownFocusX; private float mDownFocusY; private int mMaximumFlingVelocity = ViewConfiguration.getMaximumFlingVelocity(); private int mMinimumFlingVelocity = ViewConfiguration.getMinimumFlingVelocity(); private MotionEvent mCurrentDownEvent; private boolean mAlwaysInTapRegion; private int mTouchSlopSquare = mTouchSlop * mTouchSlop; private void detectGesture(MotionEvent ev, OnGestureListener listener) { final int action = ev.getAction(); if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } mVelocityTracker.addMovement(ev); final boolean pointerUp = (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP; final int skipIndex = pointerUp ? ev.getActionIndex() : -1; // Determine focal point float sumX = 0, sumY = 0; final int count = ev.getPointerCount(); for (int i = 0; i < count; i++) { if (skipIndex == i) continue; sumX += ev.getX(i); sumY += ev.getY(i); } final int div = pointerUp ? count - 1 : count; final float focusX = sumX / div; final float focusY = sumY / div; switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_POINTER_DOWN: mDownFocusX = mLastFocusX = focusX; mDownFocusY = mLastFocusY = focusY; break; case MotionEvent.ACTION_POINTER_UP: mDownFocusX = mLastFocusX = focusX; mDownFocusY = mLastFocusY = focusY; // Check the dot product of current velocities. // If the pointer that left was opposing another velocity vector, clear. mVelocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity); final int upIndex = ev.getActionIndex(); final int id1 = ev.getPointerId(upIndex); final float x1 = mVelocityTracker.getXVelocity(id1); final float y1 = mVelocityTracker.getYVelocity(id1); for (int i = 0; i < count; i++) { if (i == upIndex) continue; final int id2 = ev.getPointerId(i); final float x = x1 * mVelocityTracker.getXVelocity(id2); final float y = y1 * mVelocityTracker.getYVelocity(id2); final float dot = x + y; if (dot < 0) { mVelocityTracker.clear(); break; } } break; case MotionEvent.ACTION_DOWN: mDownFocusX = mLastFocusX = focusX; mDownFocusY = mLastFocusY = focusY; if (mCurrentDownEvent != null) { mCurrentDownEvent.recycle(); } mCurrentDownEvent = MotionEvent.obtain(ev); mAlwaysInTapRegion = true; listener.onDown(ev); break; case MotionEvent.ACTION_MOVE: final float scrollX = mLastFocusX - focusX; final float scrollY = mLastFocusY - focusY; if (mAlwaysInTapRegion) { final int deltaX = (int) (focusX - mDownFocusX); final int deltaY = (int) (focusY - mDownFocusY); int distance = (deltaX * deltaX) + (deltaY * deltaY); if (distance > mTouchSlopSquare) { listener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY); mLastFocusX = focusX; mLastFocusY = focusY; mAlwaysInTapRegion = false; } } else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) { listener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY); mLastFocusX = focusX; mLastFocusY = focusY; } break; case MotionEvent.ACTION_UP: final int pointerId = ev.getPointerId(0); mVelocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity); vy = mVelocityTracker.getYVelocity(pointerId); vx = mVelocityTracker.getXVelocity(pointerId); boolean isFling = false; if ((Math.abs(vy) > mMinimumFlingVelocity) || (Math.abs(vx) > mMinimumFlingVelocity)) { listener.onFling(mCurrentDownEvent, ev, vx, vy); isFling = true; } listener.onUp(ev, isFling); if (mVelocityTracker != null) { mVelocityTracker.recycle(); mVelocityTracker = null; } break; case MotionEvent.ACTION_CANCEL: mAlwaysInTapRegion = false; if (mVelocityTracker != null) { mVelocityTracker.recycle(); mVelocityTracker = null; } break; } } /************************************* 触摸事件处理 *****************************************/ @Override public boolean dispatchTouchEvent(MotionEvent ev) { //1.监听fling动作 2.获取手指滚动速度(存在滚动但非fling的状态)3.分发事件 boolean consume = decorator.dispatchTouchEvent(ev); detectGesture(ev, listener); detectNestedScroll(ev); return consume; } /** * 拦截事件 * * @return return true时,ViewGroup的事件有效,执行onTouchEvent事件 * return false时,事件向下传递,onTouchEvent无效 */ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { boolean intercept = decorator.interceptTouchEvent(ev); return intercept || super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent e) { boolean consume = decorator.dealTouchEvent(e); return consume || super.onTouchEvent(e); } private final int[] mScrollOffset = new int[2]; private final int[] mScrollConsumed = new int[2]; private final int[] mNestedOffsets = new int[2]; private int mActivePointerId = INVALID_POINTER; private int mLastTouchX; private int mLastTouchY; private boolean mIsBeingDragged; private boolean detectNestedScroll(MotionEvent e) { final MotionEvent vtev = MotionEvent.obtain(e); final int action = MotionEventCompat.getActionMasked(e); final int actionIndex = MotionEventCompat.getActionIndex(e); if (action == MotionEvent.ACTION_DOWN) { mNestedOffsets[0] = mNestedOffsets[1] = 0; } vtev.offsetLocation(mNestedOffsets[0], mNestedOffsets[1]); switch (action) { case MotionEvent.ACTION_DOWN: mActivePointerId = e.getPointerId(0); mLastTouchX = (int) e.getX(); mLastTouchY = (int) e.getY(); startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL); break; case MotionEventCompat.ACTION_POINTER_DOWN: mActivePointerId = e.getPointerId(actionIndex); mLastTouchX = (int) e.getX(actionIndex); mLastTouchY = (int) e.getY(actionIndex); break; case MotionEvent.ACTION_MOVE: final int index = e.findPointerIndex(mActivePointerId); if (index < 0) { Log.e("TwinklingRefreshLayout", "Error processing scroll; pointer index for id " + mActivePointerId + " not found. Did any MotionEvents get skipped?"); return false; } final int x = (int) e.getX(index); final int y = (int) e.getY(index); int dx = mLastTouchX - x; int dy = mLastTouchY - y; if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset)) { dx -= mScrollConsumed[0]; dy -= mScrollConsumed[1]; vtev.offsetLocation(mScrollOffset[0], mScrollOffset[1]); // Updated the nested offsets mNestedOffsets[0] += mScrollOffset[0]; mNestedOffsets[1] += mScrollOffset[1]; } if (!mIsBeingDragged && Math.abs(dy) > mTouchSlop) { final ViewParent parent = getParent(); if (parent != null) { parent.requestDisallowInterceptTouchEvent(true); } mIsBeingDragged = true; if (dy > 0) { dy -= mTouchSlop; } else { dy += mTouchSlop; } } if (mIsBeingDragged) { mLastTouchY = y - mScrollOffset[1]; final int scrolledDeltaY = 0; final int unconsumedY = dy - scrolledDeltaY; if (dispatchNestedScroll(0, scrolledDeltaY, 0, unconsumedY, mScrollOffset)) { mLastTouchX -= mScrollOffset[0]; mLastTouchY -= mScrollOffset[1]; vtev.offsetLocation(mScrollOffset[0], mScrollOffset[1]); mNestedOffsets[0] += mScrollOffset[0]; mNestedOffsets[1] += mScrollOffset[1]; } } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: stopNestedScroll(); mIsBeingDragged = false; mActivePointerId = INVALID_POINTER; break; } vtev.recycle(); return true; } //NestedScroll @Override public void setNestedScrollingEnabled(boolean enabled) { mChildHelper.setNestedScrollingEnabled(enabled); } @Override public boolean isNestedScrollingEnabled() { return mChildHelper.isNestedScrollingEnabled(); } @Override public boolean startNestedScroll(int axes) { return mChildHelper.startNestedScroll(axes); } @Override public void stopNestedScroll() { mChildHelper.stopNestedScroll(); } @Override public boolean hasNestedScrollingParent() { return mChildHelper.hasNestedScrollingParent(); } @Override public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) { return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow); } @Override public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow); } @Override public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { return mChildHelper.dispatchNestedFling(velocityX, velocityY, consumed); } @Override public boolean dispatchNestedPreFling(float velocityX, float velocityY) { return mChildHelper.dispatchNestedPreFling(velocityX, velocityY); } /************************************** 开放api区 *****************************************/ //设置默认的header class 名 public static void setDefaultHeader(String className) { HEADER_CLASS_NAME = className; } //设置默认的footer class 名 public static void setDefaultFooter(String className) { FOOTER_CLASS_NAME = className; } //主动刷新 public void startRefresh() { cp.startRefresh(); } //主动加载跟多 public void startLoadMore() { cp.startLoadMore(); } /** * 结束刷新 */ public void finishRefreshing() { cp.finishRefreshing(); } /** * 结束加载更多 */ public void finishLoadmore() { cp.finishLoadmore(); } /** * 手动设置刷新View */ public void setTargetView(View targetView) { if (targetView != null) mChildView = targetView; } /** * 手动设置RefreshLayout的装饰器 */ public void setDecorator(IDecorator decorator1) { if (decorator1 != null) decorator = decorator1; } /** * 设置头部刷新View */ public void setHeaderView(final IHeaderView headerView) { if (headerView != null) { mHeadLayout.removeAllViewsInLayout(); mHeadLayout.addView(headerView.getView()); mHeadView = headerView; } } /** * 设置固定在顶部的header */ @Deprecated public void addFixedExHeader(final View view) { if (view != null && mExtraHeadLayout != null) { mExtraHeadLayout.addView(view); mExtraHeadLayout.bringToFront(); if (floatRefresh) mHeadLayout.bringToFront(); cp.onAddExHead(); cp.setExHeadFixed(); } } /** * 获取额外附加的头部 */ public View getExtraHeaderView() { return mExtraHeadLayout; } /** * 设置底部View */ public void setBottomView(final IBottomView bottomView) { if (bottomView != null) { mBottomLayout.removeAllViewsInLayout(); mBottomLayout.addView(bottomView.getView()); mBottomView = bottomView; } } public void setFloatRefresh(boolean ifOpenFloatRefreshMode) { floatRefresh = ifOpenFloatRefreshMode; if (!floatRefresh) return; post(new Runnable() { @Override public void run() { if (mHeadLayout != null) mHeadLayout.bringToFront(); } }); } /** * 设置wave的下拉高度 */ public void setMaxHeadHeight(float maxHeightDp) { this.mMaxHeadHeight = DensityUtil.dp2px(getContext(), maxHeightDp); } /** * 设置下拉头的高度 */ public void setHeaderHeight(float headHeightDp) { this.mHeadHeight = DensityUtil.dp2px(getContext(), headHeightDp); } public void setMaxBottomHeight(float maxBottomHeight) { mMaxBottomHeight = DensityUtil.dp2px(getContext(), maxBottomHeight); } /** * 设置底部高度 */ public void setBottomHeight(float bottomHeightDp) { this.mBottomHeight = DensityUtil.dp2px(getContext(), bottomHeightDp); } /** * 是否允许加载更多 */ public void setEnableLoadmore(boolean enableLoadmore1) { enableLoadmore = enableLoadmore1; if (mBottomView != null) { if (enableLoadmore) mBottomView.getView().setVisibility(VISIBLE); else mBottomView.getView().setVisibility(GONE); } } /** * 是否允许下拉刷新 */ public void setEnableRefresh(boolean enableRefresh1) { this.enableRefresh = enableRefresh1; if (mHeadView != null) { if (enableRefresh) mHeadView.getView().setVisibility(VISIBLE); else mHeadView.getView().setVisibility(GONE); } } /** * 是否允许越界时显示头部刷新控件 */ public void setOverScrollTopShow(boolean isOverScrollTopShow) { this.isOverScrollTopShow = isOverScrollTopShow; } /** * 是否允许越界时显示底部刷新控件 */ public void setOverScrollBottomShow(boolean isOverScrollBottomShow) { this.isOverScrollBottomShow = isOverScrollBottomShow; } /** * 是否允许越界时显示刷新控件(setOverScrollTopShow,setOverScrollBottomShow统一设置方法) */ public void setOverScrollRefreshShow(boolean isOverScrollRefreshShow) { this.isOverScrollTopShow = isOverScrollRefreshShow; this.isOverScrollBottomShow = isOverScrollRefreshShow; } /** * 是否允许开启越界回弹模式 */ public void setEnableOverScroll(boolean enableOverScroll1) { this.enableOverScroll = enableOverScroll1; } /** * 是否开启纯净的越界回弹模式,开启时刷新和加载更多控件不显示 */ public void setPureScrollModeOn() { isPureScrollModeOn = true; isOverScrollTopShow = false; isOverScrollBottomShow = false; setMaxHeadHeight(mOverScrollHeight); setHeaderHeight(mOverScrollHeight); setMaxBottomHeight(mOverScrollHeight); setBottomHeight(mOverScrollHeight); } /** * 设置越界高度 */ public void setOverScrollHeight(float overScrollHeightDp) { this.mOverScrollHeight = DensityUtil.dp2px(getContext(), overScrollHeightDp); } /** * 设置OverScroll时自动加载更多 * * @param ifAutoLoadMore 为true表示底部越界时主动进入加载跟多模式,否则直接回弹 */ public void setAutoLoadMore(boolean ifAutoLoadMore) { autoLoadMore = ifAutoLoadMore; if (!autoLoadMore) return; setEnableLoadmore(true); } public void showRefreshingWhenOverScroll(boolean ifShow) { showRefreshingWhenOverScroll = ifShow; } public void showLoadingWhenOverScroll(boolean ifShow) { showLoadingWhenOverScroll = ifShow; } public void setEnableKeepIView(boolean ifKeep) { enableKeepIView = ifKeep; } /** * 设置刷新控件监听器 */ private RefreshListenerAdapter refreshListener; public void setOnRefreshListener(RefreshListenerAdapter refreshListener) { if (refreshListener != null) { this.refreshListener = refreshListener; } } @Override public void onPullingDown(TwinklingRefreshLayout refreshLayout, float fraction) { mHeadView.onPullingDown(fraction, mMaxHeadHeight, mHeadHeight); if (!enableRefresh) return; if (refreshListener != null) refreshListener.onPullingDown(refreshLayout, fraction); } @Override public void onPullingUp(TwinklingRefreshLayout refreshLayout, float fraction) { mBottomView.onPullingUp(fraction, mMaxHeadHeight, mHeadHeight); if (!enableLoadmore) return; if (refreshListener != null) refreshListener.onPullingUp(refreshLayout, fraction); } @Override public void onPullDownReleasing(TwinklingRefreshLayout refreshLayout, float fraction) { mHeadView.onPullReleasing(fraction, mMaxHeadHeight, mHeadHeight); if (!enableRefresh) return; if (refreshListener != null) refreshListener.onPullDownReleasing(refreshLayout, fraction); } @Override public void onPullUpReleasing(TwinklingRefreshLayout refreshLayout, float fraction) { mBottomView.onPullReleasing(fraction, mMaxBottomHeight, mBottomHeight); if (!enableLoadmore) return; if (refreshListener != null) refreshListener.onPullUpReleasing(refreshLayout, fraction); } @Override public void onRefresh(TwinklingRefreshLayout refreshLayout) { mHeadView.startAnim(mMaxHeadHeight, mHeadHeight); if (refreshListener != null) refreshListener.onRefresh(refreshLayout); } @Override public void onLoadMore(TwinklingRefreshLayout refreshLayout) { mBottomView.startAnim(mMaxBottomHeight, mBottomHeight); if (refreshListener != null) refreshListener.onLoadMore(refreshLayout); } @Override public void onFinishRefresh() { if (refreshListener != null) { refreshListener.onFinishRefresh(); } if (!cp.isEnableKeepIView() && !cp.isRefreshing()) return; mHeadView.onFinish(new OnAnimEndListener() { @Override public void onAnimEnd() { cp.finishRefreshAfterAnim(); } }); } @Override public void onFinishLoadMore() { if (refreshListener != null) { refreshListener.onFinishLoadMore(); } if (!cp.isEnableKeepIView() && !cp.isLoadingMore()) return; mBottomView.onFinish(); } @Override public void onRefreshCanceled() { if (refreshListener != null) refreshListener.onRefreshCanceled(); } @Override public void onLoadmoreCanceled() { if (refreshListener != null) refreshListener.onLoadmoreCanceled(); } public class CoContext { private AnimProcessor animProcessor; private final static int PULLING_TOP_DOWN = 0; private final static int PULLING_BOTTOM_UP = 1; private int state = PULLING_TOP_DOWN; private static final int EX_MODE_NORMAL = 0; private static final int EX_MODE_FIXED = 1; private int exHeadMode = EX_MODE_NORMAL; public CoContext() { animProcessor = new AnimProcessor(this); } public void init() { if (isPureScrollModeOn) { setOverScrollTopShow(false); setOverScrollBottomShow(false); if (mHeadLayout != null) mHeadLayout.setVisibility(GONE); if (mBottomLayout != null) mBottomLayout.setVisibility(GONE); } } public AnimProcessor getAnimProcessor() { return animProcessor; } public boolean isEnableKeepIView() { return enableKeepIView; } public boolean showRefreshingWhenOverScroll() { return showRefreshingWhenOverScroll; } public boolean showLoadingWhenOverScroll() { return showLoadingWhenOverScroll; } public float getMaxHeadHeight() { return mMaxHeadHeight; } public int getHeadHeight() { return (int) mHeadHeight; } public int getExtraHeadHeight() { return mExtraHeadLayout.getHeight(); } public int getMaxBottomHeight() { return (int) mMaxBottomHeight; } public int getBottomHeight() { return (int) mBottomHeight; } public int getOsHeight() { return (int) mOverScrollHeight; } public View getTargetView() { return mChildView; } public View getHeader() { return mHeadLayout; } public View getFooter() { return mBottomLayout; } public int getTouchSlop() { return mTouchSlop; } public void resetHeaderView() { if (mHeadView != null) mHeadView.reset(); } public void resetBottomView() { if (mBottomView != null) mBottomView.reset(); } public View getExHead() { return mExtraHeadLayout; } public void setExHeadNormal() { exHeadMode = EX_MODE_NORMAL; } public void setExHeadFixed() { exHeadMode = EX_MODE_FIXED; } public boolean isExHeadNormal() { return exHeadMode == EX_MODE_NORMAL; } public boolean isExHeadFixed() { return exHeadMode == EX_MODE_FIXED; } /** * 在添加附加Header前锁住,阻止一些额外的位移动画 */ private boolean isExHeadLocked = true; public boolean isExHeadLocked() { return isExHeadLocked; } //添加了额外头部时触发 public void onAddExHead() { isExHeadLocked = false; LayoutParams params = (LayoutParams) mChildView.getLayoutParams(); params.addRule(BELOW, mExtraHeadLayout.getId()); mChildView.setLayoutParams(params); requestLayout(); } /** * 主动刷新、加载更多、结束 */ public void startRefresh() { post(new Runnable() { @Override public void run() { setStatePTD(); if (!isPureScrollModeOn && mChildView != null) { setRefreshing(true); animProcessor.animHeadToRefresh(); } } }); } public void startLoadMore() { post(new Runnable() { @Override public void run() { setStatePBU(); if (!isPureScrollModeOn && mChildView != null) { setLoadingMore(true); animProcessor.animBottomToLoad(); } } }); } public void finishRefreshing() { onFinishRefresh(); } public void finishRefreshAfterAnim() { if (mChildView != null) { animProcessor.animHeadBack(true); } } public void finishLoadmore() { onFinishLoadMore(); if (mChildView != null) { animProcessor.animBottomBack(true); } } public boolean enableOverScroll() { return enableOverScroll; } public boolean allowPullDown() { return enableRefresh || enableOverScroll; } public boolean allowPullUp() { return enableLoadmore || enableOverScroll; } public boolean enableRefresh() { return enableRefresh; } public boolean enableLoadmore() { return enableLoadmore; } public boolean allowOverScroll() { return (!isRefreshVisible && !isLoadingVisible); } public boolean isRefreshVisible() { return isRefreshVisible; } public boolean isLoadingVisible() { return isLoadingVisible; } public void setRefreshVisible(boolean visible) { isRefreshVisible = visible; } public void setLoadVisible(boolean visible) { isLoadingVisible = visible; } public void setRefreshing(boolean refreshing) { isRefreshing = refreshing; } public boolean isRefreshing() { return isRefreshing; } public boolean isLoadingMore() { return isLoadingMore; } public void setLoadingMore(boolean loadingMore) { isLoadingMore = loadingMore; } public boolean isOpenFloatRefresh() { return floatRefresh; } public boolean autoLoadMore() { return autoLoadMore; } public boolean isPureScrollModeOn() { return isPureScrollModeOn; } public boolean isOverScrollTopShow() { return isOverScrollTopShow; } public boolean isOverScrollBottomShow() { return isOverScrollBottomShow; } public void onPullingDown(float offsetY) { pullListener.onPullingDown(TwinklingRefreshLayout.this, offsetY / mHeadHeight); } public void onPullingUp(float offsetY) { pullListener.onPullingUp(TwinklingRefreshLayout.this, offsetY / mBottomHeight); } public void onRefresh() { pullListener.onRefresh(TwinklingRefreshLayout.this); } public void onLoadMore() { pullListener.onLoadMore(TwinklingRefreshLayout.this); } public void onFinishRefresh() { pullListener.onFinishRefresh(); } public void onFinishLoadMore() { pullListener.onFinishLoadMore(); } public void onPullDownReleasing(float offsetY) { pullListener.onPullDownReleasing(TwinklingRefreshLayout.this, offsetY / mHeadHeight); } public void onPullUpReleasing(float offsetY) { pullListener.onPullUpReleasing(TwinklingRefreshLayout.this, offsetY / mBottomHeight); } public boolean dispatchTouchEventSuper(MotionEvent ev) { return TwinklingRefreshLayout.super.dispatchTouchEvent(ev); } public void onRefreshCanceled() { pullListener.onRefreshCanceled(); } public void onLoadmoreCanceled() { pullListener.onLoadmoreCanceled(); } public void setStatePTD() { state = PULLING_TOP_DOWN; } public void setStatePBU() { state = PULLING_BOTTOM_UP; } public boolean isStatePTD() { return PULLING_TOP_DOWN == state; } public boolean isStatePBU() { return PULLING_BOTTOM_UP == state; } private boolean prepareFinishRefresh = false; private boolean prepareFinishLoadMore = false; public boolean isPrepareFinishRefresh() { return prepareFinishRefresh; } public boolean isPrepareFinishLoadMore() { return prepareFinishLoadMore; } public void setPrepareFinishRefresh(boolean prepareFinishRefresh) { this.prepareFinishRefresh = prepareFinishRefresh; } public void setPrepareFinishLoadMore(boolean prepareFinishLoadMore) { this.prepareFinishLoadMore = prepareFinishLoadMore; } } }