一个简洁而不简单的安卓上下拉刷新框架

898 阅读5分钟

首先先看一下效果图吧==

下拉刷新
上拉刷新

刷新动画都很熟悉吧...so 本项目并不是刷新控件,而是一个框架,定义了一个标准,可以集成实现自己想要的效果! 源码地址:https://github.com/tohodog/QSRefreshLayout 框架内置4个刷新view CircleImageView 小圆球 BarRefreshView 变色的细条 IOSRefreshView ios上的一款刷新view XMLRefreshView 就是那款经典的上下拉刷新,不过很简陋,有需要的同学可以修改 其他的饿了么京东等均在demo里 一、简介

  • 刷新view模块化,可自由更换扩展,head foot可通用
  • 轻松实现各种刷新效果,不用自己处理触摸事件
  • 支持任意可滑动的控件
  • 更多效果更新中...

二、框架使用

##1. XML 可以在xml设置head foot控件

    <org.song.refreshlayout.QSRefreshLayout
        android:id="@+id/qs"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        
        <!--放在RecyclerView上面就是head-->
        <org.song.refreshlayout.refreshview.CircleImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

        <android.support.v7.widget.RecyclerView
            android:id="@+id/list"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#ffffff" />
            
        <!--foot-->
        <org.song.refreshlayout.refreshview.BarRefreshView
            android:layout_width="match_parent"
            android:layout_height="2dp" />


    </org.song.refreshlayout.QSRefreshLayout>

2.JAVA

一些基本的控制

QSRefreshLayout qsRefreshLayout = (QSRefreshLayout) findViewById(R.id.qs);

//qsRefreshLayout.setHeadRefreshView(new CircleImageView(this));

//qsRefreshLayout.setFootRefreshView(new BarRefreshView(this));
//自动进入头部刷新
qsRefreshLayout.enterHeadRefreshing(true);
//监听
qsRefreshLayout.setRefreshListener(new QSRefreshLayout.RefreshListener() {
            @Override
            public void changeStatus(boolean isHead, int status) {
                if (status == QSRefreshLayout.STATUS_REFRESHING) {
                    new Handler().postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            qsRefreshLayout.refreshComplete();
                        }
                    }, 3000);
                }
            }
});

//refreshview setting
CircleImageView circleImageView= (CicleImageView) qsRefreshLayout.getHeadRefreshView();
circleImageView.setColorScheme(R.color.xxx,...);

三、如何DIY一个自己的刷新view

需要view实现IRefreshView接口,定义如下

