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]()
Надеюсь, это будет кому-то полезно, так как проблема все еще существует!