Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android motion layout not working accordingly

I am new to motionlayout and been following various tutorials online like this to get an understanding of how it works. From a nutshell I have come to know it basically animates constraintSets, you have a start and end constraintSet which you can further customize with KeyFrameSets. I have this layout

enter image description here

I want to mimic Lyft's bottom sheet

enter image description here

With my layout the Where are you going button is suppose to slowly fade out as the search destination textInputs fade in. The recyclerview at the bottom is suppose to hold saved addresses, it will not be affected. I tried this implementation using a standard bottomsheet but had challenges with the animation, it had this weird flickering so I decided to use a MotionLayout with a normal view.

My bottomsheet layout is as follows

<com.google.android.material.card.MaterialCardView 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:id="@+id/cardChooseAddressBottomSheet"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clickable="true"
    app:shapeAppearance="@style/ShapeAppearanceRoundedLargeTopCorners">



    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/bottomSheetConstraintLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="@dimen/activity_horizontal_margin"
        android:layout_marginRight="@dimen/activity_horizontal_margin"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">



        <ImageView
            android:id="@+id/swipeUpHandle"
            android:layout_width="50dp"
            android:layout_height="30dp"
            android:layout_gravity="center"
            android:background="@drawable/ic_swipe_up_handle"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />



        <com.google.android.material.textview.MaterialTextView
            android:id="@+id/hiThere"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/medium_margin"
            android:text="@string/hi_there"
            android:textAppearance="@style/h6_headline"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/swipeUpHandle"
            />


        <com.google.android.material.button.MaterialButton
            android:id="@+id/btnSearch"
            style="@style/Widget.MaterialComponents.Button.OutlinedButton"
            android:layout_width="match_parent"
            android:layout_height="60dp"
            android:layout_marginTop="@dimen/medium_margin"
            android:gravity="start|center_vertical"
            android:letterSpacing="0.0"
            android:text="@string/where_are_you_going"
            android:textAllCaps="false"
            android:textAppearance="@style/subtitle1"
            android:textColor="@android:color/darker_gray"
            app:backgroundTint="@android:color/white"
            app:icon="@drawable/ic_search"
            app:iconTint="@android:color/black"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/hiThere"
            app:shapeAppearanceOverlay="@style/ShapeAppearanceRoundedMediumAllCorners" />



        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:id="@+id/addressViews"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/btnSearch">



            <com.google.android.material.textfield.TextInputLayout
                android:id="@+id/inputOrigin"
                style="@style/textInput"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="@dimen/medium_margin"
                android:hint="@string/search_destination"
                android:textColorHint="@android:color/darker_gray"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent">

                <com.google.android.material.textfield.TextInputEditText
                    android:id="@+id/edtOrigin"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:inputType="text"
                    android:textAppearance="@style/subtitle1"
                    android:textColor="@android:color/white" />

            </com.google.android.material.textfield.TextInputLayout>

            <com.google.android.material.textfield.TextInputLayout
                android:id="@+id/inputDestination"
                style="@style/textInput"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="@dimen/medium_margin"
                android:hint="@string/search_destination"
                android:textColorHint="@android:color/darker_gray"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@id/inputOrigin">

                <com.google.android.material.textfield.TextInputEditText
                    android:id="@+id/edtDestination"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:inputType="text"
                    android:textAppearance="@style/subtitle1"
                    android:textColor="@android:color/white" />

            </com.google.android.material.textfield.TextInputLayout>


        </androidx.constraintlayout.widget.ConstraintLayout>

        <androidx.recyclerview.widget.RecyclerView
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:id="@+id/recyclerAddresses"
            android:layout_marginTop="@dimen/medium_margin"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/addressViews"
            tools:listitem="@layout/recycler_view_item" />
    </androidx.constraintlayout.widget.ConstraintLayout>

</com.google.android.material.card.MaterialCardView>

And my parent layout where I include the bottomsheet is as follows

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.motion.widget.MotionLayout 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:id="@+id/motionLayout"
    app:layoutDescription="@xml/taxi_bottomsheet_scene"
    android:layout_width="match_parent"
    android:layout_height="match_parent">


    <fragment
        android:id="@+id/map"
        android:name="com.google.android.gms.maps.SupportMapFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />



    <include
        layout="@layout/choose_destination_bottom_sheet_layout"/>

