This question already has an answer here:
ListView view recycling with CustomAdapter
(1 answer)
Closed 9 years ago.
I am having issues with views being recycled. I'm using 2 layouts but some of the rows change layouts when I scroll up and down. I've tried other tutorials with little success. Any suggestions?
public class CustomAdapter extends BaseAdapter {
Date date = new Date();
private OfferList[] offers;
private String nameCompare = "";
LayoutInflater inflater = getLayoutInflater();
CustomAdapter (OfferList[] offers, FetchItemsTask fetchItemsTask) {
this.offers = offers;
}
#Override
public View getView(int position, View convertView, ViewGroup parent){
ViewHolder holder = null;
if (convertView == null) {
holder = new ViewHolder();
if (getItemViewType(position) == 0) {
convertView = inflater.inflate(R.layout.list_offer_group, null);
}
else {
nameCompare = offers[position].getName();
convertView = inflater.inflate(R.layout.list_offer, null);
}
holder.name = (TextView) convertView.findViewById(R.id.name);
holder.color = (TextView) convertView.findViewById(R.id.car_color_stock);
holder.time = (TextView) convertView.findViewById(R.id.time);
holder.offerStatus = (TextView) convertView.findViewById(R.id.offer_status);
convertView.setTag(holder);
}
else {
holder = (ViewHolder) convertView.getTag();
}
holder.name.setText(offers[position].getCustomerName());
holder.carStockColor.setText(offers[position].getVehicleYear() + " " + offers[position].getVehicleMake());
java.util.Date time1 = new java.util.Date(Long.parseLong(offers[position].getModified()));
holder.time.setText(time1.toString().substring(0, 11) + " | ");
holder.offerStatus.setText(offers[position].getDealStatus());
return convertView;
}
#Override
public int getViewTypeCount() {
return 2;
}
#Override
public int getItemViewType(int position) {
if (nameCompare.equalsIgnoreCase(offers[position].getCustomerName())) {
return 0;
}
else {
return 1;
}
}
#Override
public int getCount() {
return offers.length;
}
#Override
public Object getItem(int position) {
return position;
}
#Override
public long getItemId(int position) {
return position;
}
public class ViewHolder {
public int type;
TextView name;
TextView color;
TextView time;
TextView offerStatus;
}
}
You have to implement a different logic. The problem with your code is, that if the convertView is not null, meaning it has been recycled, but it previously inflated a different view, than that, that correlates to your data, it will display the layout of the old recycled view (the other layout and not the layout, that you need for this dataset).
So you just have to check, if the recycled view inflated the layout, that you need for this particular row or if it inflated the other view. If the former is the case, then you can just set your data, if not, then you have to inflate the right view.
Related
I have a conversation mode in my application where I wish to load one layout for one user and another layout for the other. It need not always be alternating hence I cannot use a simple "%2" to achieve it.
Based on a flag I am assigning a dynamic layout, which works. My problem is that as I scroll the layouts get distorted as in, conversation of user_1 will get layout_2 or conversation of user_2 will get layout_1, absolutely random.
I did something similar to an answer I saw here:
https://stackoverflow.com/a/16774696/4810718
There were a few posts about randomized data. That is not my issue, the order of the list items does not change however the layout get's randomly applied. I read that the items in view + 1 are kept in temporary memory, regarding this another thing I noticed was: as I keep adding items such that the scrollbar comes into picture when I add a second item outside the visibility it tends to get the layout of the topmost item (first item) visible. Scrolling would later give me seemingly randomized results.
public class ConversationAdapter extends BaseAdapter
{
private LayoutInflater inflater;
private ArrayList<ConversationContent> objects;
ImageView user;
static int ind = 0;
private class ViewHolder
{
TextView textView1;
TextView textView2;
TextView textView3;
}
public ConversationAdapter(Context context, ArrayList<ConversationContent> objects)
{
inflater = LayoutInflater.from(context);
this.objects = objects;
}
public int getCount()
{
return objects.size();
}
public ConversationContent getItem(int position)
{
return objects.get(position);
}
public long getItemId(int position)
{
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
ViewHolder holder = null;
if(convertView == null)
{
holder = new ViewHolder();
if (Main_Page3.convFlag == 1)
{
convertView = inflater.inflate(R.layout.conversation_item_1, null);
}
else
{
convertView = inflater.inflate(R.layout.conversation_item_2, null);
}
holder.textView1 = (TextView) convertView.findViewById(R.id.trans);
holder.textView1.setTypeface(Main_Activity.fontC);
holder.textView2 = (TextView) convertView.findViewById(R.id.lang);
holder.textView2.setTypeface(Main_Activity.fontC);
holder.textView3 = (TextView) convertView.findViewById(R.id.user);
holder.textView3.setTypeface(Main_Activity.fontC);
convertView.setTag(holder);
}
else
{
holder = (ViewHolder) convertView.getTag();
}
holder.textView1.setText(objects.get(position).getTranslatedText());
holder.textView2.setText(objects.get(position).getTranslationString());
SpannableString originalTextString = new SpannableString("\n" + objects.get(position).getOriginalText());
originalTextString.setSpan(new RelativeSizeSpan(0.5f), 0, originalTextString.length(), 0);
holder.textView1.append(originalTextString);
holder.textView3.setText(objects.get(position).getUser());
return convertView;
}
}
So, that's the code I've written. A possible solution I thought of was if I used an array of views and loaded them accordingly, it may work? I'm really not really sure how I should be going about doing this - I'm still pretty new to Android.
I've searched a bit but could not get a helpful solution. Please direct me to a helpful solution you find or, a working answer would be most appreciable. Thank you.
I think the best way to achieve what you want is to put the flag to determine which layout to use on your ConversationContent object, then override getViewTypeCount() and getItemViewType(int position) something like this:
#Override
int getViewTypeCount() {
return 2;
}
#Override
int getItemViewType(int position) {
if (objects.get(position).isReply()) { //isReply can be whatever you want to determine whether to change layout
return 1;
}
return 0;
}
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
ViewHolder holder = null;
if(convertView == null)
{
holder = new ViewHolder();
if (getItemViewType(position) == 1)
{
convertView = inflater.inflate(R.layout.conversation_item_1, null);
}
else
{
convertView = inflater.inflate(R.layout.conversation_item_2, null);
}
holder.textView1 = (TextView) convertView.findViewById(R.id.trans);
holder.textView1.setTypeface(Main_Activity.fontC);
holder.textView2 = (TextView) convertView.findViewById(R.id.lang);
holder.textView2.setTypeface(Main_Activity.fontC);
holder.textView3 = (TextView) convertView.findViewById(R.id.user);
holder.textView3.setTypeface(Main_Activity.fontC);
convertView.setTag(holder);
}
else
{
holder = (ViewHolder) convertView.getTag();
}
holder.textView1.setText(objects.get(position).getTranslatedText());
holder.textView2.setText(objects.get(position).getTranslationString());
SpannableString originalTextString = new SpannableString("\n" + objects.get(position).getOriginalText());
originalTextString.setSpan(new RelativeSizeSpan(0.5f), 0, originalTextString.length(), 0);
holder.textView1.append(originalTextString);
holder.textView3.setText(objects.get(position).getUser());
return convertView;
}
For listview adapter, if you want to show different layout,
like conversion mode. you would better override the following two methods:
//set your layout type here
public int getItemViewType(int position)
{
return 0;
}
//the layout count in your adapter
public int getViewTypeCount()
{
return 0;
}
Here is an example you can refer to:
public class ChatMessageAdapter extends BaseAdapter
{
private LayoutInflater mInflater;
private List<ChatMessage> mDatas;
public ChatMessageAdapter(Context context, List<ChatMessage> mDatas)
{
mInflater = LayoutInflater.from(context);
this.mDatas = mDatas;
}
#Override
public int getCount()
{
return mDatas.size();
}
#Override
public Object getItem(int position)
{
return mDatas.get(position);
}
#Override
public long getItemId(int position)
{
return position;
}
#Override
public int getItemViewType(int position)
{
ChatMessage chatMessage = mDatas.get(position);
if (chatMessage.getType() == Type.INCOMING)
{
return 0;
}
return 1;
}
#Override
public int getViewTypeCount()
{
return 2;
}
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
ChatMessage chatMessage = mDatas.get(position);
ViewHolder viewHolder = null;
if (convertView == null)
{
if (getItemViewType(position) == 0)
{
convertView = mInflater.inflate(R.layout.item_from_msg, parent,
false);
viewHolder = new ViewHolder();
viewHolder.mDate = (TextView) convertView
.findViewById(R.id.id_form_msg_date);
viewHolder.mMsg = (TextView) convertView
.findViewById(R.id.id_from_msg_info);
} else
{
convertView = mInflater.inflate(R.layout.item_to_msg, parent,
false);
viewHolder = new ViewHolder();
viewHolder.mDate = (TextView) convertView
.findViewById(R.id.id_to_msg_date);
viewHolder.mMsg = (TextView) convertView
.findViewById(R.id.id_to_msg_info);
}
convertView.setTag(viewHolder);
} else
{
viewHolder = (ViewHolder) convertView.getTag();
}
//set data here
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
viewHolder.mDate.setText(df.format(chatMessage.getDate()));
viewHolder.mMsg.setText(chatMessage.getMsg());
return convertView;
}
private final class ViewHolder
{
TextView mDate;
TextView mMsg;
}
}
I am building a calculator with a listview that is set dynamically. When its being built(with calculations), an onclickListener is set to each item to delete the row. This is all done with an AdapterClass. I have my MainClass that holds all the information. In this class, it has a running total of my items. This running total has a class that will add/subtract/retrieve my running total. The running total (as of now) is only set when the "CALCULATE" button is pressed. When I delete an item (row/calculation) I need it to update my running total in my main class. Keep in mind, the onClick used to delete the items is in my AdapterClass, not my MainClass. If you need further explanation let me know.
A better explanation
MainActivity
//This is called in my CALCULATE on click, after all the calulations have been made. THE ONLY PLACE THAT THE RUNNING TOTAL GETS CHANGED
newList=new ArrayList<Calculations>();
Calculations info = new Calculations();
info.SetType("Slab figure "+figureCount);
info.SetFigure(width+"x"+length+"x"+depth);
info.SetFigureAmount(String.format("%.2f",CubicYd)+"");
newList.add(info);
currentTotal.add(CubicYd);
total.setText(String.format("Total: "+"%.2f",currentTotal.getRunningTotal())+" Cubic Yards");
if(newList!=null&&newList.size()>0)
{
newAdapter.notifyDataSetChanged();
newAdapter.add(newList.get(0));
i++;
}
newAdapter.notifyDataSetChanged();
runningTotal
public class runningTotal {
double runningTotal = 0.0;
public double add(double newAmount) {
runningTotal = runningTotal + newAmount;
return runningTotal;
}
public double sub(double newAmount) {
runningTotal = runningTotal - newAmount;
return runningTotal;
}
public double getRunningTotal() {
return runningTotal;
}
public void setRunningTotal() {
runningTotal = 0.0;
}
}
ListAdapter
public class CustomListAdapter extends ArrayAdapter<Calculations> {
runningTotal currentTotal = new runningTotal();
Calculations c = new Calculations();
private Context appContext = null;
private ArrayList<Calculations> items = null;
public CustomListAdapter(Context context, int textViewResourceId,
ArrayList<Calculations> items) {
super(context, textViewResourceId, items);
this.appContext = context;
this.items = items;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v == null) {
LayoutInflater vi = (LayoutInflater) appContext
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.list_item, null);
}
c = items.get(position);
if (c != null) {
//Set the calculations to the list view
TextView type = (TextView) v.findViewById(R.id.tvType);
TextView figure = (TextView) v.findViewById(R.id.tvFullFigure);
TextView amount = (TextView) v.findViewById(R.id.tvAmount);
RelativeLayout layout = (RelativeLayout) v.findViewById(R.id.list_item_id);
layout.setTag(position);
//set on click to the layout that deletes the line.
layout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
String pos = view.getTag().toString();
int _position = Integer.parseInt(pos);
currentTotal.sub(Double.parseDouble(c.getFigureAmount()));
<----This is where I need to update the textView "total" in my mainAcitivity----->
items.remove(_position);
notifyDataSetChanged();
Toast.makeText(appContext, "The amount to be released is " + Double
.parseDouble(c.getFigureAmount()), Toast.LENGTH_LONG).show();
}
});
if (type != null) {
type.setText(c.getType());
}
if (figure != null) {
figure.setText(c.getFigure());
}
if (amount != null) {
amount.setText(c.getFigureAmount());
}
}
return v;
}
}
Calculations
private String type = "";
private String figure = "";
private String figureTotal = "";
public void SetType(String type){
this.type = type;
}
public String getType(){
return this.type;
}
public void SetFigure(String figure) {
this.figure = figure;
}
public String getFigure(){
return this.figure;
}
public void SetFigureAmount(String figureTotal){
this.figureTotal = figureTotal;
}
public String getFigureAmount(){
return this.figureTotal;
}
UPDATE / this my CustomListAdapter with a ViewHolder from Beginner
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
if(v == null) {
LayoutInflater vi = (LayoutInflater)appContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
holder = new ViewHolder();
holder.text = (TextView) v.findViewById(R.id.tvTotal);
v = vi.inflate(R.layout.list_item, null);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.text.setText(currentTotal.getRunningTotal()+"");
c = items.get(position);
if (c != null){
//Set the calculations to the list view
TextView type = (TextView) v.findViewById(R.id.tvType);
TextView figure = (TextView) v.findViewById(R.id.tvFullFigure);
TextView amount = (TextView) v.findViewById(R.id.tvAmount);
RelativeLayout layout = (RelativeLayout) v.findViewById(R.id.list_item_id);
layout.setTag(position);
//set on click to the layout that deletes the line.
layout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
String pos = view.getTag().toString();
int _position = Integer.parseInt(pos);
double newTotal;
double oldTotal;
oldTotal = Double.parseDouble(c.getFigureAmount());
newTotal = currentTotal.getRunningTotal() - oldTotal;
currentTotal.setRunningTotal(newTotal);
holder.text.setText("total after delete" );
items.remove(_position);
notifyDataSetChanged();
}
});
if(type!=null) {
type.setText(c.getType());
}
if(figure!=null){
figure.setText(c.getFigure());
}
if(amount !=null){
amount.setText(c.getFigureAmount());
}
}
return v;
}
static class ViewHolder{
TextView text;
}
}
I get nullPointer in the line
holder.text = (TextView) v.findViewById(R.id.tvTotal);
the problem is you're using the on click for a view inside your list, if you call list.setOnItemClickListener and wire up your onclick in your activity, rather then layout.setOnClickListener on your adapter. Note I'm not saying you can't do what you're asking to do, but this would be the recommended way to fix it.
something like this (on the main Activity where the text view lives):
listView.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Calculations c = (Calculations) parent.getAdapter().getItem(position);
currentTotal.sub(Double.parseDouble(c.getFigureAmount()));
total.setText(String.format("Total: "+"%.2f",currentTotal.getRunningTotal())+" Cubic Yards");
items.remove(_position);
notifyDataSetChanged();
Toast.makeText(appContext, "The amount to be released is " + Double
.parseDouble(c.getFigureAmount()), Toast.LENGTH_LONG).show();
}
});
though you never posted where you're setting your adapter so I'm not sure what your list view is called.
In the adapter class use a holder and try to update the veiws using the holder.. here is an example
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// A ViewHolder keeps references to children views to avoid unneccessary
// calls
// to findViewById() on each row.
ViewHolder holder;
// When convertView is not null, we can reuse it directly, there is no
// need
// to reinflate it. We only inflate a new View when the convertView
// supplied
// by ListView is null.
if (convertView == null) {
convertView = mInflater.inflate(R.layout.sample, null);
// Creates a ViewHolder and store references to the two children
// views
// we want to bind data to.
holder = new ViewHolder();
holder.name = (TextView) convertView.findViewById(R.id.text);
holder.icon = (ImageView) convertView.findViewById(R.id.icon);
convertView.setTag(holder);
} else {
// Get the ViewHolder back to get fast access to the TextView
// and the ImageView.
holder = (ViewHolder) convertView.getTag();
}
// Bind the data efficiently with the holder.
holder.name.setText(myElements.get(id));
holder.icon.setImageBitmap(mIcon1);
return convertView;
}
static class ViewHolder {
TextView name;
ImageView icon;
}
So Now, suppose you want to update textview called total
Step 1
define total in ViewHolder class like
static class ViewHolder{
/* Define all your view's to update here*/
TextView total;
}
Step 2
create a instance of ViewHolder in getView() method.
ViewHolder holder;
Step 3
use a holder to update your textview in onclick method
holder.total.setText("The text u want to set");
I want to build a dynamic ListView (like a chat list with different layouts/bubbles).
My problem is, that each row has an individual height. My code below works,
but every time I scroll down or receive a new message,
the row with a different height gets heigher.
private class dialogAdapter extends BaseAdapter {
private LayoutInflater mInflater;
public dialogAdapter(Context context) {
mInflater = LayoutInflater.from(context);
}
#Override
public boolean hasStableIds() {
return true;
}
public int getCount() {
return dialog.size();
}
public int getViewTypeCount() {
return 999999;
}
public Object getItem(int position) {
return dialog.get(position);
}
public int getItemViewType(int position) {
return position;
}
public String getType(int position) {
return dialogType.get(position);
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
Log.w("DRAGII", "POS: "+position);
if (convertView == null) {
convertView = mInflater.inflate(R.layout.bubble, null);
holder = new ViewHolder();
holder.text = (TextView) convertView.findViewById(R.id.text);
holder.parser = new URLImageParser(holder.text);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
if (position <= dialogCache.size())
dialogCache.add(position, Html.fromHtml((String)getItem(position),
holder.parser, null));
holder.text.setText(dialogCache.get(position));
holder.type = getType(position);
int bubble = R.drawable.bubble;
if (holder.type.equals("R")) bubble = R.drawable.bubble_right;
else if (holder.type.equals("L")) bubble = R.drawable.bubble_left;
holder.text.setBackgroundResource(bubble);
return convertView;
}
class ViewHolder {
TextView text;
String type = "B";
URLImageParser parser;
}
}
What should I do?
Solved this problem by using TableLayout instead of ListView.
if u are adding tableRow programmatically to tableLayout, you will have performance issues. Think it again and find a way by using listView
My problem is that I don't know whether I should use multiple list view or a custom listview item adapter which can grows dynamically. For example, for a particular user, they can have multiple activities:
- Take a picture
- Say something
- Checking in
- ...
Apparently, this list can grows as the user has done more activities. Most of the time, I often create a custom item adapter which extends from BaseAdapter and use the ItemHolder pattern as follows:
public class PlaceItemAdapter extends BaseAdapter {
private Activity context;
private List<Place> places;
private boolean notifyChanged = false;
public PlaceItemAdapter(Activity context, List<Place> places) {
super();
this.context = context;
this.places = places;
}
public int getCount() {
return places.size();
}
public Object getItem(int position) {
return places.get(position);
}
public long getItemId(int position) {
return position;
}
public static class ItemViewHolder {
TextView nameTextView;
TextView typesTextView;
TextView ratingTextView;
ImageView mapIconImageView;
}
public View getView(int position, View convertView, ViewGroup parent) {
ItemViewHolder holder;
LayoutInflater inflater = context.getLayoutInflater();
if (convertView == null) {
convertView = inflater.inflate(R.layout.place_item, null);
holder = new ItemViewHolder();
holder.nameTextView = (TextView) convertView.findViewById(R.id.place_item_xml_textview_name);
holder.typesTextView = (TextView) convertView.findViewById(R.id.place_item_xml_textview_address);
holder.ratingTextView = (TextView) convertView.findViewById(R.id.place_item_xml_textview_rating);
holder.mapIconImageView = (ImageView) convertView.findViewById(R.id.place_item_xml_imageview_location_icon);
convertView.setTag(holder);
}
else {
holder = (ItemViewHolder) convertView.getTag();
}
holder.nameTextView.setText(places.get(position).getName());
holder.typesTextView.setText(places.get(position).getAddress());
holder.ratingTextView.setText(Integer.toString(places.get(position).getRating()));
/*
* This task is time consuming!
* TODO: find a workaround to handle the image
*/
// holder.mapIconImageView.setImageBitmap(DownloadImageHelper.downloadImage(places.get(position).getIconUrl()));
holder.mapIconImageView.setImageResource(R.drawable.adium);
return convertView;
}
public void notifyDataSetChanged() {
super.notifyDataSetChanged();
notifyChanged = true;
}
}
Using this method, the number GUI widgets is fixed which means I can't make my listview item look like the picture below.
public static class ItemViewHolder {
TextView nameTextView;
TextView typesTextView;
TextView ratingTextView;
ImageView mapIconImageView;
}
My initial approach was to create a dynamic view nested inside an adapter item, however it will produce duplicate views. To avoid duplicate view, I have set convertView to null which means each time it loads, it will create a new ItemViewHolder which eventually eats up all my memory. :( So how could I handle this situation? A minimal working example would be greatly appreciated.
Duplicate View
public class FriendFeedItemAdapter extends BaseAdapter {
private List<FriendFeedItem> items;
private Activity context;
private static LayoutInflater inflater;
public ImageLoader imageLoader;
private ItemViewHolder viewHolder;
public FriendFeedItemAdapter(Activity context, List<FriendFeedItem> items) {
this.context = context;
this.items = items;
inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
imageLoader = new ImageLoader(context.getApplicationContext());
}
public int getCount() {
return items.size();
}
public Object getItem(int position) {
return items.get(position);
}
public long getItemId(int position) {
return position;
}
public static class ItemViewHolder {
TableLayout table;
ImageView imageViewUserPicture;
TextView textViewUsername;
TextView textViewWhatUserDo;
TextView textViewWhere;
TextView textViewTime;
ImageView imageViewSnapPictureBox;
TextView textViewWriteOnWallMessageBox;
}
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = inflater.inflate(R.layout.friend_list_feed_item, null);
viewHolder = new ItemViewHolder();
viewHolder.table = (TableLayout) convertView.findViewById(R.id.friend_list_feed_item_xml_tablelayout_table);
viewHolder.imageViewUserPicture = (ImageView) convertView.findViewById(R.id.friend_list_feed_item_xml_imageview_user_picture);
viewHolder.textViewUsername = (TextView) convertView.findViewById(R.id.friend_list_feed_item_xml_textview_username);
viewHolder.textViewWhatUserDo = (TextView) convertView.findViewById(R.id.friend_list_feed_item_xml_textview_what_user_do);
viewHolder.textViewWhere = (TextView) convertView.findViewById(R.id.friend_list_feed_item_xml_textview_where);
viewHolder.textViewTime = (TextView) convertView.findViewById(R.id.friend_list_feed_item_xml_textview_at_what_time);
convertView.setTag(viewHolder);
}
else {
viewHolder = (ItemViewHolder) convertView.getTag();
}
imageLoader.displayImage(items.get(position).getFriendPictureUrl(), viewHolder.imageViewUserPicture);
viewHolder.textViewUsername.setText(items.get(position).getFriendName());
viewHolder.textViewWhere.setText("at " + items.get(position).getPlaceName());
viewHolder.textViewTime.setText("#" + items.get(position).getActivityTime());
if (items.get(position).getChallengeType() == Challenge.Type.CHECK_IN) {
viewHolder.textViewWhatUserDo.setText("has checked in.");
}
else if (items.get(position).getChallengeType() == Challenge.Type.SNAP_PICTURE) {
viewHolder.textViewWhatUserDo.setText("has snap a picture.");
// add picture box
View rowView = inflater.inflate(R.layout.snap_picture_row_item, null);
viewHolder.imageViewSnapPictureBox = (ImageView) rowView.findViewById(R.id.snap_picture_row_item_xml_imageview_picture);
imageLoader.displayImage(items.get(position).getActivitySnapPictureUrl(), viewHolder.imageViewSnapPictureBox);
viewHolder.table.addView(rowView);
}
else if (items.get(position).getChallengeType() == Challenge.Type.WRITE_ON_WALL) {
viewHolder.textViewWhatUserDo.setText("has written a message on wall.");
// add message box
View rowView = inflater.inflate(R.layout.write_on_wall_row_item, null);
viewHolder.textViewWriteOnWallMessageBox = (TextView) rowView.findViewById(R.id.write_on_wall_row_item_xml_textview_wall_message);
viewHolder.textViewWriteOnWallMessageBox.setText(items.get(position).getActivityComment());
viewHolder.table.addView(rowView);
}
else if (items.get(position).getChallengeType() == Challenge.Type.QUESTION_ANSWER) {
viewHolder.textViewWhatUserDo.setText("has answered a question.");
}
else { // Challenge.Type.OTHER
viewHolder.textViewWhatUserDo.setText("has done some other challenges.");
}
return convertView;
}
}
Extensive Memory Usage
public View getView(int position, View convertView, ViewGroup parent) {
ItemViewHolder holder = null;
LayoutInflater inflater = context.getLayoutInflater();
convertView = inflater.inflate(R.layout.friend_list_feed_item, null);
// create holder
holder = new ItemViewHolder();
// default field
holder.table = (TableLayout) convertView.findViewById(R.id.friend_list_feed_item_xml_tablelayout_table);
holder.imageViewUserPicture = (ImageView) convertView.findViewById(R.id.friend_list_feed_item_xml_imageview_user_picture);
holder.textViewUsername = (TextView) convertView.findViewById(R.id.friend_list_feed_item_xml_textview_username);
holder.textViewWhatUserDo = (TextView) convertView.findViewById(R.id.friend_list_feed_item_xml_textview_what_user_do);
holder.textViewWhere = (TextView) convertView.findViewById(R.id.friend_list_feed_item_xml_textview_where);
holder.textViewTime = (TextView) convertView.findViewById(R.id.friend_list_feed_item_xml_textview_at_what_time);
convertView.setTag(holder);
holder.imageViewUserPicture.setImageURI(items.get(position).getFriendPictureUri());
holder.textViewUsername.setText(items.get(position).getFriendName());
holder.textViewWhere.setText("at " + items.get(position).getPlaceName());
holder.textViewTime.setText("#" + items.get(position).getActivityTime());
if (items.get(position).getChallengeType() == Challenge.Type.CHECK_IN) {
holder.textViewWhatUserDo.setText("has checked in.");
}
else if (items.get(position).getChallengeType() == Challenge.Type.SNAP_PICTURE) {
holder.textViewWhatUserDo.setText("has snap a picture.");
// add picture box
View rowView = inflater.inflate(R.layout.snap_picture_row_item, null);
holder.imageViewSnapPictureBox = (ImageView) rowView.findViewById(R.id.snap_picture_row_item_xml_imageview_picture);
holder.imageViewSnapPictureBox.setImageURI(items.get(position).getActivitySnapPictureUri());
holder.table.addView(rowView);
}
else if (items.get(position).getChallengeType() == Challenge.Type.WRITE_ON_WALL) {
holder.textViewWhatUserDo.setText("has written a message on wall.");
// add message box
View rowView = inflater.inflate(R.layout.write_on_wall_row_item, null);
holder.textViewWriteOnWallMessageBox = (TextView) rowView.findViewById(R.id.write_on_wall_row_item_xml_textview_wall_message);
holder.textViewWriteOnWallMessageBox.setText(items.get(position).getActivityComment());
holder.table.addView(rowView);
}
else if (items.get(position).getChallengeType() == Challenge.Type.QUESTION_ANSWER) {
holder.textViewWhatUserDo.setText("has answered a question.");
}
else { // Challenge.Type.OTHER
holder.textViewWhatUserDo.setText("has done some other challenges.");
}
return convertView;
}
If you have small number of possible variants (on your screenshots I can see 2 different list items) You have two possible variants:
Setup count of different types by this method, and provide type for every item - and you can use convertView.
Create "full" list item view and set visibility for elements, that you don't want to see in particular item.
Some code for #2:
public class ListTestActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
List<Element> list = new ArrayList<Element>();
list.add(new Element(0));
list.add(new Element(0));
list.add(new Element(1));
list.add(new Element(0));
list.add(new Element(1));
list.add(new Element(1));
list.add(new Element(0));
list.add(new Element(0));
list.add(new Element(1));
list.add(new Element(1));
list.add(new Element(1));
list.add(new Element(0));
list.add(new Element(0));
list.add(new Element(1));
list.add(new Element(0));
list.add(new Element(0));
((ListView) findViewById(android.R.id.list)).setAdapter(new SampleAdapter(this, list));
}
private class SampleAdapter extends BaseAdapter {
private List<Element> list;
private Context context;
public SampleAdapter(Context context, List<Element> list) {
this.list = list;
this.context = context;
}
#Override
public int getCount() {
return list.size();
}
#Override
public Element getItem(int position) {
return list.get(position);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null)
switch (getItemViewType(position)) {
case 0:
convertView = new CheckBox(context);
break;
default:
convertView = new Button(context);
break;
}
// Output here shows that you can lay on getItemViewType(position) as indicator of convertView type or structure
Log.e("test", getItemViewType(position) + ": " + convertView.getClass().getSimpleName());
return convertView;
}
#Override
public int getItemViewType(int position) {
return getItem(position).type;
}
#Override
public int getViewTypeCount() {
return 2;
}
#Override
public long getItemId(int position) {
return position;
}
}
private class Element {
public int type;
public Element(int type) {
this.type = type;
}
}
}
A custom adapter would solve your problem. This is because you can change the views that are being added to each row in the Listview, because you can change the content via logic that you implement in the custom adapter.
When the getView() method returns a view that is not null, this means for that particular row there is a view that was already there. As such if this is the case, you may or may not want to change content in that specific view. Or you could build a brand new view with dynamic content for that particular row.
One thing to note is that getView() will be called as many times as there are items found in your adapter.
Here's an idea that'll probably enable you to introduce as many item types as you like without having to modify adapter every time you do:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
AbstractItem abstractItem = ((AbstractItem)getItem(position));
// if null or new item type is different than the one already there
if (convertView == null || (convertView.getTag() != null
&& ((AbstractItem)convertView.getTag()).getType().equals(abstractItem.getType())) {
convertView = abstractItem.inflateSelf(getContext());
}
abstractItem.fillViewWithData(convertView);
convertView.setTag(abstractItem);
return convertView;
}
public class AbstractItem {
public abstract View inflateSelf(Context context);
public abstract String getType();
public abstract void fillViewWithData(View view);
}
public class PictureSnapItem extends AbstractItem {
// data fields
WeakReference<Bitmap> wBitmap;
String pictureComment;
...
public abstract View inflateSelf(Context context) {
// get layout inflater, inflate a layout resource, return
return ((Activity)context).getLayoutInflater.inflate(R.layout.picture_snap_item);
}
public abstract String getType() {
// return something class-specific, like this
return getClass().getCanonicalName();
}
public abstract void fillViewWithData(View view) {
// fill the view with data from fields, assuming view has been
// inflated by this very class's inflateSelf(), maybe throw exception if
// required views can't be found
ImageView img = (ImageView) view.findViewById(R.id.picture);
TextView comment = (TextView) view.findViewById(R.id.picture_comment)
}
}
... and then extend AbstractItem and add instances to adapter, no need to add any if clauses to getView() any more.
I am having list of my Facebook friends and i created a list View that's having separator as months from Jan to Dec.
I am able list my friends details, My problem is that i am able to show their Name and birthday date if i try load their "PICTURES" i can't do it..
It showing me the icon set as Background in XML.
Even the URL for the image to be load is get printed in Log, but i cant get the image..
Here is my code
private class MyCustomAdapter extends BaseAdapter {
private static final int TYPE_ITEM = 0;
private static final int TYPE_SEPARATOR = 1;
private static final int TYPE_MAX_COUNT = TYPE_SEPARATOR + 1;
private LayoutInflater mInflater;
private TreeSet<Integer> mSeparatorsSet = new TreeSet<Integer>();
public MyCustomAdapter() {
mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mData = new ArrayList<Category>();
}
public void addItem(final Category item) {
mData.add(item);
notifyDataSetChanged();
}
public void addSeparatorItem(final Category item) {
mData.add(item);
// save separator position
mSeparatorsSet.add(mData.size() - 1);
notifyDataSetChanged();
}
#Override
public int getItemViewType(int position) {
return mSeparatorsSet.contains(position) ? TYPE_SEPARATOR : TYPE_ITEM;
}
#Override
public int getViewTypeCount() {
return TYPE_MAX_COUNT;
}
#Override
public int getCount() {
return mData.size();
}
#Override
public Category getItem(int position) {
return mData.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
int type = getItemViewType(position);
System.out.println("getView " + position + " " + convertView + " type = " + type);
Category category = mData.get(position);
holder = new ViewHolder();
if (convertView == null) {
switch (type) {
case TYPE_ITEM:
convertView = mInflater.inflate(R.layout.item1, null);
holder.textView = (TextView)convertView.findViewById(R.id.text);
holder.textView1 = (TextView)convertView.findViewById(R.id.text1);
holder.icon = (ImageView) convertView.findViewById(R.id.friend_photo);
break;
case TYPE_SEPARATOR:
convertView = mInflater.inflate(R.layout.item2, null);
holder.textView = (TextView)convertView.findViewById(R.id.textSeparator);
break;
}
convertView.setTag(holder);
} else {
if(type==TYPE_ITEM){
holder.textView = (TextView)convertView.findViewById(R.id.text);
holder.textView1 = (TextView)convertView.findViewById(R.id.text1);
holder.icon = (ImageView) convertView.findViewById(R.id.friend_photo);
holder = (ViewHolder)convertView.getTag();
}else{
holder.textView = (TextView)convertView.findViewById(R.id.textSeparator);
holder = (ViewHolder)convertView.getTag();
}
}
if(type==TYPE_SEPARATOR){
holder.textView.setText(mData.get(position).dealTitle);
}else{
holder.textView.setText(mData.get(position).dealTitle);
holder.textView1.setText(mData.get(position).dealDesc);
holder.icon.setTag(mData.get(position).picture);
}
return convertView;
}
}
You need to set your ImageView's bitmap using one of the setImageFoo() calls.
setImageResource()
setImageBitmap()
setImageDrawable()
I'm not sure what type of object mData.get(position).picture is but
holder.icon.setTag(mData.get(position).picture);
isn't loading the picture. That's just setting some data on the View.
You need to use one of the setImage() methods of ImageView.
Your code looks fine, there might be a problem with the xml/layout file.
As you are loading two textViews and a ImageView from the xml for each item, it is important to know how the layout is organised to show all the added views in it.
Please post the layout files so that, one can suggest if there is any problem.