91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

SnapHelper源碼深度解析

發布時間:2020-07-22 22:43:17 來源:網絡 閱讀:831 作者:楊充 欄目:移動開發
目錄介紹
  • 01.SnapHelper簡單介紹
    • 1.1 SnapHelper作用
    • 1.2 SnapHelper類分析
    • 1.3 LinearSnapHelper類分析
    • 1.4 PagerSnapHelper類分析
  • 02.SnapHelper源碼分析
    • 2.1 attachToRecyclerView入口方法
    • 2.2 SnapHelper的抽象方法
    • 2.3 onFling方法源碼分析
  • 03.LinearSnapHelper源碼分析
    • 3.1 LinearSnapHelper實現功能
    • 3.2 calculateDistanceToFinalSnap()方法源碼
    • 3.3 findSnapView()方法源碼
    • 3.4 findTargetSnapPosition()方法源碼
    • 3.5 支持哪些LayoutManager
    • 3.6 OrientationHelper類
    • 3.7 estimateNextPositionDiffForFling計算偏移量
  • 04.自定義SnapHelper類
    • 4.1 業務需求
    • 4.2 自定義helper類

好消息

  • 博客筆記大匯總【16年3月到至今】,包括Java基礎及深入知識點,Android技術博客,Python學習筆記等等,還包括平時開發中遇到的bug匯總,當然也在工作之余收集了大量的面試題,長期更新維護并且修正,持續完善……開源的文件是markdown格式的!同時也開源了生活博客,從12年起,積累共計47篇[近20萬字],轉載請注明出處,謝謝!
  • 鏈接地址:https://github.com/yangchong211/YCBlogs
  • 如果覺得好,可以star一下,謝謝!當然也歡迎提出建議,萬事起于忽微,量變引起質變!

01.SnapHelper簡單介紹

1.1 SnapHelper作用
  • 在某些場景下,卡片列表滑動瀏覽[有的叫輪播圖],希望當滑動停止時可以將當前卡片停留在屏幕某個位置,比如停在左邊,以吸引用戶的焦點。那么可以使用RecyclerView + Snaphelper來實現,SnapHelper旨在支持RecyclerView的對齊方式,也就是通過計算對齊RecyclerView中TargetView 的指定點或者容器中的任何像素點。
1.2 SnapHelper類分析
  • 查閱可知,SnapHelper繼承自RecyclerView.OnFlingListener,并且重寫了onFling方法,這個類代碼并不多,下面會對重要方法一一解析。
    • 支持SnapHelper的RecyclerView.LayoutManager必須實現的方式:
      • RecyclerView.SmoothScroller.ScrollVectorProvider接口
      • 或者自己實現onFling(int,int)方法手動處理邏輯。
  • SnapHelper類重要的方法
    • attachToRecyclerView: 將SnapHelper attach 到指定的RecyclerView 上。
    • calculateDistanceToFinalSnap:復寫這個方法計算對齊到TargetView或容器指定點的距離,這是一個抽象方法,由子類自己實現,返回的是一個長度為2的int 數組out,out[0]是x方向對齊要移動的距離,out[1]是y方向對齊要移動的距離。
    • calculateScrollDistance: 根據每個方向給定的速度估算滑動的距離,用于Fling 操作。
    • findSnapView:提供一個指定的目標View 來對齊,抽象方法,需要子類實現
    • findTargetSnapPosition:提供一個用于對齊的Adapter 目標position,抽象方法,需要子類自己實現。
    • onFling:根據給定的x和 y 軸上的速度處理Fling。
  • 什么是Fling操作
    • 手指在屏幕上滑動 RecyclerView然后松手,RecyclerView中的內容會順著慣性繼續往手指滑動的方向繼續滾動直到停止,這個過程叫做 Fling 。 Fling 操作從手指離開屏幕瞬間被觸發,在滾動停止時結束。
