Petermonteer
Petermonteer

Reputation: 336

Scrollview malfunctioning when using default animateLayoutChanges

In my Android App I have a form in a Scrollview, composed of a series of questions answered via radio buttons. Whenever an user presses the radio button, the next question shows up from the bottom. For the animation of the new question I'm using the xml attribute animateLayoutChanges. The problem is, the Scrollview doesn't scroll to the next question that shows up.

I tried switching around a lot of attributes to no avail. The only thing that kinda worked, was setting android:layout_gravity:bottom to the linear layout nested in the scrollview (the one with id form_container), but it caused a bug where the scrollview hides the content on top that goes off screen and doesn't let me scroll up.

My XML

<LinearLayout
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:animateLayoutChanges="true"
    android:clipToPadding="false"
    android:clipChildren="false"
    android:background="@color/colorPrimaryDark">

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

    <RelativeLayout android:id="@+id/content_container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:clipToPadding="false"
        android:clipChildren="false">

        <ScrollView android:id="@+id/form_scroll"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:animateLayoutChanges="true"
            android:padding="20dp"
            android:scrollbars="none"
            android:clipToPadding="false"
            android:clipChildren="false">

            <LinearLayout android:id="@+id/form_container"
                android:orientation="vertical"
                android:layout_width="match_parent"
                android:layout_gravity="bottom"
                android:animateLayoutChanges="true"
                android:layout_height="wrap_content"
                android:clipToPadding="false"
                android:clipChildren="false">

                <LinearLayout
                    android:id="@+id/intro_container"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="vertical"
                    android:clipToPadding="false"
                    android:clipChildren="false">

                    <TextView android:id="@+id/search_title"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:paddingBottom="5dp"
                        android:text="@string/search_title"
                        android:textColor="@android:color/white"
                        android:textSize="24sp"
                        />

                    <TextView
                        android:id="@+id/search_description"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:text="@string/search_description"
                        android:textColor="@android:color/white"
                        android:textSize="22sp"
                        />

                    <ImageView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:paddingBottom="32dp"
                        android:paddingTop="30dp"
                        android:src="@drawable/random" />

                </LinearLayout>

                <LinearLayout android:id="@+id/container1"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:animateLayoutChanges="true"
                    android:orientation="vertical"
                    android:visibility="gone"
                    android:paddingTop="20dp">

                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:paddingBottom="15dp"
                        android:text="whatever"
                        android:textColor="@android:color/white"
                        android:textSize="18sp"
                        />

                    <org.apmem.tools.layouts.FlowLayout android:id="@+id/flow"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:orientation="horizontal" />

                </LinearLayout>

                <LinearLayout android:id="@+id/container2"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="vertical"
                    android:animateLayoutChanges="true"
                    android:visibility="visible"
                    >

                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:paddingBottom="15dp"
                        android:text="whatever2"
                        android:textColor="@android:color/white"
                        android:textSize="18sp"
                        android:typeface="monospace" />

                    <RadioGroup android:id="@+id/radio_group1"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:orientation="horizontal"
                        android:animateLayoutChanges="true"
                        >

                        <android.support.v7.widget.AppCompatRadioButton android:id="@+id/radio1"
                            android:layout_width="0dp"
                            android:layout_weight="1"
                            android:gravity="center"
                            android:paddingStart="20dp"
                            android:layout_height="50dp"
                            android:background="@drawable/radio"
                            android:button="@android:color/transparent"
                            android:text="whatever"
                            android:layout_marginEnd="5dp"
                            />
                        <android.support.v7.widget.AppCompatRadioButton android:id="@+id/radio2"
                            android:layout_marginStart="5dp"
                            android:layout_width="0dp"
                            android:paddingStart="20dp"
                            android:layout_weight="1"
                            android:gravity="center"
                            android:layout_height="50dp"
                            android:background="@drawable/radio"
                            android:button="@android:color/transparent"
                            android:text="whatever"
                            />
                    </RadioGroup>
                </LinearLayout>

                <LinearLayout android:id="@+id/container3"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:visibility="gone"
                    android:orientation="vertical"
                    android:paddingTop="20dp"
                    android:animateLayoutChanges="true"
                    >

                    <TextView
                        android:id="@+id/label"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:paddingBottom="10dp"
                        android:letterSpacing=".07"
                        android:text="@string/confirm"
                        android:textColor="@android:color/white"
                        android:textSize="18sp"
                        android:typeface="monospace" />


                    <include android:id="@+id/search_bar"
                        layout="@layout/custom_dummy_search_view"
                        android:layout_height="60dp"
                        android:layout_width="match_parent"/>
                </LinearLayout>

                <LinearLayout android:id="@+id/container4"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="vertical"
                    android:visibility="gone"
                    android:paddingTop="20dp"
                    android:animateLayoutChanges="true"
                    >

                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:paddingBottom="15dp"
                        android:letterSpacing=".07"
                        android:text="whatever"
                        android:textColor="@android:color/white"
                        android:textSize="18sp"
                        android:typeface="monospace" />

                    <org.apmem.tools.layouts.FlowLayout android:id="@+id/flow2"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:orientation="horizontal"
                        android:animateLayoutChanges="true"
                        />

                </LinearLayout>

                <LinearLayout android:id="@+id/container5"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="vertical"
                    android:visibility="gone"
                    android:paddingTop="20dp"
                    android:animateLayoutChanges="true"
                    >

                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:paddingBottom="15dp"
                        android:letterSpacing=".07"
                        android:text="whatever"
                        android:textColor="@android:color/white"
                        android:textSize="18sp"
                        android:typeface="monospace" />

                    <RadioGroup android:id="@+id/radio_group2"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:orientation="horizontal"
                        android:animateLayoutChanges="true"
                        >

                        <android.support.v7.widget.AppCompatRadioButton android:id="@+id/radio3"
                            android:layout_width="0dp"
                            android:layout_weight="1"
                            android:gravity="center"
                            android:paddingStart="20dp"
                            android:layout_height="50dp"
                            android:background="@drawable/radio"
                            android:button="@android:color/transparent"
                            android:text="whatever"
                            android:layout_marginEnd="5dp"
                            />
                        <android.support.v7.widget.AppCompatRadioButton android:id="@+id/radio4"
                            android:layout_marginStart="5dp"
                            android:paddingStart="20dp"
                            android:layout_width="0dp"
                            android:layout_weight="1"
                            android:gravity="center"
                            android:layout_height="50dp"
                            android:background="@drawable/radio"
                            android:button="@android:color/transparent"
                            android:text="whatever"
                            />
                    </RadioGroup>


                </LinearLayout>

                <android.support.constraint.ConstraintLayout android:id="@+id/container6"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:paddingTop="20dp"
                    android:background="@null"
                    android:visibility="gone"
                    android:animateLayoutChanges="true"
                    >

                    <Button android:id="@+id/button"
                        style="?android:attr/borderlessButtonStyle"
                        android:layout_width="match_parent"
                        android:layout_height="60dp"
                        app:layout_constraintBottom_toBottomOf="parent"
                        app:layout_constraintEnd_toEndOf="parent"
                        app:layout_constraintStart_toStartOf="parent" />

                    <ImageView android:id="@+id/search_icon"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginStart="16dp"
                        android:layout_marginTop="8dp"
                        android:layout_marginBottom="8dp"
                        android:src="@drawable/search"
                        app:layout_constraintBottom_toBottomOf="parent"
                        app:layout_constraintStart_toStartOf="parent"
                        app:layout_constraintTop_toTopOf="parent" />

                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginStart="8dp"
                        android:layout_marginTop="8dp"
                        android:layout_marginBottom="8dp"
                        android:text="whatever"
                        android:textAllCaps="true"
                        android:textSize="18sp"
                        app:layout_constraintBottom_toBottomOf="parent"
                        app:layout_constraintEnd_toEndOf="@+id/button"
                        app:layout_constraintStart_toStartOf="@+id/button"
                        app:layout_constraintTop_toTopOf="parent" />

                </android.support.constraint.ConstraintLayout>
            </LinearLayout>
        </ScrollView>
    </RelativeLayout>
