Не сворачивайте панель инструментов, когда RecyclerView умещается на экране


Я создал приложение с помощью Библиотеки дизайна Android с панелью инструментов и TabLayout.
На самом деле присутствуют 2 вкладки, обе с 2 RecyclerView, которые автоматически сворачивают панель инструментов при прокрутке.

У меня вопрос: могу ли я отключить сворачивание панели инструментов, когда в RecyclerView мало элементов и он полностью умещается на экране (как в TAB 2)?

Я видел много примеров, таких как CheeseSquare, сделанные сотрудником Google, где проблема все еще существует: даже если в RecyclerView всего 1 элемент, панель инструментов продолжает скрываться при прокрутке.

введите описание изображения здесь

Я думаю, что могу просто узнать, отображается ли на экране первый элемент RecyclerView, и если да, отключите сворачивание панели инструментов. Первое легко реализовать, а второе?

Это мой макет:

<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/coordinator_layout"
    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:fitsSystemWindows="true"
            android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_scrollFlags="scroll|enterAlwaysCollapsed"
            android:background="?attr/colorPrimary"
            app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
            app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>

        <android.support.design.widget.TabLayout
            android:id="@+id/tab_layout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@color/glucosio_pink"
            app:tabSelectedTextColor="@android:color/white"
            app:tabIndicatorColor="@color/glucosio_accent"
            app:tabTextColor="#80ffffff"/>
        </android.support.design.widget.AppBarLayout>

        <android.support.v4.view.ViewPager
            android:id="@+id/pager"
            app:layout_behavior="@string/appbar_scrolling_view_behavior"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>

    <android.support.design.widget.FloatingActionButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/main_fab"
        android:layout_margin="16dp"
        android:onClick="onFabClicked"
        app:backgroundTint="@color/glucosio_accent"
        android:src="@drawable/ic_add_black_24dp"
        android:layout_gravity="bottom|right"
        />
    </android.support.design.widget.CoordinatorLayout>





person Paolo Rotolo    schedule 04.09.2015    source источник
comment
Можете ли вы рассказать мне, как вы создаете этот гиф?   -  person Pratik Butani    schedule 16.09.2015
comment
Привет, @PratikButani! Я нашел гифку в Интернете (mzgreen.github.io/2015/06/23/). Кстати, вы можете использовать такие службы, как gifs.com, чтобы создать гифку из видео YouTube.   -  person Paolo Rotolo    schedule 16.09.2015
comment
Твитнул этому человеку по твоей ссылке :) Пусть он тебе поможет. Спасибо :) twitter.com/pratik13butani/status/644355243174526976   -  person Pratik Butani    schedule 17.09.2015
comment
Что ожидать от него, чтобы работать? Допустим, вы сворачиваете панель инструментов на первой странице, а затем переходите ко второй странице. Что должно произойти? Панель инструментов скрыта, и вы не можете ее показать, потому что на ней слишком мало элементов. А по отключению прокрутки Панели инструментов - может быть, поиграю позже. Возможно, нестандартное поведение будет решением.   -  person Michał Z.    schedule 17.09.2015
comment
@ MichałZ. По крайней мере, я хочу отключить сворачивание, если в Recycler есть только один элемент. Похоже, что Whatsapp каким-то образом решил проблему.   -  person Paolo Rotolo    schedule 17.09.2015
comment
Пожалуйста, напишите свое решение в правильном ответе, чтобы его было легче найти.   -  person PicPuc    schedule 20.09.2015
comment
Мое окончательное решение перенесено в правильный ответ. Спасибо.   -  person Paolo Rotolo    schedule 21.09.2015


Ответы (9)


Окончательное решение (спасибо Michał Z.)

Способы включения / выключения прокрутки панели инструментов:

public void turnOffToolbarScrolling() {
    Toolbar mToolbar = (Toolbar) findViewById(R.id.toolbar);
    AppBarLayout appBarLayout = (AppBarLayout) findViewById(R.id.appbar_layout);

    //turn off scrolling
    AppBarLayout.LayoutParams toolbarLayoutParams = (AppBarLayout.LayoutParams) mToolbar.getLayoutParams();
    toolbarLayoutParams.setScrollFlags(0);
    mToolbar.setLayoutParams(toolbarLayoutParams);

    CoordinatorLayout.LayoutParams appBarLayoutParams = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
    appBarLayoutParams.setBehavior(null);
    appBarLayout.setLayoutParams(appBarLayoutParams);
}

