Reputation: 336
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
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