Android自定义SwipeRefreshLayout高仿微信朋友圈下拉刷新

Android自定义SwipeRefreshLayout高仿微信朋友圈下拉刷新,第1张

概述上一篇文章里把SwipeRefreshLayout的原理简单过了一下,大致了解了其工作原理,不熟悉的可以去看一下:https://www.oudahe.com/p/16766/ 

上一篇文章里把SwipeRefreshLayout的原理简单过了一下,大致了解了其工作原理,不熟悉的可以去看一下:https://www.oudahe.com/p/16766/ 

上一篇里最后提到,SwipeRefreshLayout的可定制性是比较差的,看源码会发现跟样式相关的几个类都是private的而且方法是写死的,只暴露出了几个颜色设置的方法。这样使得SwipeRefreshLayout的使用比较简单,主要就是设置一个监听器在onRefresh方法里完成刷新逻辑。讲道理SwipeRefreshLayout的样式是挺美观的,如果以后都用这种下拉刷新样式的话,程序员就清静了,但这也是不太可能的。如果就想用官方的SwipeRefreshLayout,不想用第三方的控件,又想定制样式,该怎么办?基本上只能改源码了。下面就从修改源码的角度出发,给出自定义样式的思路。 

首先需要将SwipeRefreshLayout以及内部使用到的circleimageVIEw和MaterialProgressDrawable的源码都拷贝出来,放到一个包里,方便修改。从源码可以知道,SwipeRefreshLayout中跟样式相关的类主要有两个: 

一. circleimageVIEw,继承imagevIEw,源码就不贴了,主要是绘制背景的,进度圈就是绘制在这上面,如果要修改进度圈的位置,就应该修改circleimageVIEw的位置。 

二. MaterialProgressDrawable,继承Drawable实现Animatable接口,内部还定义了一个Ring类,主要是绘制进度圈的,如果要修改进度圈的图片和动画,就应该从这里开刀。 

下面就以社交APP的BOSS微信为例,仿照朋友圈的下拉刷新效果。 

先上效果图,可以跟手机里的微信比较一下,整体感觉还是可以的。第一次录gif,录了太长,处理的时候删了一些中间的帧) 

这段时间在高仿微信,图方便就把整体的效果也展示了,读者关注刷新页面即可。布局主要就是一个SwipeRefreshLayout内嵌一个RecyclerVIEw,滑动到顶端向下拖动时,出来的进度圈是朋友圈的那个彩虹圈,位置在左边,而且随着向下拖动会不断绕中心转啊转,此外,进度圈在到达某个位置后就不会再往下了。跟默认效果不同的还有recyclervIEw,默认是主布局是不会跟着拖动的,而微信的有一个拖动反d效果,背景是黑色。开始刷新后,主布局反d到头部,进度圈在那里转啊转,刷新完毕后进度圈就消失了,整个过程就是这样。那么就一步一步来. 

1. 调整进度圈位置
 首先要将进度圈调整到左边,根据VIEw的绘制原理,进度圈的位置应该是由父布局也就是SwipeRefreshLayout里的onLayout方法决定的,看看源码:

 @OverrIDeprotected voID onLayout(boolean changed,int left,int top,int right,int bottom) {  final int wIDth = getMeasureDWIDth();  final int height = getMeasuredHeight();  if (getChildCount() == 0) {    return;  }  if (mTarget == null) {    ensureTarget();  }  if (mTarget == null) {    return;  }  final VIEw child = mTarget;  final int childleft = getpaddingleft();  final int childtop = getpaddingtop();  final int chilDWIDth = wIDth - getpaddingleft() - getpaddingRight();  final int childHeight = height - getpaddingtop() - getpaddingBottom();  child.layout(childleft,childtop,childleft + chilDWIDth,childtop + childHeight);  int circleWIDth = mCircleVIEw.getMeasureDWIDth();  int circleHeight = mCircleVIEw.getMeasuredHeight();  mCircleVIEw.layout((wIDth / 2 - circleWIDth / 2),mCurrentTargetoffsettop,(wIDth / 2 + circleWIDth / 2),mCurrentTargetoffsettop + circleHeight);}

