Dentro de un ScrollView, estoy cambiando dinámicamente entre dos fragmentos con diferentes alturas. Lamentablemente eso lleva a saltar. Se puede ver en la siguiente animación:
- Me desplazo hacia abajo hasta llegar al botón "mostrar amarillo".
- Al presionar "mostrar amarillo" se reemplaza un enorme fragmento azul con un pequeño fragmento amarillo. Cuando esto sucede, ambos botones saltan al final de la pantalla.
Quiero que ambos botones permanezcan en la misma posición al cambiar al fragmento amarillo. ¿Cómo se puede hacer eso?
Código fuente disponible en https://github.com/wondering639/stack-dynamiccontent respectivamente https://github.com/wondering639/stack-dynamiccontent.git
Fragmentos de código relevantes:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/myScrollView"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/textView"
android:layout_width="0dp"
android:layout_height="800dp"
android:background="@color/colorAccent"
android:text="@string/long_text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button_fragment1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:text="show blue"
app:layout_constraintEnd_toStartOf="@+id/button_fragment2"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView" />
<Button
android:id="@+id/button_fragment2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:text="show yellow"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/button_fragment1"
app:layout_constraintTop_toBottomOf="@+id/textView" />
<FrameLayout
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/button_fragment2">
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.kt
package com.example.dynamiccontent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// onClick handlers
findViewById<Button>(R.id.button_fragment1).setOnClickListener {
insertBlueFragment()
}
findViewById<Button>(R.id.button_fragment2).setOnClickListener {
insertYellowFragment()
}
// by default show the blue fragment
insertBlueFragment()
}
private fun insertYellowFragment() {
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.fragment_container, YellowFragment())
transaction.commit()
}
private fun insertBlueFragment() {
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.fragment_container, BlueFragment())
transaction.commit()
}
}
fragment_blue.xml:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="400dp"
android:background="#0000ff"
tools:context=".BlueFragment" />
fragment_yellow.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="20dp"
android:background="#ffff00"
tools:context=".YellowFragment" />
INSINUACIÓN
Tenga en cuenta que este es, por supuesto, un ejemplo mínimo de trabajo para mostrar mi problema. En mi proyecto real, también tengo vistas debajo de @+id/fragment_container
. Entonces dando un tamaño fijo a@+id/fragment_container
no es una opción para mí: causaría un área en blanco grande al cambiar al fragmento amarillo bajo.
ACTUALIZACIÓN: Descripción general de las soluciones propuestas
Implementé las soluciones propuestas para propósitos de prueba y agregué mis experiencias personales con ellas.
respuesta por Cheticamp, https://stackoverflow.com/a/60323255
-> disponible en https://github.com/wondering639/stack-dynamiccontent/tree/60323255
-> FrameLayout envuelve contenido, código corto
respuesta por Pavneet_Singh, https://stackoverflow.com/a/60310807
-> disponible en https://github.com/wondering639/stack-dynamiccontent/tree/60310807
-> FrameLayout obtiene el tamaño del fragmento azul. Así que no hay envoltura de contenido. Al cambiar al fragmento amarillo, hay una brecha entre este y el contenido que lo sigue (si algún contenido lo sigue). Sin embargo, no hay renderizado adicional. ** actualización ** se proporcionó una segunda versión que muestra cómo hacerlo sin vacíos. Revisa los comentarios a la respuesta.
respuesta por Ben P., https://stackoverflow.com/a/60251036
-> disponible en https://github.com/wondering639/stack-dynamiccontent/tree/60251036
-> FrameLayout ajusta el contenido. Más código que la solución de Cheticamp. Tocar el botón "mostrar amarillo" dos veces conduce a un "error" (los botones saltan hacia abajo, en realidad mi problema original). Uno podría discutir sobre simplemente deshabilitar el botón "mostrar amarillo" después de cambiar a él, por lo que no consideraría esto como un problema real.
Respuestas:
Actualización : para mantener las otras vistas justo debajo del
framelayout
y para manejar el escenario automáticamente, necesitamos usarloonMeasure
para implementar el manejo automático, así que siga los siguientes pasos• Cree una costumbre
ConstraintLayout
como (o puede usar MaxHeightFrameConstraintLayout lib ):y úselo en su diseño en lugar de
ConstraintLayout
Esto hará un seguimiento de la altura y lo aplicará durante cada cambio de fragmento.
Salida:
Nota: Como se mencionó en los comentarios anteriores, establecer minHeight dará como resultado un paso de representación adicional y no se puede evitar en la versión actual de
ConstraintLayout
.Enfoque antiguo con FrameLayout personalizado
Este es un requisito interesante y mi enfoque es resolverlo creando una vista personalizada.
Idea :
Mi idea para la solución es ajustar la altura del contenedor manteniendo el seguimiento del niño más grande o la altura total de los niños en el contenedor.
Intentos :
Mis primeros intentos se basaron en modificar el comportamiento existente
NestedScrollView
al extenderlo, pero no proporciona acceso a todos los datos o métodos necesarios. La personalización resultó en un pobre soporte para todos los escenarios y casos extremos.Más tarde, logré la solución creando una costumbre
Framelayout
con un enfoque diferente.Implementación de soluciones
Mientras implementaba el comportamiento personalizado de las fases de medición de altura, profundicé y manipulé la altura
getSuggestedMinimumHeight
mientras rastreaba la altura de los niños para implementar la solución más optimizada, ya que no causará ninguna representación adicional o explícita porque administrará la altura durante la representación interna ciclo, así que cree unaFrameLayout
clase personalizada para implementar la solución y anule elgetSuggestedMinimumHeight
como:Ahora reemplace
FrameLayout
conMaxChildHeightFrameLayout
inactivity_main.xml
como:getSuggestedMinimumHeight()
se usará para calcular la altura de la vista durante el ciclo de vida de representación de la vista.Salida:
Con más vistas, fragmento y altura diferente. (400dp, 20dp, 500dp respectivamente)
fuente
getSuggestedMinimumHeight()
se relaciona con ellos o en qué orden se ejecutan.onMeasure
es el primer paso para medir el proceso de renderizado ygetSuggestedMinimumHeight
(y solo un captador para no generar renderizado adicional) se está utilizando en el interioronMeasure
para definir la altura.setMinHeight
llamará a requestLayout () mientras dará como resultado la medición, el diseño y el dibujo del árbol de la vista de diseñoUna solución sencilla es ajustar la altura mínima de ConstraintLayout dentro de NestedScrollView antes de cambiar los fragmentos. Para evitar saltos, la altura del diseño de restricción debe ser mayor o igual que
más
El siguiente código encapsula este concepto:
Tenga en cuenta que
layout.minimumHeight
no funcionará para RestraintLayout . Debes usarlayout.minHeight
.Para invocar esta función, haga lo siguiente:
Es similar para insertBlueFragment () . Puede, por supuesto, simplificar esto haciendo findViewById () una vez.
Aquí hay un video rápido de los resultados.
En el video, agregué una vista de texto en la parte inferior para representar elementos adicionales que pueden existir en su diseño debajo del fragmento. Si elimina esa vista de texto, el código seguirá funcionando, pero verá un espacio en blanco en la parte inferior. Así es como se ve:
Y si las vistas debajo del fragmento no llenan la vista de desplazamiento, verá las vistas adicionales más el espacio en blanco en la parte inferior.
fuente
commit
se llama antes al FragmentManageradjustMinHeight
, ¿supongo que hay un ciclo de representación adicional involucrado?Su
FrameLayout
interioractivity_main.xml
tiene un atributo de altura dewrap_content
.Los diseños de fragmentos de su hijo determinan las diferencias de altura que está viendo.
Debería publicar su xml para los fragmentos secundarios
Intente establecer una altura específica
FrameLayout
enactivity_main.xml
fuente
Resolví esto creando un oyente de diseño que realiza un seguimiento de la altura "anterior" y agrega relleno al ScrollView si la nueva altura es menor que antes.
Para habilitar esta escucha, agregue este método a ambos fragmentos:
Estos son los métodos que el oyente llama para actualizar realmente ScrollView. Agréguelos a su actividad:
Y debe establecer argumentos en sus fragmentos para que el oyente sepa cuál era la altura anterior y la posición de desplazamiento anterior. Así que asegúrese de agregarlos a sus transacciones fragmentarias:
¡Y eso debería hacerlo!
fuente