1.3 LinearSnapHelper類分析
  • LinearSnapHelper 使當前Item居中顯示,常用場景是橫向的RecyclerView,類似ViewPager效果,但是又可以快速滑動(滑動多頁)。
  • 最簡單的使用就是,如下代碼
    • 幾行代碼就可以用RecyclerView實現一個類似ViewPager的效果,并且效果還不錯。可以快速滑動多頁,當前頁劇中顯示,并且顯示前一頁和后一頁的部分。
      private void initRecyclerView() {
      LinearLayoutManager manager = new LinearLayoutManager(this);
      manager.setOrientation(LinearLayoutManager.HORIZONTAL);
      mRecyclerView.setLayoutManager(manager);
      LinearSnapHelper snapHelper = new LinearSnapHelper();
      snapHelper.attachToRecyclerView(mRecyclerView);
      SnapAdapter adapter = new SnapAdapter(this);
      mRecyclerView.setAdapter(adapter);
      adapter.addAll(getData());
      }
1.4 PagerSnapHelper類分析
  • PagerSnapHelper看名字可能就能猜到,使RecyclerView像ViewPager一樣的效果,每次只能滑動一頁(LinearSnapHelper支持快速滑動), PagerSnapHelper也是Item居中對齊。
  • 最簡單的使用就是,如下代碼
    private void initRecyclerView() {
        LinearLayoutManager manager = new LinearLayoutManager(this);
        manager.setOrientation(LinearLayoutManager.HORIZONTAL);
        mRecyclerView.setLayoutManager(manager);
        PagerSnapHelper snapHelper = new PagerSnapHelper();
        snapHelper.attachToRecyclerView(mRecyclerView);
        SnapAdapter adapter = new SnapAdapter(this);
        mRecyclerView.setAdapter(adapter);
        adapter.addAll(getData());
    }

02.SnapHelper源碼分析

2.1 attachToRecyclerView入口方法
  • 通過attachToRecyclerView方法將SnapHelper attach 到RecyclerView,看一下這個方法的源代碼
    • 如果SnapHelper之前已經附著到此RecyclerView上,則不用進行任何操作
    • 如果SnapHelper之前附著的RecyclerView和現在的不一致,就將原來設置的回調全部remove或者設置為null
    • 然后更新RecyclerView對象引用,Attach的RecyclerView不為null,設置回調Callback,主要包括滑動的回調和Fling操作的回調,初始化一個Scroller 用于后面做滑動處理,然后調用snapToTargetExistingView
    • 大概流程就是:在attachToRecyclerView()方法中會清掉SnapHelper之前保存的RecyclerView對象的回調(如果有的話),對新設置進來的RecyclerView對象設置回調,然后初始化一個Scroller對象,最后調用snapToTargetExistingView()方法對SnapView進行對齊調整。
      public void attachToRecyclerView(@Nullable RecyclerView recyclerView)
          throws IllegalStateException {
      if (mRecyclerView == recyclerView) {
          return; // nothing to do
      }
      if (mRecyclerView != null) {
          destroyCallbacks();
      }
      mRecyclerView = recyclerView;
      if (mRecyclerView != null) {
          setupCallbacks();
          mGravityScroller = new Scroller(mRecyclerView.getContext(),
                  new DecelerateInterpolator());
          snapToTargetExistingView();
      }
      }
  • 接著看看setupCallbacks()源碼
    • 上面已經說了,滑動的回調和Fling操作的回調
      private void setupCallbacks() throws IllegalStateException {
      if (mRecyclerView.getOnFlingListener() != null) {
          throw new IllegalStateException("An instance of OnFlingListener already set.");
      }
      mRecyclerView.addOnScrollListener(mScrollListener);
      mRecyclerView.setOnFlingListener(this);
      }
  • 接著看看snapToTargetExistingView()方法
    • 這個方法用于第一次Attach到RecyclerView時對齊TargetView,或者當Scroll被觸發的時候和fling操作的時候對齊TargetView 。
    • 判斷RecyclerView 和LayoutManager是否為null,接著調用findSnapView 方法來獲取需要對齊的目標View,注意:這是個抽象方法,需要子類實現
    • 通過calculateDistanceToFinalSnap 獲取x方向和y方向對齊需要移動的距離
    • 最后如果需要滾動的距離不是為0,就調用smoothScrollBy方法使RecyclerView滾動相應的距離
    • 注意:RecyclerView.smoothScrollBy()這個方法的作用就是根據參數平滑滾動RecyclerView的中的ItemView相應的距離。
      void snapToTargetExistingView() {
      if (mRecyclerView == null) {
          return;
      }
      LayoutManager layoutManager = mRecyclerView.getLayoutManager();
      if (layoutManager == null) {
          return;
      }
      View snapView = findSnapView(layoutManager);
      if (snapView == null) {
          return;
      }
      int[] snapDistance = calculateDistanceToFinalSnap(layoutManager, snapView);
      if (snapDistance[0] != 0 || snapDistance[1] != 0) {
          mRecyclerView.smoothScrollBy(snapDistance[0], snapDistance[1]);
      }
      }
  • 然后來看一下mScrollListener監聽里面做了什么

    • 該滾動監聽器的實現很簡單,只是在正常滾動停止的時候調用了snapToTargetExistingView()方法對targetView進行滾動調整,以確保停止的位置是在對應的坐標上,這就是RecyclerView添加該OnScrollListener的目的。
    • mScrolled為true表示之前進行過滾動,newState為SCROLL_STATE_IDLE狀態表示滾動結束停下來

      private final RecyclerView.OnScrollListener mScrollListener =
      new RecyclerView.OnScrollListener() {
          boolean mScrolled = false;
      
          @Override
          public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
              super.onScrollStateChanged(recyclerView, newState);
              if (newState == RecyclerView.SCROLL_STATE_IDLE && mScrolled) {
                  mScrolled = false;
                  snapToTargetExistingView();
              }
          }
      
          @Override
          public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
              if (dx != 0 || dy != 0) {
                  mScrolled = true;
              }
          }
      };
