I am creating "bubble" style messages representation in my chat application's listview adapter. This is the effect that I want to achieve:
As you can see, second user message should show up in different layout (without picture) if user wrote two or more messages in a row. How I can achieve this? Because seems it's quite complicated. Should I check last message type in adapter (doesn't look so reliable solution)? Or add and later check additional field in my message object? Any example or help would be appreciated.
I assume you have a Message class like this:
class Message {
public String text;
public String avatar;
}
So, in getView method, you can easy check previous item:
ArrayList<Message> mMessages;
...
public View getView (int position, View convertView, ViewGroup parent){
if( convertView == null ){
if (position > 0 && mMessages.get(position-1).avatar.equal(mMessages.get(position).avatar)){
// set layout for second message style
convertView = inflater.inflate(R.layout.list_without_avatar, parent, false);
} else {
// set layout for the first message style
convertView = inflater.inflate(R.layout.list_with_avatar, parent, false);
}
}
...
return convertView;
}
Hope it help!
Related
I want to add a feature on my project where if the user clicks one of the items on the listView then a checkbox would appear allowing the user to delete 1 or many items at once. Similar to Samsung Notes when you want to delete notes and or folders, etc. However, this concept is completely foreign to me and currently, I don't know where to begin to start this or what topic/resource/sample code I should look for. Also, I have a custom Array Adapter class that I used to order to work with my ListView but it came to my knowledge that you only need 1 array adapter class to make this work which made me confused since I don't know where to begin to manipulate it even further. Any help would be amazing!
Here is my Array Adapter that I have at the moment
//want to create our own custom ArrayAdapter. Going to extends the base class ArrayAdapter and hold our
//Word object
public class WordAdapter extends ArrayAdapter<WordFolder> {
//constructor - it takes the context and the list of words
WordAdapter(Context context, ArrayList<WordFolder> word){
super(context, 0, word);
}
#Override
public View getView(int position, View convertView, ViewGroup parent){
View listItemView = convertView;
if(listItemView == null){
listItemView = LayoutInflater.from(getContext()).inflate(R.layout.folder_view, parent, false);
}
//Getting the current word
WordFolder currentWord = getItem(position);
//making the 3 text view to match our word_folder.xml
TextView date_created = (TextView) listItemView.findViewById(R.id.date_created);
TextView title = (TextView) listItemView.findViewById(R.id.title);
TextView desc = (TextView) listItemView.findViewById(R.id.desc);
//using the setText to get the text and set it in the textView
date_created.setText(currentWord.getDateCreated());
title.setText(currentWord.getTitle());
desc.setText(currentWord.getTitleDesc());
return listItemView;
}
}```
In R.layout.folder_view add one and make it invisible or gone. OnLongClick make them Visible.
I want my Adapter to choose either one row_layout or another row_layout, depending on which Activity is sending the data. There are two Activities sending data.
At the moment I only know how to make the Adapter use one row_layout. I can't work out how to write the extra code to make it choose a different row_layout, depending on which Activity is sending the data. (There wont be any checkbox in the second row_layout).
Here is my Adapter:
public class ShopItemAdapter extends ArrayAdapter<ShopItem> {
public ShopItemAdapter(Context context, ArrayList<ShopItem> shopItem){
super(context, 0, shopItem);
}
#NonNull
#Override
public View getView(int position, View convertView, #NonNull ViewGroup parent) {
if(convertView == null){
convertView = LayoutInflater.from(getContext()).inflate(R.layout.row_layout, parent, false);
}
TextView tvItem = (TextView)convertView.findViewById(R.id.tvItemName);
TextView tvQty = (TextView)convertView.findViewById(R.id.tvQuantity);
CheckBox cbIsPurchased = (CheckBox)convertView.findViewById(R.id.cbIsPurchased);
ShopItem _shopItem = getItem(position);
tvItem.setText(_shopItem.getItemName());
tvQty.setText(String.valueOf(_shopItem.getQuantity()));
if(_shopItem.getIsPurchased() == 1){
cbIsPurchased.setChecked(true);
}
else{
cbIsPurchased.setChecked(false);
}
return convertView;
}
}
You can add some parameter to the ShopItemAdapter constructor to distinguish from which Activity is called, but if that implies to use many if conditionals in getView code then i think it's better to write several adapters, you will have a much clear code.
I know that there are similar questions, but i couldn't really understand, so i decided to ask again.
I am trying to do something similar to a chat, so in the list view there would be 2 types of rows, the ones of the received items and the ones of the sent items. The ones that were received will inflate the recvRow.xml layout, and the ones that were sent will inflate the sentRow.xml layout.
I have a list of items that are suposed to be shown ( name, img, etc) and i have a boolean that represents if the object was sent or received.
The problem is that those layouts have different buttons and if for example i receive something first, it will obviously load the recvRow.xml , but then if I try to send something, it will throw a nullpointerexception because on one of the buttons that only exists in the sentRow.xml layout (and doesn't exist in the recvRow.xml).
Since i am checking that boolean that i told you guys before, i don't see the need to use the getItemViewType method.
Here is my code for the getView method:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
viewHolder = null;
if (convertView == null) {
viewHolder=new ViewHolder();
LayoutInflater layoutInflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
System.out.println("Transf Rec Adapter : Going to draw AN ITEM : "+ listOfItems.get(position).getAppName());
System.out.println("Transf Rec Adapter : The received bool is : "+ listOfItems.get(position).isReceived());
if(listOfItems.get(position).isReceived()){
view=layoutInflater.inflate(R.layout.highway_transf_record_recv_row,parent,false);
viewHolder.deleteFile=(ImageView) view.findViewById(R.id.transfRecRowDelete);
viewHolder.installButton = (ImageView) view.findViewById(R.id.transfRecRowInstall);
}else{//se enviado
view=layoutInflater.inflate(R.layout.highway_transf_record_sent_row,parent,false);
viewHolder.reSendButton=(ImageView) view.findViewById(R.id.transfRecReSendButton);
}
viewHolder.appImageIcon=(ImageView) view.findViewById(R.id.transfRecRowImage);
viewHolder.appNameLabel=(TextView) view.findViewById(R.id.transfRecRowText);
viewHolder.appVersionLabel = (TextView) view.findViewById(R.id.transfRecRowAppVersion);
viewHolder.senderInfo = (TextView) view.findViewById(R.id.apkOrigin);
viewHolder.rowImageLayout = (RelativeLayout) view.findViewById(R.id.transfRecRowImageLayout);
viewHolder.appInfoLayout = (RelativeLayout) view.findViewById(R.id.appInfoLayout);
}else{
view= convertView;
viewHolder = (ViewHolder) convertView.getTag();
}
if(listOfItems.get(position).isReceived()){
System.out.println("INSIDE THE GET VIEW TRANSF REC ADAPTER : RECEIVED SOMETHING");
viewHolder.senderInfo.setText("You received this app :");
myOnClickListenerToInstall = new MyOnClickListenerToInstall(position);
viewHolder.installButton.setOnClickListener(myOnClickListenerToInstall);
myOnClickListenerToDelete= new MyOnClickListenerToDelete(position);
viewHolder.deleteFile.setOnClickListener(myOnClickListenerToDelete);
}else{
System.out.println("INSIDE THE GET VIEW TRANSF REC ADAPTER : SENT SOMETHING");
viewHolder.senderInfo.setText("You sent this app :");
ReSendListener reSendListener=new ReSendListener(position);
viewHolder.reSendButton.setOnClickListener(reSendListener);//null pointer exception here
}
viewHolder.appNameLabel.setText(listOfItems.get(position).getAppName());
viewHolder.appImageIcon.setImageDrawable(listOfItems.get(position).getIcon());
viewHolder.appVersionLabel.setText(listOfItems.get(position).getVersionName());
view.setTag(viewHolder);
return view;
}
The id's are right and everything exists on the viewHolder.
You need to use getItemViewType because of recycling. Otherwise you can try to recycle a received item into a non-received view, which will screw things up royally. However it is ok for getItemViewType to just return 0 or 1 based on that boolean. It doesn't need to be very complicated for 2 view types.
You should override getItemViewType() in your adapter. This method will indicate the type of view which should be inflated for each row. You also need to override getViewTypeCount().
I have custom listview and array adapter with ViewHolder. When click listview item, it expands new layout below. Problem is: unfortunately it is opened for every +9th item in listview.. For example: if item 0 is clicked; 0,9,18th elements opens their expand layouts. Any idea without looking code ?
I have no idea, but this sounds familair with an issue I had. I had a listview with TextView objects and multiple were selected and got typed in the same value.
I had a very, very dirty fix for that, in my custom adapter I'd always make a new View, no matter what:
#Override
public View getView (final int position, View convertView, final ViewGroup parent) {
//if (convertView == null) {
// Inflate the view from the converter
final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
convertView = layoutInflater.inflate(converter.getLayout(), parent, false);
//}
// Populate the view from the converter
converter.populateInflatedView(convertView, getItem(position));
return convertView;
}
Source can be found here if you want to know what the converter is about.
On a side note, I have created a sort of interface type of thing for a TreeView, however this is in Activity form which uses a ScrollView. So this is not really a ListView type of deal, but might help you. The Tree part can be found on the Tree-link, with an implementation in the subject package.
I had this weird scenario where every time I select a value from the spinner in the ListView's 1st Item, the last ListView'sitem its spinner value is the same as the first item. This will only happen when the total number of ListView items is 5 and above. I commented out the codes and retain just the declarations and it's still happening. Is this a bug in Android?
Clarifications:
My ListView's Scroll Listener is empty
My spinner's setOnItemSelectedListener is commented out.
Android SDK Tool version is 22.6.2
Android SDK Platform-Tools is 19.0.1
Here's the adapter code:
#Override
public View getView(final int position, View convertView, final ViewGroup parent) {
Viewholder v = new Viewholder();
v.rowView = convertView;
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (v.rowView == null) {
v.rowView = inflater.inflate(R.layout.inner_base_header_cutom, parent, false);
v.spinner = (Spinner) v.rowView.findViewById(R.id.spinner1);
v.rowView.setTag(v);
} else {
v = (Viewholder) v.rowView.getTag();
}
return v.rowView;
}
ViewHolder:
class Viewholder{
View rowView;
TextView itemPID;
TextView itemPrice;
TextView itemItemId;
Spinner spinner;
TextView subtotal;
}
XML:
<Spinner
android:id="#+id/spinner1"
android:layout_width="100dip"
android:layout_height="wrap_content"
android:layout_margin="20dip"
android:entries="#array/quanitiy"
android:layout_alignTop="#+id/itemPrice"
android:layout_toRightOf="#+id/imageDisplay" />
Array:
<string-array name="quanitiy">
<item>Quantity</item>
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
<item>5</item>
<item>6</item>
<item>7</item>
<item>8</item>
<item>9</item>
<item>10</item>
</string-array>
UPDATE
I commented out the code for OnItemClickListner. I updated the code above, still the problem exists. The only thing left is the declarations.
Scenario:
If I select 1 at the first item of the spinner [index 0] of the ListView, the last item of ListView's spinner gets also 1 without interaction. When down the listview's items until the last part, that's where I found out that they are both the same. I commented out the codes and just retained the declarations.
Ok now I got it. Thanks for the clarifications. Your problem comes from views being recycled. You should take a look at this post to understand recycling better.
The short answer
Basically, when you use the convertView in getView(), you are reusing the view of an item that is no longer visible. That's why its fields (including the spinner, the textview for the price...) are already set to something, and you should set them yourself in getView(). The values to give these textfields and the spinner should depend on the item at the specified position.
The detailed answer
Here's what recycling does (picture taken from the post previously mentioned):
Your problem
Here when you scroll to display a new item (let's say item 8, like the picture), the view used to display it is the same as the one for the first item (item 1). Therefore, when you select something on the spinner of the first item, and then scroll down, you will see the change in the last item too because your getView doesn't change the values of the views that were used for item 1 (but it is supposed to update them!).
Note: The correspondance of the first and the last item in your case is totally by chance. Usually, the view is reused for 2 items that are approximately separated by the number of items in one screen height, as you can see in the picture.
Inline explanation in YOUR code for getView
Viewholder v = new Viewholder(); // why create a new one, while you might reuse the former one?
// here rowView becomes the same as the former item's view, with all the former values
v.rowView = convertView;
// you don't need this here but only in the case rowView is null, should be in your 'if'
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (v.rowView == null) {
v.rowView = inflater.inflate(R.layout.inner_base_header_cutom, parent,false);
v.spinner = (Spinner) v.rowView.findViewById(R.id.spinner1);
v.rowView.setTag(v);
// here you inflated a new view (nothing reused)
// but you didn't initialize anything
} else {
v = (Viewholder) v.rowView.getTag();
// here you're reusing the former View and ViewHolder
// and you don't change the values
}
The solution
Your not supposed to let the items behave as they please, but you're supposed to tell them what to display when you initialize the item view's content in getView(). For the picture above, this means you need to change what used to be item 1 into item 8.
You should keep the state of each item in your adapter. You probably have a backing list or something to display different elements in your ListView depending on the position, right?
Then you should use a similar list (or the same) to store the value selected in the spinner, the value displayed by the text field for the price etc. These should probably correspond to fields of the items in your current list anyway, so you probably already have some place to store them.
On the other hand, when you reuse the convertView in getView(), ensure you initialize the spinner (and text field, and everything in this item view) with the correct values for the item at position.
Solution code (template) for getView
Viewholder v;
if (convertView == null) {
// create a new holder for the new item view
v = new Viewholder();
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v.rowView = inflater.inflate(R.layout.inner_base_header_cutom, parent,false);
v.rowView.setTag(v);
// populate the new holder's fields
v.spinner = (Spinner) v.rowView.findViewById(R.id.spinner1);
v.itemPID = (TextView) v.rowView.findViewById(...); // put the right ID here
v.itemPrice = (TextView) v.rowView.findViewById(...); // put the right ID here
v.itemItemId = (TextView) v.rowView.findViewById(...); // put the right ID here
v.subtotal = (TextView) v.rowView.findViewById(...); // put the right ID here
} else {
v = (Viewholder) convertView.getTag();
// the views of v are already populated here (reused)
}
/*
* Reused or not, the item view needs to be initialized here.
* Initialize all views contained in v with values that are
* meaningful for the item at 'position'.
*/
// for instance:
MyItemClass theItemAtPosition = myBackingList.get(position);
v.subtotal.setText(String.valueOf(theItemAtPosition.getSubtotal()));
v.spinner.setSelection(theItemAtPosition.getQuantity());
// to be continued, you get the idea ;)