LinearLayout为什么不按比例分配收缩?

我的理解是,LinearLayout应该根据子元素的布局权重分配松弛(额外空间)和/或缩小(负额外空间)。 这似乎工作,因为我期望松懈,但不是收缩。

以下的android Activity演示。 它显示了9列(垂直LinearLayouts),拥挤程度不同。 9列中的每一列都有两个孩子:

  • 第一个孩子是由3个单选按钮组成的子列,分别标有“A”,“B”,“C”
  • 第二个孩子是由2个单选按钮组成的子列,分别标记为“0”,“1”

每个子列的权重与其中单选按钮的数量(3或2)成正比,子列中每个单选按钮的权重为1。

我选择了这些权重,以便在其子孙(单选按钮)中平均分配列的额外空间(负值或正值),以便给定列中的所有5个单选按钮的大小相同。

如下图所示(开发人员选项“显示布局边界”启用),它似乎按照预期的方式工作(右侧的红色列),但不适用于缩小(蓝色列,左侧)。 在最拥挤的(最左边的)列中,这个差异是最明显的,前三个单选按钮比两个表兄弟小得多。

[屏幕快照]

这是LinearLayout的预期行为? 或者它是LinearLayout中的一个错误? 或者在我的程序中的错误?

这里的程序列表(在Kotlin ):

// app/src/main/java/com/example/donhatch/linearlayoutweightsquestionactivity/MainActivity.kt // Simple activity to test LinearLayout's slack/shrinkage distribution behavior. package com.example.donhatch.linearlayoutweightsquestionactivity import android.graphics.Color import android.os.Bundle import android.support.v7.app.AppCompatActivity import android.widget.LinearLayout import android.widget.RadioButton import android.widget.RadioGroup class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val c = applicationContext val WC = LinearLayout.LayoutParams.WRAP_CONTENT setContentView(object: LinearLayout(c) {init{ // A row of 9 columns, from crowded to uncrowded. orientation = HORIZONTAL val columnHeights = arrayOf( 200, 300, 400, 500, // crowded WC, // just right (comes out 5*112 = 560) 600, 700, 800, 900 // uncrowded ) for (columnHeight in columnHeights) { addView(object: LinearLayout(c) {init{ // A column with two child columns (radio groups). orientation = VERTICAL setBackgroundColor(when { columnHeight==WC -> Color.WHITE columnHeight Color.argb(255,224,224,255) // crowded: blue else -> Color.argb(255,255,224,224) // uncrowded: red }) addView(object: RadioGroup(c) {init{ // First child column: three radio buttons. orientation = VERTICAL for (i in 0 until 3) { addView(object: RadioButton(c) {init{ text = 'A'.plus(i) + " " // "A", "B", ... }}, LayoutParams(WC, WC, 1.0f)) } }}, LayoutParams(WC, WC, 3.0f)) // weight=3 for 3 children addView(object: RadioGroup(c) {init{ // Second child column: two radio buttons. orientation = VERTICAL for (i in 0 until 2) { addView(object: RadioButton(c) {init{ text = '0'.plus(i) + " " // "0", "1", ... }}, LayoutParams(WC, WC, 1.0f)) } }}, LayoutParams(WC, WC, 2.0f)) // weight=2 for 2 children }}, LayoutParams(WC, columnHeight)) } // for columnHeight }}) // setContentView } // onCreate } // class MainActivity 

我在LinearLayouts和RadioButtons中追踪了onMeasure的调用,我相信我明白现在发生了什么。 基于此,我认为这是LinearLayout中的一个错误。

它是什么

关注高度为300的列,其中有3 + 2个单选按钮,下面是计算过程。

父列的onMeasure()被调用heightSpec = EXACTLY 300 。 它向两个孩子询问他们想成为多大,被限制在不超过自己已知的身高300:

  • 第一个子列的onMeasure()被调用heightSpec = AT_MOST 300 。 第一个子栏的总的首选高度是336 = 3 * 112(每个单选按钮的高度都是112个像素)超过了预算,所以它回复它想要的高度是允许的最大值,即300 。
  • 第二个子列的onMeasure()被调用heightSpec = AT_MOST 300 。 第二个孩子列的总首选高度是224 = 2 * 112(2个单选按钮),它在预算之内,所以它回复它想要高度224。

所以孩子们报告的总体大小是300 + 224 = 524,这个数字太大了224个像素。 所以它按3:2的比例分配给两个孩子的收缩224。 即第一个孩子缩小(3/5)* 224 = 134.4(四舍五入为134),第二个孩子缩小(2/5)* 224 = 89.6(四舍五入为90)。 所以这两个子列分别得到高度300-134 = 166和224-90 = 134。 现在两个孩子的列被告知他们的高度,让他们分发给他们的孩子:

  • 第一个孩子的onMeasure()被调用heightSpec="EXACTLY 166" 。 它把这个高度166分配给三个孩子,给他们高度56,55,55。
  • 第二个孩子的onMeasure()被调用heightSpec="EXACTLY 134" 。 它把这个高度公平地分配给两个孩子,给他们高度67,67。

所以列中5个单选按钮的最高高度是56,55,55,67,67; 即前三个比后两个小。 这解释了不公平的分配。

什么地方出了错

在我看来,当询问孩子他们想要的高度时,错误是指定AT_MOST 300MeasureSpec模式AT_MOST在那里既不需要也不适合,并且导致收缩不公平。 MeasureSpec模式未来将是一个更好的选择:这将导致正确的答案,最终没有任何违反约束。

它应该做些什么

让我们再次进行计算,但在询问孩子他们想要的高度时,使用“ AT_MOST 300而不是“ AT_MOST 300

父列的onMeasure()被调用heightSpec = EXACTLY 300 。 它问两个孩子他们想要多大,没有任何限制:

  • 第一个子列的onMeasure()被调用heightSpec = UNSPECIFIED 。 第一个孩子列回复,它想有高度336 = 3 * 112(3个单选按钮)。
  • 第二个子列的onMeasure()被调用heightSpec = UNSPECIFIED第二个子列回复它想要高度224 = 2 * 112(2个单选按钮)。

所以孩子总的喜欢的尺寸是336 + 224 = 560,太大了260像素。 所以它按3:2的比例将收缩260分配给两个孩子。 也就是说,第一个孩子缩小(3/5)* 260 = 156,第二个孩子缩小(2/5)* 260 = 104。所以这两个子列的高度为336-156 = 180和224-104 = 120,分别。 现在,两个孩子的列被告诉他们的高度,并告诉他们的后代:

  • 第一个孩子的onMeasure()被调用heightSpec = EXACTLY 180 。 它把这个高度公平地分配给三个孩子,给他们高度60,60,60。
  • 第二个孩子的onMeasure()被调用heightSpec = EXACTLY 120它分配高度120公平给它的两个孩子,给他们高度60,60。

所以列中5个单选按钮的最后高度是60,60,60,60,60,这是所期望的行为。