亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

 更新時間:2016年07月26日 14:13:55   作者:u011443509  
這篇文章主要以社交APP的BOSS微信為例,介紹了Android自定義SwipeRefreshLayout高仿微信朋友圈下拉刷新,感興趣的小伙伴們可以參考一下

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

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

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

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

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

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

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

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

1. 調(diào)整進度圈位置
 首先要將進度圈調(diào)整到左邊,根據(jù)View的繪制原理,進度圈的位置應(yīng)該是由父布局也就是SwipeRefreshLayout里的onLayout方法決定的,看看源碼:

 @Override
protected 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)載進度圈的View,因此應(yīng)該把最后一句注釋掉,改為:

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

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

2. 實現(xiàn)拖動反彈效果
 接下來先修改recyclerview的拖動反彈效果,SwipeRefreshLayout默認的效果是不拖動的,如果要修改其實也很簡單,無非就是記錄下手指運動的距離并讓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);
//          記錄手指移動的距離,mInitialMotionY是初始的位置,DRAG_RATE是拖拽因子,默認為0.5。
          final float overscrollTop = (y - mInitialMotionY) * DRAG_RATE;
//          賦值給mTarget的top使之產(chǎn)生拖動效果
          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, 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)的我都略過了,修改的地方我也注釋了,很清晰。這樣就解決了拖動反彈的問題,得益于SwipeRefreshLayout的框架,不用考慮沖突問題,修改起來還是很簡單的。

3. 修改圖標和拖動時的動畫 

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

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

要在CustomProgressDrawable中繪制自定義的圖標,就需要暴露一個setBitmap的方法以便繪制。上篇文章提到,手指移動時會調(diào)用moveSpinner方法,并把移動的距離傳進去,該方法內(nèi)首先會經(jīng)過一堆數(shù)學(xué)的處理得出一個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) {
//    取負號是為了和微信保持一致,下拉時逆時針轉(zhuǎn)加載時順時針轉(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。這樣你就會很高興地發(fā)現(xiàn)下拉的時候圈圈也轉(zhuǎn)起來了。 

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

 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){
//        下移的位置超過最終位置后就不再下移,第一個參數(shù)為偏移量
        setTargetOffsetTopAndBottom(0, true /* requires update */);
      }else{
//        否則繼續(xù)繼續(xù)下移
        setTargetOffsetTopAndBottom(targetY - mCurrentTargetOffsetTop, true /* requires update */);
      }
}

這里先計算出一個endTarget,就是最終的位置,其他注釋的比較詳細不說了,這樣就限制住了下移的位置。
 接下來要讓刷新的時候圈圈繼續(xù)轉(zhuǎn),那就需要知道刷新時是執(zhí)行哪里的動畫。上篇文章也提到了,轉(zhuǎn)圈的動畫是在mProgress的start方法里的,來看看源碼:

 @Override
public 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)圈圈的動畫傳入
    mParent.startAnimation(mAnimation);
  } else {
    mRing.setColorIndex(0);
    mRing.resetOriginals();
    mAnimation.setDuration(ANIMATION_DURATION);
// 將轉(zhuǎn)圈圈的動畫傳入
    mParent.startAnimation(mAnimation);
  }
}

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

 private void setupAnimation() {
//    初始化旋轉(zhuǎn)動畫
    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. 修改加載完畢的動畫
 現(xiàn)在已經(jīng)基本完成了,最后還有一個結(jié)束的動畫,默認是scale動畫,而微信的是向上運動至消失,最后的動畫是通過執(zhí)行SwipeRefreshLayout的startScaleDownAnimation方法完成的,在方法內(nèi)部定義了一個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,0,0,deltaY);
//      mScaleDownAnimation.setDuration(SCALE_DOWN_DURATION);
      mScaleDownAnimation.setDuration(500);
      mCircleView.setAnimationListener(listener);
      mCircleView.clearAnimation();
      mCircleView.startAnimation(mScaleDownAnimation);
    }

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

 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確實是寫得比較封閉的,不修改源碼是基本沒法自定義樣式的,不過這樣跟著源碼過了一遍思路就比較清晰了。以后如果有機會再試著封裝一下吧~ 

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

