Android Listview is repeating items when scrolled - android

I have looked at other threads and I cannot see what is wrong with my listadapter. I have the view holder pattern and I am setting all the values for the list item outside of the convertview null check statement. Im not sure what other things would cause it to repeat the items.
public class ScheduledJobListAdapter extends BaseAdapter {
private static LayoutInflater inflater = null;
private ArrayList<Job> jobList;
private Context context;
public ScheduledJobListAdapter(Context context, ArrayList<Job> jobList) {
this.context = context;
this.jobList = jobList;
inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public int getCount() { return jobList.size(); }
#Override
public Job getItem(int position) { return jobList.get(position); }
#Override
public long getItemId(int position) { return 0; }
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
final ScheduledViewHolder holder;
if (convertView == null) {
convertView = inflater.inflate(R.layout.list_item_scheduled_job,null);
holder = new ScheduledViewHolder();
holder.job = getItem(position);
holder.container = convertView.findViewById(R.id.scheduled_job_layout);
holder.routeOrder = convertView.findViewById(R.id.route_order_textview);
holder.location = convertView.findViewById(R.id.location_textview);
holder.jobRef = convertView.findViewById(R.id.job_ref_textview);
holder.statusIndicator = convertView.findViewById(R.id.status_indicator);
convertView.setTag(holder);
} else {
holder = (ScheduledViewHolder) convertView.getTag();
}
holder.routeOrder.setText(holder.job.getRouteOrder() + "");
holder.location.setText(holder.job.getLocation());
holder.jobRef.setText(holder.job.getJobReference());
return convertView;
}
}
class ScheduledViewHolder {
Job job;
LinearLayout container;
TextView routeOrder;
TextView location;
TextView jobRef;
ImageView statusIndicator;
}

Here's the problem:
holder.job = getItem(position);
Remember the views may be recycled as you scroll, and the job assigned maybe used unintentionally for other positions if you assigned it that way. To fix it, simply assign the job after the if-else condition:
if (convertView == null) {
// ...
} else {
// ...
}
holder.job = getItem(position); // Update the job by position
holder.routeOrder.setText(holder.job.getRouteOrder() + "");
holder.location.setText(holder.job.getLocation());
holder.jobRef.setText(holder.job.getJobReference());

Related

DIsplay first letter of string to another textview in Android

I am displaying a listview in which, the row item has two textview. The second textview is for the name of companies and the first textview is for the starting letter of the companies. How this can be achieved ? Need help !!
public class ExampleAdapter extends BaseAdapter {
Context context;
ArrayList<ExamplePojo> items = new ArrayList<>();
public ExampleAdapter(Context context, ArrayList<ExamplePojo> items) {
this.context = context;
this.items = items;
}
#Override
public int getCount() {
return items.size();
}
#Override
public Object getItem(int position) {
return items.get(position);
}
#Override
public long getItemId(int position) {
return items.indexOf(items.get(position));
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (convertView == null){
convertView = inflater.inflate(R.layout.example_row_item, null);
holder = new ViewHolder();
holder.txtSentence = (TextView) convertView.findViewById(R.id.txtSentence);
holder.txtInitialLetter = (TextView) convertView.findViewById(R.id.txtInitialLetter);
convertView.setTag(holder);
}
else {
holder = (ViewHolder) convertView.getTag();
}
final ExamplePojo pojo = items.get(position);
holder.txtSentence.setText(pojo.getSentence());
holder.txtInitialLetter.setText(pojo.getSentence().charAt(0));
return convertView;
}
public class ViewHolder{
TextView txtSentence, txtInitialLetter;
}
}
Here is code for showing first character in first textview and complete name in other textview.Also put below lines in if(convertView== null) block
final ExamplePojo pojo = items.get(position);
holder.txtSentence.setText(pojo.getSentence());
holder.txtInitialLetter.setText(pojo.getSentence().substring(0, 1));

How to set visibility true of one button onclick of another button in listView - android

