Android Listview ArrayAdapter with two layouts - android

I am quite new to Android. In the following class the data is retrieved from the database and displayed in a ListView which has two different layouts.
Though it works as expected, the problem is that scrolling is not smooth because the textviews are assigned again and again. I couldn't figure out how to have them assigned only once. Please somebody help me out with this.
Thanks in advance. My apology for the code, I know it looks bad.
public class FragmentVerses extends ListFragment {
Typeface font;
ViewHolder viewHolder = new ViewHolder();
ViewHolderHeader viewHolderHeader = new ViewHolderHeader();
DatabaseHelper db;
public List<VersesModel> verses;
public List<ChapterModel> chapterName;
ArrayAdapter<VersesModel> adapter;
public FragmentVerses() {
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.verses_fragment, container, false);
db = new DatabaseHelper(getActivity());
try {
db.createDatabase();
} catch (IOException e) {
Toast.makeText(getActivity(), "Error Creating Database", Toast.LENGTH_LONG)
.show();
}
verses = db.getVerses(" WHERE " + getActivity().getIntent().getStringExtra(MainActivity.CONDITION));
chapterName = db.getChapter();
adapter = new MyListAdapter();
setListAdapter(adapter);
return view;
}
private class MyListAdapter extends ArrayAdapter<VersesModel> {
public MyListAdapter() {
super(getActivity(), R.layout.verses_custom_list, verses);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
VersesModel currentVerse = verses.get(position);
if (convertView == null) {
convertView = getActivity().getLayoutInflater().inflate(
R.layout.verses_custom_list, parent, false);
font = Typeface.createFromAsset(convertView.getContext().getAssets(), "Quran_Taha.ttf");
viewHolder.textView = (TextViewEx) convertView.findViewById(R.id.textView_Verse);
viewHolder.textViewTranslation = (TextView) convertView.findViewById(R.id.textView_VerseTranslation);
viewHolder.nView = (TextView) convertView.findViewById(R.id.textView_verseNumber);
viewHolder.textView.setTypeface(font);
viewHolder.imageView = (ImageView) convertView.findViewById(R.id.versesImageView);
convertView.setTag(viewHolder);
} else {
if (currentVerse.getVerseNumber() != 0) {
convertView = getActivity().getLayoutInflater().inflate(
R.layout.verses_custom_list, parent, false);
viewHolder.textView = (TextViewEx) convertView.findViewById(R.id.textView_Verse);
viewHolder.nView = (TextView) convertView.findViewById(R.id.textView_verseNumber);
viewHolder.textViewTranslation = (TextView) convertView.findViewById(R.id.textView_VerseTranslation);
viewHolder.textView.setTypeface(font);
viewHolder.textView.setText(currentVerse.getVerseText() + "", true);
viewHolder.textViewTranslation.setText(currentVerse.getVerseTranslation());
viewHolder.nView.setText(currentVerse.getVerseNumber() + "");
convertView.setTag(viewHolder);
} else {
convertView = getActivity().getLayoutInflater().inflate(
R.layout.verses_custom_list_header, parent, false);
ChapterModel chapterModel = chapterName.get(currentVerse.getChapterNumber() - 1);
if (viewHolderHeader.textViewChapter == null) viewHolderHeader.textViewBismillah = (TextView) convertView.findViewById(R.id.textView_Verse_Bismillah);
viewHolderHeader.textViewChapter = (TextView) convertView.findViewById(R.id.textView_Verse_ChapterName);
viewHolderHeader.textViewChapter.setText("سورة " + chapterModel.getChapterText());
viewHolderHeader.textViewBismillah.setTypeface(font);
viewHolderHeader.textViewChapter.setTypeface(font);
} else {
viewHolderHeader = (ViewHolderHeader) convertView.getTag();
}
convertView.setTag(viewHolderHeader);
}
}
return convertView;
}
}