public class CustomProgressDrawable extends MaterialProgressDrawable{

//  旋轉(zhuǎn)因子,調(diào)整旋轉(zhuǎn)速度
  private static final int ROTATION_FACTOR = 5*360;
//  加載時的動畫
  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)動畫
    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) {
//    取負號是為了和微信保持一致,下拉時逆時針轉(zhuǎn)加載時順時針轉(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)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • Android開發(fā)服務(wù)Service全面講解

    Android開發(fā)服務(wù)Service全面講解

    Android的服務(wù)是開發(fā)Android應(yīng)用程序的重要組成部分。不同于活動Activity,服務(wù)是在后臺運行,服務(wù)沒有接口,生命周期也與活動Activity非常不同。通過使用服務(wù)我們可以實現(xiàn)一些后臺操作,比如想從遠程服務(wù)器加載一個網(wǎng)頁等,下面來看看詳細內(nèi)容,需要的朋友可以參考下
    2023-02-02
  • Android?ViewStub使用方法學(xué)習(xí)

    Android?ViewStub使用方法學(xué)習(xí)

    這篇文章主要為大家介紹了Android?ViewStub使用方法學(xué)習(xí),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-11-11
  • Android視頻壓縮的示例代碼

    Android視頻壓縮的示例代碼

    本篇文章主要介紹了Android視頻壓縮的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-09-09
  • Android實現(xiàn)酷炫的頂部欄

    Android實現(xiàn)酷炫的頂部欄

    這篇文章主要為大家詳細介紹了Android實現(xiàn)酷炫的頂部欄,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-09-09
  • 詳解Android過濾emoji表情正則表達式

    詳解Android過濾emoji表情正則表達式

    這篇文章主要介紹了Android過濾emoji表情正則表達式,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下
    2018-06-06
  • Android自定義View實現(xiàn)角度選擇器

    Android自定義View實現(xiàn)角度選擇器

    前幾天在Google Photos查看照片,用了一下它的圖片剪裁功能,于是我馬上就被其界面和操作吸引。后來想模仿做一個和Google Photos裁圖頁面幾乎一模一樣的角度選擇器,本文比較基礎(chǔ),在閱讀本文前只需要掌握最基礎(chǔ)的自定義View知識和Android事件知識。下面來一起學(xué)習(xí)下吧。
    2016-11-11
  • Android 數(shù)據(jù)庫文件存取至儲存卡的方法

    Android 數(shù)據(jù)庫文件存取至儲存卡的方法

    這篇文章主要介紹了Android 數(shù)據(jù)庫文件存取至儲存卡的方法的相關(guān)資料,需要的朋友可以參考下
    2016-03-03
  • Android數(shù)據(jù)類型之間相互轉(zhuǎn)換系統(tǒng)介紹

    Android數(shù)據(jù)類型之間相互轉(zhuǎn)換系統(tǒng)介紹

    一些初學(xué)Android的朋友可能會遇到JAVA的數(shù)據(jù)類型之間轉(zhuǎn)換的苦惱;本文將為有這類需求的朋友解決此類問題
    2012-11-11
  • android開發(fā)教程之獲取使用當(dāng)前api的應(yīng)用程序名稱

    android開發(fā)教程之獲取使用當(dāng)前api的應(yīng)用程序名稱

    開發(fā)手機安全管家的時候,比如要打電話,或者照相需要知道是哪個應(yīng)用程序在調(diào)用,就可以在API接口中調(diào)用下面的代碼
    2014-02-02
  • Android用過TextView實現(xiàn)跑馬燈效果的示例

    Android用過TextView實現(xiàn)跑馬燈效果的示例

    本篇文章主要介紹了Android用過TextView實現(xiàn)跑馬燈效果的示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-08-08

最新評論