I want to set the visibility to true of btnProductDetailMinus to true on click of btnProductDetailAddToCart. This is my code.
public class ProductVariantAdapter extends BaseAdapter {
private Context context;
private ArrayList<ProductVariant> productVariants = new ArrayList<>();
/*private view holder class*/
private class ViewHolder {
TextView productVariantName;
TextView productVariantMrp;
TextView productVariantSellPrice;
Button btnProductDetailAddToCart, btnProductDetailPlus, btnProductDetailQty, btnProductDetailMinus;
}
public ProductVariantAdapter(Context context, ArrayList<ProductVariant> productVariants) {
this.context = context;
this.productVariants = productVariants;
}
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
LayoutInflater mInflater = (LayoutInflater)
context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
if (convertView == null) {
convertView = mInflater.inflate(R.layout.listview_product_variant, null);
holder = new ViewHolder();
holder.productVariantName = (TextView) convertView.findViewById(R.id.productVariantName);
holder.productVariantMrp = (TextView) convertView.findViewById(R.id.productVariantMrp);
holder.productVariantSellPrice = (TextView) convertView.findViewById(R.id.productVariantSellPrice);
holder.btnProductDetailAddToCart = (Button) convertView.findViewById(R.id.btnProductDetailAddToCart);
holder.btnProductDetailPlus = (Button)convertView.findViewById(R.id.btnProductDetailPlus);
holder.btnProductDetailQty = (Button)convertView.findViewById(R.id.btnProductDetailQty);
holder.btnProductDetailMinus = (Button)convertView.findViewById(R.id.btnProductDetailMinus);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
ProductVariant productVariant = (ProductVariant) getItem(position);
if (productVariant != null) {
holder.productVariantName.setText(productVariant.getVariant().getVariantName());
holder.productVariantMrp.setText(productVariant.getMrp().toString());
holder.productVariantSellPrice.setText(productVariant.getSellPrice().toString());
holder.btnProductDetailAddToCart.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Utility.displayToast("wonderful");
holder.btnProductDetailMinus.setVisibility(View.VISIBLE);
}
});
} else {
Toast.makeText(context, "product variant is null", Toast.LENGTH_SHORT).show();
}
return convertView;
}
#Override
public int getCount() {
return productVariants.size();
}
#Override
public Object getItem(int position) {
return productVariants.get(position);
}
#Override
public long getItemId(int position) {
return productVariants.indexOf(getItem(position));
}
}
But its giving error "Variable holder is accessed from within inner class. needs to declared final"
This is the line giving error.
holder.btnProductDetailMinus.setVisibility(View.VISIBLE);
How to fix this?
Assign holder tot a final variabele:
final ViewHolder fHolder = holder;
Then in onClick:
fHolder.btnProductDetailMinus.setVisibility(View.VISIBLE);
Move this line out of the if (convertView == null)
holder = new ViewHolder();
You can declare final ViewHolder holder = null; instead of ViewHolder holder = null;
You can just make it a Global variable.
set public static class ViewHolder instead of private class ViewHolder
One of the approach that I follow is create a Map or List in Adapter class and onClick set value and that collection, and in adapter getView method check for the collection value. .
Checkout the below code.
public class ProductVariantAdapter extends BaseAdapter {
private Context context;
private ArrayList<ProductVariant> productVariants = new ArrayList<>();
private Map<Integer, Boolean> visibilityMap = new HashMap<>();
/*private view holder class*/
private class ViewHolder {
TextView productVariantName;
TextView productVariantMrp;
TextView productVariantSellPrice;
Button btnProductDetailAddToCart, btnProductDetailPlus, btnProductDetailQty, btnProductDetailMinus;
}
public ProductVariantAdapter(Context context, ArrayList<ProductVariant> productVariants) {
this.context = context;
this.productVariants = productVariants;
}
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
LayoutInflater mInflater = (LayoutInflater)
context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
if (convertView == null) {
convertView = mInflater.inflate(R.layout.listview_product_variant, null);
holder = new ViewHolder();
holder.productVariantName = (TextView) convertView.findViewById(R.id.productVariantName);
holder.productVariantMrp = (TextView) convertView.findViewById(R.id.productVariantMrp);
holder.productVariantSellPrice = (TextView) convertView.findViewById(R.id.productVariantSellPrice);
holder.btnProductDetailAddToCart = (Button) convertView.findViewById(R.id.btnProductDetailAddToCart);
holder.btnProductDetailPlus = (Button)convertView.findViewById(R.id.btnProductDetailPlus);
holder.btnProductDetailQty = (Button)convertView.findViewById(R.id.btnProductDetailQty);
holder.btnProductDetailMinus = (Button)convertView.findViewById(R.id.btnProductDetailMinus);
holder.btnProductDetailAddToCart.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int position = (int)v.getTag();
Utility.displayToast("wonderful");
visibilityMap.put(position, true);
notifyDataSetChanged();
}
});
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.btnProductDetailAddToCart.setTag(position);
ProductVariant productVariant = (ProductVariant) getItem(position);
if (productVariant != null) {
holder.productVariantName.setText(productVariant.getVariant().getVariantName());
holder.productVariantMrp.setText(productVariant.getMrp().toString());
holder.productVariantSellPrice.setText(productVariant.getSellPrice().toString());
} else {
Toast.makeText(context, "product variant is null", Toast.LENGTH_SHORT).show();
}
if(visibilityMap.containsKey(position) && visibilityMap.get(position)){
holder.btnProductDetailMinus.setVisibility(View.VISIBLE);
}else{
holder.btnProductDetailMinus.setVisibility(View.GONE);
}
return convertView;
}
#Override
public int getCount() {
return productVariants.size();
}
#Override
public Object getItem(int position) {
return productVariants.get(position);
}
#Override
public long getItemId(int position) {
return productVariants.indexOf(getItem(position));
}
}

notifyDatasetChanged is not working inside the getView() method for Custom Adapter In Android

