切换片段时跳跃滚动

ste*_*wpf 8 android android-layout android-fragments android-scrollview fragmentmanager

在 ScrollView 中,我在两个不同高度的片段之间动态切换。不幸的是,这会导致跳跃。人们可以在以下动画中看到它:

  1. 我正在向下滚动,直到到达“显示黄色”按钮。
  2. 按“显示黄色”会用一个微小的黄色片段替换一个巨大的蓝色片段。发生这种情况时,两个按钮都会跳到屏幕的末尾。

当切换到黄色片段时,我希望两个按钮都保持在相同的位置。那怎么办呢?

卷

源代码分别位于https://github.com/wondering639/stack-dynamiccontent https://github.com/wondering639/stack-dynamiccontent.git

相关代码片段:

活动_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>
Run Code Online (Sandbox Code Playgroud)

主活动.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()
    }


}
Run Code Online (Sandbox Code Playgroud)

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" />
Run Code Online (Sandbox Code Playgroud)

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" />
Run Code Online (Sandbox Code Playgroud)

暗示

请注意,这当然是展示我的问题的最低限度的工作示例。在我的实际项目中,我在@+id/fragment_container. 所以给一个固定的大小@+id/fragment_container对我来说不是一个选择 - 当切换到低的黄色片段时,它会导致一个大的空白区域。

更新:建议的解决方案概述

我为测试目的实施了建议的解决方案,并添加了我的个人经验。

Cheticamp 的回答,https: //stackoverflow.com/a/60323255

-> 在https://github.com/wondering639/stack-dynamiccontent/tree/60323255 中可用

-> FrameLayout 包装内容,短代码

Pavneet_Singh 的回答,https: //stackoverflow.com/a/60310807

-> 在https://github.com/wondering639/stack-dynamiccontent/tree/60310807 中可用

-> FrameLayout 获取蓝色片段的大小。所以没有内容包装。当切换到黄色片段时,它和它后面的内容(如果它后面有任何内容)之间存在间隙。虽然没有额外的渲染!** 更新 ** 提供了第二个版本,展示了如何做到无间隙。检查答案的评论。

Ben P. 的回答,/sf/answers/4217572551/

-> 在https://github.com/wondering639/stack-dynamiccontent/tree/60251036 中可用

-> FrameLayout 包装内容。比 Cheticamp 的解决方案更多的代码。触摸“显示黄色”按钮两次会导致“错误”(按钮跳到底部,实际上是我原来的问题)。有人可能会争论在切换到“显示黄色”按钮后只是禁用它,所以我不会认为这是一个真正的问题。

Pav*_*ngh 1

更新:为了将其他视图保持在正下方framelayout并自动处理该场景,我们需要使用onMeasure来实现自动处理,因此请执行以下步骤

\n\n

\xe2\x80\xa2 创建自定义ConstraintLayout为(或者可以使用MaxHeightFrameConstraintLayout lib):

