how to work around change in list item layout behavior change between sdk 17 and 18?
M

3

7

I've created a simple app to illustrate a change in how LinearLayout behaves when wrapped in a RelativeLayout between SDK 17 and SDK 18. First, screenshots:

When targetSdkVersion is "17":

enter image description here

When targetSdkVersion is "18":

enter image description here

The layout for this activity is:

<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 is:

<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>

The activity's onCreate() looks like this:

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());
}

And TestAdapter looks like this:

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;
    }

When target = 17 the LinearLayout's child view is properly resized to 50% of available width both when it's inside the ListView and when it's outside the ListView. When target = 18 it's not resized when used as the layout for a list item. In this case its width stays at "0dp" and is never resized.

Ideally I would like to target SDK 19. In order to do so, though, I to accommodate this change in behavior somehow. Any thoughts on how that could be accomplished?

btw, There are reasons why the layout is as complicated and "weird" as it is; I realize there are much more straightforward ways of achieving this particular visual look.

EDIT:

To give a little more info on why I'm using this pathological layout: its used for "gauges". The View inside the LinearLayout is stretched or shrunk using a ScaleAnimation so that it occupies the desired percentage of the total available width. What makes it tricky is that I need to overlay text on top of the gauge, and I'd like the gauge's height to be determined by the text.

So the exterior RelativeLayout should be "as wide as its parent" but only "as tall as the TextView it contains". The LinearLayout inside that RelativeLayout should be exactly as wide and tall as its parent. The View inside the LinearLayout should be as tall as the LinearLayout that contains it, but only half as wide.

This is exactly the behavior I get when targeting SDK 17 and/or in SDK 18+ when the layout isn't used as the contents of a ListView item.

EDIT

Posted on the developer's group:

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

Misbehavior answered 29/1, 2014 at 20:52 Comment(1)
It appears that onLayout() is not being called for the views inside the ListView? I wonder if there is any way to verify that? Maybe you could call it after populating the ListView, just to see if it "fixes" things.Charcot
C
1

Looks like since its just a view and nothing has to be rendered, for some optimization sake measure methods were skipped. Change View to TextView and set some text and u will see it does appears as expected. I do understand that LinearLayout rendered the View differently(not sure if thats correct or ListView is correct)

<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

Cerebritis answered 7/2, 2014 at 21:5 Comment(6)
It looks like you don't even need the text. Taking the full project (see comments in my answer) and simply replacing View with TextView gives identical results between the outside and inside scenarios. Nice find! Hopefully, @user190758 can reproduce these results.Mediator
yeah, but it was rendered in single line, so I added 2 line text.Cerebritis
I think I actually tried that at one point, but it exposed some other problem (on older devices) having to do with how my animations were drawn. I may try it again. Will also try the "Space" view since this seems like the kind of situation where its supposed to be used. Or maybe ImageView and just don't set an image.Misbehavior
In retrospect I shouldn't have accepted this answer. When the interior view is a TextView instead of a View the behavior still differs between SDK 17 and SDK 18. Run the example project I posted under CommonsWare's response. It uses an interior Textview and the layout is still "wrong-looking" in SDK 18 (compared to SDK 17).Misbehavior
use either textView.requestLayout() or set some text. Its trying to optimise the call while rendering. I guess you just wanted to know whats wrong, rather than finding alternate ways to display what u have posted(as there are other ways to do it).Cerebritis
Will try. I may try setting an initial width of something other than "0dp" and see what that does. Along with trying other view types.Misbehavior
M
4

Change

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);

and I think you will have better luck.


UPDATE

Well, here's what I can tell you.

If you fire up Hierarchy View, you will see that your View supplying the blue background has a height of 0 in the "inside ListView" scenario. The width is correct, which would suggest that the problem is not tied to the weight, per the other answer to your question.

If you look at the source code to RelativeLayout, they did add some smarts for changing behavior at the dividing line between an android:targetSdkVersion of 17 versus 18+. My guess is that you're tripping over mAllowBrokenMeasureSpecs, in some scenario that is tied to the ListView container, but that's just a guess. I have a tough enough time grokking all the layout/measure stuff for a container when it's a simple container, that I wrote, and when I have had a full night of sleep -- making sense of RelativeLayout on limited sleep is beyond my abilities. And I've poked and prodded at your sample, trying various things to see if they would fix things, with no results.

With regards to your edit that you posted while I was poking at this:

  • In principle, you do not need the base layout to be a RelativeLayout. A FrameLayout should work as well, as all you need it to do is be able to stack things on the Z axis and be able to center your label. That being said, I got even stranger results when going down that path, though those results did not seem tied to targetSdkVersion, so I may have just been screwing up somewhere

  • Even if you stick with the RelativeLayout, you should not need the LinearLayout. At runtime, you know your width, and so you should be animating the width of the gauge, not its weight. Hence, just having the View be in the RelativeLayout (or FrameLayout) should be sufficient.

But I'll readily agree that this is bizarre behavior.

