Springen Scrollen beim Wechseln von Fragmenten

8

In einer ScrollView wechsle ich dynamisch zwischen zwei Fragmenten mit unterschiedlichen Höhen. Das führt leider zum Springen. Man kann es in der folgenden Animation sehen:

  1. Ich scrolle nach unten, bis ich die Schaltfläche "gelb anzeigen" erreiche.
  2. Durch Drücken von "gelb anzeigen" wird ein riesiges blaues Fragment durch ein winziges gelbes Fragment ersetzt. In diesem Fall springen beide Tasten zum Ende des Bildschirms.

Ich möchte, dass beide Tasten an derselben Position bleiben, wenn Sie zum gelben Fragment wechseln. Wie kann das gemacht werden?

rollen

Quellcode verfügbar unter https://github.com/wondering639/stack-dynamiccontent bzw. https://github.com/wondering639/stack-dynamiccontent.git

Relevante Codefragmente:

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" />

HINWEIS

Bitte beachten Sie, dass dies natürlich ein Mindestarbeitsbeispiel ist, um mein Problem aufzuzeigen. In meinem realen Projekt habe ich auch Ansichten unter dem @+id/fragment_container. Daher @+id/fragment_containerist es für mich keine Option , eine feste Größe anzugeben - dies würde beim Umschalten auf das niedrige gelbe Fragment einen großen leeren Bereich verursachen.

UPDATE: Überblick über die vorgeschlagenen Lösungen

Ich habe die vorgeschlagenen Lösungen zu Testzwecken implementiert und meine persönlichen Erfahrungen mit ihnen hinzugefügt.

Antwort von Cheticamp, https://stackoverflow.com/a/60323255

-> verfügbar unter https://github.com/wondering639/stack-dynamiccontent/tree/60323255

-> FrameLayout umschließt Inhalt und Funktionscode

Antwort von Pavneet_Singh, https://stackoverflow.com/a/60310807

-> verfügbar unter https://github.com/wondering639/stack-dynamiccontent/tree/60310807

-> FrameLayout erhält die Größe des blauen Fragments. Also kein Content Wrapping. Wenn Sie zum gelben Fragment wechseln, besteht eine Lücke zwischen diesem und dem darauf folgenden Inhalt (falls ein Inhalt darauf folgt). Kein zusätzliches Rendering! ** Update ** Es wurde eine zweite Version bereitgestellt, die zeigt, wie es ohne Lücken geht. Überprüfen Sie die Kommentare zur Antwort.

Antwort von Ben P., https://stackoverflow.com/a/60251036

-> verfügbar unter https://github.com/wondering639/stack-dynamiccontent/tree/60251036

-> FrameLayout umschließt den Inhalt. Mehr Code als die Lösung von Cheticamp. Das zweimalige Berühren der Schaltfläche "Gelb anzeigen" führt zu einem "Fehler" (Schaltflächen springen nach unten, eigentlich meine ursprüngliche Ausgabe). Man könnte darüber streiten, nur die Schaltfläche "Gelb anzeigen" nach dem Wechsel zu deaktivieren, daher würde ich dies nicht als echtes Problem betrachten.

stefan.at.wpf
quelle
Wenn ich versuche, dies zu reproduzieren, geschieht das "Springen" nur, wenn die Ansicht so weit nach oben gescrollt ist, dass der Abstand unter den Schaltflächen größer ist als die Gesamtgröße des gelben Fragments. Darunter befindet sich nichts, daher gibt es keine Möglichkeit, die Tasten in Position zu halten. Sind Sie sicher, dass dies tatsächlich ein Fehler ist?
Ben P.
Nachdem Sie mehr über Ihren Kommentar nachgedacht haben, haben Sie Recht, dass das aktuelle Verhalten technisch korrekt ist. Scrollview versucht, den gesamten Bildschirm auszufüllen, und nach dem Wechsel zum gelben Fragment befindet sich nicht genügend Inhalt darunter. Daher wird ein Bildlauf durchgeführt, um mehr Inhalt von oben zu erhalten. Ich kann mir also zwei Lösungen vorstellen: 1) Weisen Sie die Bildlaufansicht an, das Ausfüllen des Bildschirms nicht zu erzwingen. 2) Fügen Sie beim Umschalten auf das gelbe Fragment den Höhenunterschied zwischen blauem und gelbem Fragment als untere Auffüllung zum äußeren Einschränkungslayout oder zur Bildlaufansicht hinzu, falls dies der Fall ist unterstützt es direkt.
stefan.at.wpf
@ BenP. Haben Sie eine Idee, wie Sie dies am besten tun können? zB für 1) wenn es überhaupt möglich ist und für 2) wie es so gemacht wird, dass unnötiges Rendern so weit wie möglich vermieden wird.
stefan.at.wpf