2.2 SnapHelper的抽象方法
  • calculateDistanceToFinalSnap抽象方法
    • 計算最終對齊要移動的距離
      • 計算二個參數對應的 ItemView 當前的坐標與需要對齊的坐標之間的距離。該方法返回一個大小為 2 的 int 數組,分別對應out[0] 為 x 方向移動的距離,out[1] 為 y 方向移動的距離。
        @SuppressWarnings("WeakerAccess")
        @Nullable
        public abstract int[] calculateDistanceToFinalSnap(@NonNull LayoutManager layoutManager,
        @NonNull View targetView);
  • findSnapView抽象方法
    • 找到要對齊的View
      • 該方法會找到當前 layoutManager 上最接近對齊位置的那個 view ,該 view 稱為 SanpView ,對應的 position 稱為 SnapPosition 。如果返回 null ,就表示沒有需要對齊的 View ,也就不會做滾動對齊調整。
        @SuppressWarnings("WeakerAccess")
        @Nullable
        public abstract View findSnapView(LayoutManager layoutManager);
  • findTargetSnapPosition抽象方法
    • 找到需要對齊的目標View的的Position。
      • 更加詳細一點說就是該方法會根據觸發 Fling 操作的速率(參數 velocityX 和參數 velocityY )來找到 RecyclerView 需要滾動到哪個位置,該位置對應的 ItemView 就是那個需要進行對齊的列表項。我們把這個位置稱為 targetSnapPosition ,對應的 View 稱為 targetSnapView 。如果找不到 targetSnapPosition ,就返回RecyclerView.NO_POSITION 。
        public abstract int findTargetSnapPosition(LayoutManager layoutManager, int velocityX,
        int velocityY);
