国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統(tǒng) > Android > 正文

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

2019-12-12 05:52:40
字體:
供稿:網(wǎng)友

上一篇文章里把SwipeRefreshLayout的原理簡單過了一下,大致了解了其工作原理,不熟悉的可以去看一下://m.survivalescaperooms.com/article/89310.htm 

上一篇里最后提到,SwipeRefreshLayout的可定制性是比較差的,看源碼會(huì)發(fā)現(xiàn)跟樣式相關(guān)的幾個(gè)類都是private的而且方法是寫死的,只暴露出了幾個(gè)顏色設(shè)置的方法。這樣使得SwipeRefreshLayout的使用比較簡單,主要就是設(shè)置一個(gè)監(jiān)聽器在onRefresh方法里完成刷新邏輯。講道理SwipeRefreshLayout的樣式是挺美觀的,如果以后都用這種下拉刷新樣式的話,程序員就清靜了,但這也是不太可能的。如果就想用官方的SwipeRefreshLayout,不想用第三方的控件,又想定制樣式,該怎么辦?基本上只能改源碼了。下面就從修改源碼的角度出發(fā),給出自定義樣式的思路。 

首先需要將SwipeRefreshLayout以及內(nèi)部使用到的CircleImageView和MaterialProgressDrawable的源碼都拷貝出來,放到一個(gè)包里,方便修改。從源碼可以知道,SwipeRefreshLayout中跟樣式相關(guān)的類主要有兩個(gè): 

一. CircleImageView,繼承imageview,源碼就不貼了,主要是繪制背景的,進(jìn)度圈就是繪制在這上面,如果要修改進(jìn)度圈的位置,就應(yīng)該修改CircleImageView的位置。 

二. MaterialProgressDrawable,繼承Drawable實(shí)現(xiàn)Animatable接口,內(nèi)部還定義了一個(gè)Ring類,主要是繪制進(jìn)度圈的,如果要修改進(jìn)度圈的圖片和動(dòng)畫,就應(yīng)該從這里開刀。 

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

先上效果圖,可以跟手機(jī)里的微信比較一下,整體感覺還是可以的。第一次錄gif,錄了太長,處理的時(shí)候刪了一些中間的幀) 

這段時(shí)間在高仿微信,圖方便就把整體的效果也展示了,讀者關(guān)注刷新頁面即可。布局主要就是一個(gè)SwipeRefreshLayout內(nèi)嵌一個(gè)RecyclerView,滑動(dòng)到頂端向下拖動(dòng)時(shí),出來的進(jìn)度圈是朋友圈的那個(gè)彩虹圈,位置在左邊,而且隨著向下拖動(dòng)會(huì)不斷繞中心轉(zhuǎn)啊轉(zhuǎn),此外,進(jìn)度圈在到達(dá)某個(gè)位置后就不會(huì)再往下了。跟默認(rèn)效果不同的還有recyclerview,默認(rèn)是主布局是不會(huì)跟著拖動(dòng)的,而微信的有一個(gè)拖動(dòng)反彈效果,背景是黑色。開始刷新后,主布局反彈到頭部,進(jìn)度圈在那里轉(zhuǎn)啊轉(zhuǎn),刷新完畢后進(jìn)度圈就消失了,整個(gè)過程就是這樣。那么就一步一步來. 

1. 調(diào)整進(jìn)度圈位置
 首先要將進(jìn)度圈調(diào)整到左邊,根據(jù)View的繪制原理,進(jìn)度圈的位置應(yīng)該是由父布局也就是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就是轉(zhuǎn)載進(jìn)度圈的View,因此應(yīng)該把最后一句注釋掉,改為:

 //      mCircleView.layout((width / 2 - circleWidth / 2), mCurrentTargetOffsetTop,//          (width / 2 + circleWidth / 2), mCurrentTargetOffsetTop + circleHeight);//      修改進(jìn)度圈的X坐標(biāo)使之位于左邊      mCircleView.layout(childLeft, mCurrentTargetOffsetTop,          childLeft+circleWidth, mCurrentTargetOffsetTop + circleHeight); 