Antworten:

1

Update : Um die anderen Ansichten direkt unter dem zu behalten framelayoutund das Szenario automatisch zu behandeln, müssen Sie onMeasuredie automatische Behandlung implementieren. Führen Sie daher die folgenden Schritte aus

• Erstellen Sie eine benutzerdefinierte ConstraintLayoutals (oder verwenden Sie die MaxHeightFrameConstraintLayout-Bibliothek ):

import android.content.Context
import android.os.Build
import android.util.AttributeSet
import androidx.constraintlayout.widget.ConstraintLayout
import kotlin.math.max

/**
 * Created by Pavneet_Singh on 2020-02-23.
 */

class MaxHeightConstraintLayout @kotlin.jvm.JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr){

    private var _maxHeight: Int = 0

    // required to support the minHeight attribute
    private var _minHeight = attrs?.getAttributeValue(
        "http://schemas.android.com/apk/res/android",
        "minHeight"
    )?.substringBefore(".")?.toInt() ?: 0

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            _minHeight = minHeight
        }

        var maxValue = max(_maxHeight, max(height, _minHeight))

        if (maxValue != 0 && && maxValue > minHeight) {
            minHeight = maxValue
        }
        _maxHeight = maxValue
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    }

}

und verwenden Sie es in Ihrem Layout anstelle von ConstraintLayout

<?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">

    <com.example.pavneet_singh.temp.MaxHeightConstraintLayout
        android:id="@+id/constraint"
        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="Some 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.3"
            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_constraintHorizontal_bias="0.3"
            app:layout_constraintStart_toEndOf="@+id/button_fragment1"
            app:layout_constraintTop_toBottomOf="@+id/textView" />

        <Button
            android:id="@+id/button_fragment3"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginEnd="16dp"
            android:layout_marginRight="16dp"
            android:text="show green"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.3"
            app:layout_constraintStart_toEndOf="@+id/button_fragment2"
            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_fragment3" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:text="additional text\nMore data"
            android:textSize="24dp"
            app:layout_constraintTop_toBottomOf="@+id/fragment_container" />

    </com.example.pavneet_singh.temp.MaxHeightConstraintLayout>

</androidx.core.widget.NestedScrollView>

Dadurch wird die Höhe verfolgt und bei jedem Fragmentwechsel angewendet.

Ausgabe:

Hinweis: Wie bereits in den Kommentaren erwähnt , führt das Festlegen von minHeight zu einem zusätzlichen Rendering-Durchgang und kann in der aktuellen Version von nicht vermieden werden ConstraintLayout.


Alter Ansatz mit benutzerdefiniertem FrameLayout

Dies ist eine interessante Anforderung, und mein Ansatz besteht darin, sie durch Erstellen einer benutzerdefinierten Ansicht zu lösen.

Idee :

Meine Idee für die Lösung besteht darin, die Höhe des Containers anzupassen, indem die Spur des größten Kindes oder der Gesamthöhe der Kinder im Container beibehalten wird.

Versuche :

Meine ersten Versuche basierten darauf, das vorhandene Verhalten zu NestedScrollViewändern, indem es erweitert wurde, aber es bietet keinen Zugriff auf alle erforderlichen Daten oder Methoden. Die Anpassung führte zu einer schlechten Unterstützung für alle Szenarien und Randfälle.

