animateLayoutChanges="true" in BottomSheetView showing unexpected behaviour animateLayoutChanges="true" in BottomSheetView showing unexpected behaviour android android

animateLayoutChanges="true" in BottomSheetView showing unexpected behaviour


The BottomSheetBehavior does not work well with LayoutTransition (animateLayoutChanges="true") for now. I will work on a fix.

For now, you can use Transition instead. Something like this will fade the view inside and animate the size of the bottom sheet.

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

You can refer to Applying a Transition for more information including how to customize the animation.


I was running into the same issue and determined to find a fix. I was able to find the underlying cause but unfortunately I do not see a great fix at the moment.

The Cause:The problem occurs between the bottomsheet behavior and the LayoutTransition. When the LayoutTransition is created, it creates a OnLayoutChangeListener on the view so that it can capture its endValues and setup an animator with the proper values. This OnLayoutChangeListener is triggered in the bottomSheetBehavior's onLayout() call when it first calls parent.onLayout(child). The parent will layout the child as it normally would, ignoring any offsets that the behavior would change later. The problem lies here. The values of the view at this point are captured by the OnLayoutChangeListener and stored in the animator. When the animation runs, it will animate to these values, not to where your behavior defines. Unfortunately, the LayoutTransition class does not give us access to the animators to allow updating of the end values.

The Fix:Currently, I don't see an elegant fix that involves LayoutTransitions. I am going to submit a bug for a way to access and update LayoutTransition animators. For now you can disable any layoutTransition on the parent container using layoutTransition.setAnimateParentHierachy(false). Then you can animate the change yourself. I'll update my answer with a working example as soon as I can.


The question was asked more than two years ago, but unfortunately the problem persists.

I finally got a solution to keep the call to the addView and removeView functions in a BottomSheet, while having animateLayoutChanges="true".

BottomSheetBehavior cannot calculate the correct height when it changes, so the height must remain the same. To do this, I set the height of the BottomSheet to match_parent and divide it into two children: the content and a Space that changes height according to the height of the content.

To best mimic the true behavior of a BottomSheet, you also need to add a TouchToDismiss view that darkens the background when the BottomSheet is extended but also to close the BottomSheet when the user presses outside the content.

Here's the code:

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;@Overrideprotected 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);}@Overridepublic 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);}

This is the result obtained with this code:final result

Hopefully it will be useful to someone since the problem is still there!