animateLayoutChanges=“true” in BottomSheetView showing unexpected behaviour

后端 未结 4 528
没有蜡笔的小新
没有蜡笔的小新 2021-01-31 15:24

I have a BottomSheetView which has animateLayoutChanges=\"true\". Initially it shows up fine. But if change the visibility of a view (insi

相关标签:
4条回答
  • 2021-01-31 15:55

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

    This is the result obtained with this code:

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

    0 讨论(0)
  • 2021-01-31 16:03

    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.

    0 讨论(0)
  • 2021-01-31 16:05

    In the BottomSheetDialog default layout (design_bottom_sheet_dialog) there is a TOP gravity on the dialog's design_bottom_sheet FrameLayout:

     android:layout_gravity="center_horizontal|top"
    

    I don't really know why on BottomSheetDialog gravity is top.

    You need to create the same layout file (same content and name) in your project and replace this line with:

    android:layout_gravity="center_horizontal|bottom"
    
    0 讨论(0)
  • 2021-01-31 16:11

    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.

    0 讨论(0)
提交回复
热议问题