</LinearLayout>

To change visibility of the views the simplest code is used:

radioButton.setOnCheckedChangeListener { compoundButton, b ->
            mContainer2.visibility = View.VISIBLE

Edit:

Sample Code that reproduces the problem:

XML

<ScrollView android:id="@+id/form_scroll"
        xmlns:android="http://schemas.android.com/apk/res/android"
            android:background="@color/colorPrimaryDark"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:animateLayoutChanges="true"
        android:padding="20dp"
        android:scrollbars="none">
    <RelativeLayout
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:animateLayoutChanges="true"
            android:gravity="bottom"
            android:clipChildren="false"
            android:layout_height="wrap_content">

    <TextView android:id="@+id/text1"
            android:layout_width="wrap_content"
              android:visibility="visible"
              android:text="text1text1text1text1text1text1text1text1text1text1text1text1text1text1text1text1text1text1"
              android:textSize="40sp"
              android:background="@color/colorPrimary"
              android:layout_height="wrap_content"/>

    <TextView android:id="@+id/text2"
              android:layout_below="@id/text1"
              android:layout_width="wrap_content"
              android:text="text2text2text2text2text2text2text2text2text2text2text2text2text2text2text2text2text2text2text2"
              android:textSize="40sp"
              android:visibility="gone"
              android:background="@color/colorPrimary"
              android:layout_height="wrap_content"/>
    <TextView android:id="@+id/text3"
              android:layout_below="@id/text2"
              android:layout_width="wrap_content"
              android:text="text3text3text3text3text3text3text3text3text3text3text3text3text3text3text3text3text3text3text3text3text3text3text3text3text3"
              android:textSize="40sp"
              android:visibility="gone"
              android:background="@color/colorPrimary"
              android:layout_height="wrap_content"/>
    <TextView android:id="@+id/text4"
              android:layout_below="@id/text3"
              android:layout_width="wrap_content"
              android:text="text4text4text4text4text4text4text4text4text4text4text4text4text4text4text4text4text4text4text4text4text4text4text4text4text4text4text4text4text4text4text4text4text4text4text4text4text4text4text4text4"
              android:textSize="40sp"
              android:visibility="gone"
              android:background="@color/colorPrimary"
              android:layout_height="wrap_content"/>
    </RelativeLayout>
</ScrollView>

Activity:

class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    val mScroll = findViewById<ScrollView>(R.id.form_scroll)

    val mText1 = findViewById<TextView>(R.id.text1)
    val mText2 = findViewById<TextView>(R.id.text2)
    val mText3 = findViewById<TextView>(R.id.text3)
    val mText4 = findViewById<TextView>(R.id.text4)

    mText1.setOnClickListener {
        mText2.visibility = View.VISIBLE
    }
    mText2.setOnClickListener {
        mText3.visibility = View.VISIBLE
    }
    mText3.setOnClickListener {
        mText4.visibility = View.VISIBLE
    }
}
}