Später erreichte ich die Lösung, indem ich einen benutzerdefinierten FramelayoutAnsatz mit einem anderen Ansatz erstellte.

Lösungsimplementierung

Während ich das benutzerdefinierte Verhalten von Phasen der Höhenmessung implementierte, habe ich die Höhe vertieft und manipuliert, getSuggestedMinimumHeightwährend ich die Größe von Kindern verfolgte, um die optimierte Lösung zu implementieren, da dies kein zusätzliches oder explizites Rendern verursacht, da die Höhe während des internen Renderns verwaltet wird Erstellen Sie eine benutzerdefinierte FrameLayoutKlasse, um die Lösung zu implementieren, und überschreiben Sie Folgendes getSuggestedMinimumHeight:

class MaxChildHeightFrameLayout @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {

    // to keep track of max height
    private var maxHeight: Int = 0

    // required to get support the minHeight attribute
    private val minHeight = attrs?.getAttributeValue(
        "http://schemas.android.com/apk/res/android",
        "minHeight"
    )?.substringBefore(".")?.toInt() ?: 0


    override fun getSuggestedMinimumHeight(): Int {
        var maxChildHeight = 0
        for (i in 0 until childCount) {
            maxChildHeight = max(maxChildHeight, getChildAt(i).measuredHeight)
        }
        if (maxHeight != 0 && layoutParams.height < (maxHeight - maxChildHeight) && maxHeight > maxChildHeight) {
            return maxHeight
        } else if (maxHeight == 0 || maxHeight < maxChildHeight) {
            maxHeight = maxChildHeight
        }
        return if (background == null) minHeight else max(
            minHeight,
            background.minimumHeight
        )
    }

}

Ersetzen Sie nun das FrameLayoutdurch MaxChildHeightFrameLayoutin activity_main.xmlals:

<?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="Some 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" />

        <com.example.pavneet_singh.temp.MaxChildHeightFrameLayout
            android:id="@+id/fragment_container"
            android:layout_width="match_parent"
            android:minHeight="2dp"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toBottomOf="@+id/button_fragment2"/>

    </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>

getSuggestedMinimumHeight() wird verwendet, um die Höhe der Ansicht während des Rendering-Lebenszyklus der Ansicht zu berechnen.

Ausgabe:

Mit mehr Ansichten, Fragment und unterschiedlicher Höhe. (400 dp, 20 dp bzw. 500 dp)

Pavneet_Singh
quelle
Zunächst einmal vielen Dank! Aufgrund der Komplexität dieses Themas werde ich Ihre Antwort und die anderen am Samstag überprüfen. Haben Sie einen Link oder können Sie weitere Informationen zum Rendering-Lebenszyklus hinzufügen? Ich bin noch nicht so sehr mit allen Methoden wie onMeasure usw. beschäftigt und wie getSuggestedMinimumHeight()sie damit zusammenhängen oder in welcher Reihenfolge sie alle ausgeführt werden.
stefan.at.wpf
@ stefan.at.wpf onMeasureist der erste Schritt zur Messung des Renderprozesses und wird getSuggestedMinimumHeight(und nur ein Getter, der kein zusätzliches Rendern verursacht) im Inneren verwendet onMeasure, um die Höhe zu definieren. setMinHeightruft requestLayout () auf, während der Layoutansichtsbaum gemessen, angelegt und gezeichnet wird
Pavneet_Singh
@ stefan.at.wpf hat weitere Informationen und Anwendungsfälle hinzugefügt, die langwierig sind oder nicht manuell behandelt werden können, insbesondere wenn die Anzahl der Fragmente, Ansichten unbekannt ist oder dynamisch wächst. Diese Lösung ist eine dynamische Lösung für alle Szenarien und erfordert keine manuelle Behandlung.
Pavneet_Singh
habe gerade deine Antwort überprüft und getestet. Eigentlich möchte ich, dass das FrameLayout Inhalte umschließt. Andernfalls gibt es eine Lücke, wenn der Inhalt nach dem FrameLayout folgt. Würden Sie der Vollständigkeit halber eine zweite Variante hinzufügen, bei der das FrameLayout den Inhalt umschließt? Wie der Versuch sowieso, weil ich über den Renderzyklus erfahren habe :-) Überprüfen Sie das Update zu meinem ursprünglichen Beitrag, ich habe Ihre Version einschließlich des Inhalts nach dem FrameLayout implementiert. Vielleicht könnte als Grundlage für ein Wrapping FrameLayout verwendet werden :-)
stefan.at.wpf
@ stefan.at.wpf Ja, dann kann ich ein benutzerdefiniertes Einschränkungslayout (oder ein beliebiges Containerlayout) erstellen, um zu vermeiden, dass manuelle Einstellungen mit minimaler Höhe vorgenommen werden FrameLayout) Hier ist also die Demo mit Ausgabe. Sie können jedoch versuchen, das drittgrößte Fragment hinzuzufügen. Dies funktioniert mit diesem Ansatz, jedoch nicht mit der manuellen Höheneinstellung.
Pavneet_Singh
1

