Toolbar in AppBarLayout is scrollable although RecyclerView has not enough content to scroll Toolbar in AppBarLayout is scrollable although RecyclerView has not enough content to scroll android android

Toolbar in AppBarLayout is scrollable although RecyclerView has not enough content to scroll


Edit 2:

Turns out the only way to ensure Toolbar is not scrollable when RecyclerView is not scrollable is to set setScrollFlags programmatically which requires to check if RecyclerView's is scrollable. This check has to be done every time adapter is modified.

Interface to communicate with the Activity:

public interface LayoutController {    void enableScroll();    void disableScroll();}

MainActivity:

public class MainActivity extends AppCompatActivity implements     LayoutController {    private CollapsingToolbarLayout collapsingToolbarLayout;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);        setSupportActionBar(toolbar);        collapsingToolbarLayout =               (CollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar);        final FragmentManager manager = getSupportFragmentManager();        final Fragment fragment = new CheeseListFragment();        manager.beginTransaction()                .replace(R.id.root_content, fragment)                .commit();    }    @Override    public void enableScroll() {        final AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams)                                  collapsingToolbarLayout.getLayoutParams();        params.setScrollFlags(                AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL                 | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS        );        collapsingToolbarLayout.setLayoutParams(params);    }    @Override    public void disableScroll() {        final AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams)                                  collapsingToolbarLayout.getLayoutParams();        params.setScrollFlags(0);        collapsingToolbarLayout.setLayoutParams(params);    }}

activity_main.xml:

<android.support.v4.widget.DrawerLayout    xmlns:android="http://schemas.android.com/apk/res/android"    android:id="@+id/drawer_layout"    android:layout_height="match_parent"    android:layout_width="match_parent"    android:fitsSystemWindows="true">    <android.support.design.widget.CoordinatorLayout        xmlns:android="http://schemas.android.com/apk/res/android"        xmlns:app="http://schemas.android.com/apk/res-auto"        android:id="@+id/main_content"        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:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">            <android.support.design.widget.CollapsingToolbarLayout                android:id="@+id/collapsing_toolbar"                android:layout_width="match_parent"                android:layout_height="match_parent"                android:fitsSystemWindows="true"                app:contentScrim="?attr/colorPrimary">                <android.support.v7.widget.Toolbar                    android:id="@+id/toolbar"                    android:layout_width="match_parent"                    android:layout_height="?attr/actionBarSize"                    android:background="?attr/colorPrimary"                    app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>            </android.support.design.widget.CollapsingToolbarLayout>        </android.support.design.widget.AppBarLayout>        <FrameLayout            android:id="@+id/root_content"            android:layout_width="match_parent"            android:layout_height="match_parent"            android:layout_gravity="fill_vertical"            app:layout_behavior="@string/appbar_scrolling_view_behavior"/>    </android.support.design.widget.CoordinatorLayout></android.support.v4.widget.DrawerLayout>

Test Fragment:

public class CheeseListFragment extends Fragment {    private static final int DOWN = 1;    private static final int UP = 0;    private LayoutController controller;    private RecyclerView rv;    @Override    public void onAttach(Context context) {        super.onAttach(context);        try {            controller = (MainActivity) getActivity();        } catch (ClassCastException e) {            throw new RuntimeException(getActivity().getLocalClassName()                    + "must implement controller.", e);        }    }    @Nullable    @Override    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {        rv = (RecyclerView) inflater.inflate(                R.layout.fragment_cheese_list, container, false);        setupRecyclerView(rv);        // Find out if RecyclerView are scrollable, delay required        final Handler handler = new Handler();        handler.postDelayed(new Runnable() {            @Override            public void run() {                if (rv.canScrollVertically(DOWN) || rv.canScrollVertically(UP)) {                    controller.enableScroll();                } else {                    controller.disableScroll();                }            }        }, 100);        return rv;    }    private void setupRecyclerView(RecyclerView recyclerView) {        final LinearLayoutManager layoutManager = new LinearLayoutManager(recyclerView.getContext());        recyclerView.setLayoutManager(layoutManager);        final SimpleStringRecyclerViewAdapter adapter =                new SimpleStringRecyclerViewAdapter(                        getActivity(),                        // Test ToolBar scroll                        getRandomList(/* with enough items to scroll */)                        // Test ToolBar pin                        getRandomList(/* with only 3 items*/)                );        recyclerView.setAdapter(adapter);    }}

Sources:

Edit:

You should CollapsingToolbarLayout to control the behaviour.

Adding a Toolbar directly to an AppBarLayout gives you access to the enterAlwaysCollapsed and exitUntilCollapsed scroll flags, but not the detailed control on how different elements react to collapsing. [...] setup uses CollapsingToolbarLayout’s app:layout_collapseMode="pin" to ensure that the Toolbar itself remains pinned to the top of the screen while the view collapses.http://android-developers.blogspot.com.tr/2015/05/android-design-support-library.html

<android.support.design.widget.CollapsingToolbarLayout        android:layout_width="match_parent"        android:layout_height="match_parent"        app:layout_scrollFlags="scroll|exitUntilCollapsed">    <android.support.v7.widget.Toolbar        android:id="@+id/drawer_toolbar"        android:layout_width="match_parent"        android:layout_height="?attr/actionBarSize"        app:layout_collapseMode="pin"/></android.support.design.widget.CollapsingToolbarLayout>

Add

app:layout_collapseMode="pin"

to your Toolbar in xml.

