Sử dụng SnapHelper trong RecyclerView

Giới thiệu

Nhiều khi tôi dùng CH Play. Nhìn danh sách các ứng dụng nhưng không biết nó là một viewpager hay là một listview hay recyclerview. Thế là tôi mày mò thử tìm hiểu xem nó sử dụng component nào để xử lý việc vuốt danh sách item đấy giống như vuốt viewpager nó là cái gì. Thì cuối cùng tôi cũng tìm hiểu ra tính năng này đã được Android cung cấp và nó có tên là SnapHelper

SnapHelper là cái gì

SnapHelper là một class hỗ trợ việc bám các View con của RecyclerView. Ví dụ như bạn có thể nhìn thấy trong ứng dụng CH Play. Danh sách các item tiếp theo sẽ luôn luôn hiển thị ở cuối danh sách.

Android đã cung cấp thư viện để xử lý như trên ảnh. Class đó tên là LinearSnapHelper nhưng class này chỉ hỗ trợ việc snapping item ra giữa.
Để sử dụng SnapHelper bạn chỉ cần viết theo như ở bên dưới

SnapHelper snapHelper = new LinearSnapHelper();
snapHelper.attachToRecyclerView(yourRecyclerView);

Nhưng nếu làm thế này thì vẫn chưa giống với RecyclerView ở trong CH Play. Các Item Snap ở vị trí bắt đầu cơ mà?

Bây giờ chúng ta đi vào custom lại class LinearSnapHelper giống với hình trên nhé

Đầu tiên, tôi tạo 1 class có tên là StartSnapHelper kế thừa lại từ LinearSnapHelper. Và class này sẽ có overridecác methods bên dưới nhé :

1. calculateDistanceToFinalSnap

Override method này để snap một điểm cụ thể cho view mong muốn snap hoặc một container view nằm trên trục. Method này được gọi khi SnapHelper can thiệp vào quá trình fling của người dùng và nó biết chính xác khoảng cách bắt buộc để scroll đến cái view tiếp theo.

@Override
    public int[] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager layoutManager,
                                              @NonNull 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;
    }

2. findSnapView

Override method này để cung cấp một View cụ thể cho snapping. Method này được gọi khi SnapHelper đã sẵn sàng để bắt đầu snap và yêu cầu bắt buộc một View mục tiêu để snap. Nó sẽ được gọi là khi hành động scroll recyclerview dừng lại.Nó cũng sẽ được gọi là khi SnapHelper đang chuẩn bị snap sau khi fling recyclerview và yêu cầu phải có một view để tham chiếu từ các child view hiện tại của recyclerView . Nếu method này trả về null, SnapHelper sẽ không snap bất kỳ một view nào nữa cả.

@Override
    public View findSnapView(RecyclerView.LayoutManager layoutManager) {

        if (layoutManager instanceof LinearLayoutManager) {

            if (layoutManager.canScrollHorizontally()) {
                return getStartView(layoutManager, getHorizontalHelper(layoutManager));
            } else {
                return getStartView(layoutManager, getVerticalHelper(layoutManager));
            }
        }

        return super.findSnapView(layoutManager);
    }

Và đây là cả class StartSnapHelper

/**
 * Created by amitshekhar on 15/01/17.
 */

public class StartSnapHelper extends LinearSnapHelper {

    private OrientationHelper mVerticalHelper, mHorizontalHelper;

    public StartSnapHelper() {

    }

    @Override
    public void attachToRecyclerView(@Nullable RecyclerView recyclerView)
            throws IllegalStateException {
        super.attachToRecyclerView(recyclerView);
    }

    @Override
    public int[] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager layoutManager,
                                              @NonNull 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;
    }

    @Override
    public View findSnapView(RecyclerView.LayoutManager layoutManager) {

        if (layoutManager instanceof LinearLayoutManager) {

            if (layoutManager.canScrollHorizontally()) {
                return getStartView(layoutManager, getHorizontalHelper(layoutManager));
            } else {
                return getStartView(layoutManager, getVerticalHelper(layoutManager));
            }
        }

        return super.findSnapView(layoutManager);
    }

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

    private View getStartView(RecyclerView.LayoutManager layoutManager,
                              OrientationHelper helper) {

        if (layoutManager instanceof LinearLayoutManager) {
            int firstChild = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition();

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

    private OrientationHelper getVerticalHelper(RecyclerView.LayoutManager layoutManager) {
        if (mVerticalHelper == null) {
            mVerticalHelper = OrientationHelper.createVerticalHelper(layoutManager);
        }
        return mVerticalHelper;
    }

    private OrientationHelper getHorizontalHelper(RecyclerView.LayoutManager layoutManager) {
        if (mHorizontalHelper == null) {
            mHorizontalHelper = OrientationHelper.createHorizontalHelper(layoutManager);
        }
        return mHorizontalHelper;
    }
}

Bây giờ bạn có thể sử dụng nó vào trong RecyclerView của mình rồi :

SnapHelper startSnapHelper = new StartSnapHelper();
startSnapHelper.attachToRecyclerView(yourRecyclerView);

Đây là ví dụ về SnapHelper

Cảm ơn bạn đã đọc bài viết này của tôi.

HAPPY CODING!

Nguồn dịch từ Amit Shekhar  via viblo

Nguyễn Linh

Chia sẻ để cùng tiến bộ...