這樣你就會(huì)很高興地發(fā)現(xiàn)進(jìn)度圈已經(jīng)調(diào)到左邊了。 

2. 實(shí)現(xiàn)拖動(dòng)反彈效果
 接下來先修改recyclerview的拖動(dòng)反彈效果,SwipeRefreshLayout默認(rèn)的效果是不拖動(dòng)的,如果要修改其實(shí)也很簡單,無非就是記錄下手指運(yùn)動(dòng)的距離并讓recyclerview設(shè)置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);//          記錄手指移動(dòng)的距離,mInitialMotionY是初始的位置,DRAG_RATE是拖拽因子,默認(rèn)為0.5。          final float overscrollTop = (y - mInitialMotionY) * DRAG_RATE;//          賦值給mTarget的top使之產(chǎn)生拖動(dòng)效果          mTarget.setTranslationY(overscrollTop);          if (mIsBeingDragged) {            if (overscrollTop > 0) {              moveSpinner(overscrollTop);            } else {              return false;            }          }          break;        }        case MotionEvent.ACTION_UP: {//          手指松開時(shí)啟動(dòng)動(dòng)畫回到頭部          mTarget.animate().translationY(0).setDuration(200).start();          pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);          if (pointerIndex < 0) {            Log.e(LOG_TAG, "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;        } 

不相關(guān)的我都略過了,修改的地方我也注釋了,很清晰。這樣就解決了拖動(dòng)反彈的問題,得益于SwipeRefreshLayout的框架,不用考慮沖突問題,修改起來還是很簡單的。

3. 修改圖標(biāo)和拖動(dòng)時(shí)的動(dòng)畫 

接下來就是比較麻煩的圖標(biāo)和動(dòng)畫了。修改圖標(biāo)其實(shí)不難,因?yàn)镃ircleView是繼承ImageView的,完全可以通過反射取到CircleView的實(shí)例變量,然后setBitmap將你的圖標(biāo)傳進(jìn)去。但是這樣的話就沒有動(dòng)畫了,顯然也是沒啥意義的。讀者可以大致看看MaterialProgressDrawable的源碼,要實(shí)現(xiàn)默認(rèn)的動(dòng)畫還是比較復(fù)雜的,我這里要改為微信的效果,就一個(gè)圈圈轉(zhuǎn)啊轉(zhuǎn),還是比較簡單的,下面就結(jié)合上篇文章所解析的流程看看如何修改。
 首先新建一個(gè)CustomProgressDrawable類,并繼承自MaterialProgressDrawable(需要將源碼復(fù)制出來),還需要在SwipeRefreshLayout添加set方法,方便把自定義的類傳進(jìn)去。

 public void setProgressView(MaterialProgressDrawable mProgress){  this.mProgress = mProgress;  mCircleView.setImageDrawable(mProgress);}