</androidx.constraintlayout.motion.widget.MotionLayout>

And finally my taxi_bottomsheet_scene motion scene is

<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">



    <Transition
        app:constraintSetEnd="@+id/expanded"
        app:constraintSetStart="@+id/collapsed"
        app:duration="1000">

        <OnSwipe
            app:touchAnchorId="@+id/btnSearch"
            app:touchAnchorSide="top"
            app:dragDirection="dragUp"/>


    </Transition>


    <ConstraintSet android:id="@+id/expanded">

        <Constraint
            android:id="@+id/cardChooseAddressBottomSheet"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintHeight_percent="1"
            app:layout_constraintHorizontal_bias="1.0"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_bias="0" />



        <Constraint
            android:id="@+id/addressViews"
            app:layout_constraintHeight_percent="1"/>


        <Constraint
            android:id="@+id/btnSearch"
            app:layout_constraintHeight_percent="0"/>

    </ConstraintSet>




    <ConstraintSet android:id="@+id/collapsed">

        <Constraint
            android:id="@+id/cardChooseAddressBottomSheet"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintHeight_percent="0.4"
            app:layout_constraintHorizontal_bias="1.0"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_bias="1.0"  />


        <Constraint
            android:id="@+id/addressViews"
            app:layout_constraintHeight_percent="0.0"/>



        <Constraint
            android:id="@+id/btnSearch"
            app:layout_constraintHeight_percent="0.0"/>
    </ConstraintSet>





</MotionScene>

When I launch this app I cannot get the bottomsheet to slide up, it simply does not respond in any way. One thing I noticed though is after adding the app:layoutDescription="@xml/taxi_bottomsheet_scene"attribute, the bottom sheet size changed to what I had specified in the constraintSetStart but the addressViews view did not.

So my layout looks like

parent layout after attaching it to the motion scene

So my question is, where I am going wrong for my bottomsheet not to respond to my swipes and addressViews to disappear in the initial state?

like image 222
Astonio Avatar asked Oct 31 '25 03:10

Astonio


1 Answers

I finally managed to make it work, both with MotionLayout and CoordinatorLayout. I will only post the Coordinator solution as it is long and I do not have the time, if someone needs it, comment and I will post.

I created 3 layouts, 1. The main layout with the map, 2. Top bar with the to and from address EditTexts and 3. The bottom layout that slides up and reveals the top bar.

