国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

SnapHelper源碼深度解析

ThinkSNS / 2029人閱讀

摘要:為表示之前進行過滾動,為狀態表示滾動結束停下來的抽象方法抽象方法計算最終對齊要移動的距離計算二個參數對應的當前的坐標與需要對齊的坐標之間的距離。抽象方法找到要對齊的該方法會找到當前上最接近對齊位置的那個,該稱為,對應的稱為。

目錄介紹

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/yangchong2...

如果覺得好,可以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/yangchong2...

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;
        }
    };
}

關于其他內容介紹

關于其他內容介紹 01.關于博客匯總鏈接

1.技術博客匯總

2.開源項目匯總

3.生活博客匯總

4.喜馬拉雅音頻匯總

5.其他匯總

02.關于我的博客

我的個人站點:www.yczbj.org,www.ycbjie.cn

github:https://github.com/yangchong211

知乎:https://www.zhihu.com/people/...

簡書:http://www.jianshu.com/u/b7b2...

csdn:http://my.csdn.net/m0_37700275

喜馬拉雅聽書:http://www.ximalaya.com/zhubo...

開源中國:https://my.oschina.net/zbj161...

泡在網上的日子:http://www.jcodecraeer.com/me...

郵箱:yangchong211@163.com

阿里云博客:https://yq.aliyun.com/users/a... 239.headeruserinfo.3.dT4bcV

segmentfault頭條:https://segmentfault.com/u/xi...

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/72109.html

相關文章

  • RecyclerView問題匯總

    摘要:缺點自動裝箱的存在意味著每一次插入都會有額外的對象創建。對象本身是一層額外需要被創建以及被垃圾回收的對象。相較于我們舍棄了和類型的放棄了并依賴于二分法查找。 目錄介紹 25.0.0.0 請說一下RecyclerView?adapter的作用是什么,幾個方法是做什么用的?如何理解adapter訂閱者模式? 25.0.0.1 ViewHolder的作用是什么?如何理解ViewHolder...

    boredream 評論0 收藏0
  • RecyclerView封裝庫和綜合案例【包含25篇博客】

    摘要:支持復雜頁面,例如添加自定義頭部和底部布局,支持橫向滑動,還可以支持粘貼頭部類似微信好友分組,支持不規則瀑布流效果,支持側滑刪除功能。支持粘貼頭部的需求效果,這種效果類似微信好友分組的那種功能界面。 目錄介紹 1.復雜頁面庫介紹 2.本庫優勢亮點 2.1 支持多種狀態切換管理 2.2 支持添加多個header和footer 2.3 支持側滑功能和拖拽移動 2.4 其他亮點介紹 ...

    silenceboy 評論0 收藏0
  • Flink 源碼解析 —— 深度解析 Flink Checkpoint 機制

    摘要:機制博客從到學習介紹從到學習上搭建環境并構建運行簡單程序入門從到學習配置文件詳解從到學習介紹從到學習如何自定義從到學習介紹從到學習如何自定義從到學習轉換從到學習介紹中的從到學習中的幾種詳解從到學習讀取數據寫入到從到學習項目如何運行從 Flink Checkpoint 機制 https://t.zsxq.com/ynQNbeM 博客 1、Flink 從0到1學習 —— Apache Fl...

    0x584a 評論0 收藏0

發表評論

0條評論

最新活動
閱讀需要支付1元查看
<