\n\n
import android.content.Context\nimport android.os.Build\nimport android.util.AttributeSet\nimport androidx.constraintlayout.widget.ConstraintLayout\nimport kotlin.math.max\n\n/**\n * Created by Pavneet_Singh on 2020-02-23.\n */\n\nclass MaxHeightConstraintLayout @kotlin.jvm.JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null,\n    defStyleAttr: Int = 0\n) : ConstraintLayout(context, attrs, defStyleAttr){\n\n    private var _maxHeight: Int = 0\n\n    // required to support the minHeight attribute\n    private var _minHeight = attrs?.getAttributeValue(\n        "http://schemas.android.com/apk/res/android",\n        "minHeight"\n    )?.substringBefore(".")?.toInt() ?: 0\n\n    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {\n            _minHeight = minHeight\n        }\n\n        var maxValue = max(_maxHeight, max(height, _minHeight))\n\n        if (maxValue != 0 && && maxValue > minHeight) {\n            minHeight = maxValue\n        }\n        _maxHeight = maxValue\n        super.onMeasure(widthMeasureSpec, heightMeasureSpec)\n    }\n\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

并在您的布局中使用它来代替ConstraintLayout

\n\n
<?xml version="1.0" encoding="utf-8"?>\n\n<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"\n    xmlns:app="http://schemas.android.com/apk/res-auto"\n    xmlns:tools="http://schemas.android.com/tools"\n    android:id="@+id/myScrollView"\n    android:layout_width="match_parent"\n    android:layout_height="match_parent">\n\n    <com.example.pavneet_singh.temp.MaxHeightConstraintLayout\n        android:id="@+id/constraint"\n        android:layout_width="match_parent"\n        android:layout_height="match_parent"\n        tools:context=".MainActivity">\n\n        <TextView\n            android:id="@+id/textView"\n            android:layout_width="0dp"\n            android:layout_height="800dp"\n            android:background="@color/colorAccent"\n            android:text="Some long text"\n            app:layout_constraintEnd_toEndOf="parent"\n            app:layout_constraintStart_toStartOf="parent"\n            app:layout_constraintTop_toTopOf="parent" />\n\n        <Button\n            android:id="@+id/button_fragment1"\n            android:layout_width="0dp"\n            android:layout_height="wrap_content"\n            android:layout_marginStart="16dp"\n            android:layout_marginLeft="16dp"\n            android:text="show blue"\n            app:layout_constraintEnd_toStartOf="@+id/button_fragment2"\n            app:layout_constraintHorizontal_bias="0.3"\n            app:layout_constraintStart_toStartOf="parent"\n            app:layout_constraintTop_toBottomOf="@+id/textView" />\n\n        <Button\n            android:id="@+id/button_fragment2"\n            android:layout_width="0dp"\n            android:layout_height="wrap_content"\n            android:layout_marginEnd="16dp"\n            android:layout_marginRight="16dp"\n            android:text="show yellow"\n            app:layout_constraintHorizontal_bias="0.3"\n            app:layout_constraintStart_toEndOf="@+id/button_fragment1"\n            app:layout_constraintTop_toBottomOf="@+id/textView" />\n\n        <Button\n            android:id="@+id/button_fragment3"\n            android:layout_width="0dp"\n            android:layout_height="wrap_content"\n            android:layout_marginEnd="16dp"\n            android:layout_marginRight="16dp"\n            android:text="show green"\n            app:layout_constraintEnd_toEndOf="parent"\n            app:layout_constraintHorizontal_bias="0.3"\n            app:layout_constraintStart_toEndOf="@+id/button_fragment2"\n            app:layout_constraintTop_toBottomOf="@+id/textView" />\n\n        <FrameLayout\n            android:id="@+id/fragment_container"\n            android:layout_width="match_parent"\n            android:layout_height="wrap_content"\n            app:layout_constraintTop_toBottomOf="@id/button_fragment3" />\n\n        <TextView\n            android:layout_width="match_parent"\n            android:layout_height="match_parent"\n            android:text="additional text\\nMore data"\n            android:textSize="24dp"\n            app:layout_constraintTop_toBottomOf="@+id/fragment_container" />\n\n    </com.example.pavneet_singh.temp.MaxHeightConstraintLayout>\n\n</androidx.core.widget.NestedScrollView>\n
Run Code Online (Sandbox Code Playgroud)\n\n

这将跟踪高度并在每次片段更改期间应用它。

\n\n

输出:

\n\n

\n\n

注意:正如之前评论中提到的,设置minHeight会导致额外的渲染通道,这在当前版本的ConstraintLayout.

\n\n
\n\n

使用自定义 FrameLayout 的旧方法

\n\n

这是一个有趣的需求,我的方法是通过创建自定义视图来解决它。

\n\n

主意

\n\n

我的解决方案的想法是通过跟踪容器中最大的子项或子项的总高度来调整容器的高度。

\n\n

尝试

\n\n

我的前几次尝试是基于NestedScrollView通过扩展来修改现有行为,但它不提供对所有必要数据或方法的访问。定制导致对所有场景和边缘情况的支持很差。

\n\n

后来,我通过使用不同的方法创建自定义来实现解决方案Framelayout

\n\n

解决方案实施

\n\n

在实现高度测量阶段的自定义行为时,我深入挖掘并操纵高度,getSuggestedMinimumHeight同时跟踪儿童的高度以实现最优化的解决方案,因为它不会导致任何额外或显式渲染,因为它将在内部渲染期间管理高度循环因此创建一个自定义FrameLayout类来实现解决方案并重写getSuggestedMinimumHeight为:

\n\n
class MaxChildHeightFrameLayout @JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null,\n    defStyleAttr: Int = 0\n) : FrameLayout(context, attrs, defStyleAttr) {\n\n    // to keep track of max height\n    private var maxHeight: Int = 0\n\n    // required to get support the minHeight attribute\n    private val minHeight = attrs?.getAttributeValue(\n        "http://schemas.android.com/apk/res/android",\n        "minHeight"\n    )?.substringBefore(".")?.toInt() ?: 0\n\n\n    override fun getSuggestedMinimumHeight(): Int {\n        var maxChildHeight = 0\n        for (i in 0 until childCount) {\n            maxChildHeight = max(maxChildHeight, getChildAt(i).measuredHeight)\n        }\n        if (maxHeight != 0 && layoutParams.height < (maxHeight - maxChildHeight) && maxHeight > maxChildHeight) {\n            return maxHeight\n        } else if (maxHeight == 0 || maxHeight < maxChildHeight) {\n            maxHeight = maxChildHeight\n        }\n        return if (background == null) minHeight else max(\n            minHeight,\n            background.minimumHeight\n        )\n    }\n\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

现在将FrameLayout其中替换MaxChildHeightFrameLayoutactivity_main.xml

\n\n
<?xml version="1.0" encoding="utf-8"?>\n\n<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"\n    xmlns:app="http://schemas.android.com/apk/res-auto"\n    xmlns:tools="http://schemas.android.com/tools"\n    android:id="@+id/myScrollView"\n    android:layout_width="match_parent"\n    android:layout_height="match_parent">\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n        android:layout_width="match_parent"\n        android:layout_height="match_parent"\n        tools:context=".MainActivity">\n\n        <TextView\n            android:id="@+id/textView"\n            android:layout_width="0dp"\n            android:layout_height="800dp"\n            android:background="@color/colorAccent"\n            android:text="Some long text"\n            app:layout_constraintEnd_toEndOf="parent"\n            app:layout_constraintStart_toStartOf="parent"\n            app:layout_constraintTop_toTopOf="parent" />\n\n        <Button\n            android:id="@+id/button_fragment1"\n            android:layout_width="0dp"\n            android:layout_height="wrap_content"\n            android:layout_marginStart="16dp"\n            android:layout_marginLeft="16dp"\n            android:text="show blue"\n            app:layout_constraintEnd_toStartOf="@+id/button_fragment2"\n            app:layout_constraintHorizontal_bias="0.5"\n            app:layout_constraintStart_toStartOf="parent"\n            app:layout_constraintTop_toBottomOf="@+id/textView" />\n\n        <Button\n            android:id="@+id/button_fragment2"\n            android:layout_width="0dp"\n            android:layout_height="wrap_content"\n            android:layout_marginEnd="16dp"\n            android:layout_marginRight="16dp"\n            android:text="show yellow"\n            app:layout_constraintEnd_toEndOf="parent"\n            app:layout_constraintHorizontal_bias="0.5"\n            app:layout_constraintStart_toEndOf="@+id/button_fragment1"\n            app:layout_constraintTop_toBottomOf="@+id/textView" />\n\n        <com.example.pavneet_singh.temp.MaxChildHeightFrameLayout\n            android:id="@+id/fragment_container"\n            android:layout_width="match_parent"\n            android:minHeight="2dp"\n            android:layout_height="wrap_content"\n            app:layout_constraintTop_toBottomOf="@+id/button_fragment2"/>\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n</androidx.core.widget.NestedScrollView>\n
Run Code Online (Sandbox Code Playgroud)\n\n

getSuggestedMinimumHeight()将用于在视图渲染生命周期中计算视图的高度。

\n\n

输出:

\n\n

\n\n

具有更多的视图、片段和不同的高度。(分别为 400dp、20dp、500dp)\n

\n