Eine einfache Lösung besteht darin, die Mindesthöhe des ConstraintLayout in NestedScrollView anzupassen, bevor Fragmente gewechselt werden . Um ein Springen zu verhindern, muss die Höhe des ConstraintLayout größer oder gleich sein

  1. Der Betrag, um den die NestedScrollView in Richtung "y" gescrollt hat

Plus

  1. die Höhe der NestedScrollView .

Der folgende Code kapselt dieses Konzept:

private fun adjustMinHeight(nsv: NestedScrollView, layout: ConstraintLayout) {
    layout.minHeight = nsv.scrollY + nsv.height
}

Bitte beachten Sie, dass layout.minimumHeightdies für ConstraintLayout nicht funktioniert . Sie müssen verwenden layout.minHeight.

Gehen Sie wie folgt vor, um diese Funktion aufzurufen:

private fun insertYellowFragment() {
    val transaction = supportFragmentManager.beginTransaction()
    transaction.replace(R.id.fragment_container, YellowFragment())
    transaction.commit()

    val nsv = findViewById<NestedScrollView>(R.id.myScrollView)
    val layout = findViewById<ConstraintLayout>(R.id.constraintLayout)
    adjustMinHeight(nsv, layout)
}

Ähnlich verhält es sich mit insertBlueFragment () . Sie können dies natürlich vereinfachen, indem Sie findViewById () einmal ausführen .

Hier ist ein kurzes Video der Ergebnisse.

Geben Sie hier die Bildbeschreibung ein

Im Video habe ich unten eine Textansicht hinzugefügt, um zusätzliche Elemente darzustellen, die möglicherweise in Ihrem Layout unterhalb des Fragments vorhanden sind. Wenn Sie diese Textansicht löschen, funktioniert der Code weiterhin, aber unten wird ein Leerzeichen angezeigt. So sieht das aus:

Geben Sie hier die Bildbeschreibung ein

Wenn die Ansichten unter dem Fragment die Bildlaufansicht nicht ausfüllen, werden unten die zusätzlichen Ansichten sowie der Leerraum angezeigt.

Geben Sie hier die Bildbeschreibung ein

