Anky An
Anky An

Reputation: 640

Detect scrolling effects for AppBarLayout & NestedScrollView

I have this layout (code is at the bottom) which contains a CollapsingToolBarLayout at the top and a NestedScrollView at the bottom.

When I scroll up, the collapsing toolbar will start to collapse, then the scroll view will scroll up with the collapsing toolbar at first and then goes behind the collapsed tool bar.

I want to have some animations (image slides left when scrolling up and slides back when scrolling down) in the collapsing toolbar. The issue now is: sometimes, when I scroll up, the image doesn't slide left. When it slid left, and I scroll down, it doesn't slide back.

I trigger these animations through onOffsetChanged for the AppBarLayout and OnTouchListener for the NestedScrollView.

// People image slide left when user scrolls up on the scroll view
mScrollView.setOnTouchListener(scrollViewTouchListener);


// People image slide back when app bar is almost expanded
mAppBar.addOnOffsetChangedListener(appBarOffsetChangedListener);

// OnOffsetChangedListener for the AppBarLayout
    private AppBarLayout.OnOffsetChangedListener appBarOffsetChangedListener = new AppBarLayout.OnOffsetChangedListener() {
        @Override
        public void onOffsetChanged(AppBarLayout appBarLayout, int offset) {

    // If the app bar is almost expanded and people image slided left, make it slide back
    if ((offset > -20 || offset == 0) && mPeopleSlidedLeft) {
        mPeopleImage.animate().setDuration(animationTime)
                .translationX(originalPeoplePosition[0]);
        mPeopleSlidedLeft = false;
    }
}
};

// Touch listener for the scroll view
private View.OnTouchListener scrollViewTouchListener = new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        float y = event.getY();

    if (event.getAction() == MotionEvent.ACTION_MOVE) {
        float dy = y - mPreviousY;

        // if user scrolls up and people image hasn't slided left,
        if (dy < -1 && mPeopleSlidedLeft == false) {
            DisplayMetrics dm = new DisplayMetrics();
            getActivity().getWindowManager().getDefaultDisplay().getMetrics(dm);

            int xDest = dm.widthPixels / 2;
            xDest += mPeopleImage.getMeasuredWidth() / 2;
            mPeopleImage.animate().setDuration(animationTime)
                    .translationX(originalPeoplePosition[0] - xDest);
        }
    }

    mPeopleSlidedLeft = true;
    mPreviousY = y;
    return false;
}
};

Just note that the scrollview's setOnScrollChangeListener won't work as it's not triggered when the toolbar is collapsing.

A simplified version of the layout is below:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/appbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:fitsSystemWindows="true"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
        app:elevation="0dp">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/collapsing_toolbar"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginTop="@dimen/collapsing_toolbar_margin"
            android:fitsSystemWindows="true"
            android:minHeight="120dp"
            app:contentScrim="?attr/colorPrimary"
            app:expandedTitleMarginEnd="64dp"
            app:expandedTitleMarginStart="48dp"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">

            <LinearLayout
                android:id="@+id/backdrop"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@color/white"
                android:fitsSystemWindows="true"
                android:orientation="vertical"
                app:layout_collapseMode="parallax">

            </LinearLayout>

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="@dimen/toolbar_height"
                android:layout_gravity="center_horizontal"
                app:contentInsetEnd="16dp"
                app:contentInsetStart="16dp"
                app:elevation="0dp"
                app:layout_collapseMode="pin"
                app:popupTheme="@style/ThemeOverlay.AppCompat.Light">

            </android.support.v7.widget.Toolbar>

        </android.support.design.widget.CollapsingToolbarLayout>

    </android.support.design.widget.AppBarLayout>

        <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:animateLayoutChanges="true"
        android:background="@color/white"
        android:orientation="vertical"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <android.support.v4.widget.NestedScrollView
            android:id="@+id/scroll_view"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1">

        </android.support.v4.widget.NestedScrollView>

    </LinearLayout>

    <include
        layout="@layout/notification"
        android:layout_width="match_parent"
        android:layout_height="@dimen/active_inactive_time_height"
        android:layout_gravity="bottom"
        app:layout_anchorGravity="bottom|right"
        android:layout_marginBottom="@dimen/bottom_navigation_bar_offset" />
</android.support.design.widget.CoordinatorLayout> 

Can someone please have a look? I will really appreciate it!

Upvotes: 4

Views: 5707

Answers (1)

darnmason
darnmason

Reputation: 2732

I'm not sure why you're using a touch listener on the NestedScrollView, if I understand correctly you want there to be 2 states:

  1. Toolbar is expanded and people image is visible
  2. Toolbar is collapsed and people image is hidden

And the transition between these 2 states should be to slide the people image off the left of the screen?

This should be achievable with the AppBarLayout.OnOffsetChangedListener alone, and you can use the change of offset to "animate" the view moving instead of setting up trigger points which can result in a much smoother implementation.

Something like:

private AppBarLayout.OnOffsetChangedListener appBarOffsetChangedListener = new AppBarLayout.OnOffsetChangedListener() {
    @Override
    public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
        float fraction = ((float) Math.abs(verticalOffset)) / appBarLayout.getTotalScrollRange();
        int peopleRange = mPeopleImage.getRight();
        mPeopleImage.setTranslationX(fraction * peopleRange * -1);
    }
};

I've added some configuration of the timing and speed of the slid. In this case it waits until the header is collapsed 25% before sliding and the image moves left twice as fast. You could play with these numbers to get what you're looking for.

private AppBarLayout.OnOffsetChangedListener appBarOffsetChangedListener = new AppBarLayout.OnOffsetChangedListener() {
    @Override
    public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
        float fraction = ((float) Math.abs(verticalOffset)) / appBarLayout.getTotalScrollRange();
        int peopleRange = mPeopleImage.getRight();
        float delay = 0.25f;
        float speed = 2f;
        fraction = Math.max(fraction - delay, 0f) * speed;
        mPeopleImage.setTranslationX(fraction * peopleRange * -1);
    }
};

Upvotes: 4

Related Questions