ScrollViewの中に私は動的に高さの異なる二つの断片を切り替えています。残念ながらジャンプにつながること。一つは、次のアニメーションでそれを見ることができます。
- 私はボタン「黄色ショー」に達するまで、私は、スクロールダウンしています。
- 「黄色のショー」を押すと、小さな黄色の断片と巨大な青いフラグメントを置き換えます。この場合、両方のボタンは、画面の最後までジャンプします。
私は黄色のフラグメントに切り替えたときに、両方のボタンが同じ位置に滞在したいです。その方法を行うことができますか?
で入手可能なソースコードhttps://github.com/wondering639/stack-dynamiccontentそれぞれhttps://github.com/wondering639/stack-dynamiccontent.git
Relevant code snippets:
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" />
HINT
Please note that this is of course a minimum working example to show off my issue. In my real project, I also have views below the @+id/fragment_container
. So giving a fixed size to @+id/fragment_container
is not an option for me - it would cause a large blank area when switching to the low, yellow fragment.
UPDATE: Overview of proposed solutions
I implemented the proposed solutions for testing purposes and added my personal experiences with them.
answer by Cheticamp, https://stackoverflow.com/a/60323255
-> available in https://github.com/wondering639/stack-dynamiccontent/tree/60323255
-> FrameLayout wraps content, short code
answer by Pavneet_Singh, https://stackoverflow.com/a/60310807
-> available in https://github.com/wondering639/stack-dynamiccontent/tree/60310807
-> FrameLayout gets the size of the blue fragment. So no content wrapping. When switching to the yellow fragment, there's a gap between it and the content following it (if any content follows it). No additional rendering though! ** update ** a second version was provided showing how to do it without gaps. Check the comments to the answer.
answer by Ben P., https://stackoverflow.com/a/60251036
-> available in https://github.com/wondering639/stack-dynamiccontent/tree/60251036
-> FrameLayout wraps content. More code than the solution by Cheticamp. Touching the "show yellow" button twice leads to a "bug" (buttons jump down to the bottom, actually my original issue). One could argue about just disabling the "show yellow" button after switching to it, so I wouldn't consider this a real issue.
Update: To keep the other views right below the framelayout
and to handle the scenario automatically, we need to use onMeasure
to implement the auto-handling so do the following steps
• Create a custom ConstraintLayout
as:
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)
}
}
and use it in your layout in place of 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>
This will keep track of height and apply it during every fragment change.
Output:
Note: As mentioned in comments before, setting minHeight will result in additional rendering pass and it cannot be avoided in the current version of ConstraintLayout
.
Old approach with custom FrameLayout
This is an interesting requirement and my approach is to solve it by creating a custom view.
Idea:
My idea for the solution is to adjust the height of the container by keeping the track of the largest child or total height of children in the container.
Attempts:
My first few attempts were based on modifying the existing behaviour of NestedScrollView
by extending it but it doesn't provide access to all the necessary data or methods. Customisation resulted in poor support for all scenarios and edge cases.
その後、私は、カスタム作成することにより、ソリューションを達成Framelayout
異なるアプローチでは。
ソリューションの実装
高さ測定段階のカスタム動作を実装している間、私は深く掘ってと高さを操作するgetSuggestedMinimumHeight
ことは内部レンダリング時の高さを管理しますので、それは追加のまたは明示的なレンダリングを引き起こすことはありませんとして最も最適化されたソリューションを実装するために子どもたちの高さを追跡しながら、サイクルは、そのカスタム作成FrameLayout
ソリューションを実装し、上書きするクラスを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
)
}
}
今置き換えるFrameLayout
とMaxChildHeightFrameLayout
で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="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()
ビューのレンダリングのライフサイクルの間、ビューの高さを計算するのに使用されます。
出力:
複数のビュー、フラグメントと異なる高さを持ちます。(それぞれ400dp、20dp、500dp)