public void turnOnToolbarScrolling() {
    Toolbar mToolbar = (Toolbar) findViewById(R.id.toolbar);
    AppBarLayout appBarLayout = (AppBarLayout) findViewById(R.id.appbar_layout);

    //turn on scrolling
    AppBarLayout.LayoutParams toolbarLayoutParams = (AppBarLayout.LayoutParams) mToolbar.getLayoutParams();
    toolbarLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS);
    mToolbar.setLayoutParams(toolbarLayoutParams);

    CoordinatorLayout.LayoutParams appBarLayoutParams = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
    appBarLayoutParams.setBehavior(new AppBarLayout.Behavior());
    appBarLayout.setLayoutParams(appBarLayoutParams);
}


Узнайте, отображается ли последний элемент RecyclerView в моем фрагменте.
Если да, отключите прокрутку:

public void updateToolbarBehaviour(){
    if (mLayoutManager.findLastCompletelyVisibleItemPosition() == items.size()-1) {
        ((MainActivity) getActivity()).turnOffToolbarScrolling();
    } else {
        ((MainActivity)getActivity()).turnOnToolbarScrolling();
    }
}
person Paolo Rotolo    schedule 21.09.2015
comment
Вам нужно вызвать updateToolbarBehaviour() с задержкой (используя Handler-Runnable) в первый раз. В противном случае метод findLastCompletelyVisibleItemPosition() сначала возвращает -1. - person Ugurcan Yildirim; 06.01.2017
comment
Тем не менее это решение работает. А также для сворачивания панели инструментов. - person Pankaj Kumar; 04.09.2018
comment
Чертовски много кода для простых логических вещей. Это становится еще более сложным при использовании фрагмента внутри действия с панелью инструментов. - person Sai; 29.09.2018

RecyclerView теперь (начиная с версии 23.2) поддерживает wrap_content. Просто используйте wrap_content как высоту.

person Eric Cochran    schedule 28.03.2016
comment
К сожалению, у меня не работает, когда я оборачиваю RecyclerView SwipeRefreshLayout - person Artem_Iens; 28.04.2017
comment
Это сработало для меня с использованием NestedScrollView. Спасибо! - person rjr-apps; 15.09.2017
comment
Похоже, это лучшее решение. Я не уверен, что это полностью предполагаемая функция координатора / recyclerView, но она определенно работает без разработчика. Находясь в wrap_content, ресайклер не запускает события прокрутки, если его область просмотра не заполнена элементами. - person AlexG; 17.01.2020
comment
Также у меня работает! Важно отметить, что RecyclerView должен быть первым дочерним элементом вашего CoordinatorLayout (у меня сначала было вложенное представление прокрутки, что мешало Android понять, что прокрутка не нужна). - person A.Mamode; 25.01.2021

Вы можете проверить, отображается ли последний элемент в RecyclerView. Если это не так, отключите прокрутку программно, используя этот метод:

            //turn off scrolling
            AppBarLayout.LayoutParams toolbarLayoutParams = (AppBarLayout.LayoutParams) mToolbar.getLayoutParams();
            toolbarLayoutParams.setScrollFlags(0);
            mToolbar.setLayoutParams(toolbarLayoutParams);

            CoordinatorLayout.LayoutParams appBarLayoutParams = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
            appBarLayoutParams.setBehavior(null);
            appBarLayout.setLayoutParams(appBarLayoutParams);
person Michał Z.    schedule 19.09.2015
comment
Хорошо работает, спасибо! Я обновил свой вопрос как с выключенными, так и с включенными методами. - person Paolo Rotolo; 19.09.2015
comment
Работает хорошо, но если содержимое представления, в котором есть app: layout_behavior = @ string / appbar_scrolling_view_behavior, а также layout_height = match_parent, содержимое не будет центрироваться после отключения прокрутки - person Earwin delos Santos; 01.10.2015
comment
это хорошо работает, просто чтобы отключить прокрутку, но если повторно включить то же самое для другого фрагмента, это не сработает при программной установке флагов прокрутки. - person pallavi; 20.09.2016