I know this post is old, but just for future reference...
In this case you should use the ViewHolder pattern.
If you want to use 2 layouts just create two ViewHolder's and switch between then in the getView method.
Pretty similar to the accepted answer but with better performance.
Declare the view types.
private final int VIEW_TYPE_EXAMPLE = 0;
private final int VIEW_TYPE_EXAMPLE_TWO = 1;
Return as many types as you declared above.
#Override
public int getViewTypeCount() {
return 2;
}
Switch when returning the viewType when the item is at position X. in this case I only change the type when the item is the first on the list.
#Override
public int getItemViewType(int position) {
return (position == 0) ? VIEW_TYPE_EXAMPLE : VIEW_TYPE_EXAMPLE_TWO;
}
Create the view holders matching your layouts. They will hold your data.
class SecondViewHolder {
TextView mDate;
TextView mDescription;
TextView mObservations;
public SecondViewHolder(View view) {
mDate = (TextView) view.findViewById(R.id.txt_date);
mDescription = (TextView) view.findViewById(R.id.txt_description);
mObservations = (TextView) view.findViewById(R.id.txt_observations);
}
}
class FirstViewHolder {
ImageView mPhoto;
TextView mName;
TextView mAge;
public FirstViewHolder(View view) {
mPatientPhoto = (ImageView)view.findViewById(R.id.img_photo);
mPatientName = (TextView)view.findViewById(R.id.txt_name);
mPatientAge = (TextView)view.findViewById(R.id.txt_age);
}
}
Switch between then in the getView method.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
int viewType = getItemViewType(position);
switch (viewType) {
case VIEW_TYPE_EXAMPLE: {
FirstViewHolder firstViewHolder = null;
if(convertView == null){
convertView = LayoutInflater.from(getContext()).inflate(R.layout.item_example, parent, false);
firstViewHolder = new FirstViewHolder(convertView);
convertView.setTag(firstViewHolder);
}
else firstViewHolder = (FirstViewHolder)convertView.getTag();
firstViewHolder.mName.setText("Your name");
firstViewHolder.mAge.setText("20 years old");
break;
}
case VIEW_TYPE_EXAMPLE_TWO: {
SecondViewHolder holder = null;
if(convertView == null){
convertView = LayoutInflater.from(getContext()).inflate(R.layout.item_example_two, parent, false);
holder = new SecondViewHolder(convertView);
convertView.setTag(holder);
}
else holder = (SecondViewHolder)convertView.getTag();
holder.mDate.setText("01/01/2016");
holder.mDescription.setText("Description");
holder.mObservations.setText("Obs");
break;
}
}
return convertView;
}
But I can not ignore the fact that in this specific question, you should use the CursorAdapter because you are querying from a database.
You also should not do the access to the database directly.
Should create a Loader instead (does the async task but not tied to the activity).
And if you want to follow best practices and save some trouble later on, create the ContentProvider to manage your SQLite database.
But that's just too much code for me to put in this answer :/
Hope this helps someone.

Android's adapter provide a way to use multiple layouts in a single adapter.
First, tell your adapter how many layouts you need:
public int getViewTypeCount()
{
return 2;
}
Then, gives some logic to tell which layout should be used for the current item:
public int getItemViewType(int position)
{
if (verses.get(position).getVerseNumber() != 0)
{
return 0;
}
return 1;
}
Finally, in your build the appropriate view:
public View getView(int position, View convertView, ViewGroup parent)
{
if (this.getItemViewType(position) == 0)
{
// TODO Build the appropriate view
return view;
}
// TODO Build the appropriate other view
return view;
}