Upvotes: 3

Views: 753

Answers (1)

sound
sound

Reputation: 1999

You'll need to wait for your layout to change before scrolling in your scrollView. For that, you can use the viewTreeObserver and addOnGlobalLayoutListener To avoid any leak of your listener, you destroy it directly after use. I'm using the extension provided by Antonio Leiva here, have a look on his article to better understand how the viewTreeObserver and listener work

Once the measured are done, you need to know the size of the full content of your scrollView. You need to find the first child of the scrollView to find the height: getChildAt(0).height

Unfortunately, I haven't been able to use the smoothScroll without calling it twice in the afterMeasured and I'm not able to explain this behavior...

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val mScroll = findViewById<ScrollView>(R.id.form_scroll)

        val mText1 = findViewById<TextView>(R.id.text1)
        val mText2 = findViewById<TextView>(R.id.text2)
        val mText3 = findViewById<TextView>(R.id.text3)
        val mText4 = findViewById<TextView>(R.id.text4)

        mText1.setOnClickListener {
            mText2.visibility = View.VISIBLE
            mText2.afterMeasured {
                mScroll.scrollTo(0, mScroll.getChildAt(0).height)
            }

        }
        mText2.setOnClickListener {
            mText3.visibility = View.VISIBLE
            mText3.afterMeasured {
                mScroll.scrollTo(0, mScroll.getChildAt(0).height)
            }
        }
        mText3.setOnClickListener {
            mText4.visibility = View.VISIBLE

            mText4.afterMeasured {
                mScroll.scrollTo(0, mScroll.getChildAt(0).height)
            }
        }
    }
}

inline fun View.afterMeasured(crossinline f: View.() -> Unit) {
    viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
        override fun onGlobalLayout() {
            if (measuredWidth > 0 && measuredHeight > 0) {
                println(measuredHeight)
                f()
                viewTreeObserver.removeOnGlobalLayoutListener(this)
            }
        }
    })
}

Upvotes: 1

Related Questions