11

I have a MotionLayout inside the NestedScrollView:

<androidx.core.widget.NestedScrollView
        android:id="@+id/scroll_content"
        android:layout_width="match_parent"
        android:fillViewport="true">
    <androidx.constraintlayout.motion.widget.MotionLayout
            android:id="@+id/content_parent"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:paddingBottom="10dp"
            app:layoutDescription="@xml/main_scene">
            <View 1>
            <View 2>
            <View 3>
    </androidx.constraintlayout.motion.widget.MotionLayout>

My state 1 shows View 1 only.
My state 2 shows View 2 only.
My state 3 shows View 1 + View 2(below View 1) + View 3(below View 2)

Since state 3 appends multiple views vertically, it is the longest vertically.

However, I can only scroll down to the amount set for state 1 & state 2. It does not reset the height inside the scrollView.

Am I doing something wrong?

I tried following at onTransitionCompleted():

scroll_content.getChildAt(0).invalidate()
scroll_content.getChildAt(0).requestLayout()
scroll_content.invalidate()
scroll_content.requestLayout()

They did not solve my issue.

jclova
  • 5,466
  • 16
  • 52
  • 78

2 Answers2

2

Adding motion:layoutDuringTransition="honorRequest" inside the <Transition> in your layoutDescription XML file fixes the issue. This was added to ConstraintLayout in version 2.0.0-beta4

Vladimir Jovanović
  • 2,261
  • 2
  • 24
  • 43
  • 1
    It helped with my issue when I wanted to update some view inside Motion Layout during animation. Without it, children views' requestLayout calls were ignored by parent motion layout and these views' dimensions were not updated. Using "honorRequest" fixed that. – RustyChicken Nov 19 '21 at 11:07
1

Unfortunately, I also encountered such a problem, but found a workaround

<ScrollView>
  <LinearLayout>
   <MotionLayout>

and after the animation is completed

override fun onTransitionCompleted(contentContainer: MotionLayout?, p1: Int) {
                val field = contentContainer::class.java.getDeclaredField("mEndWrapHeight")
                field.isAccessible = true
                val newHeight = field.getInt(contentContainer)
                contentContainer.requestNewSize(contentContainer.width, newHeight)
        }

requestViewSize this is an extension function

internal fun View.requestNewSize(width: Int, height: Int) {
   layoutParams.width = width
   layoutParams.height = height
   layoutParams = layoutParams
}

if you twitch when changing height, just add animateLayoutChanges into your main container and your MotionLayout.

Add if necessary in code

yourView.layoutTransition.enableTransitionType(LayoutTransition.CHANGING)

--------UPDATE--------

I think I found a more correct option for animating the change in height.

Just the first line in the method onTransitionEnd, insert scroll.fullScroll (ScrollView.FOCUS_UP). I added so that the code for changing the height is executed in 500 milliseconds

override fun onTransitionCompleted(contentContainer: MotionLayout?, currentState: Int) {
            if (currentState == R.id.second_state) {
                scroll.fullScroll(ScrollView.FOCUS_UP)
                GlobalScope.doAfterDelay(500) {
                    if (currentState == R.id.second_state) {
                        val field = contentContainer::class.java.getDeclaredField("mEndWrapHeight")
                        field.isAccessible = true
                        val newHeight = field.getInt(contentContainer)
                        contentContainer.requestNewSize(contentContainer.width, newHeight)
                    }
                }
            }
        }

doAfterDelay is a function extension for coroutine

fun GlobalScope.doAfterDelay(time: Long, code: () -> Unit) {
    launch {
        delay(time)
        launch(Dispatchers.Main) { code() }
    }
}

But you can use alitenative