There are two many things to changes have a look at code below it will give you idea. You dont have to inflate layout everytime and no need to call findViewById everytime as well look at sample code below
ViewHolder holder;
if ((convertView == null)) {
convertView = layoutInflater
.inflate(R.layout.list_item,
viewGroup, false);
holder = new ViewHolder();
holder.itemImage = (ImageView) convertView
.findViewById(R.id.logo);
holder.itemName = ((TextView) convertView
.findViewById(R.id.title_product_search));
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
//assign text and images to controls after this
holder.itemName.setText("text");
imageLoader.displayImage(item.imageUrl, holder.itemImage,
options);

Here is what i did.
private class MyListAdapter extends ArrayAdapter<VersesModel> {
public MyListAdapter() {
super(getActivity(), R.layout.verses_custom_list, verses);
}
#Override
public int getViewTypeCount() {
return 2;
}
#Override
public int getItemViewType(int position) {
if (verses.get(position).getVerseNumber() != 0) {
return 0;
}
return 1;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
VersesModel currentVerse = verses.get(position);
if (convertView == null) {
viewHolder = new ViewHolder();
switch (getItemViewType(position)) {
case 0:
convertView = getActivity().getLayoutInflater().inflate(
R.layout.verses_custom_list, parent, false);
viewHolder.textView = (TextViewEx) convertView.findViewById(R.id.textView_Verse);
viewHolder.nView = (TextView) convertView.findViewById(R.id.textView_verseNumber);
viewHolder.textViewTranslation = (TextView) convertView.findViewById(R.id.textView_VerseTranslation);
font = Typeface.createFromAsset(convertView.getContext().getAssets(), "Quran_Taha.ttf");
viewHolder.textView.setTypeface(font);
Toast.makeText(getActivity(), "" + position, Toast.LENGTH_SHORT).show();
break;
case 1:
convertView = getActivity().getLayoutInflater().inflate(
R.layout.verses_custom_list_header, parent, false);
chapterModel = chapterName.get(currentVerse.getChapterNumber() - 1);
viewHolder.textView = (TextViewEx) convertView.findViewById(R.id.textView_Verse_Bismillah);
viewHolder.nView = (TextView) convertView.findViewById(R.id.textView_Verse_ChapterName);
font = Typeface.createFromAsset(convertView.getContext().getAssets(), "Quran_Taha.ttf");
viewHolder.textView.setTypeface(font);
viewHolder.nView.setTypeface(font);
break;
}
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
switch (getItemViewType(position)) {
case 0:
viewHolder.textView.setText(currentVerse.getVerseText() + "", true);
viewHolder.textViewTranslation.setText(currentVerse.getVerseTranslation());
viewHolder.nView.setText(currentVerse.getVerseNumber() + "");
break;
case 1:
viewHolder.nView.setText("سورة " + chapterModel.getChapterText());
break;
}
return convertView;
}
}
The toast displays 3 times when the list loads and the scrolling is slower at the beginning.

Related

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

Convert View And Multiple Types BaseAdapter

I have created an adapter that feeds a list view. It is working ok but is a little jenky so I am trying to use the Convert View to avoid inflating a view on every getView call. I changed the getview method to the following
#Override
public View getView(int position, View convertView, ViewGroup parent) {
Suggestion suggestion = getItem(position);
int type = getItemViewType(position);
ImageView imageView;
TextView titleTv;
ImageView checkBox;
switch (type) {
case Suggestion.CONTACT_SUGGESTION_ID:
ContactSuggestion contactSuggestion = (ContactSuggestion) suggestion;
String id = contactSuggestion.getId();
if (convertView == null) {
convertView = layoutInflater.inflate(R.layout.contact_history_item, parent, false);
}
imageView = (ImageView) convertView.findViewById(R.id.imageView);
PhotoRetreiver photoRetreiver = (PhotoRetreiver) imageView.getTag();
if (photoRetreiver != null) {
photoRetreiver.cancel(true);
}
if (images.containsKey(id)) {
imageView.setImageBitmap(images.get(id));
} else {
photoRetreiver = new PhotoRetreiver(context, id, imageView);
imageView.setTag(photoRetreiver);
photoRetreiver.execute();
}
titleTv = (TextView) convertView.findViewById(R.id.titleTv);
titleTv.setText(suggestion.getTitle());
checkBox = (ImageView) convertView.findViewById(R.id.favouriteIv);
checkBox.setOnClickListener(this);
checkBox.setId(position);
if (suggestion.isFavourite()) {
checkBox.setBackgroundResource(R.drawable.search_fav_active);
checkBox.setTag(true);
} else {
checkBox.setTag(false);
}
break;
case Suggestion.GOOGLE_SUGGESTION_ID:
if (convertView == null) {
convertView = layoutInflater.inflate(R.layout.google_history_item, parent, false);
}
imageView = (ImageView) convertView.findViewById(R.id.imageView);
titleTv = (TextView) convertView.findViewById(R.id.titleTv);
imageView.setImageResource(resources.getIdentifier(suggestion.getInActiveImageResource(), "drawable", "com.allryder.android"));
titleTv.setText(suggestion.getTitle());
checkBox = (ImageView) convertView.findViewById(R.id.favouriteIv);
checkBox.setOnClickListener(this);
checkBox.setId(position);
if (suggestion.isFavourite()) {
checkBox.setBackgroundResource(R.drawable.search_fav_active);
checkBox.setTag(true);
} else {
checkBox.setTag(false);
}
break;
case Suggestion.STATION_SUGGESTION_ID:
if (convertView == null) {
convertView = layoutInflater.inflate(R.layout.station_history_item, parent, false);
}
StationSuggestion stationSuggestion = (StationSuggestion) suggestion;
StationView stationView = (StationView) convertView.findViewById(R.id.stationView);
stationView.setStationTypes(stationSuggestion.getStationTypes());
titleTv = (TextView) convertView.findViewById(R.id.titleTv);
titleTv.setText(suggestion.getTitle());
checkBox = (ImageView) convertView.findViewById(R.id.favouriteIv);
checkBox.setOnClickListener(this);
checkBox.setId(position);
if (suggestion.isFavourite()) {
checkBox.setBackgroundResource(R.drawable.search_fav_active);
checkBox.setTag(true);
} else {
checkBox.setTag(false);
}
break;
case Suggestion.CURRENT_LOCATION_SUGGESTION_ID:
if (convertView == null) {
convertView = layoutInflater.inflate(R.layout.current_location_item, parent, false);
}
titleTv = (TextView) convertView.findViewById(R.id.titleTv);
titleTv.setText(suggestion.getTitle());
break;
}
TextView detailTv = (TextView) convertView.findViewById(R.id.detailTv);
detailTv.setText(suggestion.getDescription());
convertView.setTag(type);
return convertView;
}
Then overwrote the following to methods so the adapter knows which view type is should pass into the getview method
#Override
public int getViewTypeCount() {
return 4;
}
#Override
public int getItemViewType(int position) {
return getItem(position).getTypeId();
}
However I receive a a null pointer on this line
checkBox.setOnClickListener(this);
So checkbox is not instantiated.
You can see that I added a tag to the convert view this was just to check that the tag would equal the type when convert view is not null. And it always is.
Why the hell can the adapter not find checkbox?

List items position repeating in getview

I am creating a custom list view using baseadapter.i have 10 list item in my list.my problem is that afetr 6 items ,the first 4 are repeating.i just printed position values in getview.it gives 0,1,2,3,4,5,6,7,8,9,0,1,2,3.My code is below.
thanx in advance
public class ProductListAdapter extends BaseAdapter implements OnClickListener{
/*
* developer :sanu
* date :10-4-2013
* time :3.34 pm
*/
public View row;
private String[] productName;
private String[] producttype;
private String[] priceRangeFrom;
private String[] priceRangeTo;
private String[] productImage;
private Activity activity;
private static LayoutInflater inflater=null;
static String posClicked;
ViewHolder holder;
Integer height1;
Integer width1;
Typeface tf;
Integer FirmaCount;
public ImageLoader imageLoader;
public ProductListAdapter(Activity a,String[] name,String[] type,String[] price_from,String[] price_to,String[] image,Typeface tf) {
activity = a;
productName = name;
producttype = type;
priceRangeFrom = price_from;
priceRangeTo = price_to;
productImage = image;
inflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
imageLoader=new ImageLoader(activity.getApplicationContext());
}
public int getCount() {
return productName.length;
}
public Object getItem(int position) {
return position;
}
public long getItemId(int position) {
return position;
}
public int getViewTypeCount (int position)
{
return position;
}
public static class ViewHolder{
public TextView nameProduct;
public TextView typeProduct;
public TextView priceRangeProduct;
public ImageView productImage;
public ImageView plusImage;
public RelativeLayout mainLayout;
public int position;
}
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null){
convertView = inflater.inflate(R.layout.product_list_details,parent, false);
holder=new ViewHolder();
holder.nameProduct =(TextView)convertView.findViewById(R.id.name);
holder.typeProduct =(TextView)convertView.findViewById(R.id.product);
holder.priceRangeProduct =(TextView)convertView.findViewById(R.id.pricerange);
holder.productImage =(ImageView)convertView.findViewById(R.id.image);
holder.plusImage =(ImageView)convertView.findViewById(R.id.dot);
holder.mainLayout = (RelativeLayout)convertView.findViewById(R.id.mainlayout);
holder.nameProduct.setText(productName[position]);
if(producttype[position].length()>18)
{
holder.typeProduct.setText(producttype[position].substring(0,18)+"...");
}
else
{
holder.typeProduct.setText(producttype[position]);
}
holder.priceRangeProduct.setText(priceRangeFrom[position].substring(0,priceRangeFrom[position].length()-2)+" To "+priceRangeTo[position].substring(0, priceRangeTo[position].length()-2));
imageLoader.DisplayImage(productImage[position], holder.productImage);
convertView.setTag(holder);
}
else
{
holder = (ViewHolder)convertView.getTag();
}
holder.plusImage.setTag(Integer.toString(position));
holder.plusImage.setOnClickListener(this);
holder.mainLayout.setTag(Integer.toString(position));
holder.mainLayout.setOnClickListener(this);
return convertView;
}
This sounds like a case of View re-cyclcing. Android will pass a pre-populated view to the getView method. It does so to minimize object creation. When an existing row-view is scrolled off screen, Android might try to recycle that view to display a row that is now on-screen. You need to account for the fact that this view may have been used to display data for another row (which is now off screen).
You have the following line
holder.typeProduct.setText
within the following conditional:
if(convertView == null){
Move that line outside of the conditional, and all should be well.
It's like EJK said. You are not recycling your view correctly. Change your code to this and notice where I put the setText calls
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null){
convertView = inflater.inflate(R.layout.product_list_details,parent, false);
holder=new ViewHolder();
holder.nameProduct =(TextView)convertView.findViewById(R.id.name);
holder.typeProduct =(TextView)convertView.findViewById(R.id.product);
holder.priceRangeProduct =(TextView)convertView.findViewById(R.id.pricerange);
holder.productImage =(ImageView)convertView.findViewById(R.id.image);
holder.plusImage =(ImageView)convertView.findViewById(R.id.dot);
holder.mainLayout = (RelativeLayout)convertView.findViewById(R.id.mainlayout);
convertView.setTag(holder);
}
else
{
holder = (ViewHolder)convertView.getTag();
}
holder.plusImage.setTag(Integer.toString(position));
holder.plusImage.setOnClickListener(this);
holder.mainLayout.setTag(Integer.toString(position));
holder.mainLayout.setOnClickListener(this);
//setText functions are here
holder.nameProduct.setText(productName[position]);
if(producttype[position].length()>18)
{
holder.typeProduct.setText(producttype[position].substring(0,18)+"...");
}
else
{
holder.typeProduct.setText(producttype[position]);
}
holder.priceRangeProduct.setText(priceRangeFrom[position].substring(0,priceRangeFrom[position].length()-2)+" To "+priceRangeTo[position].substring(0, priceRangeTo[position].length()-2));
imageLoader.DisplayImage(productImage[position], holder.productImage);
return convertView;
}
Change your getView to
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null){
convertView = inflater.inflate(R.layout.product_list_details,parent, false);
holder=new ViewHolder();
holder.nameProduct =(TextView)convertView.findViewById(R.id.name);
holder.typeProduct =(TextView)convertView.findViewById(R.id.product);
holder.priceRangeProduct =(TextView)convertView.findViewById(R.id.pricerange);
holder.productImage =(ImageView)convertView.findViewById(R.id.image);
holder.plusImage =(ImageView)convertView.findViewById(R.id.dot);
holder.mainLayout = (RelativeLayout)convertView.findViewById(R.id.mainlayout);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.nameProduct.setText(productName[position]);
if(producttype[position].length()>18)
{
holder.typeProduct.setText(producttype[position].substring(0,18)+"...");
}
else
{
holder.typeProduct.setText(producttype[position]);
}
holder.priceRangeProduct.setText(priceRangeFrom[position].substring(0,priceRangeFrom[position].length()-2)+" To "+priceRangeTo[position].substring(0, priceRangeTo[position].length()-2));
imageLoader.DisplayImage(productImage[position], holder.productImage);
holder.plusImage.setTag(Integer.toString(position));
holder.plusImage.setOnClickListener(this);
holder.mainLayout.setTag(Integer.toString(position));
holder.mainLayout.setOnClickListener(this);
return convertView;
}
Also check this
How ListView's recycling mechanism works
Change getView()
Declare ViewHolder before if (convertView == null)
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = inflater.inflate(R.layout.product_list_details,
parent, false);
holder = new ViewHolder();
holder.nameProduct = (TextView) convertView.findViewById(R.id.name);
holder.typeProduct = (TextView) convertView
.findViewById(R.id.product);
holder.priceRangeProduct = (TextView) convertView
.findViewById(R.id.pricerange);
holder.productImage = (ImageView) convertView
.findViewById(R.id.image);
holder.plusImage = (ImageView) convertView.findViewById(R.id.dot);
holder.mainLayout = (RelativeLayout) convertView
.findViewById(R.id.mainlayout);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.nameProduct.setText(productName[position]);
if (producttype[position].length() > 18) {
holder.typeProduct.setText(producttype[position].substring(0, 18)
+ "...");
} else {
holder.typeProduct.setText(producttype[position]);
}
holder.priceRangeProduct.setText(priceRangeFrom[position].substring(0,
priceRangeFrom[position].length() - 2)
+ " To "
+ priceRangeTo[position].substring(0,
priceRangeTo[position].length() - 2));
imageLoader.DisplayImage(productImage[position], holder.productImage);
holder.plusImage.setTag(Integer.toString(position));
holder.plusImage.setOnClickListener(this);
holder.mainLayout.setTag(Integer.toString(position));
holder.mainLayout.setOnClickListener(this);
return convertView;
}