Mediator answered 29/1, 2014 at 21:58 Comment(13)
Doesn't seem to make a difference. Though, I should probably be doing it the way you recommend anyway. Also, I found another person who ran into what (I think) is the same issue: #17993915Misbehavior
@user190758: If you use RelativeLayout as a root element in a layout resource, the three-parameter inflate() method is strongly recommended. That's why I thought it might address the problem you have here. If you create a full project (ZIP file, GitHub repo, etc.) that reproduces your problem, I'd be interested in taking a look at it.Mediator
I'll bundle it up tomorrow and post a zip file here. Thanks for taking a look!Misbehavior
Decided I couldn't wait. Here you go: drive.google.com/file/d/0B6DvDY2BvxUTZHUxTHkzNUZvVDgMisbehavior
A clue might be in the RelativeLayout documentation, which reads: Note: In platform version 17 and lower, RelativeLayout was affected by a measurement bug that could cause child views to be measured with incorrect MeasureSpec values. ... This was triggered when a RelativeLayout container was placed in a scrolling container, such as a ScrollView or HorizontalScrollView. If a custom view not equipped to properly measure with the MeasureSpec mode UNSPECIFIED was placed in a RelativeLayout, this would silently work anyway as RelativeLayout would pass a very large AT_MOST MeasureSpec instead.Trusting
Link didn't fit: developer.android.com/reference/android/widget/…Trusting
@frozenkoi: Yeah, that's what mAllowBrokenMeasureSpecs is for. However, the problem does not involve a custom view. Even if you extend "custom view" to include the plain View being used for the gauge, I can get boffo behavior without that. I removed everything that seemed the least bit squirrelly, and I still had layout problems.Mediator
I think I saw the same behavior when I used FrameLayout instead of RelativeLayout.Misbehavior
So I was looking at the API diffs between 17 and 18 here (developer.android.com/sdk/api_diff/18/changes.html) and noticed the isInLayout() method was added to View. It's described as returning "whether the view hierarchy is currently undergoing a layout pass. This information is useful to avoid situations such as calling requestLayout() during a layout pass." I wonder if there's a bug in LinearLayout that causes it to erroneously return true from this method and thereby prevent itself from being properly laid out?Misbehavior
@user190758: Unlikely. LinearLayout does not implement isInLayout(), instead inheriting the default implementation from View.Mediator
I grabbed the platform sources and diffed AbsListView between 17 and 18. There were some changes having to do with "transient state views". Perhaps that's the culprit. At some point in AbsListView it iterates over a sparse array of Views and calls forceLayout() on each one. If that sparse array happened to be (erroneously) empty for some reason, that might explain why the list items aren't laying themselves out properly. Just a stab in the dark.Misbehavior
@Mediator bump, found somethingCerebritis
Here's another S.O. question that might be related to this issue: #18751618Misbehavior
C
1

Looks like since its just a view and nothing has to be rendered, for some optimization sake measure methods were skipped. Change View to TextView and set some text and u will see it does appears as expected. I do understand that LinearLayout rendered the View differently(not sure if thats correct or ListView is correct)

<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

Cerebritis answered 7/2, 2014 at 21:5 Comment(6)
It looks like you don't even need the text. Taking the full project (see comments in my answer) and simply replacing View with TextView gives identical results between the outside and inside scenarios. Nice find! Hopefully, @user190758 can reproduce these results.Mediator
yeah, but it was rendered in single line, so I added 2 line text.Cerebritis
I think I actually tried that at one point, but it exposed some other problem (on older devices) having to do with how my animations were drawn. I may try it again. Will also try the "Space" view since this seems like the kind of situation where its supposed to be used. Or maybe ImageView and just don't set an image.Misbehavior
In retrospect I shouldn't have accepted this answer. When the interior view is a TextView instead of a View the behavior still differs between SDK 17 and SDK 18. Run the example project I posted under CommonsWare's response. It uses an interior Textview and the layout is still "wrong-looking" in SDK 18 (compared to SDK 17).Misbehavior
use either textView.requestLayout() or set some text. Its trying to optimise the call while rendering. I guess you just wanted to know whats wrong, rather than finding alternate ways to display what u have posted(as there are other ways to do it).Cerebritis
Will try. I may try setting an initial width of something other than "0dp" and see what that does. Along with trying other view types.Misbehavior
R
0

i think the way layout_weight (try int numbers >= 0) is used changed. in combination with android:layout_width="0dp" (try match_parent) there is even more unstability.

a other problem with the weight parameter is, that some devices handle it different then others.

in gerneral i recommend to use only LinearLayouts and no weight if possible. you can do the same stuff with gravity (e.g. gravity="center"), layout_gravity, padding and margin but it is save to work.

edit - example1:

<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>

example2:

<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>
Rumney answered 30/1, 2014 at 1:20 Comment(2)
"a other problem with the weight parameter is, that some devices handle it different then others" -- and your proof of this is, what, exactly? I have about 50 Android devices, so I would be very interested in seeing a demonstration of your claimed behavior.Mediator
Ditto. I've never seen it behave differently on a per-device level. However, as this question shows, things sometimes behave differently across SDK versions.Misbehavior

© 2022 - 2024 — McMap. All rights reserved.