Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MotionLayout inside the ScrollView does not update the height after the state change

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.

like image 772
jclova Avatar asked Jun 12 '19 18:06

jclova


2 Answers

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

like image 136
Vladimir Jovanović Avatar answered Sep 20 '22 04:09

Vladimir Jovanović


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