ListView with two different layout on scroll getting null pointer exception

I am using ListView with two different row layouts. The data is set and displayed properly but when i scroll up its getting null pointer exception. In logs its pointing to holder.mainContainer as null pointer, I had added comment in code where i am getting null pointer exception.
public static class ViewHolder {
....
public TextView title;
public RelativeLayout mainContainer;
public LinearLayout categoryContainer;
public TextView submenuCategoryTitle;
....
}
public View getView(int position, View convertView, ViewGroup parent) {
View vi = convertView;
ViewHolder holder;
final MultiTierMenu multiTierMenu = mMultiTierMenuList.get(position);
if (convertView == null) {
holder = new ViewHolder();
if(multiTierMenu instanceof SubMenu) {
vi = inflater.inflate(R.layout.event_list_snippet),
null);
....
holder.mainContainer = (RelativeLayout) vi.findViewById(R.id. snippet_container);
holder.title = (TextView) vi.findViewById(R.id.list_title);
....
vi.setTag(holder);
}
else if(multiTierMenu instanceof MenuCategory) {
vi = inflater.inflate(R.layout.submenu_category_list_item),
null);
....
holder.categoryContainer = (LinearLayout) vi.findViewById(R.id.category_container);
holder.submenuCategoryTitle = (TextView) vi.findViewById(R.id.subcategory_title);
....
vi.setTag(holder);
}
else
holder = (ViewHolder) vi.getTag();
//SubMenu
if(multiTierMenu instanceof SubMenu) {
final SubMenu subMenu = (SubMenu) multiTierMenu;
//GETTING NULL POINTER EXCEPTION HERE ON SCROLL UP
//if(null != holder.mainContainer) {
if(subMenu.getIsVisibleMenu())
==> holder.mainContainer.setVisibility(View.VISIBLE);
else
holder.mainContainer.setVisibility(View.GONE);
//}
//ADDING NULL CHECK ABOVE - GETTING NULL POINTER EXCEPTION HERE
holder.title.setText(subMenu.getTitle());
....
}
else if(multiTierMenu instanceof MenuCategory) {
final MenuCategory menuCategory = (MenuCategory) multiTierMenu;
holder.submenuCategoryTitle.setText(menuCategory.getTitle());
....
}
return vi;
}
Did you implement following two methods?
getItemViewType
you have to return each number by position
#Override
public int getItemViewType(int position) {
if (position % 2 == 0) {// example
return 0;// MenuCategory
}
else {
return 1;// SubMenu???
}
}
getViewTypeCount
you have to return the number of views
#Override
public int getViewTypeCount() {
return 2;
}
if their methods are implemented, "inflate" is needed only in (convertView == null).
like following...
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
int type = getItemViewType(position);
if (convertView == null){
holder = new ViewHolder();
switch (type) {
case 0:// MenuCategory
convertView = inflater.inflate(R.layout.event_list_snippet, null);
holder.mainContainer = (RelativeLayout) convertView.findViewById(R.id. snippet_container);
holder.title = (TextView) convertView.findViewById(R.id.list_title);
break;
case 1:// SubMenu???
convertView = inflater.inflate(R.layout.submenu_category_list_item, null);
holder.categoryContainer = (LinearLayout) convertView.findViewById(R.id.category_container);
holder.submenuCategoryTitle = (TextView) convertView.findViewById(R.id.subcategory_title);
break;
}
convertView.setTag(holder);
}
else {
holder = (ViewHolder) convertView.getTag();
}
// assign data into view
switch (type) {
case 0:// MenuCategory
holder.submenuCategoryTitle.setText(menuCategory.getTitle());
break;
case 1:// SubMenu???
holder.title.setText(subMenu.getTitle());
break;
}

