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.
Related
Have a fragment with a listView that should be inflated as follows :
one with a different layout (custom row),position two with (slider) and from position 3 (item_layout) have gone through quite a lot of resources on the web and stack overflow which didn't solve my purpose looking for a sample or tutorial of this kind.
It would be better to use header instead of different types of items, because you will not recycle first two rows.
Note: When first introduced, this method could only be called before
setting the adapter with setAdapter(ListAdapter).
Example:
ListView listView = view.findViewById(R.id.list);
View header = LayoutInflater.from(getActivity()).inflate(R.layout.your_first_two_rows, listView, false);
listView.addHeaderView(header);
listView.setAdapter(new YourCustomAdapter(...));
private static final int TYPE_ITEM0 = 0;
private static final int TYPE_ITEM1 = 1;
private static final int TYPE_ITEM2 = 2;
#Override
public int getViewTypeCount() {
return 3;
}
#Override
public int getItemViewType(int position) {
if(getItem(position) instanceof Type1List){
type = TYPE_ITEM0;
}
else if(getItem(position) instanceof BingImageBean){
type = TYPE_ITEM1;
}
else if(getItem(position) instanceof InuvoSearchType3Bean){
type = TYPE_ITEM2;
}
return type;
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
final int viewType = getItemViewType(position);
if(viewType == 0){
// InuvoSearch Type 1
InuvoSearchType_1_ViewHolder holder;
if(convertView == null || (Integer)convertView.getTag(R.id.view_type) != 0){
holder = new InuvoSearchType_1_ViewHolder();
convertView = mInflater.inflate(R.layout.inuvosearch_list_type1,parent,false);
holder.txtInuvoTitle = (TextView) convertView.findViewById(R.id.txtInuvoTitle);
convertView.setTag(holder);
convertView.setTag(R.id.view_type, 0);
}
else {
holder = (InuvoSearchType_1_ViewHolder) convertView.getTag();
}
return convertView;
}
else if(viewType == 1)
{
// Bing Related Images
ImageViewHolder holder;
if(convertView == null || (Integer)convertView.getTag(R.id.view_type) != 1){
holder = new ImageViewHolder();
convertView = mInflater.inflate(R.layout.imagesearch_list,parent,false);
holder.imageView1 = (ImageView)convertView.findViewById(R.id.image1);
convertView.setTag(holder);
convertView.setTag(R.id.view_type, 1);
}
else{
holder = (ImageViewHolder) convertView.getTag();
}
return convertView;
}
else if(viewType ==2)
{
// InuvoSearch Type 3
InuvoSearchType_3_ViewHolder holder;
if(convertView == null || (Integer)convertView.getTag(R.id.view_type) != 2){
holder = new InuvoSearchType_3_ViewHolder();
convertView = mInflater.inflate(R.layout.inuvosearch_list_type3,parent,false);
convertView.setTag(holder);
convertView.setTag(R.id.view_type, 2);
}
else{
holder = (InuvoSearchType_3_ViewHolder) convertView.getTag();
}
holder.baseLayout.setId(position);
return convertView;
}
return null;
}
static class ImageViewHolder{
//Bing Related Images Holder:
private ImageView imageView1,imageView2,imageView3;
private ProgressBar progressBariamgeView1,progressBariamgeView2,progressBariamgeView3;
private RelativeLayout baseLayout;
}
static class InuvoSearchType_1_ViewHolder{
//InuvoSearchType_1_ViewHolder:
private TextView txtInuvoTitle,txtInuvoDescription,txtInuvoLink;
private ImageView icon;
private RelativeLayout baseLayout;
}
static class InuvoSearchType_3_ViewHolder{
//InuvoSearchType_3_ViewHolder:
private TextView txtInuvoTitle,txtInuvoDescription,txtInuvoLink;
private ImageView icon;
private RelativeLayout baseLayout;
}
Like I shown in the above code, we can create different layout for listview using getItemViewType in getView method. Based on the type value, set the xml layout. The above code must be used in Adapterclass of the listview
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.
I am trying to inflate two different Layouts in my ListView using a SimpleAdapter. I have done the following coding and both the layouts are getting inflated , but the contents of the ArrayList<HashMap<String,String>> are not set into the ListView. Can anyone guide me step by step where I am going wrong?
ListAdapter k = new SimpleAdapter(Table_Order_Activity.this,
cart_activity.table_order_items, R.layout.menulist,
new String[] { "Food_Name", "Pref_Name", "Food_Currency",
"Food_Price", "Food_Price_Total", "Pref_ID",
"Food_Image" }, new int[] { R.id.cat_name,
R.id.textView1, R.id.textView2, R.id.textView3,
R.id.url, R.id.veggie, R.id.Category }) {
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
View v = super.getView(position, convertView, parent);
LayoutInflater inflater = (LayoutInflater) getApplicationContext()
.getSystemService(
Table_Order_Activity.this.LAYOUT_INFLATER_SERVICE);
int type = getItemViewType(position);
Log.i("position + convertview + type ", "" + position + ","
+ convertView + "," + type);
TextView image_url = (TextView) v.findViewById(R.id.Category);
ImageView image = (ImageView) v.findViewById(R.id.imageView1);
imageloader.DisplayImage(image_url.getText().toString(), image);
if (type == 0) {
v = inflater.inflate(R.layout.r2, parent, false);
} else if (type == 1) {
v = inflater.inflate(R.layout.menulist, parent, false);
}
return v;
}
#Override
public int getItemViewType(int position) {
int type = 0;
Log.i("position in getitemviewtype", "" + position);
if (position == 0) {
type = FIRST_TYPE;
} else if (position == 1) {
type = SECOND_TYPE;
}
/*
* else if (position == 2) { type = FIRST_TYPE; } else if
* (position == 3) { type = SECOND_TYPE; }
*/
return type;
}
};
table_order_list.setAdapter(k);
You have override the get view method of simple adapter and It is messed up v is over write by another layout with no data bind. Instead of using simple adapter, create your own custom adapter by extending base adapter.
public class CustomAdapter extends BaseAdapter {
public static final int FIRST_TYPE = 0;
public static final int SECOND_TYPE = 1;
ArrayList<HashMap<String, String>> data;
Context mContext;
public CustomAdapter(Context context,
ArrayList<HashMap<String, String>> data) {
this.data = data;
this.mContext = context;
}
#Override
public int getCount() {
return data.size();
}
#Override
public Object getItem(int position) {
return data.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public int getItemViewType(int position) {
int type = FIRST_TYPE;
if (position == 0) {
type = FIRST_TYPE;
} else if (position == 1) {
type = SECOND_TYPE;
}
return type;
}
#Override
public int getViewTypeCount() {
return 2;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) mContext
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
int type = getItemViewType(position);
if (type == FIRST_TYPE) {
v = inflater.inflate(R.layout.r2, parent, false);
} else if (type == SECOND_TYPE) {
v = inflater.inflate(R.layout.menulist, parent, false);
}
TextView image_url = (TextView) v.findViewById(R.id.Category);
ImageView image = (ImageView) v.findViewById(R.id.imageView1);
imageloader.DisplayImage(image_url.getText().toString(), image);
//Set your data here to views.
return v;
}
}
Hope it will help. :)
Before trying to help you:
Why in the name of God you don't extend either the BaseAdapter or ArrayAdapter?
At the following link you can find details about how to implement what you need:
http://www.vogella.com/tutorials/AndroidListView/article.html.
http://thinkandroid.wordpress.com/2010/01/13/custom-baseadapters/.
Your case it's pretty clear, custom adapter. If this guy didn't bother to explain to this extent I would do it for you, but it's there...
I was using standart android ListView with simle_list_item_multiple_choice item layout and custom adapter. It saved it's stated on pause and restored on resume. But after few days of working on I've implemented SectionIndexer, StickyListHeadersAdapter interface within my adapter and changed ListView to StickyListHeadersListView from this library. Also there were other changes in application but these are only attached to ListView. However now when my application resumes working listview is coming scrolled to begining and with all items unchecked. I've tryed to remove SectionIndexer interface and sticky headers support but it had no effect. Maybe there is some hidden options of listview which I need to enable?
(tried saveState property - no effect)
public class WordAdapter extends ArrayAdapter<Word> implements SectionIndexer, StickyListHeadersAdapter{
// -----------------------------------------------------------------------
//
// Fields
//
// -----------------------------------------------------------------------
private int mResID;
private List<Word> mList;
private String[] mSectionNames;
private StickyListHeadersListView mListView;
private int[] mSectionIndexes;
private boolean mHeadersEnabled;
// -----------------------------------------------------------------------
//
// Constructor
//
// -----------------------------------------------------------------------
public WordAdapter(Context context, StickyListHeadersListView listView, int resID, List<Word> list, String[] sectionNames, int[] sectionIndexes, boolean enableHeaders) {
super(context, resID, list);
mResID = resID;
mList = list;
mListView = listView;
mSectionIndexes = sectionIndexes;
mSectionNames = sectionNames;
mHeadersEnabled = enableHeaders;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(mResID, parent, false);
holder = new ViewHolder();
holder.root = convertView;
holder.textView = (TextView) convertView.findViewById(android.R.id.text1);
holder.checkBox = (CheckBox) convertView.findViewById(android.R.id.checkbox);
convertView.setTag(holder);
}
holder = (ViewHolder) convertView.getTag();
holder.textView.setText(getItem(position).getValue());
if(mListView.isItemChecked(position))
holder.root.setBackgroundColor(getContext().getResources().getColor(R.color.highlight));
else
holder.root.setBackgroundColor(getContext().getResources().getColor(android.R.color.transparent));
return convertView;
}
#Override
public View getHeaderView(int position, View convertView, ViewGroup parent) {
if(!mHeadersEnabled)
return new View(getContext());
HeaderViewHolder holder;
if (convertView == null) {
holder = new HeaderViewHolder();
convertView = LayoutInflater.from(getContext()).inflate(R.layout.list_item_header, parent, false);
holder.labelView = (TextView) convertView.findViewById(R.id.list_item_header_label);
convertView.setTag(holder);
} else {
holder = (HeaderViewHolder) convertView.getTag();
}
String label;
if(mSectionNames != null){
label = mSectionNames[getSectionForPosition(position)];
holder.labelView.setText(label);
}
return convertView;
}
#Override
public long getHeaderId(int position) {
long result = getSectionForPosition(position);
return result;
}
#Override
public int getPositionForSection(int section) {
return (mSectionIndexes == null || section < 0 ) ? 0 : (section == mSectionIndexes.length ? mList.size():mSectionIndexes[section]);
}
#Override
public int getSectionForPosition(int position) {
if(mSectionIndexes != null && position >= mSectionIndexes[0]) {
for(int i = 0; i < mSectionIndexes.length; ++i)
if(position < mSectionIndexes[i])
return i-1;
return (mSectionIndexes.length - 1);
}
return 0;
}
#Override
public Object[] getSections() {
return mSectionNames == null ? new Object[0] : mSectionNames;
}
// -------------------------------------------------------------------------------
//
// Internal classes
//
// -------------------------------------------------------------------------------
private static class ViewHolder {
public View root;
public TextView textView;
public CheckBox checkBox;
}
private static class HeaderViewHolder {
public TextView labelView;
}
Here is adapter code.
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.