Solution 1 using CoordinatorLayout

  1. The topbar

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:focusable="true"
        android:layout_marginBottom="@dimen/medium_margin"
        app:layout_scrollFlags="noScroll">
    
    
        <com.google.android.material.appbar.MaterialToolbar
            android:id="@+id/topToolBar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:navigationIcon="@drawable/ic_arrow_back_black_24dp" />
    
    
        <androidx.constraintlayout.widget.ConstraintLayout
            android:id="@+id/addressViews"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_marginStart="@dimen/activity_horizontal_margin"
            android:layout_marginEnd="@dimen/activity_horizontal_margin"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/topToolBar">
    
    
            <androidx.appcompat.widget.AppCompatImageView
                android:id="@+id/imgOrigin"
                android:layout_width="@dimen/activity_horizontal_margin"
                android:layout_height="@dimen/activity_horizontal_margin"
                android:layout_marginStart="@dimen/medium_margin"
                android:layout_marginEnd="@dimen/medium_margin"
                app:layout_constraintBottom_toBottomOf="@+id/inputOrigin"
                app:layout_constraintEnd_toStartOf="@+id/inputOrigin"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="@+id/inputOrigin"
                app:srcCompat="@drawable/ic_origin"
                app:tint="@color/colorAccent" />
    
    
            <View
                android:layout_width="2dp"
                android:layout_height="0dp"
                android:layout_marginTop="@dimen/xsmall_margin"
                android:layout_marginBottom="@dimen/xsmall_margin"
                android:background="@drawable/accent_to_color_primary_dark__negative_90_gradient"
                app:layout_constraintBottom_toTopOf="@+id/imgDestination"
                app:layout_constraintEnd_toEndOf="@+id/imgOrigin"
                app:layout_constraintStart_toStartOf="@+id/imgOrigin"
                app:layout_constraintTop_toBottomOf="@+id/imgOrigin" />
    
            <com.google.android.material.textfield.TextInputLayout
                android:id="@+id/inputOrigin"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginTop="@dimen/medium_margin"
                android:hint="@string/pick_up_location"
                android:marqueeRepeatLimit="marquee_forever"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toEndOf="@+id/imgOrigin"
                app:layout_constraintTop_toTopOf="parent">
    
                <com.google.android.material.textfield.TextInputEditText
                    android:id="@+id/edtOrigin"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:background="@color/white"
                    android:inputType="textPostalAddress"
                    android:singleLine="true"
                    android:textAppearance="@style/subtitle1" />
    
            </com.google.android.material.textfield.TextInputLayout>
    
    
            <androidx.appcompat.widget.AppCompatImageView
                android:id="@+id/imgDestination"
                android:layout_width="@dimen/activity_horizontal_margin"
                android:layout_height="@dimen/activity_horizontal_margin"
                android:layout_marginStart="@dimen/medium_margin"
                android:layout_marginEnd="@dimen/medium_margin"
                app:layout_constraintBottom_toBottomOf="@+id/inputDestination"
                app:layout_constraintEnd_toStartOf="@+id/inputDestination"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="@+id/inputDestination"
                app:srcCompat="@drawable/ic_destination"
                app:tint="@color/colorPrimaryDark" />
    
            <com.google.android.material.textfield.TextInputLayout
                android:id="@+id/inputDestination"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginTop="@dimen/medium_margin"
                android:hint="@string/search_destination"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toEndOf="@+id/imgDestination"
                app:layout_constraintTop_toBottomOf="@id/inputOrigin">
    
                <com.google.android.material.textfield.TextInputEditText
                    android:id="@+id/edtDestination"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:background="@color/white"
                    android:inputType="textPostalAddress"
                    android:singleLine="true"
                    android:textAppearance="@style/subtitle1" />
    
            </com.google.android.material.textfield.TextInputLayout>
    
        </androidx.constraintlayout.widget.ConstraintLayout>
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    
    </com.google.android.material.appbar.AppBarLayout>
    
  2. The bottom layout that acts like a bottom sheet

    <com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/cardChooseAddressBottomSheet"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:cardBackgroundColor="@color/white"
    app:behavior_hideable="false"
    app:layout_behavior="@string/bottom_sheet_behavior">
    
    
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">
    
    
    
        <ImageView
            android:id="@+id/swipeUpHandle"
            android:layout_width="35dp"
            android:layout_height="30dp"
            android:layout_gravity="center"
            android:layout_marginLeft="@dimen/activity_horizontal_margin"
            android:layout_marginRight="@dimen/activity_horizontal_margin"
            android:background="@drawable/ic_swipe_up_handle"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    
    
        <com.google.android.material.textview.MaterialTextView
            android:id="@+id/hiThere"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/hi_there"
            android:layout_marginLeft="@dimen/activity_horizontal_margin"
            android:layout_marginRight="@dimen/activity_horizontal_margin"
            android:textAppearance="@style/h6_headline"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/swipeUpHandle" />
    
    
        <com.google.android.material.button.MaterialButton
            android:id="@+id/btnSearch"
            style="@style/Widget.MaterialComponents.Button.OutlinedButton"
            android:layout_width="match_parent"
            android:layout_height="60dp"
            android:layout_marginLeft="@dimen/activity_horizontal_margin"
            android:layout_marginRight="@dimen/activity_horizontal_margin"
            android:gravity="start|center_vertical"
            android:letterSpacing="0.0"
            android:text="@string/where_are_you_going"
            android:textAllCaps="false"
            android:textAppearance="@style/subtitle1"
            android:textColor="@android:color/darker_gray"
            app:backgroundTint="@android:color/white"
            app:icon="@drawable/ic_search"
            app:iconSize="@dimen/medium_icon"
            app:iconTint="@color/colorAccent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/hiThere"
            app:shapeAppearanceOverlay="@style/ShapeAppearanceRoundedMediumAllCorners" />
    
    
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recyclerAddresses"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/btnSearch" />
    
    
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recyclerSearchAddresses"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:background="@color/white"
            android:visibility="gone"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/btnSearch" />
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    
    </com.google.android.material.card.MaterialCardView>
    
  3. And finally the two layouts included in my map layout

    <?xml version="1.0" encoding="utf-8"?>
    <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:id="@+id/coordinator"
        android:layout_height="match_parent">
    
        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
    
            <fragment
                android:id="@+id/map"
                android:name="com.google.android.gms.maps.SupportMapFragment"
                android:layout_width="match_parent"
                android:layout_height="0dp"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintHeight_percent="0.67"
                app:layout_constraintHorizontal_bias="0.0"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />
    
            <androidx.appcompat.widget.AppCompatImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/imgMapCenter"
                android:layout_marginBottom="@dimen/xlarge_margin"
                android:visibility="invisible"
                android:tint="@color/colorAccent"
                app:layout_constraintBottom_toBottomOf="@id/map"
                app:layout_constraintEnd_toEndOf="@id/map"
                app:layout_constraintStart_toStartOf="@id/map"
                app:layout_constraintTop_toTopOf="@id/map"
                app:srcCompat="@drawable/ic_destination" />
        </androidx.constraintlayout.widget.ConstraintLayout>
    
    
        <com.google.android.material.floatingactionbutton.FloatingActionButton
            android:id="@+id/fabMyLocation"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="@dimen/activity_horizontal_margin"
            app:backgroundTint="@color/white"
            app:fabSize="mini"
            app:layout_anchor="@id/cardChooseAddressBottomSheet"
            app:layout_anchorGravity="top|right"
            app:srcCompat="@drawable/ic_origin"
            app:tint="@color/colorAccent" />
        <include layout="@layout/taxi_fragment_set_destination_top_bar" />
    
        <include layout="@layout/taxi_fragment_bottom_sheet_addresses_layout" />
    
    </androidx.coordinatorlayout.widget.CoordinatorLayout>
    

