Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Stop AppBarLayout scrolling off screen when NestedScrollView is empty

I have a fairly typical List functionality using a CoordinatorLayout, AppBarLayout, SwipeRefreshLayout and RecyclerView -

When the RecyclerView has enough content to scroll, the page seems fine. When the RecyclerView is empty or doesn't have enough content to scroll however, the behavior is that the AppBarLayout children with app:layout_scrollFlags="scroll|enterAlwaysCollapsed" will continue to scroll - which looks odd.

Is there a way to stop the AppBarLayout children scrolling when the NestedScrollView is empty?

enter image description here

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

    <android.support.design.widget.CoordinatorLayout
        android:id="@+id/coordinatorLayout"
        android:background="@android:color/transparent"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <android.support.design.widget.AppBarLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@android:color/transparent"
            android:elevation="4dp">

            <LinearLayout
                android:id="@+id/eventHeader"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="16dp"
                android:background="@color/green"
                android:orientation="horizontal"
                app:layout_scrollFlags="scroll|enterAlwaysCollapsed">

                <View
                    android:layout_width="0dp"
                    android:layout_height="0dp"
                    android:layout_weight="1"/>

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="scroll|enterAlwaysCollapsed"
                    android:textColor="@color/white"
                    android:textSize="15sp"/>

                <View
                    android:layout_width="0dp"
                    android:layout_height="0dp"
                    android:layout_weight="1"/>

            </LinearLayout>

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

        <android.support.v4.widget.SwipeRefreshLayout
            android:id="@+id/swipeToRefresh"
            android:layout_width="match_parent"
            android:layout_gravity="fill_vertical"
            android:layout_height="match_parent"
            app:layout_behavior="@string/appbar_scrolling_view_behavior">

            <android.support.v7.widget.RecyclerView
                android:id="@+id/recyclerView"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="@android:color/transparent"
                android:dividerHeight="0dp"
                android:layout_gravity="fill_vertical"
                android:drawSelectorOnTop="true"
                android:listSelector="@drawable/selector_ripple_grey_transparent"
                android:scrollbars="vertical"/>

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

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

    <TextView
        android:id="@+id/noData"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:padding="16dp"
        android:text="@string/no_data_available"
        android:textSize="17sp"/>

</FrameLayout>
like image 571
Graeme Avatar asked Feb 05 '16 13:02

Graeme


People also ask

How do I disable scrolling in collapsing toolbar layout Android?

The solution is simple, we just need to set the app:scrimAnimationDuration=”0" in our collapsing toolbar layout like the below code snippet. Now just run the code and see the results, you will see then there will be no fading animation anymore.

What is AppBarLayout in android?

AppBarLayout is a vertical LinearLayout which implements many of the features of material designs app bar concept, namely scrolling gestures. Children should provide their desired scrolling behavior through AppBarLayout.

What is Coordinator layout in android?

CoordinatorLayout is a super-powered FrameLayout . CoordinatorLayout is intended for two primary use cases: As a top-level application decor or chrome layout. As a container for a specific interaction with one or more child views.

How do you set layout behavior programmatically?

You can set the behavior on an instance of CoordinatorLayout. LayoutParams with setBehavior method. To get a proper Behavior object that represents the same thing as @string/appbar_scrolling_view_behavior you should create an instance of AppBarLayout. ScrollingViewBehavior .


2 Answers

Not sure how elegant a solution this is but, I overrode the onStartNestedScroll() event to only fire if the NestedScrollView is scrollable (In this case a RecyclerView)

in onCreate():

CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) mAppBarLayout.getLayoutParams();
layoutParams.setBehavior(new AppBarLayoutNoEmptyScrollBehavior(mAppBarLayout, mRecyclerView));

Behavior:

public class AppBarLayoutNoEmptyScrollBehavior extends AppBarLayout.Behavior {

    AppBarLayout mAppBarLayout;
    RecyclerView mRecyclerView;
    public AppBarLayoutNoEmptyScrollBehavior(AppBarLayout appBarLayout, RecyclerView recyclerView) {
        mAppBarLayout = appBarLayout;
        mRecyclerView = recyclerView;
    }

    public boolean isRecylerViewScrollable(RecyclerView recyclerView) {
        int recyclerViewHeight = recyclerView.getHeight(); // Height includes RecyclerView plus AppBarLayout at same level
        int appCompatHeight    = mAppBarLayout != null ? mAppBarLayout.getHeight() : 0;
        recyclerViewHeight -= appCompatHeight;

        return recyclerView.computeVerticalScrollRange() > recyclerViewHeight;
    }

    @Override
    public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, View directTargetChild, View target, int nestedScrollAxes) {
        if (isRecylerViewScrollable(mRecyclerView)) {
            return super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes);
        }
        return false;
    }

}

EDIT

Edited solution as RecyclerView gives height as visible RecyclerView height and AppBarLayout height (which is the CoordinatorLayout height).

However, if your scroll gesture starts on the visible AppBarLayout area, a scroll will still take place, even if you add this Behavior to the AppBarLayout as well. This answer therefore is not a fix for the problem.

like image 161
Graeme Avatar answered Oct 26 '22 19:10

Graeme


(Based on : Reference)

(1) Create this class.

public class AppBarLayoutBehaviorForEmptyRecyclerView extends AppBarLayout.Behavior
{
    private boolean canRecyclerViewBeScrolled = false;

    public AppBarLayoutBehaviorForEmptyRecyclerView()
    {
    }

    public AppBarLayoutBehaviorForEmptyRecyclerView(Context context, AttributeSet attrs)
    {
        super(context, attrs);
    }

    @Override
    public boolean onInterceptTouchEvent(CoordinatorLayout parent, AppBarLayout child, MotionEvent ev)
    {
        return canRecyclerViewBeScrolled && super.onInterceptTouchEvent(parent, child, ev);
    }

    @Override
    public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, View directTargetChild, View target, int nestedScrollAxes)
    {
        updateScrollable(target);
        return canRecyclerViewBeScrolled && super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes);
    }

    @Override
    public boolean onNestedFling(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, float velocityX, float velocityY, boolean consumed)
    {
        return canRecyclerViewBeScrolled && super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);
    }

    private void updateScrollable(View targetChild)
    {
        if(targetChild instanceof RecyclerView)
        {
            RecyclerView.Adapter adapter = ((RecyclerView) targetChild).getAdapter();

            canRecyclerViewBeScrolled = adapter != null && adapter.getItemCount() > 0;
        }
        else
        {
            canRecyclerViewBeScrolled = true;
        }
    }
}

(2) Add to your AppBarLayout XML element the following attribute:

app:layout_behavior="com.xxxx.xxxxxx.AppBarLayoutBehaviorForEmptyRecyclerView"
like image 22
OneEyeQuestion Avatar answered Oct 26 '22 18:10

OneEyeQuestion