    <android.support.v7.widget.Toolbar        android:id="@+id/toolbar"        android:layout_width="match_parent"        android:layout_height="?attr/actionBarSize"        android:background="?attr/colorPrimary"        app:layout_scrollFlags="scroll|enterAlways"        app:layout_collapseMode="pin"        app:theme="@style/ToolbarStyle" />


So, proper credit, this answer almost solved it for me https://stackoverflow.com/a/32923226/5050087. But since it was not showing the toolbar when you actually had an scrollable recyclerview and its last item was visible (it would not show the toolbar on the first scroll up), I decided to modify it and adapt it for an easier implementation and for dynamic adapters.

First, you must create a custom layout behavior for you appbar:

public class ToolbarBehavior extends AppBarLayout.Behavior{private boolean scrollableRecyclerView = false;private int count;public ToolbarBehavior() {}public ToolbarBehavior(Context context, AttributeSet attrs) {    super(context, attrs);}@Overridepublic boolean onInterceptTouchEvent(CoordinatorLayout parent, AppBarLayout child, MotionEvent ev) {    return scrollableRecyclerView && super.onInterceptTouchEvent(parent, child, ev);}@Overridepublic boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, View directTargetChild, View target, int nestedScrollAxes, int type) {    updatedScrollable(directTargetChild);    return scrollableRecyclerView && super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes, type);}@Overridepublic boolean onNestedFling(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, float velocityX, float velocityY, boolean consumed) {    return scrollableRecyclerView && super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);}private void updatedScrollable(View directTargetChild) {    if (directTargetChild instanceof RecyclerView) {        RecyclerView recyclerView = (RecyclerView) directTargetChild;        RecyclerView.Adapter adapter = recyclerView.getAdapter();        if (adapter != null) {            if (adapter.getItemCount()!= count) {                scrollableRecyclerView = false;                count = adapter.getItemCount();                RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();                if (layoutManager != null) {                    int lastVisibleItem = 0;                    if (layoutManager instanceof LinearLayoutManager) {                        LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;                        lastVisibleItem = Math.abs(linearLayoutManager.findLastCompletelyVisibleItemPosition());                    } else if (layoutManager instanceof StaggeredGridLayoutManager) {                        StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager;                        int[] lastItems = staggeredGridLayoutManager.findLastCompletelyVisibleItemPositions(new int[staggeredGridLayoutManager.getSpanCount()]);                        lastVisibleItem = Math.abs(lastItems[lastItems.length - 1]);                    }                    scrollableRecyclerView = lastVisibleItem < count - 1;                }            }        }    } else scrollableRecyclerView = true;  }}

Then, you only need to define this behavior for you appbar in your layout file:

<android.support.design.widget.AppBarLayout    android:layout_width="match_parent"    android:layout_height="wrap_content"    android:fitsSystemWindows="true"    app:layout_behavior="com.yourappname.whateverdir.ToolbarBehavior"    >

I haven't tested it for screen rotation so let me know if it works like this. I guess it should work as I don't think the count variable is saved when the rotation happens, but let me know if it doesn't.

This was the easiest and cleanest implementation for me, enjoy it.


It is not a bug, all the events in a viewGroup are handled this way. Because your recyclerview is a child of coordinatorLayout so whenever the event is generated, it is first checked for parent and if parent is not interested only then it is passed down to child.See google documentation