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

前端 未结 9 724
挽巷
挽巷 2020-12-12 15:34

Is it really intended that the Toolbar in a AppBarLayout is scrollable although the main container with the "appbar_scrolling_view_behavior" has not enough content

相关标签:
9条回答
  • 2020-12-12 16:05

    Thanks, I created a custom class of RecyclerView but the key is still using setNestedScrollingEnabled(). It worked fine on my side.

    public class RecyclerViewCustom extends RecyclerView implements ViewTreeObserver.OnGlobalLayoutListener
    {
        public RecyclerViewCustom(Context context)
        {
            super(context);
        }
    
        public RecyclerViewCustom(Context context, @Nullable AttributeSet attrs)
        {
            super(context, attrs);
        }
    
        public RecyclerViewCustom(Context context, @Nullable AttributeSet attrs, int defStyle)
        {
            super(context, attrs, defStyle);
        }
    
        /**
         *  This supports scrolling when using RecyclerView with AppbarLayout
         *  Basically RecyclerView should not be scrollable when there's no data or the last item is visible
         *
         *  Call this method after Adapter#updateData() get called
         */
        public void addOnGlobalLayoutListener()
        {
            this.getViewTreeObserver().addOnGlobalLayoutListener(this);
        }
    
        @Override
        public void onGlobalLayout()
        {
            // If the last item is visible or there's no data, the RecyclerView should not be scrollable
            RecyclerView.LayoutManager layoutManager = getLayoutManager();
            final RecyclerView.Adapter adapter = getAdapter();
            if (adapter == null || adapter.getItemCount() <= 0 || layoutManager == null)
            {
                setNestedScrollingEnabled(false);
            }
            else
            {
                int lastVisibleItemPosition = ((LinearLayoutManager) layoutManager).findLastCompletelyVisibleItemPosition();
                boolean isLastItemVisible = lastVisibleItemPosition == adapter.getItemCount() - 1;
                setNestedScrollingEnabled(!isLastItemVisible);
            }
    
            unregisterGlobalLayoutListener();
        }
    
        private void unregisterGlobalLayoutListener()
        {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
            {
                getViewTreeObserver().removeOnGlobalLayoutListener(this);
            }
            else
            {
                getViewTreeObserver().removeGlobalOnLayoutListener(this);
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-12 16:07

    I suggested you try this sample that for support desing library elements.

    this a layout like your layout in the sample.

    <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.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"
                app:layout_scrollFlags="scroll|enterAlways" />
    
            <android.support.design.widget.TabLayout
                android:id="@+id/tabs"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />
    
        </android.support.design.widget.AppBarLayout>
    
        <android.support.v4.view.ViewPager
            android:id="@+id/viewpager"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_behavior="@string/appbar_scrolling_view_behavior" />
    
    </android.support.design.widget.CoordinatorLayout>
    
    0 讨论(0)
  • 2020-12-12 16:11

    I've implemented it using my own Behavior class which might be attached to AppBarLayout:

    public class CustomAppBarLayoutBehavior extends AppBarLayout.Behavior {
    
    private RecyclerView recyclerView;
    private int additionalHeight;
    
    public CustomAppBarLayoutBehavior(RecyclerView recyclerView, int additionalHeight) {
        this.recyclerView = recyclerView;
        this.additionalHeight = additionalHeight;
    }
    
    public boolean isRecyclerViewScrollable(RecyclerView recyclerView) {
        return recyclerView.computeHorizontalScrollRange() > recyclerView.getWidth() || recyclerView.computeVerticalScrollRange() > (recyclerView.getHeight() - additionalHeight);
    }
    
    @Override
    public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, View directTargetChild, View target, int nestedScrollAxes) {
        if (isRecyclerViewScrollable(mRecyclerView)) {
            return super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes);
        }
        return false;
    }
    

    }

    And below is the code how to set this behavior:

    final View appBarLayout = ((DrawerActivity) getActivity()).getAppBarLayoutView();
    CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
    layoutParams.setBehavior(new AppBarLayoutNoEmptyScrollBehavior(recyclerView, getResources().getDimensionPixelSize(R.dimen.control_bar_height)));
    
    0 讨论(0)
  • 2020-12-12 16:13

    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:

    • Change scroll flags programmatically
    • Original code by Chris Banes
    • Need a postDelayed to ensure RecyclerView children are ready for calculations

    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" />
    
    0 讨论(0)
  • 2020-12-12 16:13

    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);
    }
    
    @Override
    public boolean onInterceptTouchEvent(CoordinatorLayout parent, AppBarLayout child, MotionEvent ev) {
        return scrollableRecyclerView && super.onInterceptTouchEvent(parent, child, ev);
    }
    
    @Override
    public 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);
    }
    
    @Override
    public 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.

    0 讨论(0)
  • 2020-12-12 16:15

    In your Toolbar remove the scroll flag, leaving only the enterAlways flag and you should get the effect you intended. For completeness, your layout should look like:

    <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/coordinatorLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <android.support.design.widget.AppBarLayout
            android:id="@+id/appBarLayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
    
            <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="enterAlways"
                app:theme="@style/ToolbarStyle" />
        </android.support.design.widget.AppBarLayout>
    
        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_behavior="@string/appbar_scrolling_view_behavior" />
    </android.support.design.widget.CoordinatorLayout>
    
    0 讨论(0)
提交回复
热议问题