SpringView(上拉加载和下拉刷新的库)使用笔记

SpringView优点:

能对header/footer(头部/尾部)的样式和动画进行高度自定义,单独将header/footer独立出来,几乎可以实现任何你想要的效果,只需要继承BaseHeader/BaseFooter实现对应接口就可以。

能动态地替换header/footer,只需要设置不同的头尾即可:springView.setHeader(MyHeader());

在不重写源生控件的情况下,完美支持系统源生的listView、RecyclerView、ScrollView、WebView等,你依然使用google提供的官方控件,SpringView完全不做干涩。

使用简单,对于简单的需求甚至不用写任何代码,只需要在布局中为SpringView设置header=”@layout/…”属性即可。

SpringView是非常轻量级的实现,提供了非常容易拓展的对外接口

SpringView支持多点触控,可以两只手连续拖拽,你可以定制一些趣味的动画(例如demo5的仿acfun效果)

SpringView提供了2种拖拽方式(重叠和跟随),可以动态地切换

SpringView为不想去自定义头/尾的懒人提供了7种默认的实现(模仿了阿里,腾讯,美团等多种风格)如下,还会继续增加

基本使用方式

(1)添加依赖

 compile 'com.liaoinstan.springview:library:1.2.6'

(2)在布局文件中添加SpringView,注意SpringView和ScrollView有同样的限制:只能有一个子元素
并把你想要拖拽的控件放在SpringView中,给SpringView添加app:header=”@layout/…”属性,设置一个自己编写的头部的布局即可(footer同理):

    <com.liaoinstan.springview.widget.SpringView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:header="@layout/myheader"
            app:footer="@layout/myfooter">

            <listView
                android:layout_width="match_parent"
                android:layout_height="match_parent"/>

     </com.liaoinstan.springview.widget.SpringView>

当然,你也可以不再布局中设置,使用代码动态添加:

 //DefaultHeader/Footer是SpringView已经实现的默认头/尾之一
    //更多还有MeituanHeader、AliHeader、RotationHeader等如上图7种
    springView.setHeader(new DefaultHeader(this));
    springView.setFooter(new DefaultFooter(this));

刷新和加载更多的事件处理
如果需要处理的话,只需在代码中添加监听:

springView.setListener(new SpringView.OnFreshListener() {
            @Override
            public void onRefresh() {
                Toast.makeText(getApplicationContext(),"下拉刷新中",Toast.LENGTH_SHORT).show();
               // list.clear();
               // 网络请求;
               // mStarFragmentPresenter.queryData();
                //一分钟之后关闭刷新的方法
                finishFreshAndLoad();
            }

            @Override
            public void onLoadmore() {
                Toast.makeText(getApplicationContext(),"玩命加载中...",Toast.LENGTH_SHORT).show();
                finishFreshAndLoad();
            }
        });

在刷新和加载更多的时候等待1秒调用onFinishFreshAndLoad()结束刷新动作

  /**
     * 关闭加载提示
     */
    private void finishFreshAndLoad() {
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                spRefresh.onFinishFreshAndLoad();
            }
        }, 1000);
    }

以上就是它的基本使用方式;

我们可以看一下它的几个默认的加载效果实现;
(1)第一个效果;

这里写图片描述

这是它添加的默认的下拉头和上拉加载;

springView.setHeader(new DefaultHeader(this));
springView.setFooter(new DefaultFooter(this));

它的源代码;

/**
 * 通过类继承BaseHeader来实现动态添加头 ;
 */
public class DefaultHeader extends BaseHeader {
    //传进来的上下文;
    private Context context;
    //旋转动画的int
    private int rotationSrc;
    //箭头
    private int arrowSrc;
    //定义刷新时间的标记;
    private long freshTime;
    //动画执行时间180mm
    private final int ROTATE_ANIM_DURATION = 180;
    //箭头朝上的动画;
    private RotateAnimation mRotateUpAnim;
    //箭头朝下的动画;
    private RotateAnimation mRotateDownAnim;