2.3 onFling方法源碼分析
  • SnapHelper繼承了 RecyclerView.OnFlingListener,實現了onFling方法。
    • 獲取RecyclerView要進行fling操作需要的最小速率,為啥呢?因為只有超過該速率,ItemView才會有足夠的動力在手指離開屏幕時繼續滾動下去。
      @Override
      public boolean onFling(int velocityX, int velocityY) {
      LayoutManager layoutManager = mRecyclerView.getLayoutManager();
      if (layoutManager == null) {
          return false;
      }
      RecyclerView.Adapter adapter = mRecyclerView.getAdapter();
      if (adapter == null) {
          return false;
      }
      int minFlingVelocity = mRecyclerView.getMinFlingVelocity();
      return (Math.abs(velocityY) > minFlingVelocity || Math.abs(velocityX) > minFlingVelocity)
              && snapFromFling(layoutManager, velocityX, velocityY);
      }
  • 接著看看snapFromFling方法源代碼,就是通過該方法實現平滑滾動并使得在滾動停止時itemView對齊到目的坐標位置

    • 首先layoutManager必須實現ScrollVectorProvider接口才能繼續往下操作
    • 然后通過createSnapScroller方法創建一個SmoothScroller,這個東西是一個平滑滾動器,用于對ItemView進行平滑滾動操作
    • 根據x和y方向的速度來獲取需要對齊的View的位置,需要子類實現
    • 最終通過 SmoothScroller 來滑動到指定位置

      private boolean snapFromFling(@NonNull LayoutManager layoutManager, int velocityX,
          int velocityY) {
      if (!(layoutManager instanceof ScrollVectorProvider)) {
          return false;
      }
      
      RecyclerView.SmoothScroller smoothScroller = createSnapScroller(layoutManager);
      if (smoothScroller == null) {
          return false;
      }
      
      int targetPosition = findTargetSnapPosition(layoutManager, velocityX, velocityY);
      if (targetPosition == RecyclerView.NO_POSITION) {
          return false;
      }
      
      smoothScroller.setTargetPosition(targetPosition);
      layoutManager.startSmoothScroll(smoothScroller);
      return true;
      }
    • 總結一下可知:snapFromFling()方法會先判斷layoutManager是否實現了ScrollVectorProvider接口,如果沒有實現該接口就不允許通過該方法做滾動操作。接下來就去創建平滑滾動器SmoothScroller的一個實例,layoutManager可以通過該平滑滾動器來進行滾動操作。SmoothScroller需要設置一個滾動的目標位置,將通過findTargetSnapPosition()方法來計算得到的targetSnapPosition給它,告訴滾動器要滾到這個位置,然后就啟動SmoothScroller進行滾動操作。
  • 接著看下createSnapScroller這個方法源碼

    • 先判斷layoutManager是否實現了ScrollVectorProvider這個接口,沒有實現該接口就不創建SmoothScroller
    • 這里創建一個LinearSmoothScroller對象,然后返回給調用函數,也就是說,最終創建出來的平滑滾動器就是這個LinearSmoothScroller
    • 在創建該LinearSmoothScroller的時候主要考慮兩個方面:
      • 第一個是滾動速率,由calculateSpeedPerPixel()方法決定;
      • 第二個是在滾動過程中,targetView即將要進入到視野時,將勻速滾動變換為減速滾動,然后一直滾動目的坐標位置,使滾動效果更真實,這是由onTargetFound()方法決定。
    @Nullable
    protected LinearSmoothScroller createSnapScroller(LayoutManager layoutManager) {
        if (!(layoutManager instanceof ScrollVectorProvider)) {
            return null;
        }
        return new LinearSmoothScroller(mRecyclerView.getContext()) {
            @Override
            protected void onTargetFound(View targetView, RecyclerView.State state, Action action) {
                int[] snapDistances = calculateDistanceToFinalSnap(mRecyclerView.getLayoutManager(),
                        targetView);
                final int dx = snapDistances[0];
                final int dy = snapDistances[1];
                final int time = calculateTimeForDeceleration(Math.max(Math.abs(dx), Math.abs(dy)));
                if (time > 0) {
                    action.update(dx, dy, time, mDecelerateInterpolator);
                }
            }
    
            @Override
            protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
                return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
            }
        };
    }

03.LinearSnapHelper源碼分析

3.1 LinearSnapHelper實現功能
  • LinearSnapHelper實現了SnapHelper,并且實現SnapHelper的三個抽象方法,從而讓ItemView滾動居中對齊。那么具體怎么做到呢?
