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

首頁 > 系統 > Android > 正文

Android下拉刷新控件SwipeRefreshLayout源碼解析

2019-12-12 05:52:47
字體:
來源:轉載
供稿:網友

SwipeRefreshLayout是Android官方的下拉刷新控件,使用簡單,界面美觀,不熟悉的朋友可以隨便搜索了解一下,這里就不廢話了,直接進入正題。 

首先給張流程圖吧,標出了幾個主要方法的作用,可以結合著看一下哈。

 

這種下拉刷新控件的原理不難,基本就是監聽手指的運動,獲取手指的坐標,通過計算判斷出是哪種操作,然后就是回調相應的接口了。SwipeRefreshLayout是繼承自ViewGroup的,根據Android的事件分發機制,觸摸事件應該是先傳遞到ViewGroup,根據onInterceptTouchEvent的返回值決定是否攔截事件的,那么就onInterceptTouchEvent出發: 

@Override public boolean onInterceptTouchEvent(MotionEvent ev) {  ensureTarget();  final int action = MotionEventCompat.getActionMasked(ev);  if (mReturningToStart && action == MotionEvent.ACTION_DOWN) {   mReturningToStart = false;  }  if (!isEnabled() || mReturningToStart || canChildScrollUp()    || mRefreshing || mNestedScrollInProgress) {   // Fail fast if we're not in a state where a swipe is possible   return false;  }  switch (action) {   case MotionEvent.ACTION_DOWN:    setTargetOffsetTopAndBottom(mOriginalOffsetTop - mCircleView.getTop(), true);    mActivePointerId = MotionEventCompat.getPointerId(ev, 0);    mIsBeingDragged = false;    final float initialDownY = getMotionEventY(ev, mActivePointerId);    if (initialDownY == -1) {     return false;    }    mInitialDownY = initialDownY;    break;   case MotionEvent.ACTION_MOVE:    if (mActivePointerId == INVALID_POINTER) {     Log.e(LOG_TAG, "Got ACTION_MOVE event but don't have an active pointer id.");     return false;    }    final float y = getMotionEventY(ev, mActivePointerId);    if (y == -1) {     return false;    }    final float yDiff = y - mInitialDownY;    if (yDiff > mTouchSlop && !mIsBeingDragged) {     mInitialMotionY = mInitialDownY + mTouchSlop;     mIsBeingDragged = true;     mProgress.setAlpha(STARTING_PROGRESS_ALPHA);    }    break;   case MotionEventCompat.ACTION_POINTER_UP:    onSecondaryPointerUp(ev);    break;   case MotionEvent.ACTION_UP:   case MotionEvent.ACTION_CANCEL:    mIsBeingDragged = false;    mActivePointerId = INVALID_POINTER;    break;  }  return mIsBeingDragged; }

是否攔截的情況有很多種,這里如果滿足五個條件之一就直接返回false,使用時觸摸事件發生沖突的話就可以從這里出發分析,這里也不具體展開了。簡單看一下,在ACTION_DOWN中記錄下手指坐標,ACTION_MOVE中計算出移動的距離,并且判斷是否大于閾值,是的話就將mIsBeingDragged標志位設為true,ACTION_UP中則將mIsBeingDragged設為false。最后返回的是mIsBeingDragged。

SwipeRefreshLayout一般是嵌套可滾動的View使用的,正常滾動時會滿足前面的條件,這時不進行攔截,只有當滾動到頂部才會進入后面action的判斷。在手指按下和抬起期間mIsBeingDragged為true,也就是說進行攔截,接下來就是如何處理了,看看onTouchEvent:

 @Override public boolean onTouchEvent(MotionEvent ev) {    ....  switch (action) {   case MotionEvent.ACTION_DOWN:    mActivePointerId = MotionEventCompat.getPointerId(ev, 0);    mIsBeingDragged = false;    break;   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);    final float overscrollTop = (y - mInitialMotionY) * DRAG_RATE;    if (mIsBeingDragged) {     if (overscrollTop > 0) {      moveSpinner(overscrollTop);     } else {      return false;     }    }    break;   }   ....   case MotionEvent.ACTION_UP: {    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;   }   case MotionEvent.ACTION_CANCEL:    return false;  }  return true; } 

這里省略了一些代碼,前面還有幾行跟上面的類似,也是在滿足其中一個條件時直接返回;switch中也還有幾行處理多指觸控的,這些都略過了。看一下ACTION_MOVE中計算了手指移動的距離,這時的mIsBeingDragged正常情況下應為true,當距離大于零就會執行moveSpinner。在ACTION_UP中則會執行finishSpinner,到這里就可以猜出,執行刷新的邏輯主要就在這兩個方法中。 