    private TextView headerTitle;
    private TextView headerTime;
    private ImageView headerArrow;
    private ProgressBar headerProgressbar;

    /**
     * 构造方法中传进来上下文,然后传递给三个参数的构造方法;
     * @param context
     */
    public DefaultHeader(Context context){
        //第一个参数是上下文,第二个参数是加载的圆圈,其中是加载一个旋转的动画,第三个参数是箭头;
        this(context, R.drawable.progress_small,R.drawable.arrow);
    }

    public DefaultHeader(Context context,int rotationSrc,int arrowSrc){
        this.context = context;
        this.rotationSrc = rotationSrc;
        this.arrowSrc = arrowSrc;
        //添加一个逆时针旋转动画使朝下的箭头转动到朝上;
        mRotateUpAnim = new RotateAnimation(0.0f, -180.0f,
                Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
                0.5f);
        mRotateUpAnim.setDuration(ROTATE_ANIM_DURATION);
        //动画执行完停在当前位置;
        mRotateUpAnim.setFillAfter(true);
        //添加一个顺时针旋转动画使朝上的箭头转动到朝下;
        mRotateDownAnim = new RotateAnimation(-180.0f, 0.0f,
                Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
                0.5f);
        mRotateDownAnim.setDuration(ROTATE_ANIM_DURATION);
        mRotateDownAnim.setFillAfter(true);
    }

    /***
     * //获取Header;
     * @param inflater
     * @param viewGroup
     * @return
     */
    @Override
    public View getView(LayoutInflater inflater,ViewGroup viewGroup) {
        //添加头部的布局view;
        View view = inflater.inflate(R.layout.default_header, viewGroup, true);
        //头部标题
        headerTitle = (TextView) view.findViewById(R.id.default_header_title);
        //记录刷新的时间;
        headerTime = (TextView) view.findViewById(R.id.default_header_time);
        //箭头;
        headerArrow = (ImageView) view.findViewById(R.id.default_header_arrow);
        //加载的圆圈;
        headerProgressbar = (ProgressBar) view.findViewById(R.id.default_header_progressbar);
        //将int加入到控件上;
        headerProgressbar.setIndeterminateDrawable(ContextCompat.getDrawable(context, rotationSrc));

        headerArrow.setImageResource(arrowSrc);
        return view;
    }

    /**
     * 拖拽开始前回调
     * 设置头部的控件时间
     * @param rootView
     */
    @Override
    public void onPreDrag(View rootView) {
        //如果刷新时间为零.表示没有刷新过;
        if (freshTime==0){
            //获取到当前的运行时间;
            freshTime = System.currentTimeMillis();
        }else {
            //如果不是第一次刷新,获取当前刷新时间然后减掉-上次一刷新的时间
            int m = (int) ((System.currentTimeMillis()-freshTime)/1000/60);
            if(m>=1 && m<60){
                headerTime.setText( m +"分钟前");
            }else if (m>=60){
                int h = m/60;
                headerTime.setText( h +"小时前");
            }else if(m>60*24){
                int d = m/(60*24);
                headerTime.setText( d +"天前");
            }else if(m==0){
                headerTime.setText("刚刚");
            }
        }
    }

    /***
     * //手指拖拽过程中不断回调,dy为拖拽的距离,可以根据拖动的距离添加拖动过程动画
     * @param rootView
     * @param dy 拖动距离,下拉为+,上拉为-
     */
    @Override
    public void onDropAnim(View rootView, int dy) {
    }