3.2 calculateDistanceToFinalSnap()方法源碼
  • calculateDistanceToFinalSnap源碼如下所示

    • 如果是水平方向滾動的,則計算水平方向需要移動的距離,否則水平方向的移動距離為0
    • 如果是豎直方向滾動的,則計算豎直方向需要移動的距離,否則豎直方向的移動距離為0
    • distanceToCenter方法主要作用是:計算水平或者豎直方向需要移動的距離

      @Override
      public int[] calculateDistanceToFinalSnap(
          @NonNull RecyclerView.LayoutManager layoutManager, @NonNull View targetView) {
      int[] out = new int[2];
      if (layoutManager.canScrollHorizontally()) {
          out[0] = distanceToCenter(layoutManager, targetView,
                  getHorizontalHelper(layoutManager));
      } else {
          out[0] = 0;
      }
      
      if (layoutManager.canScrollVertically()) {
          out[1] = distanceToCenter(layoutManager, targetView,
                  getVerticalHelper(layoutManager));
      } else {
          out[1] = 0;
      }
      return out;
      }
  • 接著看看distanceToCenter方法
    • 計算對應的view的中心坐標到RecyclerView中心坐標之間的距離
    • 首先是找到targetView的中心坐標
    • 接著也就是找到容器【RecyclerView】的中心坐標
    • 兩個中心坐標的差值就是targetView需要滾動的距離
      private int distanceToCenter(@NonNull RecyclerView.LayoutManager layoutManager,
          @NonNull View targetView, OrientationHelper helper) {
      final int childCenter = helper.getDecoratedStart(targetView)
              + (helper.getDecoratedMeasurement(targetView) / 2);
      final int containerCenter;
      if (layoutManager.getClipToPadding()) {
          containerCenter = helper.getStartAfterPadding() + helper.getTotalSpace() / 2;
      } else {
          containerCenter = helper.getEnd() / 2;
      }
      return childCenter - containerCenter;
      }
3.3 findSnapView()方法源碼
  • 也就是找到要對齊的View
    • 根據layoutManager的布局方式(水平布局方式或者豎向布局方式)區分計算,但最終都是通過findCenterView()方法來找snapView的。
      @Override
      public View findSnapView(RecyclerView.LayoutManager layoutManager) {
      if (layoutManager.canScrollVertically()) {
          return findCenterView(layoutManager, getVerticalHelper(layoutManager));
      } else if (layoutManager.canScrollHorizontally()) {
          return findCenterView(layoutManager, getHorizontalHelper(layoutManager));
      }
      return null;
      }
  • 接著看看findCenterView方法源代碼

    • 查詢當前是否支持垂直滾動還是橫向滾動
    • 循環LayoutManager的所有子元素,計算每個 childView的中點距離Parent 的中點,找到距離最近的一個,就是需要居中對齊的目標View

      @Nullable
      private View findCenterView(RecyclerView.LayoutManager layoutManager,
          OrientationHelper helper) {
      int childCount = layoutManager.getChildCount();
      if (childCount == 0) {
          return null;
      }
      
      View closestChild = null;
      final int center;
      if (layoutManager.getClipToPadding()) {
          center = helper.getStartAfterPadding() + helper.getTotalSpace() / 2;
      } else {
          center = helper.getEnd() / 2;
      }
      int absClosest = Integer.MAX_VALUE;
      
      for (int i = 0; i < childCount; i++) {
          final View child = layoutManager.getChildAt(i);
          int childCenter = helper.getDecoratedStart(child)
                  + (helper.getDecoratedMeasurement(child) / 2);
          int absDistance = Math.abs(childCenter - center);
      
          /** if child center is closer than previous closest, set it as closest  **/
          if (absDistance < absClosest) {
              absClosest = absDistance;
              closestChild = child;
          }
      }
      return closestChild;
      }
