0

I want to have a Listview in which the rows change their layout based on a variable that is inside the object I want to display.

So I wrote a custom ArrayAdapter and overrode the getView() method like this:

public class ChatAdapter extends ArrayAdapter<Nachricht> {

    public ChatAdapter(Context context, ArrayList<Nachricht> users) {
        super(context, R.layout.message_layout_standard, users);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        // SimpleDateFormat to change the view of the date
        SimpleDateFormat dformat = new SimpleDateFormat("dd.MM.yyyy   HH:mm");

        // Get the data item for this position
        Nachricht nachricht = getItem(position);

        // Check if an existing view is being reused, otherwise inflate the view
        if (convertView == null) {
            if(nachricht.SENDER_ID.equals(DataService.options.ownUser.USER_ID)){
                convertView = LayoutInflater.from(getContext()).inflate(R.layout.message_layout_own, null);
            }else{
                convertView = LayoutInflater.from(getContext()).inflate(R.layout.message_layout_other, null);
            }
        }

        // Lookup view for data population
        TextView chatdate = (TextView) convertView.findViewById(R.id.date_chat);
        TextView chatsender = (TextView) convertView.findViewById(R.id.sender_chat);
        TextView chatmessage = (TextView) convertView.findViewById(R.id.message_chat);

        if(!nachricht.SENDER_ID.equals(DataService.options.ownUser.USER_ID)){

            // Set the sender of the message
            chatsender.setText("" + DataService.freunde.get(DataService.freunde.indexOf(new Freund(nachricht.SENDER_ID, ""))).name);
            // Set the Message
            chatmessage.setText(nachricht.TEXT);

            //Set the Date/Clock of the Message
            chatdate.setText(dformat.format(nachricht.DATE));

        }else{
            // Set the sender of the message
            chatsender.setText("Du");
            // Set the Message
            chatmessage.setText(nachricht.TEXT);
            chatdate.setText(dformat.format(nachricht.DATE));

        }

        // Return the completed view to render on screen
        return convertView;

    }
}

The xml for my views looks like this. Only the margins and colors are different:

<?xml version="1.0" encoding="utf-8"?>

<!--
    Dies ist das Layout zu der Java-Class 'ChatAdapter'
    Es erstellt die Chat-Nachrichten in der ListView
    der einzelnen Chats.
-->


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:layout_width="wrap_content">


        <TextView
            android:id="@+id/sender_chat"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="left"
            android:height="20sp"
            android:text="sender_chat"
            android:textColor="#000000"
            android:textSize="11sp"
            android:textStyle="bold" />

        <ImageView
            android:id="@+id/background_chat"
            android:scaleType="fitXY"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:layout_marginLeft="5dp"
            android:layout_marginTop="5dp"
            android:layout_marginRight="70dp"
            android:layout_below="@+id/sender_chat"
            android:layout_above="@+id/date_chat"
            android:src="@drawable/message_own" />

        <TextView
            android:textColor="#000000"
            android:id="@+id/message_chat"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:gravity="left"
            android:layout_marginLeft="15dp"
            android:layout_marginTop="7dp"
            android:layout_marginBottom="7dp"
            android:layout_marginRight="70dp"
            android:layout_below="@+id/sender_chat"

            android:text="message_chat"
            android:textSize="15sp" />



        <TextView
            android:textColor="#000000"
            android:layout_below="@+id/message_chat"
            android:id="@+id/date_chat"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:height="20dp"
            android:text="date_chat"
            android:textSize="11sp"/>

</RelativeLayout>

And the fragment layout where the list is placed:

    <?xml version="1.0" encoding="utf-8"?>

<RelativeLayout
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    xmlns:android="http://schemas.android.com/apk/res/android">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Senden"
        android:id="@+id/button"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="true" />

    <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/nachrichtChatText"
        android:layout_alignTop="@+id/button"
        android:layout_toLeftOf="@+id/button"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true" />

    <ListView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/talkView"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="true"
        android:layout_above="@+id/button"
        android:layout_below="@+id/button2"
        android:cacheColorHint="@android:color/transparent"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceMedium"
        android:text="Chatmitglieder"
        android:id="@+id/chatTeilnehmer"
        android:layout_alignParentTop="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_toLeftOf="@+id/button2"
        android:layout_above="@+id/talkView" />

    <Button
        style="?android:attr/buttonStyleSmall"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text=" + "
        android:id="@+id/buttonChangeChat"
        android:layout_alignParentTop="true"
        android:layout_alignRight="@+id/button"
        android:layout_alignEnd="@+id/button" />
</RelativeLayout>