Я применил несколько иной подход, чтобы решить эту проблему.

Я создал собственный AppBarBehavior, который отключает себя на основе ячеек.

public class CustomAppBarBehavior extends AppBarLayout.Behavior {

    private RecyclerView recyclerView;
    private boolean enabled;

    public CustomAppBarBehavior() {
    }

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

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

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

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

    private void updatedEnabled() {
        enabled = false;
        if(recyclerView != null) {
            RecyclerView.Adapter adapter = recyclerView.getAdapter();
            if (adapter != null) {
                int count = adapter.getItemCount();
                RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
                if (layoutManager != null) {
                    int lastItem = 0;
                    if (layoutManager instanceof LinearLayoutManager) {
                        LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
                        lastItem = Math.abs(linearLayoutManager.findLastCompletelyVisibleItemPosition());
                    } else if (layoutManager instanceof StaggeredGridLayoutManager) {
                        StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager;
                        int[] lastItems = staggeredGridLayoutManager.findLastCompletelyVisibleItemPositions(new int[staggeredGridLayoutManager.getSpanCount()]);
                        lastItem = Math.abs(lastItems[lastItems.length - 1]);
                    }
                    enabled = lastItem < count - 1;
                }
            }
        }
    }

    public void setRecyclerView(RecyclerView recyclerView) {
        this.recyclerView = recyclerView;
    }
}

Затем установите настраиваемое поведение в макете панели приложения.

appBarBehavior = new CustomAppBarBehavior();
CoordinatorLayout.LayoutParams appBarLayoutParams = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
appBarLayoutParams.setBehavior(appBarBehavior);
appBarLayout.setLayoutParams(appBarLayoutParams);

Последнее изменение страницы пейджера просмотра обновило RecyclerView по поведению

private ViewPager.OnPageChangeListener pageChangeListener = new ViewPager.OnPageChangeListener() {
        @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { }

        @Override
        public void onPageSelected(final int position) {             
            appBarLayout.setExpanded(true, true);
            appBarLayout.post(new Runnable() {
                @Override
                public void run() {
                    appBarBehavior.setRecyclerView(childFragments.get(position).getRecyclerView());
                }
            });
        }

        @Override
        public void onPageScrollStateChanged(int state) { }
    };

Это должно работать при изменении наборов данных.

person Stuart Campbell    schedule 03.10.2015
comment
Очень интересно. Спасибо, что поделился. - person Paolo Rotolo; 03.10.2015
comment
О, также вам нужно сбросить recyclerview при вращении, имея в виду, что пейджер просмотра дает индекс 0 после восстановления, даже если его на самом деле нет на этой странице. Фрагменты - это радость. - person Stuart Campbell; 03.10.2015

Добавьте этот код после изменения данных в адаптере:

recyclerView.afterMeasured {
    val isTurnedOff = recyclerView.turnOffNestedScrollingIfEnoughItems()
    if (isTurnedOff) appBarLayout.setExpanded(true)
}

А это функции:

inline fun <T: View> T.afterMeasured(crossinline action: T.() -> Unit) {
    viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
        override fun onGlobalLayout() {             
            viewTreeObserver.removeOnGlobalLayoutListener(this)
            action()
        }
    })
}


fun RecyclerView.turnOffNestedScrollingIfEnoughItems(): Boolean {
    val lm = (layoutManager as LinearLayoutManager)
    val count = if (lm.itemCount <= 0) 0 else lm.itemCount - 1
    val isFirstVisible = lm.findFirstCompletelyVisibleItemPosition() == 0
    val isLastItemVisible = lm.findLastCompletelyVisibleItemPosition() == count

    isNestedScrollingEnabled = !(isLastItemVisible && isFirstVisible)
    return isNestedScrollingEnabled.not()
}
person Artur Dumchev    schedule 05.07.2018