    /***
     * //手指拖拽过程中每次经过临界点时回调,upORdown是向上经过还是向下经过
     * @param rootView
     * @param upORdown 是上拉还是下拉
     */
    @Override
    public void onLimitDes(View rootView, boolean upORdown) {
        //upORdown 为false时表示过了临界点
        if (!upORdown){
            //修改标题信息;
            headerTitle.setText("松开刷新数据");
            //判断箭头可见
            if (headerArrow.getVisibility()==View.VISIBLE)
                //加入向上的动画;
                headerArrow.startAnimation(mRotateUpAnim);
        }
        else {
            //没过临界点,还是下拉刷新
            headerTitle.setText("下拉刷新");
            if (headerArrow.getVisibility()==View.VISIBLE)
                //动画转为向下;
                headerArrow.startAnimation(mRotateDownAnim);
        }
    }

    /***
     *  //拉动超过临界点后松开时回调
     */
    @Override
    public void onStartAnim() {
        //再一次记录刷新的时间;
        freshTime = System.currentTimeMillis();
        //设置标题信息是正在刷新;
        headerTitle.setText("正在刷新");
        //隐藏箭头;
        headerArrow.setVisibility(View.INVISIBLE);
        //清除箭头动画;
        headerArrow.clearAnimation();
        //设置加载圈进行显示;
        headerProgressbar.setVisibility(View.VISIBLE);
    }

    /**
     *  //头部已经全部弹回时回调,也就是头部不可见的时候
     */
    @Override
    public void onFinishAnim() {
        //显示箭头
        headerArrow.setVisibility(View.VISIBLE);
        //隐藏圆圈的加载动画;
        headerProgressbar.setVisibility(View.INVISIBLE);
    }
}

上拉加载的默认footer

/**
 * 默认的上啦加载;
 */
public class DefaultFooter extends BaseFooter {
    private Context context;
    private int rotationSrc;
    private TextView footerTitle;
    private ProgressBar footerProgressbar;

    public DefaultFooter(Context context){
        this(context,R.drawable.progress_small);
    }

    public DefaultFooter(Context context,int rotationSrc){
        this.context = context;
        this.rotationSrc = rotationSrc;
    }

    @Override
    public View getView(LayoutInflater inflater, ViewGroup viewGroup) {
        View view = inflater.inflate(R.layout.default_footer, viewGroup, true);
        footerTitle = (TextView) view.findViewById(R.id.default_footer_title);
        footerProgressbar = (ProgressBar) view.findViewById(R.id.default_footer_progressbar);
        footerProgressbar.setIndeterminateDrawable(ContextCompat.getDrawable(context,rotationSrc));
        return view;
    }
    /**
     * 拖拽开始前回调
     * 设置头部的控件时间
     * @param rootView
     */
    @Override
    public void onPreDrag(View rootView) {
    }
    /***
     * //手指拖拽过程中不断回调,dy为拖拽的距离,可以根据拖动的距离添加拖动过程动画
     * @param rootView
     * @param dy 拖动距离,下拉为+,上拉为-
     */
    @Override
    public void onDropAnim(View rootView, int dy) {
    }
    /***
     * //手指拖拽过程中每次经过临界点时回调,upORdown是向上经过还是向下经过
     * @param rootView
     * @param upORdown 是上拉还是下拉
     */
    @Override
    public void onLimitDes(View rootView, boolean upORdown) {
        if (upORdown) {
            footerTitle.setText("松开载入更多");
        } else {
            footerTitle.setText("查看更多");
        }
    }

    /***
     *  //拉动超过临界点后松开时回调
     */
    @Override
    public void onStartAnim() {
        footerTitle.setVisibility(View.INVISIBLE);
        footerProgressbar.setVisibility(View.VISIBLE);
    }
    /**
     *  //头部已经全部弹回时回调,也就是头部不可见的时候
     */
    @Override
    public void onFinishAnim() {
        footerTitle.setText("查看更多");
        footerTitle.setVisibility(View.VISIBLE);
        footerProgressbar.setVisibility(View.INVISIBLE);
    }
}

