70

I must be missing something with android:visibility changes within a motion layout. Here’s a simplified version of my layout.

<MotionLayout>
 <ImageView android:id="@+id/HeaderBackground" />
Bunch of image views, Text views that are constrainted to the HeaderBackground. 
<RecyclerView android:visibility="visible">
<EditText android:visibility="visible">
<CustomViewGroup1 android:visibility="gone">
</MotionLayout>

I have a motionScreen that sets a Transition onswipe based on the recycler view to reduce the height of the HeaderBackground and hide some of the images/text views (i.e a collasping toolbar). However, if there’s some fatal error, I want to show CustomViewGroup1 on top/infront of the RecyclerView but toggling the visibility of the RecyclerView to gone and CustomViewGroup1 to visible doesn’t make the CustomViewGroup1 show.

I could move the CustomViewGroup group out of the MotionLayout and add a framelayout as the root of the activity and hide the whole MotionLayout. But this is less than ideal as I’d have to copy of the toolbar bar and icons. So I guess the question is about visibility changes and potentially z ordering?

I'm using androidx.constraintlayout:constraintlayout:2.0.0-beta2

scottyab
  • 23,621
  • 16
  • 94
  • 105

7 Answers7

113

Turns out this was my inexperience with how MotionLayout works. By default MotionLayout controls the visibility of all views within it. But you can opt out child views by using the app:visibilityMode="ignore" attribute and defining the view in the <ConstraintSet>

In my case <CustomViewGroup1> looks like this...

<Constraint
      android:id="@id/CustomViewGroup1"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toBottomOf="@+id/HeaderBackground"
      app:layout_constraintVertical_weight="1"
      app:visibilityMode="ignore" />

And this is defined the same in both collasped and expended ConstraintSets as I don't want it to move/animation when the recycler view is scrolled.

Thanks to John Hoford for the advice in another channel.

scottyab
  • 23,621
  • 16
  • 94
  • 105
47

My approach is to exclude the view from the motion layout, ignore the visibility from the scene so it can be programmatic and also inherit the constraints in the end constraint set.

The documentation mentioned the app:applyMotionScene="boolean" but it doesn't tell you where. It has to be in a PropertySet

Also visibilityMode only worked for me inside a PropertySet as well

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

    <Transition
        ...
    </Transition>

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

        <Constraint android:id="@id/viewId">
            <PropertySet
                app:applyMotionScene="false"
                app:visibilityMode="ignore" />
        </Constraint>

    </ConstraintSet>

    <ConstraintSet
        android:id="@+id/end"
        motion:deriveConstraintsFrom="@id/start">
        ...
    </ConstraintSet>

</MotionScene>

Careful with the app namespace auto-import from motion scene, that one is wrong you have to use the same that is used on the layouts.

The benefit of this is:

  • No width, height or constraints in the scene
  • No need to repeat the properties since is derived from the start constraint set
cutiko
  • 9,887
  • 3
  • 45
  • 59
  • Mahn!!! You saved me... applyMotionScene was the real deal. I saw it on the documentation but it didn’t make any real sense to me... Oh thanks – Paul Okeke Jan 20 '21 at 18:26
  • 1
    @PaulOkeke check this serie https://youtu.be/o8c1RO3WgBA and welcome glad to help – cutiko Jan 20 '21 at 19:43
7

Programmatically way

View clickableArea = motionLayout.findViewById(R.id.video_overlay_thumbnail);    
motionLayout.getConstraintSet(R.id.start).getConstraint(clickableArea.getId()).propertySet.mVisibilityMode = 1; // 1 - ignore or 0 - normal
clickableArea.setVisibility(GONE);
Albert
  • 163
  • 2
  • 7
3

The other option depending on your use case is to make sure the view you want to control the visibility of isn't a direct descendant of the MotionLayout. Place it in a container view and you'll be able to set the visibility as normal

3

You can change visibility children of Motion layout promatically, by this way:

fun View.changeVisibility(visibility: Int) {
        val motionLayout = parent as MotionLayout
        motionLayout.constraintSetIds.forEach {
            val constraintSet = motionLayout.getConstraintSet(it) ?: return@forEach
            constraintSet.setVisibility(this.id, visibility)
            constraintSet.applyTo(motionLayout)
        }
    }
2

Set visibilityMode property to normal, working fine for me with 2.0.0-beta3.

 app:visibilityMode="normal"
 android:visibility="visible"
1'hafs
  • 559
  • 7
  • 25
0

According to Mr. Albert's answer, which is the good one, I would like to suggest another solution. Setting the PropertySet value programmatically may lead to java.lang.NullPointerException when the phone configuration is changed (for example switching Night Mode). So it would be better to add a Constraint with PropertySet in your Motion Layout's "scene" file instead:

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

        <Constraint
            //some of scene constraints
        </Constraint>

        <Constraint>
            //some of scene constraints
        </Constraint>

        <Constraint android:id="@+id/ivEdit"> //id of your view to change visibility
            <PropertySet
                app:visibilityMode="ignore"/>
        </Constraint>

    </ConstraintSet>
FarVoyager
  • 49
  • 4