本来Google推出的SwipeRefreshLayout已经能够满足大部分的需求了。然而,由于其定制性较差,下拉刷新的样式无法修改,而且被嵌套的View也无法跟随手指的滑动而滑动。基于以上考虑,定制自己强大的SuperSwipeRefreshLayout。
Feature
- 非侵入式,对原来的ListView、RecyclerView没有任何影响,用法和SwipeRefreshLayout类似。
- 可自定义头部View的样式,调用setHeaderView方法即可
- 支持更多:RecyclerView,ListView,ScrollView,GridView等等。
- 被包含的View(RecyclerView,ListView etc.)可跟随手指的滑动而滑动
默认是跟随手指的滑动而滑动,也可以设置为不跟随:setTargetScrollWithLayout(false) - 回调方法更多
比如:onRefresh() onPullDistance(int distance)和onPullEnable(boolean enable)
开发人员可以根据下拉过程中distance的值做一系列动画。
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.os.Build;
import android.os.Handler;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Transformation;
import android.widget.AbsListView;
import android.widget.RelativeLayout;
import android.widget.ScrollView;
/**
* @Author Zheng Haibo
* @PersonalWebsite http://www.mobctrl.net
* @Description 自定义CustomeSwipeRefreshLayout<br>
* 支持下拉刷新和上拉加载更多<br>
* 非侵入式,对原来的ListView、RecyclerView没有任何影响,用法和SwipeRefreshLayout类似。<br>
* 可自定义头部View的样式,调用setHeaderView方法即可 <br>
* 可自定义页尾View的样式,调用setFooterView方法即可 <br>
* 支持RecyclerView,ListView,ScrollView,GridView等等。<br>
* 被包含的View(RecyclerView,ListView etc.)可跟随手指的滑动而滑动<br>
* 默认是跟随手指的滑动而滑动,也可以设置为不跟随:setTargetScrollWithLayout(false) 回调方法更多<br>
* 比如:onRefresh() onPullDistance(int distance)和onPullEnable(boolean
* enable)<br>
* 开发人员可以根据下拉过程中distance的值做一系列动画。 <br>
*/
@SuppressLint("ClickableViewAccessibility")
public class SuperSwipeRefreshLayout extends ViewGroup {
private static final String LOG_TAG = "CustomeSwipeRefreshLayout";
private static final int HEADER_VIEW_HEIGHT = 50;// HeaderView height (dp)
private static final float DECELERATE_INTERPOLATION_FACTOR = 2f;
private static final int INVALID_POINTER = -1;
private static final float DRAG_RATE = .5f;
private static final int SCALE_DOWN_DURATION = 150;
private static final int ANIMATE_TO_TRIGGER_DURATION = 200;
private static final int ANIMATE_TO_START_DURATION = 200;
private static final int DEFAULT_CIRCLE_TARGET = 64;
// SuperSwipeRefreshLayout内的目标View,比如RecyclerView,ListView,ScrollView,GridView
// etc.
private View mTarget;
private OnPullRefreshListener mListener;// 下拉刷新listener
private OnPushLoadMoreListener mOnPushLoadMoreListener;// 上拉加载更多
private boolean mRefreshing = false;
private boolean mLoadMore = false;
private int mTouchSlop;
private float mTotalDragDistance = -1;
private int mMediumAnimationDuration;
private int mCurrentTargetOffsetTop;
private boolean mOriginalOffsetCalculated = false;
private float mInitialMotionY;
private boolean mIsBeingDragged;
private int mActivePointerId = INVALID_POINTER;
private boolean mScale;
private boolean mReturningToStart;
private final DecelerateInterpolator mDecelerateInterpolator;
private static final int[] LAYOUT_ATTRS = new int[]{android.R.attr.enabled};
private HeadViewContainer mHeadViewContainer;
private RelativeLayout mFooterViewContainer;
private int mHeaderViewIndex = -1;
private int mFooterViewIndex = -1;
protected int mFrom;
private float mStartingScale;
protected int mOriginalOffsetTop;
private Animation mScaleAnimation;
private Animation mScaleDownAnimation;
private Animation mScaleDownToStartAnimation;
// 最后停顿时的偏移量px,与DEFAULT_CIRCLE_TARGET正比
private float mSpinnerFinalOffset;
private boolean mNotify;
private int mHeaderViewWidth;// headerView的宽度
private int mFooterViewWidth;
private int mHeaderViewHeight;
private int mFooterViewHeight;
private boolean mUsingCustomStart;
private boolean targetScrollWithLayout = true;
private int pushDistance = 0;
private CircleProgressView defaultProgressView = null;
private boolean usingDefaultHeader = true;
private float density = 1.0f;
private boolean isProgressEnable = true;
/**
* 下拉时,超过距离之后,弹回来的动画监听器
*/
private Animation.AnimationListener mRefreshListener = new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
isProgressEnable = false;
}
@Override
public void onAnimationRepeat(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
isProgressEnable = true;
if (mRefreshing) {
if (mNotify) {
if (usingDefaultHeader) {
ViewCompat.setAlpha(defaultProgressView, 1.0f);
defaultProgressView.setOnDraw(true);
new Thread(defaultProgressView).start();
}
if (mListener != null) {
mListener.onRefresh();
}
}
} else {
mHeadViewContainer.setVisibility(View.GONE);
if (mScale) {
setAnimationProgress(0);
} else {
setTargetOffsetTopAndBottom(mOriginalOffsetTop
- mCurrentTargetOffsetTop, true);
}
}
mCurrentTargetOffsetTop = mHeadViewContainer.getTop();
updateListenerCallBack();
}
};
/**
* 更新回调
*/
private void updateListenerCallBack() {
int distance = mCurrentTargetOffsetTop + mHeadViewContainer.getHeight();
if (mListener != null) {
mListener.onPullDistance(distance);
}
if (usingDefaultHeader && isProgressEnable) {
defaultProgressView.setPullDistance(distance);
}
}
/**
* 添加头布局
*
* @param child
*/
public void setHeaderView(View child) {
if (child == null) {
return;
}
if (mHeadViewContainer == null) {
return;
}
usingDefaultHeader = false;
mHeadViewContainer.removeAllViews();
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
mHeaderViewWidth, mHeaderViewHeight);
layoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
mHeadViewContainer.addView(child, layoutParams);
}
public void setFooterView(View child) {
if (child == null) {
return;
}
if (mFooterViewContainer == null) {
return;
}
mFooterViewContainer.removeAllViews();
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
mFooterViewWidth, mFooterViewHeight);
mFooterViewContainer.addView(child, layoutParams);
}
public SuperSwipeRefreshLayout(Context context) {
this(context, null);
}
@SuppressWarnings("deprecation")
public SuperSwipeRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
/**
* getScaledTouchSlop是一个距离,表示滑动的时候,手的移动要大于这个距离才开始移动控件。如果小于这个距离就不触发移动控件
*/
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
mMediumAnimationDuration = getResources().getInteger(
android.R.integer.config_mediumAnimTime);
setWillNotDraw(false);
mDecelerateInterpolator = new DecelerateInterpolator(
DECELERATE_INTERPOLATION_FACTOR);
final TypedArray a = context
.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
setEnabled(a.getBoolean(0, true));
a.recycle();
WindowManager wm = (WindowManager) context
.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
final DisplayMetrics metrics = getResources().getDisplayMetrics();
mHeaderViewWidth = (int) display.getWidth();
mFooterViewWidth = (int) display.getWidth();
mHeaderViewHeight = (int) (HEADER_VIEW_HEIGHT * metrics.density);
mFooterViewHeight = (int) (HEADER_VIEW_HEIGHT * metrics.density);
defaultProgressView = new CircleProgressView(getContext());
createHeaderViewContainer();
createFooterViewContainer();
// ViewCompat.setChildrenDrawingOrderEnabled(this, true);
mSpinnerFinalOffset = DEFAULT_CIRCLE_TARGET * metrics.density;
density = metrics.density;
mTotalDragDistance = mSpinnerFinalOffset;
}
/**
* 孩子节点绘制的顺序
*
* @param childCount
* @param i
* @return
*/
@Override
protected int getChildDrawingOrder(int childCount, int i) {
// 将新添加的View,放到最后绘制
if (mHeaderViewIndex < 0 && mFooterViewIndex < 0) {
return i;
}
if (i == childCount - 2) {
return mHeaderViewIndex;
}
if (i == childCount - 1) {
return mFooterViewIndex;
}
int bigIndex = mFooterViewIndex > mHeaderViewIndex ? mFooterViewIndex
: mHeaderViewIndex;
int smallIndex = mFooterViewIndex < mHeaderViewIndex ? mFooterViewIndex
: mHeaderViewIndex;
if (i >= smallIndex && i < bigIndex - 1) {
return i + 1;
}
if (i >= bigIndex || (i == bigIndex - 1)) {
return i + 2;
}
return i;
}
/**
* 创建头布局的容器
*/
private void createHeaderViewContainer() {
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
(int) (mHeaderViewHeight * 0.8),
(int) (mHeaderViewHeight * 0.8));
layoutParams.addRule(RelativeLayout.CENTER_HORIZONTAL);
layoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
mHeadViewContainer = new HeadViewContainer(getContext());
mHeadViewContainer.setVisibility(View.GONE);
defaultProgressView.setVisibility(View.VISIBLE);
defaultProgressView.setOnDraw(false);
mHeadViewContainer.addView(defaultProgressView, layoutParams);
addView(mHeadViewContainer);
}
/**
* 添加底部布局
*/
private void createFooterViewContainer() {
mFooterViewContainer = new RelativeLayout(getContext());
mFooterViewContainer.setVisibility(View.GONE);
addView(mFooterViewContainer);
}
/**
* 设置
*
* @param listener
*/
public void setOnPullRefreshListener(OnPullRefreshListener listener) {
mListener = listener;
}
public void setHeaderViewBackgroundColor(int color) {
mHeadViewContainer.setBackgroundColor(color);
}
/**
* 设置上拉加载更多的接口
*
* @param onPushLoadMoreListener
*/
public void setOnPushLoadMoreListener(
OnPushLoadMoreListener onPushLoadMoreListener) {
this.mOnPushLoadMoreListener = onPushLoadMoreListener;
}
/**
* Notify the widget that refresh state has changed. Do not call this when
* refresh is triggered by a swipe gesture.
*
* @param refreshing Whether or not the view should show refresh progress.
*/
public void setRefreshing(boolean refreshing) {
if (refreshing && mRefreshing != refreshing) {
// scale and show
mRefreshing = refreshing;
int endTarget = 0;
if (!mUsingCustomStart) {
endTarget = (int) (mSpinnerFinalOffset + mOriginalOffsetTop);
} else {
endTarget = (int) mSpinnerFinalOffset;
}
setTargetOffsetTopAndBottom(endTarget - mCurrentTargetOffsetTop,
true /* requires update */);
mNotify = false;
startScaleUpAnimation(mRefreshListener);
} else {
setRefreshing(refreshing, false /* notify */);
if (usingDefaultHeader) {
defaultProgressView.setOnDraw(false);
}
}
}
private void startScaleUpAnimation(AnimationListener listener) {
mHeadViewContainer.setVisibility(View.VISIBLE);
mScaleAnimation = new Animation() {
@Override
public void applyTransformation(float interpolatedTime,
Transformation t) {
setAnimationProgress(interpolatedTime);
}
};
mSc