要在CustomProgressDrawable中繪制自定義的圖標(biāo),就需要暴露一個(gè)setBitmap的方法以便繪制。上篇文章提到,手指移動(dòng)時(shí)會(huì)調(diào)用moveSpinner方法,并把移動(dòng)的距離傳進(jìn)去,該方法內(nèi)首先會(huì)經(jīng)過一堆數(shù)學(xué)的處理得出一個(gè)rotation,再把它傳入mProgress的setProgressRotation,也就是說setProgressRotation方法是通過傳入的角度來轉(zhuǎn)圈圈的。朋友圈的效果就是一直讓中心轉(zhuǎn),所以很容易改寫:

 private float rotation;  private Bitmap mBitmap;  public void setBitmap(Bitmap mBitmap) {    this.mBitmap = mBitmap;  }  @Override  public void setProgressRotation(float rotation) {//    取負(fù)號(hào)是為了和微信保持一致,下拉時(shí)逆時(shí)針轉(zhuǎn)加載時(shí)順時(shí)針轉(zhuǎn),旋轉(zhuǎn)因子是為了調(diào)整轉(zhuǎn)的速度。    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,0,mBitmap.getWidth(),mBitmap.getHeight());    c.drawBitmap(mBitmap,src,bound,paint);  } 

就是不斷旋轉(zhuǎn)canvas再繪制bitmap。這樣你就會(huì)很高興地發(fā)現(xiàn)下拉的時(shí)候圈圈也轉(zhuǎn)起來了。 

4. 設(shè)置進(jìn)度圈下拉界限和實(shí)現(xiàn)加載時(shí)的動(dòng)畫
此時(shí)正在刷新的時(shí)候圈圈是不會(huì)轉(zhuǎn)的,而且圈圈默認(rèn)是跟著手指拖動(dòng)的,沒有界限,而朋友圈的效果是圈圈在下拉到一個(gè)位置后就不再繼續(xù)下拉了,先來解決下拉位置的問題。
 在moveSpinner方法中,調(diào)用完setProgressRotation方法來轉(zhuǎn)圈后,就會(huì)調(diào)用setTargetOffsetTopAndBottom來改變mProgress的位置,代碼就不貼了。既然我們要限定下拉的位置,那就應(yīng)該在這里加以限制,當(dāng)下移到刷新的位置時(shí)就不再下移了,代碼如下:

 private void moveSpinner(float overscrollTop) {…//      setTargetOffsetTopAndBottom(targetY - mCurrentTargetOffsetTop, true /* requires update */);//      最終刷新的位置      int endTarget;      if (!mUsingCustomStart) {//        沒有修改使用默認(rèn)的值        endTarget = (int) (mSpinnerFinalOffset - Math.abs(mOriginalOffsetTop));      } else {//        否則使用定義的值        endTarget = (int) mSpinnerFinalOffset;      }      if(targetY>=endTarget){//        下移的位置超過最終位置后就不再下移,第一個(gè)參數(shù)為偏移量        setTargetOffsetTopAndBottom(0, true /* requires update */);      }else{//        否則繼續(xù)繼續(xù)下移        setTargetOffsetTopAndBottom(targetY - mCurrentTargetOffsetTop, true /* requires update */);      }}

這里先計(jì)算出一個(gè)endTarget,就是最終的位置,其他注釋的比較詳細(xì)不說了,這樣就限制住了下移的位置。
 接下來要讓刷新的時(shí)候圈圈繼續(xù)轉(zhuǎn),那就需要知道刷新時(shí)是執(zhí)行哪里的動(dòng)畫。上篇文章也提到了,轉(zhuǎn)圈的動(dòng)畫是在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);// 將轉(zhuǎn)圈圈的動(dòng)畫傳入    mParent.startAnimation(mAnimation);  } else {    mRing.setColorIndex(0);    mRing.resetOriginals();    mAnimation.setDuration(ANIMATION_DURATION);// 將轉(zhuǎn)圈圈的動(dòng)畫傳入    mParent.startAnimation(mAnimation);  }}

主要其實(shí)就最后一句,將轉(zhuǎn)圈圈的動(dòng)畫傳入,mAnimation就是默認(rèn)的轉(zhuǎn)動(dòng)動(dòng)畫,感興趣可以自己去看看,我們只需要自定義轉(zhuǎn)圈圈的動(dòng)畫并傳入該方法就可以了。有了剛才的setProgressRotation方法,只需要定義一個(gè)動(dòng)畫并不斷改變r(jià)otation的值并執(zhí)行這個(gè)方法就好了,代碼如下:

 private void setupAnimation() {//    初始化旋轉(zhuǎn)動(dòng)畫    mAnimation = new Animation(){      @Override      protected void applyTransformation(float interpolatedTime, Transformation t) {        setProgressRotation(-interpolatedTime);      }    };    mAnimation.setDuration(5000);//    無限重復(fù)    mAnimation.setRepeatCount(Animation.INFINITE);    mAnimation.setRepeatMode(Animation.RESTART);//    均勻轉(zhuǎn)速    mAnimation.setInterpolator(new LinearInterpolator());  }  @Override  public void start() {    mParent.startAnimation(mAnimation);  } 

這樣就OK了! 

5. 修改加載完畢的動(dòng)畫
 現(xiàn)在已經(jīng)基本完成了,最后還有一個(gè)結(jié)束的動(dòng)畫,默認(rèn)是scale動(dòng)畫,而微信的是向上運(yùn)動(dòng)至消失,最后的動(dòng)畫是通過執(zhí)行SwipeRefreshLayout的startScaleDownAnimation方法完成的,在方法內(nèi)部定義了一個(gè)scale動(dòng)畫,我們只需要注釋掉并自己定義一個(gè)動(dòng)畫就好了:

 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,0,0,deltaY);//      mScaleDownAnimation.setDuration(SCALE_DOWN_DURATION);      mScaleDownAnimation.setDuration(500);      mCircleView.setAnimationListener(listener);      mCircleView.clearAnimation();      mCircleView.startAnimation(mScaleDownAnimation);    }

也就是一個(gè)偏移動(dòng)畫~
 在activity中進(jìn)行一些設(shè)置,傳入朋友圈的圖標(biāo)后就能得到開頭的效果了:

 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 {//  在子線程睡眠三秒后發(fā)送消息停止刷新。            Thread.sleep(3000);          } catch (InterruptedException e) {            e.printStackTrace();          }          handler.sendEmptyMessage(0);        }      }).start();    }  });

以上就基本通過修改SwipeRefreshLayout的源碼仿照了朋友圈的下拉刷新效果了。從源碼可以看出SwipeRefreshLayout確實(shí)是寫得比較封閉的,不修改源碼是基本沒法自定義樣式的,不過這樣跟著源碼過了一遍思路就比較清晰了。以后如果有機(jī)會(huì)再試著封裝一下吧~ 

最后再附上CustomProgressDrawable的完整代碼吧。SwipeRefreshLayout的太長就不發(fā)了,該改的地方應(yīng)該都提到了。 

public class CustomProgressDrawable extends MaterialProgressDrawable{//  旋轉(zhuǎn)因子,調(diào)整旋轉(zhuǎn)速度  private static final int ROTATION_FACTOR = 5*360;//  加載時(shí)的動(dòng)畫  private Animation mAnimation;  private View mParent;  private Bitmap mBitmap;//  旋轉(zhuǎn)角度  private float rotation;  private Paint paint;  public CustomProgressDrawable(Context context, View parent) {    super(context, parent);    mParent = parent;    paint = new Paint();    setupAnimation();  }  private void setupAnimation() {//    初始化旋轉(zhuǎn)動(dòng)畫    mAnimation = new Animation(){      @Override      protected void applyTransformation(float interpolatedTime, Transformation t) {        setProgressRotation(-interpolatedTime);      }    };    mAnimation.setDuration(5000);//    無限重復(fù)    mAnimation.setRepeatCount(Animation.INFINITE);    mAnimation.setRepeatMode(Animation.RESTART);//    均勻轉(zhuǎn)速    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) {//    取負(fù)號(hào)是為了和微信保持一致,下拉時(shí)逆時(shí)針轉(zhuǎn)加載時(shí)順時(shí)針轉(zhuǎn),旋轉(zhuǎn)因子是為了調(diào)整轉(zhuǎn)的速度。    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,0,mBitmap.getWidth(),mBitmap.getHeight());    c.drawBitmap(mBitmap,src,bound,paint);  }}

以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持武林網(wǎng)。

發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 龙陵县| 黄骅市| 大姚县| 家居| 五原县| 盐边县| 邳州市| 客服| 突泉县| 民县| 哈巴河县| 井冈山市| 乡城县| 宁海县| 华蓥市| 察雅县| 漾濞| 扎鲁特旗| 西乌珠穆沁旗| 兴海县| 花莲县| 铜梁县| 舞阳县| 信丰县| 平顶山市| 三河市| 永定县| 黄浦区| 定结县| 翁牛特旗| 星子县| 花莲市| 靖宇县| 潍坊市| 西藏| 介休市| 溆浦县| 呼和浩特市| 望奎县| 台北县| 嵊州市|