package com.linxiao.framework.widget.pullrefresh;
import android.content.Context;
import android.support.annotation.Nullable;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.NestedScrollingChild;
import android.support.v4.view.NestedScrollingChildHelper;
import android.support.v4.view.NestedScrollingParent;
import android.support.v4.view.NestedScrollingParentHelper;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.Transformation;
import android.widget.FrameLayout;
/**
* 自定义下拉刷新布局
* <p>
* 下拉刷新头部使用{@link RefreshView} 铺成 MATCH_PARENT 的布局,
* 各种下拉刷新效果直接继承 RefreshView 并在其中实现即可
* </p>
* Created by linxiao on 2017/6/21.
*/
public class PullRefreshLayout extends FrameLayout implements NestedScrollingParent, NestedScrollingChild {
private static final String TAG = PullRefreshLayout.class.getSimpleName();
public interface OnRefreshListener {
void onRefresh();
}
public interface OnInterceptTouchEventListener {
boolean onInterceptTouchEvent(MotionEvent ev);
}
private static final int INVALID_POINTER = -1;
// 最大下拉距离 单位dp
private static final int DEFAULT_MAX_DRAG_DISTANCE = 80;
// 滑动阻尼
private static final float DRAG_RATE = .5f;
// 嵌套滑动辅助
private float mTotalUnconsumed;
private NestedScrollingParentHelper mNestedScrollingParentHelper;
private NestedScrollingChildHelper mNestedScrollingChildHelper;
private int[] mParentScrollConsumed = new int[2];
private int[] mParentOffsetInWindow = new int[2];
private boolean mNestedScrollInProgress;
private boolean dragging;
// 下拉刷新动画View
private RefreshView mRefreshView;
// 下拉刷新子容器
private View mTargetView;
// 插值器,用于回弹动画
private Interpolator mDecelerateInterpolator;
// 滑动判断距离
private float preX;
private int mTouchSlop;
private int mSpinnerFinalOffset;
// 滑动总距离
private int mTotalDragDistance;
// 初始Y轴坐标
private float mInitialMotionY;
// 滑动距离百分比
private float mDragPercent;
// 回弹时间
public int mDurationToStartPosition;
public int mDurationToCorrectPosition;
// 初始偏移量
private int mInitialOffsetTop;
// 当前距离顶部偏移量
private int mCurrentTranslationY;
// 动画缓存值
private int mFrom;
// 触发下拉的触摸点Id
private int mActivePointerId;
// 是否正在刷新
private boolean refreshing = false;
// 是否正在拉动
private boolean draggingHeader;
// 是否向下层容器传递触控事件
private boolean dispatchTouchDown;
// 默认下拉刷新监听器
private OnRefreshListener mOnRefreshListener;
// 事件拦截监听器
private OnInterceptTouchEventListener mInterceptListener;
private Animation moveToStartAnimation = new Animation() {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
int targetTop = mFrom - (int) (mFrom * interpolatedTime);
int offset = (int) (targetTop - getCurrentMoveDistance());
moveRefreshHeader(offset, false);
}
};
private Animation moveToRefreshAnimation = new Animation() {
@Override
public void applyTransformation(float interpolatedTime, Transformation t) {
int endTarget = mSpinnerFinalOffset;
int targetTop = (mFrom + (int) ((endTarget - mFrom) * interpolatedTime));
int offset = (int) (targetTop - getCurrentMoveDistance());
moveRefreshHeader(offset, false);
}
};
private Animation.AnimationListener mRefreshListener = new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
mRefreshView.setVisibility(View.VISIBLE);
if (refreshing) {
mRefreshView.startRefreshAnim();
}
}
@Override
public void onAnimationRepeat(Animation animation) {}
@Override
public void onAnimationEnd(Animation animation) {
if (!refreshing) {
mRefreshView.stopRefreshAnim();
moveToStartPosition();
}
mCurrentTranslationY = getCurrentMoveDistance();
}
};
private Animation.AnimationListener mToStartListener = new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
mRefreshView.stopRefreshAnim();
}
@Override
public void onAnimationRepeat(Animation animation) {}
@Override
public void onAnimationEnd(Animation animation) {
mCurrentTranslationY = getCurrentMoveDistance();
}
};
public PullRefreshLayout(Context context) {
super(context);
init(context, null);
}
public PullRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public PullRefreshLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
mDurationToStartPosition = getResources().getInteger(android.R.integer.config_longAnimTime);
mDurationToCorrectPosition = getResources().getInteger(android.R.integer.config_longAnimTime);
mSpinnerFinalOffset = mTotalDragDistance = dp2px(DEFAULT_MAX_DRAG_DISTANCE);
mDecelerateInterpolator = new DecelerateInterpolator(2);
setWillNotDraw(false);
ViewCompat.setChildrenDrawingOrderEnabled(this, true);
setRefreshView(new DefaultRefreshView(context));
mNestedScrollingParentHelper = new NestedScrollingParentHelper(this);
mNestedScrollingChildHelper = new NestedScrollingChildHelper(this);
setNestedScrollingEnabled(true);
}
/**
* 设置下拉刷新显示头布局
* */
public void setRefreshView(RefreshView refreshView) {
if (refreshView == null) {
return;
}
removeView(mRefreshView);
mRefreshView = refreshView;
mRefreshView.setVisibility(View.GONE);
addView(mRefreshView, 0, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
mSpinnerFinalOffset = mTotalDragDistance = mRefreshView.getMaxDragDistance();
}
/* ------------- implements from NestedScrollingParent ------------- */
@Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
return isEnabled() && !refreshing
&& (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
}
@Override
public void onNestedScrollAccepted(View child, View target, int axes) {
// Reset the counter of how much leftover scroll needs to be consumed.
mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, axes);
// Dispatch up to the nested parent
startNestedScroll(axes & ViewCompat.SCROLL_AXIS_VERTICAL);
mTotalUnconsumed = 0;
mNestedScrollInProgress = true;
}
@Override
public void onStopNestedScroll(Vi