无入侵的猫眼下拉刷新,无需重写RecyclerView,像SwipeRefreshLayout一样使用简单
最近在做一个高仿的猫眼App,观察猫眼电影App的下拉刷新后,发现其中还是有小细节的,所以决定自己动手撸了一个猫眼下拉刷新(其实是网上实在没有找到可以抄的代码。。),结合SuperRefreshLayout加上自定义的一个刷新头部达到了猫眼App的下拉刷新效果
先上图
效果和正版基本上是一样的,接下来贴出实现方法和思路
由于是基于SuperRefreshLayout重写的下拉刷新的接口实现,所以原有的功能都会有(无入侵,支持RecyclerView,ScrollView,etc)
下拉刷新几个阶段:
1.下拉过程中,达到一定的距离后红圈开始逐渐显示
2.还未达到可以刷新的距离时向上滑动,红圈会逐渐消失
3.当达到刷新距离时,松手红圈开始转动
需要编写的几个类
1.刷新的头部(红圈和灰色猫眼icon)
2.实现SuperSwipeRefreshLayout.OnPullRefreshListener 接口
先看头部View
主要实现在onDraw方法
public class RefreshView extends ImageView {
private Paint mPaint;
private float progress;//进度,就是显示的程度
private boolean isAnimate;//判断是否在旋转
private int rotateProgress;//旋转角度
private Handler mHandler;
public RefreshView(Context context) {
this(context, null);
}
public RefreshView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RefreshView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
rotateProgress = 0;
progress = 0f;
mHandler = new Handler();
}
@SuppressLint("DrawAllocation")
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int minWidth = (int) (this.getWidth() * progress);
int minHeight = (int) (this.getHeight() * progress);
//开始滑动的时候才进行绘制
if (minWidth > 1 && minHeight > 1) {
Bitmap inner = BitmapFactory.decodeResource(getResources(), R.drawable.bg_pull_process);
Bitmap circle = BitmapFactory.decodeResource(getResources(), R.drawable.ic_progress_out);
float scaleW = (float) getWidth() / (float) inner.getWidth();
float scaleH = (float) getHeight() / (float) inner.getHeight();
int scaleWidth = (int) (inner.getWidth() * scaleW);
int scaleHeight = (int) (inner.getHeight() * scaleH);
//画内部的灰色猫icon,canvas画图,大小根据图片的
canvas.drawBitmap(inner, null, new Rect(0, 0, scaleWidth, scaleHeight), mPaint);
Matrix matrix = new Matrix();
//顺时针旋转180度,因为一开始应该是从下网上画图片
matrix.postRotate(rotateProgress, (float) scaleWidth / 2.0f, (float) scaleHeight / 2.0f);
//创建一个新的可缩放的bitmap
Bitmap temp_circle = Bitmap.createScaledBitmap(circle, scaleWidth, scaleHeight, true);
//进度最大为1f,超过设为1f
if (progress >= 1.0f) {
progress = 1.0f;
}
//创建一个bitmap,其实就是从下到上画一个图,
// progress每次变动都会重新绘制外圈,
// createBitmap最后一个参数就是所画图片的高度,
// 因为是根据显示程度来绘制,所以高度*(progress) 其中progess<=1,就会有渐变的效果
Bitmap mask_outter_circle = Bitmap.createBitmap(temp_circle, 0, 0,
temp_circle.getWidth(), progress == 1.0f ?
temp_circle.getHeight()
: (int) (temp_circle.getHeight() * progress));
//画红圈
canvas.drawBitmap(mask_outter_circle, matrix, mPaint);
inner.recycle();
temp_circle.recycle();
mask_outter_circle.recycle();
circle.recycle();
}
}
/**
* 设置红色圆圈显示程度
*/
public void setProgress(float progress) {
this.progress = progress;
this.invalidate();
}
/**
* 开始旋转动画
*/
public void startAnimate() {
if (!isAnimate) {
isAnimate = true;
mHandler.post(mRunnable);
}
}
/**
* 结束旋转动画
*/
public void stopAnimate() {
isAnimate = false;
mHandler.removeCallbacks(mRunnable);
rotateProgress = 180;
progress = 0f;
}
private Runnable mRunnable = new Runnable() {
@Override
public void run() {
rotateProgress += 8;
if (isAnimate) {
mHandler.postDelayed(mRunnable, 10);
}
RefreshView.this.invalidate();
}
}; }
实现SuperSwipeRefreshLayout.OnPullRefreshListener,完成下拉刷新的逻辑
`
public class MyPullToRefreshListener implements SuperSwipeRefreshLayout.OnPullRefreshListener {
private OnRefreshListener mOnRefreshListener;
private boolean isRefresh;//是否可以刷新
private int mScrollDistance;//滑动距离
private RefreshView refreshView;//刷新的头部
private int height;//头部高度
private SuperSwipeRefreshLayout mSwipeRefreshLayout;
private final int scrollPx;//需要滑动的距离
public MyPullToRefreshListener(Context context, SuperSwipeRefreshLayout superSwipeRefreshLayout) {
this.mSwipeRefreshLayout = superSwipeRefreshLayout;
setupRefreshView(context);
scrollPx = UiUtils.dp2px(context, 25);
}
private void setupRefreshView(Context context) {
refreshView = new RefreshView(context);
mSwipeRefreshLayout.setHeaderView(refreshView);
height = mSwipeRefreshLayout.getHeaderHeight();
}
/**
* 加载成功之后回调
*/
public void refreshDone() {
//RxJava的延迟操作700毫秒停止动画
Observable.timer(700, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
.map(new Func1() {
@Override
public Void call(Long aLong) {
//停止旋转
refreshView.stopAnimate();
//刷新结束
mSwipeRefreshLayout.setRefreshing(false);
return null;
}
})
.subscribe();
}
/**
* 刷新操作
*/
@Override
public void onRefresh() {
if (mOnRefreshListener != null) {
mOnRefreshListener.refresh();
refreshView.startAnimate();
}
}
//根据滑动的距离来对View的进行绘制
@Override
public void onPullDistance(int distance) {
if (mScrollDistance > distance) {
//当不在刷新状态并且滑动距离超过scrollPx之后才开始变色
if (!isRefresh && distance > scrollPx) {
// Log.d("滑动", "onPullDistance: 向上变颜色");
//渐变程度,其实就是一个线性渐变的函数,y/x = scale;
//由于需要滑动到一定的程度再进行渐变,
// 函数为 (y-scrollPx)/x = scale------->(y-scrollPx)/x = y/x
//其中y = 滑动距离,x = 刷新布局的高度
float scale = ((float) distance - scrollPx) / (((2 * height * scrollPx) / distance));
refreshView.setProgress(scale <= 0f ? 0f : scale);
}
} else {
if (!isRefresh && distance > scrollPx) {
float scale = ((float) distance - scrollPx) / (((2 * height * scrollPx) / distance));
refreshView.setProgress(scale > 1f ? 1f : scale);
// Log.d("滑动", "onPullDistance: 向下变颜色" + distance + "---" + height + "---scale:" + scale);
}
}
mScrollDistance = distance;
}
@Override
public void onPullEnable(boolean enable) {
isRefresh = enable;
}
public void setOnRefreshListener(OnRefreshListener OnRefreshListener) {
this.mOnRefreshListener = OnRefreshListener;
}
/**
* 刷新回调,在该方法中执行刷新数据操作
*/
public interface OnRefreshListener {
void refresh();
}}
使用方法
XML包裹需要下拉刷新的控件
Activity/Fragment代码
MyPullToRefreshListener pullToRefreshListener = new MyPullToRefreshListener(mContext,swip);
pullToRefreshListener.setOnRefreshListener(new MyPullToRefreshListener.OnRefreshListener() {
@Override
public void refresh() {
//实现你的刷新功能
}
});
swip.setOnPullRefreshListener(pullToRefreshListener);
//刷新成功或者失败后调用
pullToRefreshListener.refreshDone();
最后,语文学的差,表达能力欠佳,写了这么多也不知道自己表达清楚没有,但是效果还是出来了,第一次写,恳请各位包容。
项目是持续在更新的高仿猫眼电影,没有单独的Demo,如果有需要请留言,会单独开一个工程Demo