Cheticamp
quelle
Nun, diese einfache Lösung ist die manuelle Implementierung (mühsam) meiner Lösung zum Anpassen der Mindesthöhe, die auf verschiedene Container angewendet wird :). Jedenfalls habe ich auch an diesen Ansatz gedacht, aber er funktioniert nicht in allen Szenarien. Wenn das erste Fragment kleiner als das zweite Fragment ist, schlägt diese Logik fehl, da Sie nicht die gewünschte Höhe haben. Ein anderes Beispiel: Was ist, wenn es mehr als zwei Fragmente gibt (ziemlich häufig) und das dritte größer ist als die vorherigen?
Pavneet_Singh
@Pavneet_Singh Ich habe Ihren Code nicht untersucht, daher kann ich keine Ähnlichkeiten kommentieren. Obwohl ich aus Ihrem Kommentar vermute, dass die beiden nicht gleich sind. Ich glaube, dass das, was ich vorgestellt habe, ein allgemeines ist. Die Schaltflächen springen, weil das untergeordnete Element der NestedScrollView (das ConstraintLayout) nicht genügend Höhe hat, um den Teil der Bildlaufansicht abzudecken, der außerhalb des Bildschirms gescrollt wird, sowie den Teil, der auf dem Bildschirm angezeigt wird. Solange diese Höhenanforderung erfüllt ist (durch die Berechnung der Mindesthöhe), spielt es keine Rolle, was sonst noch unter den Schaltflächen passiert. Möglicherweise sehen Sie sich meinen ersten Beitrag vor der Bearbeitung an.
Cheticamp
Es ist nicht der Code, sondern nur die Kernidee für die Lösung, aber überhaupt kein Problem. Ich verstehe das Konzept ziemlich gut und wie ich bereits sagte, stimmen Ihre Höhenanforderungen nicht überein, wenn das erste Fragment klein ist und daher nicht funktioniert. Darüber hinaus ist das explizite Einstellen und Verwalten der Höhe mühsam und führt auch zu zusätzlichem Rendern.
Pavneet_Singh
@Pavneet_Singh Ich frage mich jetzt, ob Sie die Antwort kommentieren, von der Sie glauben, dass Sie sie sind. Ich stelle keine Höhe explizit ein und das kleine erste Fragment ist kein Problem AFAIK.
Cheticamp
Cheticamp, wollte mich nur vielmals bedanken und Sie wissen lassen, dass ich mir diese und die anderen Antworten am Samstag genauer ansehen werde. Jetzt befasse ich mich nicht so sehr mit dem Rendering-Lebenszyklus, aber wie der FragmentManager commitzuvor aufgerufen wurde adjustMinHeight, gehe ich davon aus, dass ein zusätzlicher Rendering-Zyklus erforderlich ist.
stefan.at.wpf
0

Ihr FrameLayoutInneres activity_main.xmlhat ein Höhenattribut von wrap_content.

Ihre untergeordneten Fragmentlayouts bestimmen die Höhenunterschiede, die Sie sehen.

Sollte Ihre XML für die untergeordneten Fragmente veröffentlichen

Versuchen Sie, eine bestimmte Höhe auf die FrameLayoutin Ihrem einzustellenactivity_main.xml

DevJZ
quelle
Vielen Dank für Ihre Antwort, Ihre Erklärung macht Sinn. Ich habe meinen Beitrag aktualisiert und das XML für beide Fragmente hinzugefügt. In diesem Beispiel haben beide feste (aber natürlich unterschiedliche) Höhen. In meiner "echten" App ist ihre Höhe natürlich wrap_content. Ich kann die Höhe des FrameLayout nicht auf eine feste Höhe einstellen, da ich in meiner realen App nach dem FrameLayout einige Inhalte habe. Ich habe es nicht in das Beispiel aufgenommen, sorry \: Irgendeine Idee, wie ich mit meinem Fall umgehen könnte?
stefan.at.wpf
0

Ich habe dieses Problem gelöst, indem ich einen Layout-Listener erstellt habe, der die "vorherige" Höhe verfolgt und der ScrollView eine Auffüllung hinzufügt, wenn die neue Höhe geringer als zuvor ist.

  • HeightLayoutListener.kt
class HeightLayoutListener(
        private val activity: MainActivity,
        private val root: View,
        private val previousHeight: Int,
        private val targetScroll: Int
) : ViewTreeObserver.OnGlobalLayoutListener {

    override fun onGlobalLayout() {
        root.viewTreeObserver.removeOnGlobalLayoutListener(this)

        val padding = max((previousHeight - root.height), 0)
        activity.setPaddingBottom(padding)
        activity.setScrollPosition(targetScroll)
    }

    companion object {

        fun create(fragment: Fragment): HeightLayoutListener {
            val activity = fragment.activity as MainActivity
            val root = fragment.view!!
            val previousHeight = fragment.requireArguments().getInt("height")
            val targetScroll = fragment.requireArguments().getInt("scroll")

            return HeightLayoutListener(activity, root, previousHeight, targetScroll)
        }
    }
}

