12/08/2018, 12:48

[Android] Tương Tác Các Thành Phần Trong Layout Khi Scroll List(Part 2)

Trong bài viết lần trước, tôi đã trình bày một ví dụ đơn giản về tương tác các thành phần trong layout trong khi scroll list. Trong Phần 2 này, tôi đi sâu hơn vấn đề này qua ví dụ implement hiệu ứng scroll Toolbar của app Google Play. Tìm hiểu concept trên, điểm khác biệt so với concept lần ...

Trong bài viết lần trước, tôi đã trình bày một ví dụ đơn giản về tương tác các thành phần trong layout trong khi scroll list. Trong Phần 2 này, tôi đi sâu hơn vấn đề này qua ví dụ implement hiệu ứng scroll Toolbar của app Google Play.

playstore.gif

Tìm hiểu concept trên, điểm khác biệt so với concept lần trước dễ nhận thấy là hiệu ứng trượt của thanh Toolbar khi scroll list:

  • Thanh bar của màn hình này gồm 2 thành phần Toolbar và Indicator của viewpager.
  • Khi list scroll lên, Toolbar ẩn đi, thanh Indicator scroll đến top rồi neo lại
  • Khi list scroll xuống, Toolbar hiện ra, Toolbar hiện ra thì thanh Indicator trượt xuống đến hết chiều dài như ban đầu thì dừng lại.

Như vậy so với concept cũ, ta cần xem xét thêm việc neo lại và scroll thanh Indicator một cách hợp lý. Dưới đây là một ví dụ đơn giản chúng ta sẽ implement để minh hoạ concept trên:

goal.gif

Phân tích

Để neo được phần Indicator và scroll lên xuống như concept, cần tìm ra khoảng đã scroll được của thanh Toolbar. Không như concept trước, trạng thái và vị trí của thanh Indicator phụ thuộc vào độ dịch chuyển của thanh Toolbar. Như vậy các must item là:

  • Lắng nghe sự kiện scroll của list
  • Ghi lại vị trí scroll của thanh Toolbar
  • Khi vuốt list scroll lên: tab Indicator scroll lên theo Toolbar cho đến khi Toolbar ẩn hẳn đi thì Indicator neo lại. Khi vuốt list scroll xuống: tab Indicator và thanh toolbar đều show ra.

Implement

Đầu tiên, thực hiện implement project như trình bày ở part 1. Sau khi có được concept 1, cải tiến một chút phần onScrolled() để tính ra vị trí thanh Toolbar

 public abstract class HidingScrollListener extends RecyclerView.OnScrollListener {

    private static final float HIDE_THRESHOLD = 10;
    private static final float SHOW_THRESHOLD = 70;

    private int mToolbarOffset = 0;
    private boolean mControlsVisible = true;
    private int mToolbarHeight;

    public HidingScrollListener(Context context) {
        mToolbarHeight = Utils.getToolbarHeight(context);
    }

    @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
        super.onScrollStateChanged(recyclerView, newState);

        if(newState == RecyclerView.SCROLL_STATE_IDLE) {
            if (mControlsVisible) {
                if (mToolbarOffset > HIDE_THRESHOLD) {
                    setInvisible();
                } else {
                   setVisible();
                }
            } else {
                if ((mToolbarHeight - mToolbarOffset) > SHOW_THRESHOLD) {
                    setVisible();
                } else {
                    setInvisible();
                }
            }
        }

    }

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);

        clipToolbarOffset();
        onMoved(mToolbarOffset);

        if((mToolbarOffset <mToolbarHeight && dy>0) || (mToolbarOffset >0 && dy<0)) {
            mToolbarOffset += dy;
        }

    }

    private void clipToolbarOffset() {
        if(mToolbarOffset > mToolbarHeight) {
            mToolbarOffset = mToolbarHeight;
        } else if(mToolbarOffset < 0) {
            mToolbarOffset = 0;
        }
    }

    private void setVisible() {
        if(mToolbarOffset > 0) {
            onShow();
            mToolbarOffset = 0;
        }
        mControlsVisible = true;
    }

    private void setInvisible() {
        if(mToolbarOffset < mToolbarHeight) {
            onHide();
            mToolbarOffset = mToolbarHeight;
        }
        mControlsVisible = false;
    }

    public abstract void onMoved(int distance);
    public abstract void onShow();
    public abstract void onHide();

}

 So với lúc nhận sự kiện listener scroll của part1, có một điểm mới ở đây

```java

 if((mToolbarOffset <mToolbarHeight && dy>0) || (mToolbarOffset >0 && dy<0)) {
    mToolbarOffset += dy;
}

  • Biến mToolbarOffset được dùng để ghi lại vị trí của Toolbar hiện tại, mỗi lần scroll thêm một đơn vị dy, ta ghi vào biến này để lưu lại vị trí. Lưu ý mToolbarOffset tăng lên mỗi lần scroll up, tuy nhiên giá trị này luôn bé hơn mToolbarHeight.

  • Giá trị Threshold giữ cho scroll mượt mà, Toolbar chỉ scroll khi dy đạt ngưỡng

  • Trong listener trên có hàm abstract mới onMove(), mục đích chính của onMove() để truyền giá trị scrolled từ bên ngoài list vào:

 private void initRecyclerView() {
    final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
    recyclerView.setLayoutManager(new LinearLayoutManager(this));
    RecyclerAdapter recyclerAdapter = new RecyclerAdapter(createItemList());
    recyclerView.setAdapter(recyclerAdapter);

    recyclerView.setOnScrollListener(new HidingScrollListener(this) {

        @Override
        public void onMoved(int distance) {
            mToolbarContainer.setTranslationY(-distance);
        }

        @Override
        public void onShow() {
            mToolbarContainer.animate().translationY(0).setInterpolator(new DecelerateInterpolator(2)).start();
        }

        @Override
        public void onHide() {
            mToolbarContainer.animate().translationY(-mToolbarHeight).setInterpolator(new AccelerateInterpolator(2)).start();
        }

    });
}

Như vậy đã giới hạn xong phạm vi scroll của thanh Toolbar. Giờ chạy thử xem project sẽ chạy thế nào: nosnap.gif

OK, thanh Toolbar đã hoạt động mượt mà, chúc mừng bạn! So sánh với những gì thu được ở part1, nhận thấy ở cách làm trong part2 này, layout toolbar di chuyển mượt mà hơn.

Trong part 3 của loạt bài này, tôi sẽ trình bày tiếp phần điều khiển Tab Indicator.

0