3.4 findTargetSnapPosition()方法源碼
  • LinearSnapHelper實現了SnapHelper,來看一下在findTargetSnapPosition操作了什么

    • 如果是水平方向滾動的列表,估算出水平方向SnapHelper響應fling,對齊要滑動的position和當前position的差,否則,水平方向滾動的差值為0
    • 如果是豎直方向滾動的列表,估算出豎直方向SnapHelper響應fling,對齊要滑動的position和當前position的差,否則,豎直方向滾動的差值為0
    • 這個方法在計算targetPosition的時候把布局方式和布局方向都考慮進去了。布局方式可以通過layoutManager.canScrollHorizontally()/layoutManager.canScrollVertically()來判斷,布局方向就通過RecyclerView.SmoothScroller.ScrollVectorProvider這個接口中的computeScrollVectorForPosition()方法來判斷。

      @Override
      public int findTargetSnapPosition(RecyclerView.LayoutManager layoutManager, int velocityX,
          int velocityY) {
      if (!(layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) {
          return RecyclerView.NO_POSITION;
      }
      
      final int itemCount = layoutManager.getItemCount();
      if (itemCount == 0) {
          return RecyclerView.NO_POSITION;
      }
      
      final View currentView = findSnapView(layoutManager);
      if (currentView == null) {
          return RecyclerView.NO_POSITION;
      }
      
      final int currentPosition = layoutManager.getPosition(currentView);
      if (currentPosition == RecyclerView.NO_POSITION) {
          return RecyclerView.NO_POSITION;
      }
      
      RecyclerView.SmoothScroller.ScrollVectorProvider vectorProvider =
              (RecyclerView.SmoothScroller.ScrollVectorProvider) layoutManager;
      // deltaJumps sign comes from the velocity which may not match the order of children in
      // the LayoutManager. To overcome this, we ask for a vector from the LayoutManager to
      // get the direction.
      PointF vectorForEnd = vectorProvider.computeScrollVectorForPosition(itemCount - 1);
      if (vectorForEnd == null) {
          // cannot get a vector for the given position.
          return RecyclerView.NO_POSITION;
      }
      
      int vDeltaJump, hDeltaJump;
      if (layoutManager.canScrollHorizontally()) {
          hDeltaJump = estimateNextPositionDiffForFling(layoutManager,
                  getHorizontalHelper(layoutManager), velocityX, 0);
          if (vectorForEnd.x < 0) {
              hDeltaJump = -hDeltaJump;
          }
      } else {
          hDeltaJump = 0;
      }
      if (layoutManager.canScrollVertically()) {
          vDeltaJump = estimateNextPositionDiffForFling(layoutManager,
                  getVerticalHelper(layoutManager), 0, velocityY);
          if (vectorForEnd.y < 0) {
              vDeltaJump = -vDeltaJump;
          }
      } else {
          vDeltaJump = 0;
      }
      
      int deltaJump = layoutManager.canScrollVertically() ? vDeltaJump : hDeltaJump;
      if (deltaJump == 0) {
          return RecyclerView.NO_POSITION;
      }
      
      int targetPos = currentPosition + deltaJump;
      if (targetPos < 0) {
          targetPos = 0;
      }
      if (targetPos >= itemCount) {
          targetPos = itemCount - 1;
      }
      return targetPos;
      }
3.5 支持哪些LayoutManager
  • SnapHelper為了適配layoutManager的各種情況,特意要求只有實現了RecyclerView.SmoothScroller.ScrollVectorProvider接口的layoutManager才能使用SnapHelper進行輔助滾動對齊。官方提供的LinearLayoutManager、GridLayoutManager和StaggeredGridLayoutManager都實現了這個接口,所以都支持SnapHelper。
3.6 OrientationHelper類
  • 如何創建OrientationHelper對象呢?如下所示

    • 比如,上面三個抽象方法都使用到了這個類,這個類是干嘛的?
    • 計算位置的時候用的是OrientationHelper這個工具類,它是LayoutManager用于測量child的一個輔助類,可以根據Layoutmanager的布局方式和布局方向來計算得到ItemView的大小位置等信息。
      
      @NonNull
      private OrientationHelper getVerticalHelper(@NonNull RecyclerView.LayoutManager layoutManager) {
      if (mVerticalHelper == null || mVerticalHelper.mLayoutManager != layoutManager) {
          mVerticalHelper = OrientationHelper.createVerticalHelper(layoutManager);
      }
      return mVerticalHelper;
      }

    @NonNull
    private OrientationHelper getHorizontalHelper(
    @NonNull RecyclerView.LayoutManager layoutManager) {
    if (mHorizontalHelper == null || mHorizontalHelper.mLayoutManager != layoutManager) {
    mHorizontalHelper = OrientationHelper.createHorizontalHelper(layoutManager);
    }
    return mHorizontalHelper;
    }

3.7 estimateNextPositionDiffForFling計算偏移量
  • 如下所示
    • 首先,計算滾動的總距離,這個距離受到觸發fling時的速度的影響,得到一個distances數組
    • 然后計算每個ItemView的長度
    • 根據是橫向布局還是縱向布局,來取對應布局方向上的滾動距離
    • 總結大概流程就是:用滾動總距離除以itemview的長度,從而估算得到需要滾動的item數量,此數值就是位置偏移量。而滾動距離是通過SnapHelper的calculateScrollDistance()方法得到的,ItemView的長度是通過computeDistancePerChild()方法計算出來。
      private int estimateNextPositionDiffForFling(RecyclerView.LayoutManager layoutManager,
          OrientationHelper helper, int velocityX, int velocityY) {
      int[] distances = calculateScrollDistance(velocityX, velocityY);
      float distancePerChild = computeDistancePerChild(layoutManager, helper);
      if (distancePerChild <= 0) {
          return 0;
      }
      int distance =
              Math.abs(distances[0]) > Math.abs(distances[1]) ? distances[0] : distances[1];
      return (int) Math.round(distance / distancePerChild);
      }

04.自定義SnapHelper類

4.1 業務需求
  • LinearSnapHelper 實現了居中對齊,那么我們只要更改一下對齊的規則就行,更改為開始對齊(計算目標 View到 Parent start 要滑動的距離),其他的邏輯和 LinearSnapHelper 是一樣的。因此我們選擇繼承 LinearSnapHelper
  • 大概流程
    • 重寫calculateDistanceToFinalSnap方法,計算SnapView當前位置與目標位置的距離
    • 寫findSnapView方法,找到當前時刻的SnapView
    • 可以發現完成上面兩個方法就可以呢,但是感覺滑動效果不太好。滑動比較快時,會滾動很遠。在分析了上面的代碼可知,滾動速率,由createSnapScroller方法中的calculateSpeedPerPixel()方法決定。那么是不是可以修改一下速率就可以解決問題呢。最后測試真的可以,ok,完成了。
    • 當然還會發現滾動時候,會滑動多個item,如果相對item個數做限制,可以在findTargetSnapPosition()方法中處理。
  • 代碼地址:https://github.com/yangchong211/YCBanner
4.2 自定義helper類
  • 重寫calculateDistanceToFinalSnap方法

    • 這里需要知道,在LinearSnapHelper中,out[0]和out[1]是通過distanceToCenter獲取的。那么既然要設置開始對齊,那么這里需要創建distanceToStart方法
      
      @Nullable
      @Override
      public int[] calculateDistanceToFinalSnap(RecyclerView.LayoutManager layoutManager, View targetView) {
      int[] out = new int[2];
      if (layoutManager.canScrollHorizontally()) {
          out[0] = distanceToStart(targetView, getHorizontalHelper(layoutManager));
      } else {
          out[0] = 0;
      }
      if (layoutManager.canScrollVertically()) {
          out[1] = distanceToStart(targetView, getVerticalHelper(layoutManager));
      } else {
          out[1] = 0;
      }
      return out;
      }

    private int distanceToStart(View targetView, OrientationHelper helper) {
    return helper.getDecoratedStart(targetView) - helper.getStartAfterPadding();
    }

  • 寫findSnapView方法,找到當前時刻的SnapView

    @Nullable
    @Override
    public View findSnapView(RecyclerView.LayoutManager layoutManager) {
        if (layoutManager instanceof LinearLayoutManager) {
            if (layoutManager.canScrollHorizontally()) {
                return findStartView(layoutManager, getHorizontalHelper(layoutManager));
            } else {
                return findStartView(layoutManager, getVerticalHelper(layoutManager));
            }
        }
        return super.findSnapView(layoutManager);
    }
    
    private View findStartView(RecyclerView.LayoutManager layoutManager, OrientationHelper helper) {
        if (layoutManager instanceof LinearLayoutManager) {
            int firstChild = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition();
            //需要判斷是否是最后一個Item,如果是最后一個則不讓對齊,以免出現最后一個顯示不完全。
            boolean isLastItem = ((LinearLayoutManager) layoutManager).findLastCompletelyVisibleItemPosition()
                    == layoutManager.getItemCount() - 1;
            if (firstChild == RecyclerView.NO_POSITION || isLastItem) {
                return null;
            }
            View child = layoutManager.findViewByPosition(firstChild);
            if (helper.getDecoratedEnd(child) >= helper.getDecoratedMeasurement(child) / 2
                    && helper.getDecoratedEnd(child) > 0) {
                return child;
            } else {
                if (((LinearLayoutManager) layoutManager).findLastCompletelyVisibleItemPosition()
                        == layoutManager.getItemCount() - 1) {
                    return null;
                } else {
                    return layoutManager.findViewByPosition(firstChild + 1);
                }
            }
        }
        return super.findSnapView(layoutManager);
    }
  • 修改滾動速率

    @Nullable
    protected LinearSmoothScroller createSnapScroller(final RecyclerView.LayoutManager layoutManager) {
        if (!(layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) {
            return null;
        }
        return new LinearSmoothScroller(mRecyclerView.getContext()) {
            @Override
            protected void onTargetFound(View targetView, RecyclerView.State state, RecyclerView.SmoothScroller.Action action) {
                int[] snapDistances = calculateDistanceToFinalSnap(mRecyclerView.getLayoutManager(), targetView);
                final int dx;
                final int dy;
                if (snapDistances != null) {
                    dx = snapDistances[0];
                    dy = snapDistances[1];
                    final int time = calculateTimeForDeceleration(Math.max(Math.abs(dx), Math.abs(dy)));
                    if (time > 0) {
                        action.update(dx, dy, time, mDecelerateInterpolator);
                    }
                }
            }
    
            @Override
            protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
                //這個地方可以自己設置
                return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
            }
        };
    }
關于其他內容介紹

SnapHelper源碼深度解析

關于其他內容介紹

01.關于博客匯總鏈接
  • 1.技術博客匯總
  • 2.開源項目匯總
  • 3.生活博客匯總
  • 4.喜馬拉雅音頻匯總
  • 5.其他匯總
02.關于我的博客
  • 我的個人站點:www.yczbj.org,www.ycbjie.cn
  • github:https://github.com/yangchong211
  • 知乎:https://www.zhihu.com/people/yang-chong-69-24/pins/posts
  • 簡書:http://www.jianshu.com/u/b7b2c6ed9284
  • csdn:http://my.csdn.net/m0_37700275
  • 喜馬拉雅聽書:http://www.ximalaya.com/zhubo/71989305/
  • 開源中國:https://my.oschina.net/zbj1618/blog
  • 泡在網上的日子:http://www.jcodecraeer.com/member/content_list.php?channelid=1
  • 郵箱:yangchong211@163.com
  • 阿里云博客:https://yq.aliyun.com/users/article?spm=5176.100- 239.headeruserinfo.3.dT4bcV
  • segmentfault頭條:https://segmentfault.com/u/xiangjianyu/articles
向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

嵊泗县| 阿克| 巧家县| 南和县| 鄱阳县| 德兴市| 诏安县| 铁岭县| 格尔木市| 永仁县| 滁州市| 闻喜县| 维西| 平顶山市| 德惠市| 内江市| 高清| 虞城县| 陇南市| 尉氏县| 鹤山市| 舟曲县| 泰顺县| 长垣县| 平度市| 兰考县| 丹阳市| 益阳市| 南木林县| 夏邑县| 眉山市| 深州市| 开封县| 汉源县| 洛南县| 克山县| 山阴县| 米林县| 牡丹江市| 乳源| 镇江市|