Fügen Sie diese Methode beiden Fragmenten hinzu, um diesen Listener zu aktivieren:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    val listener = HeightLayoutListener.create(this)
    view.viewTreeObserver.addOnGlobalLayoutListener(listener)
}

Dies sind die Methoden, die der Listener aufruft, um die ScrollView tatsächlich zu aktualisieren. Fügen Sie sie Ihrer Aktivität hinzu:

fun setPaddingBottom(padding: Int) {
    val wrapper = findViewById<View>(R.id.wrapper) // add this ID to your ConstraintLayout
    wrapper.setPadding(0, 0, 0, padding)

    val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(wrapper.width, View.MeasureSpec.EXACTLY)
    val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
    wrapper.measure(widthMeasureSpec, heightMeasureSpec)
    wrapper.layout(0, 0, wrapper.measuredWidth, wrapper.measuredHeight)
}

fun setScrollPosition(scrollY: Int) {
    val scroll = findViewById<NestedScrollView>(R.id.myScrollView)
    scroll.scrollY = scrollY
}

Und Sie müssen Argumente für Ihre Fragmente festlegen, damit der Hörer die vorherige Höhe und die vorherige Bildlaufposition kennt. Stellen Sie also sicher, dass Sie sie zu Ihren Fragmenttransaktionen hinzufügen:

private fun insertYellowFragment() {
    val fragment = YellowFragment().apply {
        this.arguments = createArgs()
    }

    val transaction = supportFragmentManager.beginTransaction()
    transaction.replace(R.id.fragment_container, fragment)
    transaction.commit()
}


private fun insertBlueFragment() {
    val fragment = BlueFragment().apply {
        this.arguments = createArgs()
    }

    val transaction = supportFragmentManager.beginTransaction()
    transaction.replace(R.id.fragment_container, fragment)
    transaction.commit()
}

private fun createArgs(): Bundle {
    val scroll = findViewById<NestedScrollView>(R.id.myScrollView)
    val container = findViewById<View>(R.id.fragment_container)

    return Bundle().apply {
        putInt("scroll", scroll.scrollY)
        putInt("height", container.height)
    }
}

Und das sollte es tun!

Ben P.
quelle
Vielen Dank für diese ausführliche Antwort! Geben Sie mir etwas Zeit zum Experimentieren und warten Sie auf andere Antworten, ohne zu flackern. Ansonsten werde ich es natürlich als gute Ausgangsbasis akzeptieren und das Kopfgeld belohnen :-)
stefan.at.wpf
@ stefan.at.wpf Ich habe meine Antwort mit einer Problemumgehung für das Flackern aktualisiert
Ben P.
noch ein Mal vielen Dank! Ich werde am Samstag genauer hinsehen :-)
stefan.at.wpf
Ich habe Ihre aktualisierte Version überprüft und es funktioniert gut. Wenn man wieder gelb berührt, springt es wie in der Beschreibung meiner ursprünglichen Ausgabe, aber ich würde dies nicht als praktisches Problem betrachten. Verursacht Ihre Version einen zusätzlichen Renderzyklus? Es fällt mir schwer, EINE Antwort aus all den tollen Antworten hier auszuwählen \:
stefan.at.wpf
Ah, ja, die Auffüllung wird nur so aktualisiert, dass sie mindestens so groß ist wie die vorherige. Wenn also das "vorherige" Fragment gelb ist und Sie erneut gelb hinzufügen, wird die Auffüllung zu 0. Sie können dies lösen, indem Sie ein einzelnes "Maximum beibehalten "Wert statt nur des vorherigen. Und hey, stimmen Sie ab, was Sie hilfreich finden, und vergeben Sie, wen Sie wollen!
Ben P.