3 holders inside listview and Performance

I Have a 3 holders. 1 holder for 1 item.
Method getView looks:
public View getView(int position, View convertView, ViewGroup parent) {
mCursor.moveToPosition(position);
int type = checkDialogType(mCursor);
Holder holder = null;
if (convertView != null){
holder = (Holder)convertView.getTag(type);
if (holder == null){
holder = createHolderByType(type, parent);
}
} else {
holder = createHolderByType(type, parent);
}
return holder.fillView(mCursor, position); //convertView
}
Where content of createHolderByType():
public Holder createHolderByType(int type, ViewGroup parent){
View v;
if (type == IN_TYPE){
v = mInflater.inflate(R.layout.dialog_incoming_item, parent, false);
return new InHolder(v, mCursor, mContext, this, IN_TYPE);
} else if (type == OUT_TYPE){
v = mInflater.inflate(R.layout.dialogs_outcoming_item, parent,false);
return new OutHolder(v, mCursor, mContext, this, OUT_TYPE);
} else {
v = mInflater.inflate(R.layout.dialogs_chat_item, parent, false);
return new ChatHolder(v, mCursor, mContext, this, CHAT_TYPE);
}
}
It works fine, But for 39 items in listView method createHolderByType was called 19 times. For create a holder I need inflate xml. It's very expensive. If i trying use same view for holder or saving holder in memory - it doesn't work. Can i increase performance with some tricks?
UPD:
There is one holder:
public class InHolder extends Holder {
private View baseView;
private TextView fio;
private TextView message;
private TextView date;
private ImageView isOnline;
private int type;
private ImageView senderIMG;
public InHolder(View v, Cursor dialogCursor, Context context, DialogCurAdapter adapter, int type){
super(dialogCursor, context, adapter);
fio = (TextView)v.findViewById(R.id.fio);
senderIMG = (ImageView)v.findViewById(R.id.sender_image);
message = (TextView)v.findViewById(R.id.message_preview);
date = (TextView)v.findViewById(R.id.date);
isOnline = (ImageView)v.findViewById(R.id.isonline);
this.type= type;
baseView = v;
baseView.setTag(type, this);
}
#Override
public View fillView(Cursor dialogCursor, final int position) {
try{
int ownerID = dialogCursor.getInt(ownerIndex);
User usr = mDButils.getUserByID(ownerID);
String messageText = Html.fromHtml(dialogCursor.getString(bodyIndex)).toString();
date.setText(DateUtils.getTime(dialogCursor.getInt(dateIndex), mContext));
message.setText(messageText);
if (messageText == null || messageText.equals("")){
if (dialogCursor.getInt(hasAttIndex) == 1){
message.setText(mContext.getResources().getString(R.string.attachment));
message.setTextColor(getColor(R.color.date_blue_to_white_selector));
}
}
if (dialogCursor.getInt(hasAttIndex) == 1){
String[] attaches = dialogCursor.getString(attTypeIndex).split(",");
}
if (dialogCursor.getInt(readstateIndex) == 0){
baseView.setBackgroundDrawable(getDrawable(R.drawable.lightblue_to_transparent_selector));
} else baseView.setBackgroundDrawable(getDrawable(R.drawable.white_to_blue_selector));
if (usr != null){
mImageLoader.displayImage(usr.getImageURL(), senderIMG);
fio.setText(usr.getFirstName() + " " + usr.getLastName());
isOnline.setVisibility(usr.isOnLine() == 1 ? View.VISIBLE : View.INVISIBLE);
} else {
AsyncUserLoader userLoader = new AsyncUserLoader(mContext) {
#Override
protected void onPostExecute(User user) {
if (user != null){
mDialogAdapter.updateItem(position);
}
}
};
userLoader.execute(Integer.parseInt(ownerID + ""));
}
}catch (Exception e){
e.printStackTrace();
}
return baseView;
}
#Override
public int getType() {
return type;
}
}
You could have multiple layouts implementation for BaseAdapter as given in this post and this article
Note: in getItemViewType(int position) you can use checkDialogType(mCursor); to find what layout type to use. But getItemViewType(int position) should return value between 0 - 2 (when count is 3).
I found a solution. I made a complex container when all views which i need. And depend of type of view i make they visible or invisible. So i don't need inflate xml when convertView doesn't match with type.

Categories

Resources