AnimateLayoutChanges = "true" в BottomSheetView, демонстрирующий неожиданное поведение

У меня есть BottomSheetView, который имеет animateLayoutChanges="true". Первоначально это проявляется хорошо. Но если изменить visibility представления (внутри BottomSheetView) от GONE до VISIBLE, приложение перепутает вычисления, а мой BottomSheetView перемещается в верхнюю часть экрана. Я попытался установить layout_gravity=bottom в корень макета BottomSheetView. Но успеха нет.

Здесь у меня есть образ моего BottomSheetView, прежде чем изменять видимость любого вида. (Нажмите на изображение для увеличения)

enter image description here

После изменения видимости представления (GONE до VISIBLE или VISIBLE до GONE) мой файл BottomSheetView перемещается вверх. (Нажмите на изображение для увеличения)

enter image description here

Я предполагаю, что Android возится, делая вычисления об измерении вида width и height. Любой способ решить эту проблему?

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

Ожидаемые решения:

1 > Запретить BottomSheetView изменять свое положение даже при изменении visibility вида.

ИЛИ

2 > Сопоставьте родительский элемент BottomSheetView, чтобы он не выглядел плохим после того, как он испортил вычисления.

Ответ 1

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

Причина: Проблема возникает между поведением нижнего листа и LayoutTransition. Когда LayoutTransition создается, он создает OnLayoutChangeListener в представлении, чтобы он мог захватывать свои endValues ​​и настраивать аниматор с соответствующими значениями. Этот OnLayoutChangeListener запускается в вызове bottomSheetBehavior onLayout(), когда он сначала вызывает parent.onLayout(child). Родитель будет макетировать ребенка, как обычно, игнорируя любые смещения, которые поведение изменит позже. Проблема здесь. Значения представления в этой точке захватываются функцией OnLayoutChangeListener и сохраняются в аниматоре. Когда анимация запускается, она будет анимировать эти значения, а не там, где ваше поведение определяет. К сожалению, класс LayoutTransition не дает нам доступ к аниматорам, позволяя обновлять конечные значения.

Исправление: В настоящее время я не вижу элегантного решения, которое включает LayoutTransitions. Я собираюсь предоставить ошибку для доступа и обновления аниматоров LayoutTransition. На данный момент вы можете отключить любой layoutTransition в родительском контейнере с помощью layoutTransition.setAnimateParentHierachy(false). Затем вы можете анимировать изменение самостоятельно. Я могу обновить свой ответ рабочим примером, как только смогу.

Ответ 2

На данный момент BottomSheetBehavior не работает с LayoutTransition (animateLayoutChanges="true"). Я буду работать над исправлением.

На данный момент вы можете использовать вместо Transition. Примерно так будет исчезать вид изнутри и анимироваться размер нижнего листа.

ViewGroup bottomSheet = ...;
View hidingView = ...;

TransitionManager.beginDelayedTransition(bottomSheet);
hidingView.setVisibility(View.GONE);

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

Ответ 3

В макете по умолчанию BottomSheetDialog (design_bottom_sheet_dialog) есть TOP тяжести в диалоговом design_bottom_sheet FrameLayout:

 android:layout_gravity="center_horizontal|top"

Я действительно не знаю, почему на Bottom SheetDialog гравитация является лучшей.

Вам нужно создать такой же файл макета (с тем же содержимым и именем) в вашем проекте и заменить эту строку:

android:layout_gravity="center_horizontal|bottom"

Ответ 4

Вопрос был задан более двух лет назад, но, к сожалению, проблема сохраняется.

Наконец-то я получил решение сохранить вызов addView и removeView в BottomSheet, имея animateLayoutChanges="true".

BottomSheetBehavior не может рассчитать правильную высоту при ее изменении, поэтому высота должна оставаться неизменной. Чтобы сделать это, я установил высоту BottomSheet match_parent и разделил его на два дочерних match_parent: содержимое и Space которое изменяет высоту в соответствии с высотой содержимого.

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

Вот код:

activity.xml

<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/show_bottom_sheet"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Show bottom sheet"/>

    <View
        android:id="@+id/touch_to_dismiss"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clickable="true"
        android:background="#9000"/>

    <LinearLayout
        android:id="@+id/bottom_sheet"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">

        <Space
            android:id="@+id/space"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_weight="1"/>

        <LinearLayout
            android:id="@+id/bottom_sheet_content"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:animateLayoutChanges="true">

            <Button
                android:id="@+id/add_or_remove_another_view"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Add another view"/>

            <TextView
                android:id="@+id/another_view"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Another view"/>

        </LinearLayout>

    </LinearLayout>

</androidx.coordinatorlayout.widget.CoordinatorLayout>

activity.java

BottomSheetBehavior bottomSheetBehavior;
View touchToDismiss;
LinearLayout bottomSheet;
Button showBottomSheet;
Space space;
LinearLayout bottomSheetContent;
Button addOrRemoveAnotherView;
TextView anotherView;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    touchToDismiss = findViewById(R.id.touch_to_dismiss);
    touchToDismiss.setVisibility(View.GONE);
    touchToDismiss.setOnClickListener(this);

    bottomSheet = findViewById(R.id.bottom_sheet);

    bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet);
    bottomSheetBehavior.setPeekHeight(0);
    bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
    bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
        @Override
        public void onStateChanged(@NonNull View bottomSheet, int newState) {
            if (newState == BottomSheetBehavior.STATE_HIDDEN || newState == BottomSheetBehavior.STATE_COLLAPSED) {
                touchToDismiss.setVisibility(View.GONE);
            }else {
                touchToDismiss.setVisibility(View.VISIBLE);
            }
        }

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

    showBottomSheet = findViewById(R.id.show_bottom_sheet);
    showBottomSheet.setOnClickListener(this);

    space = findViewById(R.id.space);

    bottomSheetContent = findViewById(R.id.bottom_sheet_content);

    addOrRemoveAnotherView = findViewById(R.id.add_or_remove_another_view);
    addOrRemoveAnotherView.setOnClickListener(this);

    anotherView = findViewById(R.id.another_view);
    bottomSheetContent.removeView(anotherView);
}

@Override
public void onClick(View v) {
    if (v == showBottomSheet)
        bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
    else if (v == addOrRemoveAnotherView) {
        if (anotherView.getParent() == null)
            bottomSheetContent.addView(anotherView);
        else
            bottomSheetContent.removeView(anotherView);
    }
    else if (v == touchToDismiss)
        bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
}

/**
 * Since the height does not change and remains at match_parent, it is required to calculate the true offset.
 * @return Real offset of the BottomSheet content.
 */
public float getRealOffset() {
    float num = (space.getHeight() + bottomSheetContent.getHeight()) - (bottomSheet.getY() + space.getHeight());

    float den = bottomSheetContent.getHeight();

    return (num / den);
}

Это результат, полученный с помощью этого кода: final result

Надеюсь, это будет кому-то полезно, так как проблема все еще существует!