Динамически изменять высоту BottomSheetBehavior

Я использую BottomSheetBehavior от Google недавно выпустила AppCompat v23.2. Высота моего нижнего листа зависит от содержимого, отображаемого внутри нижнего листа (аналогично тому, что Google делает сами в своем приложении "Карты").

Он отлично работает с первоначально загруженными данными, но мое приложение изменяет содержимое, отображаемое во время выполнения, и когда это происходит, нижний лист остается на своей старой высоте, что приводит либо к неиспользуемому пространству внизу, либо к срезу обзора.

Есть ли способ сообщить макет нижнего листа, чтобы пересчитать высоту, используемую для расширенного состояния (когда высота ViewGroup установлен в MATCH_HEIGHT) или каким-либо способом вручную установить необходимую высоту?


РЕДАКТИРОВАТЬ: Я также пытался вручную позвонить invalidate() на ViewGroup и родитель этого, но без какого-либо успеха.

10 ответов

У меня была такая же проблема с RelativeLayout как мой нижний лист. Высота не будет пересчитана. Пришлось прибегнуть к установке высоты по новому пересчитанному значению и вызвать BottomSheetBehavior.onLayoutChild,

Это мое временное решение:

coordinatorLayout = (CoordinatorLayout)findViewById(R.id.coordinator_layout);
bottomSheet = findViewById(R.id.bottom_sheet);

int accountHeight = accountTextView.getHeight();
accountTextView.setVisibility(View.GONE);

bottomSheet.getLayoutParams().height = bottomSheet.getHeight() - accountHeight;
bottomSheet.requestLayout();
behavior.onLayoutChild(coordinatorLayout, bottomSheet, ViewCompat.LAYOUT_DIRECTION_LTR);

Ты можешь использовать BottomSheetBehavior#setPeekHeight для этого.

FrameLayout bottomSheet = (FrameLayout) findViewById(R.id.bottom_sheet);
BottomSheetBehavior<FrameLayout> behavior = BottomSheetBehavior.from(bottomSheet);
behavior.setPeekHeight(newHeight);

Это не приводит к автоматическому перемещению нижнего листа на высоту взгляда. Ты можешь позвонить BottomSheetBehavior#setState чтобы настроить нижний лист на новую высоту взгляда.

behavior.setState(BottomSheetBehavior.STATE_COLLAPSED);

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

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    return inflater.inflate(R.layout.your_bottom_sheet_layout, container, false)
}

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
    val dialog = super.onCreateDialog(savedInstanceState) as BottomSheetDialog
    dialog.setContentView(R.layout.your_bottom_sheet_layout)

    dialog.setOnShowListener {
        val castDialog = it as BottomSheetDialog
        val bottomSheet = castDialog.findViewById<View?>(R.id.design_bottom_sheet)
        val behavior = BottomSheetBehavior.from(bottomSheet)
        behavior.state = BottomSheetBehavior.STATE_EXPANDED
        behavior.setBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
            override fun onStateChanged(bottomSheet: View, newState: Int) {
                if (newState == BottomSheetBehavior.STATE_DRAGGING) {
                    behavior.state = BottomSheetBehavior.STATE_EXPANDED
                }
            }

            override fun onSlide(bottomSheet: View, slideOffset: Float) {}
        })
    }

    return dialog
}

Хотя проблема была решена в библиотеке поддержки>=24.0.0, если по какой-то причине вам все еще нужно использовать более старую версию, здесь есть обходной путь.

mBottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
        @Override
        public void onStateChanged(@NonNull final View bottomSheet, int newState) {
            bottomSheet.post(new Runnable() {
                @Override
                public void run() {
                    //workaround for the bottomsheet  bug
                    bottomSheet.requestLayout();
                    bottomSheet.invalidate();
                }
            });
        }

        @Override
        public void onSlide(@NonNull View bottomSheet, float slideOffset) {
        }
    });

Для фрагмента диалогового окна нижнего листа прочтите следующее: Фрагмент диалогового окна нижнего листа развернуть на всю высоту

@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
   super.onActivityCreated(savedInstanceState);

   BottomSheetDialog dialog = (BottomSheetDialog) getDialog();

   FrameLayout bottomSheet = dialog.findViewById(com.google.android.material.R.id.design_bottom_sheet);
   BottomSheetBehavior behavior = BottomSheetBehavior.from(bottomSheet);
   behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
   behavior.setPeekHeight(0);
}

