如何解决SDK 17和18之间列表项布局行为变化的问题?

7
我创建了一个简单的应用程序来说明在SDK 17和SDK 18之间将LinearLayout包装在RelativeLayout中时的行为变化。首先,是屏幕截图:
当targetSdkVersion为“17”时:

enter image description here

当targetSdkVersion为“18”时:

enter image description here

这个活动的布局是:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#00ffff"
    android:orientation="vertical" >

    <include layout="@layout/test_list_item"/>

    <ListView
        android:id="@+id/listview"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:overScrollMode="never"
        android:scrollingCache="false" >
    </ListView>

</LinearLayout>

text_list_item.xml 是:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="#ff0000" >

    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:background="#ffff00"
        android:padding="10dp"
        android:text="foobar"
        android:textColor="#000000" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignBottom="@id/text"
        android:layout_alignTop="@id/text"
        android:background="#00ff00"
        android:orientation="horizontal"
        android:weightSum="1.0" >

        <View
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="0.5"
            android:background="#0000ff" />
    </LinearLayout>

</RelativeLayout>

该活动的onCreate()方法如下:
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_test);
    findViewById(R.id.text).bringToFront();
    final ListView mListView = (ListView) findViewById(R.id.listview);
    mListView.setAdapter(new TestAdapter());
}

而 TestAdapter 看起来像这样:
public class TestAdapter extends BaseAdapter {

    private static final int COUNT = 3;

    @Override
    public int getCount() {
        return COUNT;
    }

    @Override
    public Object getItem(int position) {
        return null;
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public boolean areAllItemsEnabled() {
        return true;
    }

    @Override
    public boolean isEnabled(int position) {
        return false;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        final View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.test_list_item, null);
        view.findViewById(R.id.text).bringToFront();
        return view;
    }

当target = 17时,无论LinearLayout的子视图是在ListView内还是在ListView外部,都会正确地调整大小为可用宽度的50%。当target = 18时,当将其用作列表项的布局时,它不会被调整大小。在这种情况下,它的宽度保持为“0dp”,从未被调整大小。
理想情况下,我想要针对SDK 19进行目标设置。但是,为了做到这一点,我需要以某种方式适应此行为更改。您有什么想法吗?
顺便说一句,布局之所以如此复杂和“奇怪”,是因为有原因;我意识到有更简单的方法来实现这种特定的视觉效果。
编辑:为了提供关于我使用这个病态布局的更多信息:它用于“仪表盘”。 LinearLayout中的View使用ScaleAnimation进行拉伸或缩小,以占用总可用宽度的所需百分比。使它棘手的是,我需要在仪表盘上覆盖文本,并且我希望仪表盘的高度由文本确定。
因此,外部RelativeLayout应该“与其父级一样宽”,但仅“与其包含的TextView一样高”。内部RelativeLayout应该与其父级完全相同。LinearLayout中的View应该与包含它的LinearLayout一样高,但是只有一半宽度。
当针对SDK 17和/或在SDK 18+中不将布局用作ListView项目的内容时,这正是我得到的行为。
编辑:
发布在开发者组上:

https://groups.google.com/forum/#!msg/android-developers/KuoY5Tklyms/TbNq6ndD_PwJ


似乎ListView中的视图没有调用onLayout()?我想知道是否有任何方法可以验证?也许在填充ListView后,您可以调用它,只是为了看看它是否“修复”了问题。 - Dale Wilson
3个回答

4

更改

final View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.test_list_item, null);

to:

final View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.test_list_item, parent, false);

我认为你会有更好的运气。


更新

好吧,这是我能告诉你的。

如果你启动层次结构查看器,你会发现提供蓝色背景的View在“内部ListView”场景中高度为0。宽度是正确的,这表明问题与权重无关,与你问题的其他答案不同。

如果你查看RelativeLayout的源代码,你会发现他们增加了一些智能功能,用于在android:targetSdkVersion为17和18+之间的分界线上改变行为。我猜你可能会被mAllowBrokenMeasureSpecs绊倒,在某些与ListView容器相关的情况下,但这只是一个猜测。当容器是简单容器时,我已经很难理解所有的布局/测量内容,并且当我有一个完整的夜晚睡眠时,我写的容器也很难理解 -- 在有限的睡眠时间内理解RelativeLayout超出了我的能力范围。我已经试过各种方法来尝试修复你的示例,但没有结果。

关于你在我解决问题的时候发布的编辑:

  • 原则上,你不需要将基本布局设置为RelativeLayout。一个FrameLayout也应该可以工作,因为你所需要的只是能够在Z轴上堆叠东西并能够居中标签。尽管如此,当我走这条路时,得到了更奇怪的结果,尽管这些结果似乎与targetSdkVersion无关,所以我可能只是在某个地方搞砸了

  • 即使你坚持使用RelativeLayout,你也不需要LinearLayout。在运行时,你知道你的宽度,所以你应该动画化仪表的宽度,而不是它的权重。因此,只需将View放入RelativeLayout(或FrameLayout)中即可。

但我完全同意这是奇怪的行为。


