Search code examples
androidlayoutscrollandroid-toolbarandroid-coordinatorlayout

Android retractable or collapsed toolbar for chat view


I'm using a collapsed toolbar for a chat view, everything working fine I have

app:layout_behavior="@string/appbar_scrolling_view_behavior"

on my recycler view and

app:layout_scrollFlags="scroll|enterAlways|snap"

on the retractable header

I'm using it for a chat view so the first element in recyclerView are at the bottom of the list due to

LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
layoutManager.setReverseLayout(true);

The thing is I would like to hide header when I'm scrolling up and hide it when I'm scrolling down, the exact inverse of the actual behavior. And the header appears when I reach the top of the list, (the oldest message) instead I would like to make it visible when I reach the bottom when I'm showing the latest message


Solution

  • I found a solution I'm using the old way with OnScrollListener for retractable header and inverse it for fit to the reverse layout manager

    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;
    private int mTotalScrolledDistance;
    
    public HidingScrollListener(int mToolbarHeight) {
        this.mToolbarHeight = mToolbarHeight;
    }
    
    @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
        super.onScrollStateChanged(recyclerView, newState);
        if (newState == RecyclerView.SCROLL_STATE_IDLE) {
            if (mTotalScrolledDistance < mToolbarHeight) {
                setVisible();
            } else {
                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);
        dy = dy * -1;
        clipToolbarOffset();
        onMoved(mToolbarOffset);
        if ((mToolbarOffset < mToolbarHeight && dy > 0) || (mToolbarOffset > 0 && dy < 0)) {
            mToolbarOffset += dy;
        }
        mTotalScrolledDistance += 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();
    }
    

    And then I'm using it like this in my Fragment

      dataBinding.chatDetailRoot.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                mToolbarHeight = dataBinding.chatDetailAdsHeader.getHeight();
                dataBinding.chatDetailRecyclerView.addOnScrollListener(new HidingScrollListener(mToolbarHeight) {
                    @Override
                    public void onMoved(int distance) {
                        dataBinding.chatDetailAdsHeader.setTranslationY(-distance);
                    }
    
                    @Override
                    public void onShow() {
                        dataBinding.chatDetailAdsHeader.animate().translationY(0).setInterpolator(new DecelerateInterpolator(2)).start();
                    }
    
                    @Override
                    public void onHide() {
                        dataBinding.chatDetailAdsHeader.animate().translationY(-mToolbarHeight).setInterpolator(new AccelerateInterpolator(2)).start();
                    }
                });
                dataBinding.chatDetailRoot.getViewTreeObserver().removeOnGlobalLayoutListener(this);
            }
        });
    }
    

    I'm using a viewTreeObserver because I need to get the toolbar height and chatDetailAdsHeader is my AppBarLayout component