第二种效果仿美团上拉和下拉;
这里写图片描述

       //第一个参数上下文,第二个参数下拉拖拽是的动画int,第三个参数是放开刷新的动画int
        springView.setHeader(new MeituanHeader(this,pullAnimSrcs,refreshAnimSrcs));
        springView.setFooter(new MeituanFooter(this,loadingAnimSrcs));

它的源代码;

public class MeituanHeader extends BaseHeader {

    private AnimationDrawable animationPull;
    private AnimationDrawable animationPullFan;
    private AnimationDrawable animationRefresh;

    private Context context;
    private ImageView header_img;
    private int[] pullAnimSrcs = new int[]{R.drawable.mt_pull,R.drawable.mt_pull01,R.drawable.mt_pull02,R.drawable.mt_pull03,R.drawable.mt_pull04,R.drawable.mt_pull05};
    private int[] refreshAnimSrcs = new int[]{R.drawable.mt_refreshing01,R.drawable.mt_refreshing02,R.drawable.mt_refreshing03,R.drawable.mt_refreshing04,R.drawable.mt_refreshing05,R.drawable.mt_refreshing06};

    public MeituanHeader(Context context){
        this(context,null,null);
    }

    /***
     * 可以直接传入下拉时的动画数组,以及刷新动画的数组;
     * @param context
     * @param pullAnimSrcs
     * @param refreshAnimSrcs
     */
    public MeituanHeader(Context context,int[] pullAnimSrcs,int[] refreshAnimSrcs){

        this.context = context;
        if (pullAnimSrcs!=null) this.pullAnimSrcs = pullAnimSrcs;
        if (refreshAnimSrcs!=null) this.refreshAnimSrcs = refreshAnimSrcs;
       // Drawable animation可以加载Drawable资源实现帧动画。AnimationDrawable是实现Drawable animations的基本类
        //直接在代码中动态添加frame:帧动画;
        animationPull = new AnimationDrawable();
        animationPullFan = new AnimationDrawable();
        animationRefresh = new AnimationDrawable();

        for (int i=1;i< this.pullAnimSrcs.length;i++) {
               //第一个参数是drawable,第二个参数是动画时间
            //ContextCompat.getDrawable使用默认的activity主题加载drawable
            animationPull.addFrame(ContextCompat.getDrawable(context, this.pullAnimSrcs[i]),100);
           // 设置Android:oneshot属性为true,表示此次动画只执行一次,最后停留在最后一帧。设置为false则动画循环播放。文件可以添加为Image背景,触发的时候播放。
            animationRefresh.setOneShot(true);
        }
        for (int i= this.pullAnimSrcs.length-1;i>=0;i--){
            animationPullFan.addFrame(ContextCompat.getDrawable(context, this.pullAnimSrcs[i]), 100);
            animationRefresh.setOneShot(true);
        }
        for (int src: this.refreshAnimSrcs) {
            animationRefresh.addFrame(ContextCompat.getDrawable(context, src),150);
            animationRefresh.setOneShot(false);
        }
    }
    /***
     * //获取Header;
     * @param inflater
     * @param viewGroup
     * @return
     */
    @Override
    public View getView(LayoutInflater inflater,ViewGroup viewGroup) {
        View view = inflater.inflate(R.layout.meituan_header, viewGroup, true);
        //初始化控件image;
        header_img = (ImageView) view.findViewById(R.id.meituan_header_img);
        if (pullAnimSrcs !=null&& pullAnimSrcs.length>0)
            header_img.setImageResource(pullAnimSrcs[0]);
        return view;
    }