It works fine until I have two views with different layouts in my list. Then the list seems to switch back and forth between the layouts every few seconds.

Picture for the curious: https://i.stack.imgur.com/xrJVX.jpg

I tried to only use one layout and change the properties of it but when I scrolled too fast I had some strange display errors. Problably due to the recycling of the listview.

Next thing I'd try is using different row classes for each item but maybe any of you has an idea how to get this to work?

Stan
  • 11
  • 5

2 Answers2

0

You have to override getViewTypeCount() and getItemViewType() in a BaseAdapter to specify that you have two different types of views.

Here's an example.

Community
  • 1
  • 1
Pycpik
  • 2,316
  • 1
  • 16
  • 21
  • If I use these methods I have to call getItemViewType() in my getView() method and then call some if/else statements, right? Why can't I use my if/else where I choose the layout? – Stan Apr 17 '14 at 18:26
  • you could do it, but the problem is that you will often get convertViews of the wrong type that you need to transform into the right one. As both come from different inflated layouts, that's gonna be tricky. If you inflate always the same layout and then manually change the background and padding, it can easily be done your way. Regarding the "strange display errors", maybe it's this: ListView has problems with rows of variable height when scrolling, it's related to getting a big convertView of a former big message that is replaced with a small message or vice-versa – rupps Apr 17 '14 at 19:00
  • @rupps Okay, now I only have one layout and change that with RelativeLayout.LayoutParams. The problem is with the switching is still there. My guess is that the adapter doesn't get the right item for the asked position from time to time. Is that possible? I noticed that one time for a short period of time the two views where the same when they should have been different. – Stan Apr 17 '14 at 20:13
  • @stan, well, it's **you** who fills the required item for the adapter, the adapter asks for it in the `position` parameter of `getView`, and then you fill an old view (`convertView`) with new data. BTW-> If item types involve `LayoutParam` changes (`leftMargin`...) you might need to call `requestLayout` so they get refreshed (I wonder if this will be slow anyways). You can try to use `setPadding` vs. changing LayoutParams to see if it helps! (`setPadding` is independent of `LayoutParams`) Oh, also try changing `gravity` left<>right as another approach – rupps Apr 17 '14 at 20:30
  • @stan you could post a graphic capture so I can understand better what the problem is (wrong data & right colors? right data & wrong colors?, dynamic getViews are always tricky :) – rupps Apr 17 '14 at 20:39
  • @rupps you are brilliant! When using setPadding instead of changing the layout it works like a charm. tbh, I don't really understand why a layout change causes such a different behaviour but hey, it works. If you're curious, before it looked like this, always switching back and forth: http://imgur.com/5KbXKu2 Thank you very much, would you like to add your comment as an answer, so I can mark it as answered? – Stan Apr 18 '14 at 08:12
  • layout properties are used during the "measure" and "layout" phase (there are 3 phases, the last one is "render"). When you dynamically change layout properties, you have to call "requestLayout" so the changes take effect (you go back to phase 1). Ideally, a view only goes through passes 1 & 2 one time, when you create it, then its just rendered (phase 3). Padding is not a layout property, is a specific view feature that you can change anytime. Your previous approach would probably work if you call requestLayout, but the layout/measure phases are pretty slow, so I think it would be a bad idea! – rupps Apr 18 '14 at 09:24
0

For your different types I'd choose to change properties not related to the LayoutParams, for example use setPadding or a view to the left whose visibility you toggle between gone/invisible or gone/visible. I like the setPadding approach, seems to me it will be the quickest and cleanest thing.

I bet the mess is somehow related to changing Layout Parameters for every getView(): It should work, but you would need to call requestLayout inside getView everytime and it can adversely affect performance. I'm no expert at this, but it's even probable that the time you win for using a convertView is lost when you force the measure and layout phase again and again for every view (something that in the case of a quick fling is happening tens of times continuously)

From http://developer.android.com/training/custom-views/optimizing-view.html: Another very expensive operation is traversing layouts. Any time a view calls requestLayout(), the Android UI system needs to traverse the entire view hierarchy to find out how big each view needs to be. If it finds conflicting measurements, it may need to traverse the hierarchy multiple times. UI designers sometimes create deep hierarchies of nested ViewGroup objects in order to get the UI to behave properly. These deep view hierarchies cause performance problems. Make your view hierarchies as shallow as possible.

Besides the setPadding solution, if you are adding more types in the future (ie. a file attach/voice/images...) it's not a bad idea to implement the ViewTypeCount, etc... as @Picpik suggests. This way, in convertView you will get a view to reuse of the same type as the new view and you can safely use the different layouts approach.

rupps
  • 9,712
  • 4
  • 55
  • 95