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.sstatic.net/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?
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.