Думаю, это лучшее решение. Вам необходимо определить собственное поведение AppBarLayout:

class CustomScrollingViewBehavior : AppBarLayout.Behavior {

    constructor() : super()
    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)

    var shouldScroll = true

    override fun onStartNestedScroll(coordinatorLayout: CoordinatorLayout, child: AppBarLayout, directTargetChild: View, target: View, axes: Int, type: Int): Boolean {
        return shouldScroll && when (target) {
            is NestedScrollView, is ScrollView, is RecyclerView -> {
                return target.canScrollVertically(1) || target.canScrollVertically(-1)
            }
            else -> super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, axes, type)
        }
    }
}

И тогда вам просто нужно использовать его в своем макете как атрибут AppBarLayout:

...

 <com.google.android.material.appbar.AppBarLayout
            android:id="@+id/appbar"
            android:layout_width="match_parent"
            android:layout_height="56dp"
            app:layout_behavior=".CustomScrollingViewBehavior">

...

Вот и все.

Примечание: настраиваемое поведение также поддерживает полное отключение прокрутки - вам просто нужно установить для флага shouldScroll значение false.

customScrollingViewBehavior.shouldScroll = false
person Vasil Stolyarchuk    schedule 07.07.2020

Если что-то помогает в Котлине на основе правильного ответа, я сделал это для Котлина:

fun changeToolbarScroll(isToScrolling: Boolean){

    val params = toolbar.layoutParams as AppBarLayout.LayoutParams
    val appBarLayoutParams = appBarLayout.layoutParams as 
                             CoordinatorLayout.LayoutParams
    params.scrollFlags = 0
    toolbar.layoutParams = params
    appBarLayoutParams.behavior = null
    appBarLayout.layoutParams = appBarLayoutParams

    if(isToScrolling){
        params.scrollFlags =
            AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL or 
            AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS
        toolbar.layoutParams = params

        appBarLayoutParams.behavior = AppBarLayout.Behavior()
        appBarLayout.layoutParams = appBarLayoutParams
    }
}

В моем случае у меня проблема с MainActivity, которая управляет навигацией, панелью инструментов и другими общими вещами с помощью 2 фрагментов, первый Framgent использует RecyclerView, а второй предназначен для демонстрации подробностей. Проблемы возникают, когда я устанавливаю меню и меняю MenuItem с MainAcitivity.

Это может показаться глупым и совершенно логичным, но помните: Всегда вносите изменения в Menu или MenuItems перед вызовом supportFragmentManager.beginTransaction (), если в противном случае фрагменты изменений не работают или не обновляются должным образом, независимо от изменений. добавить, .replace (), показать () ...

fun showDetailImageFragment(searchImage: SearchImage) {
    val searchFragment = 
    supportFragmentManager.findFragmentByTag(SEARCH_IMAGES)

    changeToolbarScroll(false)

    if (supportActionBar != null) {
        supportActionBar!!.collapseActionView()
        supportActionBar!!.setDisplayHomeAsUpEnabled(true)
        supportActionBar!!.title = getString(R.string.detail_image_title)
    }

    actionSearch.isVisible = false
    actionNighMOde.isVisible = false
    actionAppSetings.isVisible = false
    actionAbout.isVisible = false

    supportFragmentManager.beginTransaction()
        .setCustomAnimations(
            R.animator.fade_in,
            R.animator.fade_out,
            R.animator.fade_in,
            R.animator.fade_out
        )
        .hide(searchFragment!!)
        .add(
            R.id.frameLayout,
            DetailImageFragment().newInstance(searchImage)
        ).addToBackStack(null)
        .commit()
}
person meleAstur    schedule 03.11.2019

Просто удалите свиток из

app:layout_scrollFlags="scroll|enterAlways"

Так и должно быть

app:layout_scrollFlags="enterAlways"
person Waran-    schedule 02.04.2017

Вы можете добавить в свой XML свойство layout_behaviour со значением @string/appbar_scrolling_view_behavior следующим образом:

app:layout_behavior="@string/appbar_scrolling_view_behavior"
person Kumar Sunny    schedule 15.11.2018