如果您在布局资源中使用RelativeLayout作为根元素,则强烈建议使用三参数的inflate()方法。这就是我认为它可能会解决您在此处遇到的问题的原因。如果您创建一个可以重现您的问题的完整项目(ZIP文件,GitHub存储库等),我很乐意查看它。 - CommonsWare
我明天会将其打包并在此发布一个压缩文件。谢谢查看! - jph
决定不能再等了。这是链接:https://drive.google.com/file/d/0B6DvDY2BvxUTZHUxTHkzNUZvVDg - jph
一个线索可能在RelativeLayout文档中,其中写道:注意:在平台版本17及以下,RelativeLayout受到测量错误的影响,这可能会导致子视图使用不正确的MeasureSpec值进行测量。...当将RelativeLayout容器放置在滚动容器(例如ScrollView或HorizontalScrollView)中时,就会触发此问题。如果将未配备使用MeasureSpec模式UNSPECIFIED进行正确测量的自定义视图放置在RelativeLayout中,则RelativeLayout仍将静默地工作,因为RelativeLayout将传递一个非常大的AT_MOST MeasureSpec。 - frozenkoi
@frozenkoi:是的,这就是mAllowBrokenMeasureSpecs的作用。然而,问题并不涉及自定义视图。即使您将“自定义视图”扩展到包括用于表盘的普通View,我也可以在没有它的情况下获得良好的行为。我删除了所有看起来有点奇怪的东西,但我仍然遇到布局问题。 - CommonsWare
显示剩余8条评论

1
看起来由于只是一个视图而不需要渲染任何内容,为了一些优化的目的,测量方法被跳过了。将View更改为TextView并设置一些文本,您会发现它会如预期般出现。我确实理解LinearLayout以不同的方式呈现了View(不确定这是否正确或ListView是否正确)。
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#ff0000" >

<TextView
    android:id="@+id/text"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerInParent="true"
    android:background="#ffff00"
    android:padding="10dp"
    android:text="foobar"
    android:textColor="#000000" />

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_alignBottom="@id/text"
    android:layout_alignTop="@id/text"
    android:background="#00ff00"
    android:orientation="horizontal"
    android:weightSum="1.0" >

    <TextView
        android:id="@+id/testView"
        android:layout_width="0dp"
        android:layout_height="fill_parent"
        android:layout_weight="0.5"
        android:background="#0000ff"
        android:singleLine="false" />
</LinearLayout>

 @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        final View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.text_list_item, null);

        view.findViewById(R.id.text).bringToFront();
        TextView testView = (TextView)view.findViewById(R.id.testView);
        testView.setText("some test text for viewNo:"+String.valueOf(position));

        return view;
    }

enter image description here


看起来你甚至不需要这段文本。只需在完整项目中(请参见我的答案中的注释)将“View”替换为“TextView”,即可在内部和外部情况下获得相同的结果。很好的发现!希望@user190758能够重现这些结果。 - CommonsWare
是的,但它被渲染成单行,所以我添加了两行文本。 - con_9
我想我实际上曾经尝试过这个,但它暴露了一些其他问题(在旧设备上),与我的动画如何绘制有关。我可能会再次尝试它。我还将尝试“空间”视图,因为这似乎是应该使用的情况。或者只使用ImageView而不设置图像。 - jph
回想起来,我不应该接受这个答案。当内部视图是TextView而不是View时,SDK 17和SDK 18之间的行为仍然有所不同。运行我在CommonsWare的回复下发布的示例项目。它使用了一个内部Textview,而在SDK 18中布局仍然“看起来不对”(与SDK 17相比)。 - jph
请使用textView.requestLayout()或者设置一些文本内容。这样可以在渲染时尽可能的优化调用。我猜您只是想知道问题出在哪里,而不是寻找其他显示已发布内容的替代方法(因为还有其他的方式可以做到)。 - con_9
会尝试。我可能会尝试将某些东西的初始宽度设置为非“0dp”,并查看其效果。同时也会尝试其他视图类型。 - jph

0

我认为layout_weight(尝试使用整数数字 >= 0)的用法已经改变。结合android:layout_width="0dp"(尝试使用match_parent),不稳定性甚至更大。

另一个weight参数的问题是,一些设备处理它的方式与其他设备不同。

总的来说,我建议如果可能的话只使用LinearLayouts而不使用weight。 您可以使用gravity(例如gravity="center")、layout_gravitypaddingmargin来完成相同的工作,但这是安全的。

编辑 - 示例1:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="#ff0000" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#00ff00"
        android:orientation="horizontal">

        <View
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:background="#0000ff" />

        <TextView
            android:id="@+id/text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:background="#ffff00"
            android:padding="10dp"
            android:text="foobar"
            android:textColor="#000000" />

        <View
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:background="#00ff00" />
    </LinearLayout>

</LinearLayout>

例子2:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="#ff0000" >

    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:background="#ffff00"
        android:padding="10dp"
        android:text="foobar"
        android:textColor="#000000" />

</LinearLayout>

关于权重参数的另一个问题是,有些设备对其处理方式与其他设备不同。请问,您能提供什么证据来支持这一说法呢?我拥有大约50个安卓设备,因此非常有兴趣看到您所声称的行为的演示。 - CommonsWare
同样的。我从未见过它在每个设备上表现不同。然而,正如这个问题所展示的那样,有时候在SDK版本之间会有不同的行为。 - jph

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接