Dentro de um ScrollView, alterno dinamicamente entre dois fragmentos com alturas diferentes. Infelizmente isso leva ao salto. Pode-se vê-lo na seguinte animação:
- Estou rolando para baixo até chegar ao botão "mostrar amarelo".
- Pressionar "mostrar amarelo" substitui um enorme fragmento azul por um pequeno fragmento amarelo. Quando isso acontece, os dois botões saltam para o final da tela.
Eu quero que os dois botões fiquem na mesma posição ao mudar para o fragmento amarelo. Como isso pode ser feito?
Código-fonte disponível em https://github.com/wondering639/stack-dynamiccontent, respectivamente https://github.com/wondering639/stack-dynamiccontent.git
Trechos 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" />
DICA
Observe que este é, obviamente, um exemplo de trabalho mínimo para mostrar meu problema. No meu projeto real, também tenho visualizações abaixo do @+id/fragment_container
. Portanto, atribuir um tamanho fixo a @+id/fragment_container
não é uma opção para mim - isso causaria uma grande área em branco ao mudar para o fragmento baixo e amarelo.
ATUALIZAÇÃO: Visão geral das soluções propostas
Implementei as soluções propostas para fins de teste e adicionei minhas experiências pessoais a elas.
resposta por Cheticamp, https://stackoverflow.com/a/60323255
-> disponível em https://github.com/wondering639/stack-dynamiccontent/tree/60323255
-> FrameLayout agrupa conteúdo, código curto
responder por Pavneet_Singh, https://stackoverflow.com/a/60310807
-> disponível em https://github.com/wondering639/stack-dynamiccontent/tree/60310807
-> FrameLayout obtém o tamanho do fragmento azul. Portanto, sem quebra de conteúdo. Ao alternar para o fragmento amarelo, existe um espaço entre ele e o conteúdo a seguir (se houver algum conteúdo). Nenhuma renderização adicional! ** atualização ** Uma segunda versão foi fornecida mostrando como fazê-lo sem falhas. Verifique os comentários para a resposta.
resposta por Ben P., https://stackoverflow.com/a/60251036
-> disponível em https://github.com/wondering639/stack-dynamiccontent/tree/60251036
-> FrameLayout agrupa o conteúdo. Mais código que a solução da Cheticamp. Tocar no botão "mostrar amarelo" duas vezes leva a um "bug" (os botões saltam para baixo, na verdade, é o meu problema original). Alguém poderia argumentar sobre apenas desativar o botão "mostrar amarelo" depois de mudar para ele, para que eu não considerasse isso um problema real.
fonte
Respostas:
Atualização : para manter as outras visualizações logo abaixo do
framelayout
e para manipular o cenário automaticamente, precisamos usaronMeasure
para implementar o tratamento automático, assim como as seguintes etapas• Crie um personalizado
ConstraintLayout
como (ou pode usar MaxHeightFrameConstraintLayout lib ):e use-o em seu layout no lugar de
ConstraintLayout
Isso acompanhará a altura e a aplicará durante cada alteração de fragmento.
Resultado:
Nota: Conforme mencionado nos comentários anteriores, a configuração de minHeight resultará em um passo de renderização adicional e não poderá ser evitado na versão atual do
ConstraintLayout
.Abordagem antiga com FrameLayout personalizado
Esse é um requisito interessante e minha abordagem é resolvê-lo criando uma exibição personalizada.
Idéia :
Minha idéia para a solução é ajustar a altura do contêiner, mantendo o controle do maior filho ou da altura total de filhos no contêiner.
Tentativas :
Minhas primeiras tentativas foram baseadas na modificação do comportamento existente
NestedScrollView
, estendendo-o, mas ele não fornece acesso a todos os dados ou métodos necessários. A personalização resultou em suporte insuficiente para todos os cenários e casos extremos.Mais tarde, consegui a solução criando um costume
Framelayout
com uma abordagem diferente.Implementação da solução
Ao implementar o comportamento personalizado das fases de medição de altura, eu me aprofundava e manipulava a altura
getSuggestedMinimumHeight
enquanto rastreia a altura dos filhos para implementar a solução mais otimizada, pois não causará nenhuma renderização adicional ou explícita, pois gerenciará a altura durante a renderização interna ciclo para criar umaFrameLayout
classe personalizada para implementar a solução e substituir ogetSuggestedMinimumHeight
como:Agora substitua
FrameLayout
porMaxChildHeightFrameLayout
poractivity_main.xml
como:getSuggestedMinimumHeight()
será usado para calcular a altura da vista durante o ciclo de vida da renderização da vista.Resultado:
Com mais visualizações, fragmentos e diferentes alturas. (400dp, 20dp, 500dp respectivamente)
fonte
getSuggestedMinimumHeight()
está relacionado a eles ou em qual ordem eles são executados.onMeasure
é o primeiro passo para medir o processo de renderização egetSuggestedMinimumHeight
(e apenas um getter para não causar renderização adicional) está sendo usado dentroonMeasure
para definir a altura.setMinHeight
chamará requestLayout () enquanto resultará na medição, layout e desenho da árvore de exibição de layoutUma solução direta é ajustar a altura mínima do ConstraintLayout no NestedScrollView antes de alternar fragmentos. Para evitar pulos, a altura do ConstraintLayout deve ser maior ou igual a
mais
O código a seguir encapsula esse conceito:
Observe que
layout.minimumHeight
não funcionará no ConstraintLayout . Você deve usarlayout.minHeight
.Para invocar esta função, faça o seguinte:
É semelhante para insertBlueFragment () . Obviamente, você pode simplificar isso fazendo findViewById () uma vez.
Aqui está um rápido vídeo dos resultados.
No vídeo, adicionei uma exibição de texto na parte inferior para representar itens adicionais que podem existir no seu layout abaixo do fragmento. Se você excluir essa exibição de texto, o código ainda funcionará, mas você verá um espaço em branco na parte inferior. Aqui está o que parece:
E se as vistas abaixo do fragmento não preencherem a vista de rolagem, você verá as vistas adicionais mais o espaço em branco na parte inferior.
fonte
commit
é chamado antesadjustMinHeight
, presumo que haja um ciclo de renderização adicional envolvido?Seu
FrameLayout
interioractivity_main.xml
tem um atributo de altura dewrap_content
.Os layouts de fragmentos filhos estão determinando as diferenças de altura que você vê.
Deve postar seu xml para os fragmentos filhos
Tente definir uma altura específica para o
FrameLayout
seuactivity_main.xml
fonte
Resolvi isso criando um ouvinte de layout que monitora a altura "anterior" e adiciona preenchimento ao ScrollView se a nova altura for menor do que antes.
Para habilitar esse ouvinte, adicione este método aos dois fragmentos:
Estes são os métodos que o ouvinte chama para realmente atualizar o ScrollView. Adicione-os à sua atividade:
E você precisa definir argumentos para seus fragmentos para que o ouvinte saiba qual era a altura anterior e a posição de rolagem anterior. Portanto, adicione-os às suas transações de fragmento:
E isso deveria bastar!
fonte