看這兩個方法前,要知道兩個重要的成員變量:一個是mCircleView,是CircleImageView的實例,繼承了ImageView,主要繪制進度圈的背景;另一個是mProgress,是MaterialProgressDrawable的實例,繼承自Drawable且實現Animatable接口,主要繪制進度圈,SwipeRefreshLayout正是通過調用其方法來繪制動畫。接下來就先看一下moveSpinner:

 <span style="font-size:18px;">private void moveSpinner(float overscrollTop) {  mProgress.showArrow(true);  float originalDragPercent = overscrollTop / mTotalDragDistance;  float dragPercent = Math.min(1f, Math.abs(originalDragPercent));  float adjustedPercent = (float) Math.max(dragPercent - .4, 0) * 5 / 3;  float extraOS = Math.abs(overscrollTop) - mTotalDragDistance;  float slingshotDist = mUsingCustomStart ? mSpinnerFinalOffset - mOriginalOffsetTop    : mSpinnerFinalOffset;  float tensionSlingshotPercent = Math.max(0, Math.min(extraOS, slingshotDist * 2)    / slingshotDist);  float tensionPercent = (float) ((tensionSlingshotPercent / 4) - Math.pow(    (tensionSlingshotPercent / 4), 2)) * 2f;  float extraMove = (slingshotDist) * tensionPercent * 2;  int targetY = mOriginalOffsetTop + (int) ((slingshotDist * dragPercent) + extraMove);  // where 1.0f is a full circle  if (mCircleView.getVisibility() != View.VISIBLE) {   mCircleView.setVisibility(View.VISIBLE);  }  if (!mScale) {   ViewCompat.setScaleX(mCircleView, 1f);   ViewCompat.setScaleY(mCircleView, 1f);  }  if (mScale) {   setAnimationProgress(Math.min(1f, overscrollTop / mTotalDragDistance));  }  if (overscrollTop < mTotalDragDistance) {   if (mProgress.getAlpha() > STARTING_PROGRESS_ALPHA     && !isAnimationRunning(mAlphaStartAnimation)) {    // Animate the alpha    startProgressAlphaStartAnimation();   }  } else {   if (mProgress.getAlpha() < MAX_ALPHA && !isAnimationRunning(mAlphaMaxAnimation)) {    // Animate the alpha    startProgressAlphaMaxAnimation();   }  }  float strokeStart = adjustedPercent * .8f;  mProgress.setStartEndTrim(0f, Math.min(MAX_PROGRESS_ANGLE, strokeStart));  mProgress.setArrowScale(Math.min(1f, adjustedPercent));  float rotation = (-0.25f + .4f * adjustedPercent + tensionPercent * 2) * .5f;  mProgress.setProgressRotation(rotation);  setTargetOffsetTopAndBottom(targetY - mCurrentTargetOffsetTop, true /* requires update */); }</span>

showArrow是顯示箭頭,中間那一坨主要也是一些math和設置進度圈的樣式,倒數第二行執行了setProgressRotation,傳入的是經過一堆計算后的rotation,這堆計算主要是優化效果,比如在剛開始移動時增長比較快,超過刷新的距離后就增長比較慢。傳入該方法后,mProgress就根據它來繪制進度圈,因此主要的動畫就應該在這個方法內。最后一行執行setTargetOffsetTopAndBottom,我們來看一下:

 <span style="font-size:18px;">private void setTargetOffsetTopAndBottom(int offset, boolean requiresUpdate) {  mCircleView.bringToFront();  mCircleView.offsetTopAndBottom(offset);  mCurrentTargetOffsetTop = mCircleView.getTop();  if (requiresUpdate && android.os.Build.VERSION.SDK_INT < 11) {   invalidate();  } }</span>

 比較簡單,就是調整進度圈的位置并進行記錄。最后來看一下finishSpinner:

 <span style="font-size:18px;">private void finishSpinner(float overscrollTop) {  if (overscrollTop > mTotalDragDistance) {   setRefreshing(true, true /* notify */);  } else {   // cancel refresh   mRefreshing = false;   mProgress.setStartEndTrim(0f, 0f);   Animation.AnimationListener listener = null;   if (!mScale) {    listener = new Animation.AnimationListener() {     @Override     public void onAnimationStart(Animation animation) {     }     @Override     public void onAnimationEnd(Animation animation) {      if (!mScale) {       startScaleDownAnimation(null);      }     }     @Override     public void onAnimationRepeat(Animation animation) {     }    };   }   animateOffsetToStartPosition(mCurrentTargetOffsetTop, listener);   mProgress.showArrow(false);  } }</span>

 邏輯也很簡單,當移動的距離超過設定值時就執行setRefreshing(true,true),在該方法里更新一些成員變量的值后會執行animateOffsetToCorrectPosition,由名字就知道是執行動畫將進度圈移動到正確位置的(也就是頭部)。如果移動的距離沒有超過設定值,就會執行animateOffsetToStartPosition。一起看一下animateOffsetToCorrectPosition和animateOffsetToStartPosition這兩個方法:

 <span style="font-size:18px;">private void animateOffsetToCorrectPosition(int from, AnimationListener listener) {  mFrom = from;  mAnimateToCorrectPosition.reset();  mAnimateToCorrectPosition.setDuration(ANIMATE_TO_TRIGGER_DURATION);  mAnimateToCorrectPosition.setInterpolator(mDecelerateInterpolator);  if (listener != null) {   mCircleView.setAnimationListener(listener);  }  mCircleView.clearAnimation();  mCircleView.startAnimation(mAnimateToCorrectPosition); } private void animateOffsetToStartPosition(int from, AnimationListener listener) {  if (mScale) {   // Scale the item back down   startScaleDownReturnToStartAnimation(from, listener);  } else {   mFrom = from;   mAnimateToStartPosition.reset();   mAnimateToStartPosition.setDuration(ANIMATE_TO_START_DURATION);   mAnimateToStartPosition.setInterpolator(mDecelerateInterpolator);   if (listener != null) {    mCircleView.setAnimationListener(listener);   }   mCircleView.clearAnimation();   mCircleView.startAnimation(mAnimateToStartPosition);  } }</span>

邏輯基本相同,進行一些設置后,最后都會執行mCircleView的startAnimation,只是傳入的值以及監聽器不同。 

如果是要執行刷新的操作,傳入的值是頭部高度,監聽器為:

 <span style="font-size:18px;">private Animation.AnimationListener mRefreshListener = new Animation.AnimationListener() {  @Override  public void onAnimationStart(Animation animation) {  }  @Override  public void onAnimationRepeat(Animation animation) {  }  @Override  public void onAnimationEnd(Animation animation) {   if (mRefreshing) {    // Make sure the progress view is fully visible    mProgress.setAlpha(MAX_ALPHA);    mProgress.start();    if (mNotify) {     if (mListener != null) {      mListener.onRefresh();     }    }    mCurrentTargetOffsetTop = mCircleView.getTop();   } else {    reset();   }  } };</span>

動畫完成后,也就是進度圈移動到頭部后,會執行mProgress.start();這里執行的就是在刷新時進度圈轉啊轉的動畫。接下來注意到如果mListener不為空就會執行onRefresh方法,這個mListener其實就是執行setOnRefreshListener所設置的監聽器,因此在這里完成刷新。如果是執行回到初始位置的操作,傳入的值為初始高度(也就是頂部之上),監聽器為

 <span style="font-size:18px;">listener = new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationEnd(Animation animation) {  if (!mScale) {   startScaleDownAnimation(null);  } } @Override public void onAnimationRepeat(Animation animation) { }};</span>

移動到初始位置后會執行startScaleDownAnimation,也就是消失的動畫了,到這里整個刷新流程就結束了。

這樣就基本把SwipeRefreshLayout的流程過了一遍,但是要實現這樣一個控件還是有很多小問題需要考慮的,這里主要是把思路理清,知道如果出現問題該怎樣解決。另外從源碼也可以看出swipeRefreshLayout的定制性是比較差的,也不知道google是不是故意這樣希望以后全都用這種統一樣式的下拉刷新。。當然有一些第三方下拉刷新的定制性還是比較好的,使用上也不難。但是有些人(比如我)是比較傾向于使用官方的控件的,不到萬不得已都不想用第三方工具。下次會寫一篇探討一下用swipeRefreshLayout實現自定義樣式的文章~

后續還有一篇從修改swipeRefreshLayout的源碼出發自定義樣式高仿微信朋友圈的下拉刷新效果的文章,有興趣可以看一下哈//m.survivalescaperooms.com/article/89311.htm

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持武林網。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 博湖县| 越西县| 调兵山市| 新源县| 南皮县| 台南市| 通河县| 伊宁市| 虹口区| 成都市| 屏东市| 九龙城区| 香河县| 策勒县| 东乡| 工布江达县| 青川县| 仲巴县| 英山县| 白城市| 栾城县| 辽阳县| 馆陶县| 武鸣县| 读书| 南雄市| 延安市| 海安县| 东台市| 定远县| 鄂托克旗| 黎城县| 邢台市| 宽城| 新乡市| 赤峰市| 洛川县| 海晏县| 泽州县| 轮台县| 镇康县|