У меня есть два вертикальных элемента ScrollView в вертикальном LinearLayout. Если я установлю для обоих Layout_weight значение 1, они оба будут использовать половину полной высоты. Однако это также имеет место, если одному из них требуется, например, только четверть высоты из-за его содержания. В таком случае я хотел бы сжать первый ScrollView и использовать лишнее пространство для второго ScrollView (и наоборот).
Есть идеи, как я могу этого добиться? Окружающий макет не обязательно должен быть LinearLayout. (Я видел это, но оно не отвечает на мой вопрос)
Хорошо, поскольку, похоже, нет возможности напрямую использовать LinearLayout, я создал собственный макет. Он должен иметь ровно два дочерних элемента ScrollView, но его можно легко адаптировать, если внутри нужны дополнительные представления.
/**
* A vertical LinearLayout containing 2 ScrollViews.
* The height of each ScrollView is growing with its content, as long as both
* ScrollViews fit into the parent.
* If one ScrollView needs to scroll its content, and the other is still smaller than half
* of the parent, the smaller one takes only as much space as needed.
* Only if both views need more than half of the parent space,
* each of them receives exactly half of it.
*/
class DoubleScrollView : LinearLayout {
private lateinit var scrollView1: ScrollView
private lateinit var scrollView2: ScrollView
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyle: Int) :
super(context, attrs, defStyle)
init {
orientation = VERTICAL
}
override fun onFinishInflate() {
super.onFinishInflate()
//In this simple example, we assume to have exactly 2 ScrollViews as children
scrollView1 = getChildAt(0) as ScrollView
scrollView2 = getChildAt(1) as ScrollView
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
//Allow the scroll views to set the full content height at first
scrollView1.setLayout(ViewGroup.LayoutParams.WRAP_CONTENT, 0f)
scrollView2.setLayout(ViewGroup.LayoutParams.WRAP_CONTENT, 0f)
val widthSpec = MeasureSpec.makeMeasureSpec(width, EXACTLY)
val heightSpec = MeasureSpec.makeMeasureSpec(height, AT_MOST)
scrollView1.measure(widthSpec, heightSpec)
scrollView2.measure(widthSpec, heightSpec)
// Based on the height the full content would need, adapt the LayoutParams
// of the ScrollViews.
val height = View.MeasureSpec.getSize(heightMeasureSpec)
val height1 = scrollView1.measuredHeight
val height2 = scrollView2.measuredHeight
if (height1 + height2 >= height) {
if (height1 < height / 2) {
//First wrap content, second fills the rest
scrollView2.setLayout(0, 1f)
} else if (height2 < height / 2) {
//Second wrap content, first fills the rest
scrollView1.setLayout(0, 1f)
} else {
//First and second both with same weight
scrollView1.setLayout(0, 1f)
scrollView2.setLayout(0, 1f)
}
} //else: both views can just wrap content
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
}
private fun View.setLayout(height: Int, weight: Float) {
this.layoutParams.height = height
(this.layoutParams as LinearLayout.LayoutParams).weight = weight
}
}
Для тестирования и демонстрации я создал следующее минимальное приложение.
Активность:
class MainActivity : AppCompatActivity() {
private val mainViewModel: MainViewModel by viewModels()
private lateinit var vb: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
vb = ActivityMainBinding.inflate(layoutInflater)
setContentView(vb.root)
mainViewModel.text1.observe(this) { text -> updateText(vb.text1, text) }
mainViewModel.text2.observe(this) { text -> updateText(vb.text2, text) }
listOf(vb.plus1, vb.minus1, vb.plus2, vb.minus2).forEachIndexed() { i, button ->
button.setOnClickListener { mainViewModel.changeText(i <= 1, i % 2 == 0) }
}
}
private fun updateText(textView: TextView, text: String?) {
textView.text = text
}
}
Модель просмотра:
class MainViewModel : ViewModel() {
val text1: LiveData<String> = MutableLiveData("")
val text2: LiveData<String> = MutableLiveData("")
fun changeText(first: Boolean, plus: Boolean) {
val liveData = if (first) text1 else text2
val text = liveData.value!!
val size = text.count { it == '#' } + if (plus) 1 else -1
val lines = Array(size.coerceAtLeast(0)) { "This is line #$it" }
(liveData as MutableLiveData).value = lines.joinToString("\n")
}
}
Макет:
<?xml version = "1.0" encoding = "utf-8"?>
<yourPackage.DoubleScrollView xmlns:android = "http://schemas.android.com/apk/res/android"
android:id = "@+id/container"
android:layout_width = "match_parent"
android:layout_height = "match_parent"
android:orientation = "vertical">
<ScrollView
android:id = "@+id/scrollView1"
android:layout_width = "match_parent"
android:layout_height = "wrap_content">
<LinearLayout
android:layout_width = "match_parent"
android:layout_height = "wrap_content"
android:orientation = "vertical">
<Button
android:id = "@+id/plus1"
android:layout_width = "wrap_content"
android:layout_height = "wrap_content"
android:text = "+"></Button>
<Button
android:id = "@+id/minus1"
android:layout_width = "wrap_content"
android:layout_height = "wrap_content"
android:text = "-"></Button>
<TextView
android:id = "@+id/text1"
android:layout_width = "wrap_content"
android:layout_height = "wrap_content" />
</LinearLayout>
</ScrollView>
<ScrollView
android:id = "@+id/scrollView2"
android:layout_width = "match_parent"
android:layout_height = "wrap_content">
<LinearLayout
android:layout_width = "match_parent"
android:layout_height = "wrap_content"
android:orientation = "vertical">
<Button
android:id = "@+id/plus2"
android:layout_width = "wrap_content"
android:layout_height = "wrap_content"
android:text = "+"></Button>
<Button
android:id = "@+id/minus2"
android:layout_width = "wrap_content"
android:layout_height = "wrap_content"
android:text = "-"></Button>
<TextView
android:id = "@+id/text2"
android:layout_width = "wrap_content"
android:layout_height = "wrap_content" />
</LinearLayout>
</ScrollView>
</yourPackage.DoubleScrollView>
Нажимая кнопки + и -, вы можете заполнить представления прокрутки и посмотреть, как на это отреагирует макет.