``` View getView();
void updateStatus(int status);//更新刷新状态

void updateProgress(float progress);//刷新进度 0 ~ 1(触发刷新)~ 更大

boolean isBringToFront();//是否view放在顶层

float dragRate();//滑动速度控制

int triggerDistance();//触发刷新的距离 [实际触摸距离*dragRate()>triggerDistance() 触发刷新

int maxDistance();//最大滑动距离 <=0不限制

int getThisViewOffset(int offset);//根据触摸位移 确定该view的位移 大于0=headview 小于0=footview

int getTargetOffset(int offset);//根据触摸位移 确定滚动view的位移 大=headview 小于0=footview

int completeAnimaDuration();//完成刷新后到消失的动画时间, <=0使用默认时间

void isHeadView(boolean isHead);//是否顶部刷新view
<h3>下面就来一起实现一个刷新demo</h3>

public class DemoRefreshView extends FrameLayout implements IRefreshView {

private int h;

public DemoRefreshView(@NonNull Context context) {
    this(context, null);
}

public DemoRefreshView(Context context, AttributeSet attrs) {
    super(context, attrs);
    init();
    float density = context.getResources().getDisplayMetrics().density;
    h = (int) (density * 50);

    //todo 如果在xml配置view 则此参数无效 会被xml的参数覆盖
    //设置这个控件的大小 目前不支持margin gravity等参数,默认居中
    setLayoutParams(new ViewGroup.LayoutParams(h, h));
    setBackgroundColor(Color.BLUE);

}

private void init() {

}

public void setH(int h) {
    this.h = h;
    requestLayout();
}

@Override//确定view大小,你可以在这里强制配置自己的view大小
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    //super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    //如我的view需要长宽都锁死,不被setLayoutParams所修改
    //则可以这样,然后再提供setH(int h)给外部
    setMeasuredDimension(h, h);
}

//返回当前view的实例即可
@Override
public View getView() {
    return this;
}

/**
 * 刷新的状态回调
 * 刷新控件共有5种状态
 * STATUS_NORMAL = 0;//普通状态
 * STATUS_DRAGGING = 1;//手指拖曳时(未到触发距离)
 * STATUS_DRAGGING_REACH = 2;//手指拖曳(可以触发刷新的距离)
 * STATUS_REFRESHING = 3;//刷新中
 * STATUS_REFRESHED = 4;//刷新完成后到view隐藏的这一段状态
 * 开发者可以根据状态来设置view
 */
private int status;

@Override
public void updateStatus(int status) {
    this.status = status;
    switch (status) {
        //就只是变下颜色
        case QSRefreshLayout.STATUS_DRAGGING_REACH:
            setBackgroundColor(Color.RED);
            break;
        case QSRefreshLayout.STATUS_REFRESHING:
            setBackgroundColor(Color.GREEN);
            break;
        case QSRefreshLayout.STATUS_DRAGGING:
        case QSRefreshLayout.STATUS_REFRESHED:
        case QSRefreshLayout.STATUS_NORMAL:
            setBackgroundColor(Color.BLUE);
            break;
    }
}

/**
 * 刷新的进度
 * 刷新进度 0 ~ 1(触发刷新)~ 更大
 * 一些特效动画就可以根据这个值来更新状态
 */
@Override
public void updateProgress(float progress) {
    //不是拖曳状态忽略
    if (status != QSRefreshLayout.STATUS_DRAGGING && status != QSRefreshLayout.STATUS_DRAGGING_REACH)
        return;
    if (progress > 1)
        progress = 1;
    //这里就实现一个颜色渐变吧

    int startColor = Color.BLUE;
    int a1 = (startColor >> 24) & 0x000000FF;
    int r1 = (startColor >> 16) & 0x000000FF;
    int g1 = (startColor >> 8) & 0x000000FF;
    int b1 = startColor & 0x000000FF;

    int endColor = Color.RED;
    int a2 = (endColor >> 24) & 0x000000FF;
    int r2 = (endColor >> 16) & 0x000000FF;
    int g2 = (endColor >> 8) & 0x000000FF;
    int b2 = endColor & 0x000000FF;

    int r = (int) (r1 + (r2 - r1) * progress);
    int g = (int) (g1 + (g2 - g1) * progress);
    int b = (int) (b1 + (b2 - b1) * progress);
    int a = (int) (a1 + (a2 - a1) * progress);

    int newColor = Color.argb(a, r, g, b);
    setBackgroundColor(newColor);

}

/**
 * 是否view在顶层
 * 比如饿了么刷新就需要在底层 返回false
 * 谷歌的小圆球刷新就需要在顶层 返回true
 */
@Override
public boolean isBringToFront() {
    return false;//显示在目标的下方
}

/**
 * 控制拖曳的速率
 * 比如返回.5f,手指拖动100像素,本框架会认为是50像素
 */
@Override
public float dragRate() {
    return .5f;
}

/**
 * 触发刷新的距离
 */
@Override
public int triggerDistance() {
    return getMeasuredHeight();//触发距离为本view的高度
}

/**
 * 最大拖动的距离
 * <=0不限制
 */
@Override
public int maxDistance() {
    return 0;//不限制
}

/**
 * 根据触摸位移 确定该view的位移
 *
 * @param offset 当前的拖动距离(实际触摸距离*dragRate()), headview大于0, footview小于0
 * @return 返回这个view的移动值 这个值将会确定view的位置
 */
@Override
public int getThisViewOffset(int offset) {
    //这里我们实现一个视差移动
    offset = Math.abs(offset);
    int t = triggerDistance();
    int i;
    if (offset > t)
        i = offset;
    else
        i = t / 2 + offset / 2;
    return isHead ? i : -i;//顶部和底部view 移动值是相反的
}

/**
 * 根据触摸位移 确定目标view的位移
 *
 * @param offset 当前的拖动距离(实际触摸距离*dragRate()), headview大于0, footview小于0
 * @return 返回目标view(就是listview, 等)的移动值 这个值将会确定view的位置
 * 这个返回值一般要么是
 * 0不会动
 * offset 原值返回
 */
@Override
public int getTargetOffset(int offset) {
    return offset;//返回原值,会跟随手指而移动
}

/**
 * 完成刷新后到消失的动画时间, <=0使用默认时间300ms
 * 如果有一些动画刷新完成后执行时间较长或需要调慢 可以这里返回时间ms
 * (BarRefreshView使用了)
 */
@Override
public int completeAnimaDuration() {
    return 0;
}

private boolean isHead;

//标记这个view是拿去做head还是foot,用变量记录下来,也可以在这里实现一些初始化
@Override
public void isHeadView(boolean isHead) {
    this.isHead = isHead;
}

}

运行效果如下,recycle设置半透明了,方便看效果
![这里写图片描述](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2017/9/27/b445efb07d1ed52bbd29b5a4f5535259~tplv-t2oaga2asx-image.image)