    /**
     * 拖拽开始前回调
     * 设置头部的控件时间
     * @param rootView
     */
    @Override
    public void onPreDrag(View rootView) {

    }
    /***
     * //手指拖拽过程中不断回调,dy为拖拽的距离,可以根据拖动的距离添加拖动过程动画
     * @param rootView
     * @param dy 拖动距离,下拉为+,上拉为-
     */
    @Override
    public void onDropAnim(View rootView, int dy) {
        //dp像素转换成px这是移动的最大高度
        int maxw = DensityUtil.dip2px(context, 45);
        //计算出移动的绝对值高度;
        float w = maxw*Math.abs(dy)/rootView.getMeasuredHeight();
        //超出最大高度就结束方法;
        if (w>maxw) return;
        //获取到图片控件的父类布局;
        ViewGroup.LayoutParams layoutParams = header_img.getLayoutParams();
        //不断的赋值给父类布局的高度;
        layoutParams.width = (int) w;
        //然后将新的数据设置给控件
        header_img.setLayoutParams(layoutParams);
    }
    /***
     * //手指拖拽过程中每次经过临界点时回调,upORdown是向上经过还是向下经过
     * @param rootView
     * @param upORdown 是上拉还是下拉
     */
    @Override
    public void onLimitDes(View rootView, boolean upORdown) {
        if (!upORdown){
            //没突破临界点的操作;
            header_img.setImageDrawable(animationPull);
            animationPull.start();
        }else {
            //突破临界点的操作;
            header_img.setImageDrawable(animationPullFan);
            animationPullFan.start();
        }
    }
    /***
     *  //拉动超过临界点后松开时回调
     */
    @Override
    public void onStartAnim() {
        header_img.setImageDrawable(animationRefresh);
        animationRefresh.start();
    }
    /**
     *  //头部已经全部弹回时回调,也就是头部不可见的时候
     */
    @Override
    public void onFinishAnim() {
        if (pullAnimSrcs !=null&& pullAnimSrcs.length>0)
            header_img.setImageResource(pullAnimSrcs[0]);
    }
}

上拉加载

public class MeituanFooter extends BaseFooter {

    private AnimationDrawable animationLoading;

    private Context context;
    private ImageView footer_img;
    private int[] loadingAnimSrcs = new int[]{R.drawable.mt_loading01,R.drawable.mt_loading02};

    public MeituanFooter(Context context){
        this(context,null);
    }
    public MeituanFooter(Context context,int[] loadingAnimSrcs){
        this.context = context;
        if (loadingAnimSrcs!=null) this.loadingAnimSrcs = loadingAnimSrcs;
        animationLoading = new AnimationDrawable();
        for (int src: this.loadingAnimSrcs) {
            animationLoading.addFrame(ContextCompat.getDrawable(context, src),150);
            animationLoading.setOneShot(false);
        }
    }

    @Override
    public View getView(LayoutInflater inflater,ViewGroup viewGroup) {
        View view = inflater.inflate(R.layout.meituan_footer, viewGroup, true);
        footer_img = (ImageView) view.findViewById(R.id.meituan_footer_img);
        if (animationLoading!=null)
            footer_img.setImageDrawable(animationLoading);
        return view;
    }

    @Override
    public void onPreDrag(View rootView) {
        animationLoading.stop();
        if (animationLoading!=null && animationLoading.getNumberOfFrames()>0)
            footer_img.setImageDrawable(animationLoading.getFrame(0));
    }

    @Override
    public void onDropAnim(View rootView, int dy) {
    }

    @Override
    public void onLimitDes(View rootView, boolean upORdown) {
    }

    @Override
    public void onStartAnim() {
        if (animationLoading!=null)
            footer_img.setImageDrawable(animationLoading);
        animationLoading.start();
    }

    @Override
    public void onFinishAnim() {
        animationLoading.stop();
        if (animationLoading!=null && animationLoading.getNumberOfFrames()>0)
            footer_img.setImageDrawable(animationLoading.getFrame(0));
    }
}

因此自己定义时的方式

如何自定义一个Header或Footer

详细的你可以看下几种默认实现的Header源码,这里只简单介绍下:

我们要做一个简单的能够记录拖拽了多少次的Header。

首先新建一个自定义头部布局,就是在RelativeLayout中放一个TextView啦:

这里写图片描述

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="80dp"
    android:background="@drawable/view_post_comment_bg"
    android:layout_gravity="top">

    <TextView
        android:layout_width="120dp"
        android:gravity="center"
        android:layout_height="wrap_content"
        android:text="this is TextView  "
        android:background="#ff0000"
        android:textColor="#ffffff"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true"
        android:id="@+id/textView" />
</RelativeLayout>

接着,新建一个MyHeader继承自BaseHeader:

public class MyHeader extends BaseHeader{
    //获取Header
    @Override
    public View getView(LayoutInflater inflater, ViewGroup viewGroup) {}

    //拖拽开始前回调
    @Override
    public void onPreDrag(View rootView) {}

    //手指拖拽过程中不断回调,dy为拖拽的距离,可以根据拖动的距离添加拖动过程动画
    @Override
    public void onDropAnim(View rootView, int dy) {}

    //手指拖拽过程中每次经过临界点时回调,upORdown是向上经过还是向下经过
    @Override
    public void onLimitDes(View rootView, boolean upORdown) {}

    //拉动超过临界点后松开时回调
    @Override
    public void onStartAnim() {}

    //头部已经全部弹回时回调
    @Override
    public void onFinishAnim() {}
}

注释已经很清楚了,其中必须实现的方法是getView(),将头部的View实例返回给SpringView,其余方法有需求就实现。

实现getView()方法,添加一个成员变量保存头部中的TextView:

    private TextView textView;
    @Override
    public View getView(LayoutInflater inflater, ViewGroup viewGroup) {
        View view = inflater.inflate(R.layout.header_my, viewGroup, true);
        textView = (TextView)view.findViewById(R.id.textView);
        return view;
    }

实现onLimitDes方法,在每次经过临界点时改变TextView的内容:

    private int i = 0;
    @Override
    public void onLimitDes(View rootView, boolean upORdown) {
        i++;
        textView.setText("this is TextView "+i);
    }

OK,在Activity中为SpringView设置我们自定义的MyHeader就可以了,再设置一个监听器,在刷新和加载更多的时候等待1秒调用onFinishFreshAndLoad()结束刷新动作。


        springView = (SpringView) findViewById(R.id.springview);
        springView.setHeader(new MyHeader());

        springView.setListener(new SpringView.OnFreshListener() {
            @Override
            public void onRefresh() {
                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        springView.onFinishFreshAndLoad();
                    }
                }, 1000);
            }
            @Override
            public void onLoadmore() {
                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        springView.onFinishFreshAndLoad();
                    }
                }, 1000);
            }
        });
如何自定义最大下拉高度,临界高度,和回弹高度

在BaseHeader(BaseFooter同理)中默认已经实现3个方法,分别返回的是临界高度(limit hight),下拉最大高度(max height),下拉弹动高度(spring height): 
如果有更加复杂的需求,需要更改这些高度的话,就在自己的Header中重写这些方法,注释已经很清楚了:

public abstract class BaseHeader implements SpringView.DragHander{
    /**
     * 这个方法用于设置当前View的临界高度(limit hight),即拉动到多少会被认定为刷新超作,而没到达该高度则不会执行刷新
     * 返回值大于0才有效,如果<=0 则设置为默认header的高度
     * 默认返回0
     */
    @Override
    public int getDragLimitHeight(View rootView) {
        return 0;
    }

    /**
     * 这个方法用于设置下拉最大高度(max height),无论怎么拉动都不会超过这个高度
     * 返回值大于0才有效,如果<=0 则默认600px
     * 默认返回0
     */
    @Override
    public int getDragMaxHeight(View rootView) {
        return 0;
    }

    /**
     * 这个方法用于设置下拉弹动高度(spring height),即弹动后停止状态的高度
     * 返回值大于0才有效,如果<=0 则设置为默认header的高度
     * 默认返回0
     */
    @Override
    public int getDragSpringHeight(View rootView) {
        return 0;
    }
}
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值