Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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

like image 228
jph Avatar asked Jan 29 '14 20:01

jph


2 Answers

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.

like image 189
CommonsWare Avatar answered Sep 29 '22 00:09

CommonsWare


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

like image 30
con_9 Avatar answered Sep 28 '22 23:09

con_9