And my fragment

   final private BottomSheetBehavior.BottomSheetCallback addressBottomSheetCallBack = new BottomSheetBehavior.BottomSheetCallback() {
        @Override
        public void onStateChanged(@NonNull View bottomSheet, int newState) {
            switch (newState) {
                case BottomSheetBehavior.STATE_HIDDEN:
                    break;

                case BottomSheetBehavior.STATE_SETTLING:
                    break;

                case BottomSheetBehavior.STATE_EXPANDED:
                    if (!allPermissionsGranted())
                        requestForLocationPermissions();
                    topAddressBar.setVisibility(Visibility.VISIBLE);
                     fabMyLocation.hide();
                    break;

                case BottomSheetBehavior.STATE_COLLAPSED:
                    if (!allPermissionsGranted())
                        requestForLocationPermissions();
                    topAddressBar.setVisibility(Visibility.INVISIBLE);
                    fabMyLocation.show();
                    break;

                case BottomSheetBehavior.STATE_DRAGGING:
                    break;

                case BottomSheetBehavior.STATE_HALF_EXPANDED:
                    break;
            }
        }



   BottomSheetBehavior addressBottomSheetBehavior = BottomSheetBehavior.from(cardChooseAddressBottomSheet);

      topAddressBar.post(() -> {
            CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) cardChooseAddressBottomSheet.getLayoutParams();
            layoutParams.height = ((Resources.getSystem().getDisplayMetrics().heightPixels + (int) utils.percentageOf(62, btnSearch.getMeasuredHeight())) - topAddressBar.getMeasuredHeight());
        });


        addressBottomSheetBehavior.setPeekHeight((int) utils.percentageOf(29, Resources.getSystem().getDisplayMetrics().heightPixels), true);
        addressBottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);


        topToolBar.setNavigationOnClickListener(view12 -> {
            if (addressBottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) {
                utopAddressBar.setVisibility(INVISIBLE);
                addressBottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
            } else
                Navigation.findNavController(view12).navigateUp();
        });

        addressBottomSheetBehavior.addBottomSheetCallback(addressBottomSheetCallBack);

Notice am using INVISIBLE instead of GONE on the topAddressBar? that is because everytime I called GONE the layout would ideally recalculate according to my assumption and the map would flicker, to stop that I had to use invisible as the layout does not shrink instead it still takes up the same space but just not visible.

Also notice I am adding padding cardChooseAddressBottomSheet.getLayoutParams() this is because I need the Sheet not to go too deep underneath the topAddressBar as not to hide my recyclerview content. The current padding makes sure the recyclerview is fully visible and everything else on top of it is underneath the topAddressBar

like image 142
Astonio Avatar answered Nov 02 '25 18:11

Astonio



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!