其中的mTarget就是主布局也就是recyclervIEw,而mCircleVIEw就是转载进度圈的VIEw,因此应该把最后一句注释掉,改为:

 //      mCircleVIEw.layout((wIDth / 2 - circleWIDth / 2),//          (wIDth / 2 + circleWIDth / 2),mCurrentTargetoffsettop + circleHeight);//      修改进度圈的X坐标使之位于左边      mCircleVIEw.layout(childleft,childleft+circleWIDth,mCurrentTargetoffsettop + circleHeight); 

这样你就会很高兴地发现进度圈已经调到左边了。 

2. 实现拖动反d效果
 接下来先修改recyclervIEw的拖动反d效果,SwipeRefreshLayout默认的效果是不拖动的,如果要修改其实也很简单,无非就是记录下手指运动的距离并让recyclervIEw设置translation就好了,那么找到ontouchEvent方法,修改ACTION_MOVE和ACTION_UP的部分:             

  case MotionEvent.ACTION_MOVE: {          pointerIndex = MotionEventCompat.findPointerIndex(ev,mActivePointerID);          if (pointerIndex < 0) {            Log.e(LOG_TAG,"Got ACTION_MOVE event but have an invalID active pointer ID.");            return false;          }          final float y = MotionEventCompat.getY(ev,pointerIndex);//          记录手指移动的距离,mInitialMotionY是初始的位置,DRAG_RATE是拖拽因子,默认为0.5。          final float overscrolltop = (y - mInitialMotionY) * DRAG_RATE;//          赋值给mTarget的top使之产生拖动效果          mTarget.setTranslationY(overscrolltop);          if (mIsBeingDragged) {            if (overscrolltop > 0) {              moveSpinner(overscrolltop);            } else {              return false;            }          }          break;        }        case MotionEvent.ACTION_UP: {//          手指松开时启动动画回到头部          mTarget.animate().translationY(0).setDuration(200).start();          pointerIndex = MotionEventCompat.findPointerIndex(ev,"Got ACTION_UP event but don't have an active pointer ID.");            return false;          }          final float y = MotionEventCompat.getY(ev,pointerIndex);          final float overscrolltop = (y - mInitialMotionY) * DRAG_RATE;          mIsBeingDragged = false;          finishSpinner(overscrolltop);          mActivePointerID = INVALID_POINTER;          return false;        } 

不相关的我都略过了,修改的地方我也注释了,很清晰。这样就解决了拖动反d的问题,得益于SwipeRefreshLayout的框架,不用考虑冲突问题,修改起来还是很简单的。

3. 修改图标和拖动时的动画 

接下来就是比较麻烦的图标和动画了。修改图标其实不难,因为CircleVIEw是继承ImageVIEw的,完全可以通过反射取到CircleVIEw的实例变量,然后setBitmap将你的图标传进去。但是这样的话就没有动画了,显然也是没啥意义的。读者可以大致看看MaterialProgressDrawable的源码,要实现默认的动画还是比较复杂的,我这里要改为微信的效果,就一个圈圈转啊转,还是比较简单的,下面就结合上篇文章所解析的流程看看如何修改。
 首先新建一个CustomProgressDrawable类,并继承自MaterialProgressDrawable(需要将源码复制出来),还需要在SwipeRefreshLayout添加set方法,方便把自定义的类传进去。

 public voID setProgressVIEw(MaterialProgressDrawable mProgress){  this.mProgress = mProgress;  mCircleVIEw.setimageDrawable(mProgress);}

要在CustomProgressDrawable中绘制自定义的图标,就需要暴露一个setBitmap的方法以便绘制。上篇文章提到,手指移动时会调用moveSpinner方法,并把移动的距离传进去,该方法内首先会经过一堆数学的处理得出一个rotation,再把它传入mProgress的setProgressRotation,也就是说setProgressRotation方法是通过传入的角度来转圈圈的。朋友圈的效果就是一直让中心转,所以很容易改写:

 private float rotation;  private Bitmap mBitmap;  public voID setBitmap(Bitmap mBitmap) {    this.mBitmap = mBitmap;  }  @OverrIDe  public voID setProgressRotation(float rotation) {//    取负号是为了和微信保持一致,下拉时逆时针转加载时顺时针转,旋转因子是为了调整转的速度。    this.rotation = -rotation*ROTATION_FACTOR;    invalIDateSelf();  }  @OverrIDe  public voID draw(Canvas c) {    Rect bound = getBounds();    c.rotate(rotation,bound.exactCenterX(),bound.exactCenterY());    Rect src = new Rect(0,mBitmap.getWIDth(),mBitmap.getHeight());    c.drawBitmap(mBitmap,src,bound,paint);  } 

就是不断旋转canvas再绘制bitmap。这样你就会很高兴地发现下拉的时候圈圈也转起来了。 

4. 设置进度圈下拉界限和实现加载时的动画
此时正在刷新的时候圈圈是不会转的,而且圈圈默认是跟着手指拖动的,没有界限,而朋友圈的效果是圈圈在下拉到一个位置后就不再继续下拉了,先来解决下拉位置的问题。
 在moveSpinner方法中,调用完setProgressRotation方法来转圈后,就会调用setTargetoffsettopAndBottom来改变mProgress的位置,代码就不贴了。既然我们要限定下拉的位置,那就应该在这里加以限制,当下移到刷新的位置时就不再下移了,代码如下:

 private voID moveSpinner(float overscrolltop) {…//      setTargetoffsettopAndBottom(targetY - mCurrentTargetoffsettop,true /* requires update */);//      最终刷新的位置      int endTarget;      if (!mUsingCustomStart) {//        没有修改使用默认的值        endTarget = (int) (mSpinnerFinalOffset - Math.abs(mOriginalOffsettop));      } else {//        否则使用定义的值        endTarget = (int) mSpinnerFinalOffset;      }      if(targetY>=endTarget){//        下移的位置超过最终位置后就不再下移,第一个参数为偏移量        setTargetoffsettopAndBottom(0,true /* requires update */);      }else{//        否则继续继续下移        setTargetoffsettopAndBottom(targetY - mCurrentTargetoffsettop,true /* requires update */);      }}

这里先计算出一个endTarget,就是最终的位置,其他注释的比较详细不说了,这样就限制住了下移的位置。
 接下来要让刷新的时候圈圈继续转,那就需要知道刷新时是执行哪里的动画。上篇文章也提到了,转圈的动画是在mProgress的start方法里的,来看看源码:

 @OverrIDepublic voID start() {  mAnimation.reset();  mRing.storeOriginals();  // Already showing some part of the ring  if (mRing.getEndTrim() != mRing.getStartTrim()) {    mFinishing = true;    mAnimation.setDuration(ANIMATION_DURATION/2);// 将转圈圈的动画传入    mParent.startAnimation(mAnimation);  } else {    mRing.setcolorIndex(0);    mRing.resetOriginals();    mAnimation.setDuration(ANIMATION_DURATION);// 将转圈圈的动画传入    mParent.startAnimation(mAnimation);  }}

主要其实就最后一句,将转圈圈的动画传入,mAnimation就是默认的转动动画,感兴趣可以自己去看看,我们只需要自定义转圈圈的动画并传入该方法就可以了。有了刚才的setProgressRotation方法,只需要定义一个动画并不断改变rotation的值并执行这个方法就好了,代码如下:

 private voID setupAnimation() {//    初始化旋转动画    mAnimation = new Animation(){      @OverrIDe      protected voID applytransformation(float interpolatedTime,transformation t) {        setProgressRotation(-interpolatedTime);      }    };    mAnimation.setDuration(5000);//    无限重复    mAnimation.setRepeatCount(Animation.INFINITE);    mAnimation.setRepeatMode(Animation.RESTART);//    均匀转速    mAnimation.setInterpolator(new linearInterpolator());  }  @OverrIDe  public voID start() {    mParent.startAnimation(mAnimation);  } 

这样就OK了! 

5. 修改加载完毕的动画
 现在已经基本完成了,最后还有一个结束的动画,默认是scale动画,而微信的是向上运动至消失,最后的动画是通过执行SwipeRefreshLayout的startScaleDownAnimation方法完成的,在方法内部定义了一个scale动画,我们只需要注释掉并自己定义一个动画就好了:

 private voID startScaleDownAnimation(Animation.AnimationListener Listener) {//      mScaleDownAnimation = new Animation() {//        @OverrIDe//        public voID applytransformation(float interpolatedTime,transformation t) {//          setAnimationProgress(1 - interpolatedTime);//        }//      };      //      最终的偏移量就是mCircleVIEw距离顶部的高度      final int deltaY = -mCircleVIEw.getBottom();      mScaleDownAnimation = new TranslateAnimation(0,deltaY);//      mScaleDownAnimation.setDuration(SCALE_DOWN_DURATION);      mScaleDownAnimation.setDuration(500);      mCircleVIEw.setAnimationListener(Listener);      mCircleVIEw.clearanimation();      mCircleVIEw.startAnimation(mScaleDownAnimation);    }

也就是一个偏移动画~
 在activity中进行一些设置,传入朋友圈的图标后就能得到开头的效果了:

 CustomProgressDrawable drawable = new CustomProgressDrawable(this,mRefreshLayout);  Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.moments_refresh_icon);  drawable.setBitmap(bitmap);  mRefreshLayout.setProgressVIEw(drawable);  mRefreshLayout.setBackgroundcolor(color.BLACK);  mRefreshLayout.setProgressBackgroundcolorSchemecolor(color.BLACK);  mRefreshLayout.setonRefreshListener(new CustomSwipeRefreshLayout.OnRefreshListener(){    @OverrIDe    public voID onRefresh() {      final Handler handler = new Handler(){        @OverrIDe        public voID handleMessage(Message msg) {          super.handleMessage(msg);            mRefreshLayout.setRefreshing(false);        }      };      new Thread(new Runnable() {        @OverrIDe        public voID run() {          try {//  在子线程睡眠三秒后发送消息停止刷新。            Thread.sleep(3000);          } catch (InterruptedException e) {            e.printstacktrace();          }          handler.sendEmptyMessage(0);        }      }).start();    }  });

以上就基本通过修改SwipeRefreshLayout的源码仿照了朋友圈的下拉刷新效果了。从源码可以看出SwipeRefreshLayout确实是写得比较封闭的,不修改源码是基本没法自定义样式的,不过这样跟着源码过了一遍思路就比较清晰了。以后如果有机会再试着封装一下吧~ 

最后再附上CustomProgressDrawable的完整代码吧。SwipeRefreshLayout的太长就不发了,该改的地方应该都提到了。 

public class CustomProgressDrawable extends MaterialProgressDrawable{//  旋转因子,调整旋转速度  private static final int ROTATION_FACTOR = 5*360;//  加载时的动画  private Animation mAnimation;  private VIEw mParent;  private Bitmap mBitmap;//  旋转角度  private float rotation;  private Paint paint;  public CustomProgressDrawable(Context context,VIEw parent) {    super(context,parent);    mParent = parent;    paint = new Paint();    setupAnimation();  }  private voID setupAnimation() {//    初始化旋转动画    mAnimation = new Animation(){      @OverrIDe      protected voID applytransformation(float interpolatedTime,transformation t) {        setProgressRotation(-interpolatedTime);      }    };    mAnimation.setDuration(5000);//    无限重复    mAnimation.setRepeatCount(Animation.INFINITE);    mAnimation.setRepeatMode(Animation.RESTART);//    均匀转速    mAnimation.setInterpolator(new linearInterpolator());  }  @OverrIDe  public voID start() {    mParent.startAnimation(mAnimation);  }  public voID setBitmap(Bitmap mBitmap) {    this.mBitmap = mBitmap;  }  @OverrIDe  public voID setProgressRotation(float rotation) {//    取负号是为了和微信保持一致,下拉时逆时针转加载时顺时针转,旋转因子是为了调整转的速度。    this.rotation = -rotation*ROTATION_FACTOR;    invalIDateSelf();  }  @OverrIDe  public voID draw(Canvas c) {    Rect bound = getBounds();    c.rotate(rotation,paint);  }}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程小技巧。

总结

以上是内存溢出为你收集整理的Android自定义SwipeRefreshLayout高仿微信朋友圈下拉刷新全部内容,希望文章能够帮你解决Android自定义SwipeRefreshLayout高仿微信朋友圈下拉刷新所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/web/1141381.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-31
下一篇 2022-05-31

发表评论

登录后才能评论

评论列表(0条)

保存