I am basically trying hide and show a text in the list row when I am clicking a button in the list row. I have added the onClick() for the button inside getView() method and then calling the notifyDataSetChanged(). But it is not working. No change in the text visibility. Here is my custom Adapter code:
public class ListAdapter extends BaseAdapter {
private Context context;
private List<String> mListQuestion = null;
private List<String> mListAnswer = null;
ViewHolder holder = null;
boolean flag = false;
public ListAdapter(Context context, List<String> question, List<String> answer ) {
this.mListQuestion = question;
this.mListAnswer = answer;
this.context = context;
}
#Override
public Object getItem(int position)
{
return mListQuestion.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public int getCount() {
return mListQuestion.size();
}
#Override
#SuppressWarnings("deprecation")
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null)
{
LayoutInflater vi = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = vi.inflate(R.layout.list_faq_item, null);
holder = new ViewHolder();
holder.tvQuestion = (TextView) convertView.findViewById(R.id.text);
holder.tvAns = (TextView) convertView.findViewById(R.id.anstext);
holder.ivArrow = (Button)convertView.findViewById(R.id.arrow_expand);
convertView.setTag(holder);
}
else
{
holder = (ViewHolder) convertView.getTag();
}
holder.tvQuestion.setText(mListQuestion.get(position));
holder.tvAns.setText(mListAnswer.get(position));
holder.ivArrow.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View v)
{
if (flag == false)
{
Logger.d("arrow clicked when flag is false");
holder.tvAns.setVisibility(View.VISIBLE);
holder.ivArrow.setBackgroundResource(R.drawable.up_arrow);
flag = true;
}
else if (flag == true)
{
Logger.d("arrow clicked when flag is true");
holder.tvAns.setVisibility(View.GONE);
holder.ivArrow.setBackgroundResource(R.drawable.down_arrow);
flag = false;
}
notifyDataSetChanged();
}
});
return convertView;
}
static class ViewHolder {
TextView tvQuestion;
TextView tvAns;
Button ivArrow;
}
}
Can someone please tell what I am doing wrong here.
Thanks in Advance.
-Arindam.
public class ListAdapter extends BaseAdapter {
private Context context;
private List<String> mListQuestion = null;
private List<String> mListAnswer = null;
ViewHolder holder = null;
private List<Boolean> textViewVisibileState;
public ListAdapter(Context context, List<String> question, List<String> answer ) {
this.mListQuestion = question;
this.mListAnswer = answer;
this.context = context;
this.textViewVisibileState=new ArrayList<>(Arrays.asList(new Boolean[getCount()]));
Collections.fill(this.textViewVisibileState,false);
}
#Override
public Object getItem(int position)
{
return mListQuestion.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public int getCount() {
return mListQuestion.size();
}
#Override
#SuppressWarnings("deprecation")
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null)
{
LayoutInflater vi = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = vi.inflate(R.layout.list_faq_item, null);
holder = new ViewHolder();
holder.tvQuestion = (TextView) convertView.findViewById(R.id.text);
holder.tvAns = (TextView) convertView.findViewById(R.id.anstext);
holder.ivArrow = (Button)convertView.findViewById(R.id.arrow_expand);
convertView.setTag(holder);
}
else
{
holder = (ViewHolder) convertView.getTag();
}
holder.tvQuestion.setText(mListQuestion.get(position));
holder.tvAns.setText(mListAnswer.get(position));
if(textViewVisibileState.get(position))
{
holder.tvAns.setVisibility(View.GONE);
holder.ivArrow.setBackgroundResource(R.drawable.down_arrow);
}
else
{
Logger.d("arrow clicked when flag is false");
holder.tvAns.setVisibility(View.VISIBLE);
holder.ivArrow.setBackgroundResource(R.drawable.up_arrow);
}
holder.ivArrow.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View v)
{
if (textViewVisibileState.get(position))
{
textViewVisibileState.set(position,false);
}
else
{
textViewVisibileState.set(position,true);
}
notifyDataSetChanged();
}
});
return convertView;
}
static class ViewHolder {
TextView tvQuestion;
TextView tvAns;
Button ivArrow;
}
}
This will work.
The variable flag is not context sensitive to object holder. So flag is always = false in your case. How about setVisibility(View.GONE) initially? And then setVisibility(View.VISIBLE) ONLY when ivArrow is clicked upon.
Calling notifyDataSetChanged() causes ListView to rebuild everything. It will remove its child views, call getView() for all the items that are visible, and thus you will rebind all the data for those items.
But your data hasn't actually changed. You haven't modified anything in the questions list, so binding the data again is meaningless. Instead you have tried to change something in your ViewHolder object, but there's no guarantee that the convertView you get after a notifyDataSetChanged() is for the same position as before, so it's possible that some other item has been affected (or perhaps none at all?).
Try removing the call to notifyDataSetChanged() from the OnClickListener. A visibility change should cause a re-layout of the view hierarchy, but as long as you haven't told ListView that the data has changed, it should keep all its current children.
Create an instance of the adapter, e.g Adapter myAdapter = new Adapter, set it to a listview or recyclerview e.g listview.setAdapter(mydapter) and everytime you add new data to it call adapter.notifyDataSetChanged()

Android, dynamic Listview layout changing on scroll

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;
}
}

How to add a dynamic view to a ListView item at runtime?

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.

Categories

Resources