Я борюсь с проблемой, похожей на вашу.

Решением для меня была ручная установка высоты bottomSheet.

Наличие представления viewA, имеющего BottomSheetBehaviour и настраиваемый метод modifyHeight(), который изменяет высоту представления:

viewA?.modifyHeight()
viewA?.measure(
            MeasureSpec.makeMeasureSpec(
                width,
                MeasureSpec.EXACTLY
            ),
            MeasureSpec.makeMeasureSpec(
                0,
                MeasureSpec.UNSPECIFIED
            )
        )
val layoutParams = LayoutParams(viewA.measuredWidth, viewA.measuredHeight)
val bottomSheet = BottomSheetBehavior.from(viewA)
layoutParams.behavior = bottomSheet
viewA.layoutParams = layoutParams

Мой макет будет примерно таким:

    <com.yourpackage.ViewA
    android:id="@+id/viewA"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:behavior_peekHeight="50dp"
    app:layout_behavior="@string/bottom_sheet_behavior" />

Важно повторно использовать bottomSheetBehaviour старого layoutParams, потому что он содержит peekHeight и слушателей, которые вы, возможно, прикрепили.

Я столкнулся с той же проблемой, когда пытался обновить высоту просмотра на основе его содержимого, была найдена высота из предыдущего макета. Это имеет смысл, поскольку новый макет еще не состоялся. Посредством публикации в потоке пользовательского интерфейса высота макета вычисляется после нового макета, и делается еще один запрос макета, чтобы обновить нижний лист до нужной высоты.

void show() {
    setVisibility(View.VISIBLE);
    post(new Runnable() {
        @Override
        public void run() {
            mBottomSheetBehavior.setPeekHeight(findViewById(R.id.sheetPeek).getHeight());
            requestLayout();
        }
    })
}

Я столкнулся с той же проблемой, когда использовал BistomSheet в представлении реселлера, и элементы менялись динамически. Как упомянул @sosite в своем комментарии, проблема зарегистрирована, и они исправили ее в последней версии. Журнал проблемы здесь

Просто обновите вашу библиотеку поддержки дизайна до версии 24.0.0 и проверьте.

Я следовал совету @HaraldUnander, и он дал мне идею, которая действительно сработала. Если вы запустили поток (не смог заставить его работать с методом post как он) после BottomSheetBehavior.state программно настроен на STATE_COLLAPSED, тогда вы уже можете получить высоту ваших просмотров и установить peekHeight в зависимости от его содержания.

Итак, сначала вы установите BottomSheetBehavior:

BottomSheetBehavior.from(routeCaptionBottomSheet).state = BottomSheetBehavior.STATE_COLLAPSED

И тогда вы устанавливаете peekHeight динамически:

thread {
    activity?.runOnUiThread {
        val dynamicHeight = yourContainerView.height
        BottomSheetBehavior.from(bottomSheetView).peekHeight = dynamicHeight
    }
}

Если используется Java (я использую Kotlin с Anko для потоков), это может сделать:

new Thread(new Runnable() {
    public void run() {
        val dynamicHeight = yourContainerView.height
        BottomSheetBehavior.from(bottomSheetView).peekHeight = dynamicHeight
    }
}).start();

Вот прослушиватель нажатия кнопки переключения, который у меня есть, чтобы установить высоту выбора нижнего листа с анимацией.

      FrameLayout standardBottomSheet = findViewById(R.id.standardBottomSheet);

BottomSheetBehavior<FrameLayout> bottomSheetBehavior = BottomSheetBehavior.from(standardBottomSheet);    

btnToggleBottomSheet.setOnClickListener(new HPFM_OnSingleClickListener() {
                @Override
                public void onSingleClick(View v) {
                    if (bottomSheetBehavior.getPeekHeight() == 0) {
                        ObjectAnimator.ofInt(bottomSheetBehavior, "peekHeight", 200).setDuration(300).start();
                    }
                    else {
                        ObjectAnimator.ofInt(bottomSheetBehavior, "peekHeight", 0).setDuration(300).start();
                    }
                }
            });
Другие вопросы по тегам