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

xiaoxiao2021-02-28  59

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; } }
转载请注明原文地址: https://www.6miu.com/read-33468.html

最新回复(0)