Android - Listview with image and text - android

I just started learning android and I'm at a point where I want to do the question described below but I'm not sure how to start.
I have an array of data with the following data,
1, text1, image1.png
2, text2, image2.png
3, text3, null
4, null, image3.png
I know how to create a ListView with ArrayAdapter along with their xml layout following some tutorial.
As you see in the array above sometimes it doesn't contain an image, sometimes it doesn't contain text and sometimes it has both.
My question is how to make that work with layout so that it dynamically changes based on the array values?
In other words how can I start thinking about building a listview+ArrayAdapter+layout where I can view an imageiew only where the array record has an image only, viewing a textview when there is a text only and viewing both of them when both are available.
A link to a tutorial will be extremely helpful

You could create a type MyCustomType that represents one array element (In your case it holds a number, a text and an image). Furthermore you need to implement your custom array adapter. This adapter uses an ArrayList of your MyCustomType.
public class CustomAdapter extends ArrayAdapter<MyCustomType> {
//...
private ArrayList<MyCustomType> foo;
public CustomAdapter(Context context, Activity bar, ArrayList<MyCustomType> foo) {
super(bar, R.layout.row, foo);
this.foo = foo;
mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
Override getViewTypeCount() to determine how many different kinds of rows you have. getItemViewType returns the kind of row that has to be displayed.
Your getView method could be similiar to his one:
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
int type = getItemViewType(position); // decide what row type has to be displayed
if (convertView == null) {
convertView = mInflater.inflate(R.layout.row, parent, false);
viewHolder = new ViewHolder();
viewHolder.number = //...
viewHolder.text = //...
viewHolder.image = //...
convertView.setTag(viewHolder);
}
else {
viewHolder = (ViewHolder) convertView.getTag(); // avoided to call findViewById
}
switch(type) {
case TYPE1:
//... you could just change the visibility of the corresponding view
break;
case TYPE 2:
// ...
break;
}
return convertView;
}

My advice is to use a custom array adapter. There is a good tutorial here.
The official documentation can be found here.
Essentially you will create a class that extends the ArrayAdapter class. Implement the overrides and put your handling to show or not show particular views in the getView method. This method will fire for each item in the list passed it.

Related

Update adapter items with add dynamic drawable on getview() lead to show wrong data

I have a custom adapter that list my items. in each Item I check database and draw some circles with colors.
As you see in code I check if convertView==null defines new viewHolder and draw my items. but when I scroll listview very fast every drawn data ( not title and texts) show wrongs!
How I can manage dynamic View creation without showing wrong data?!
UPDATE
This is my attempts:
I used ui-thread to update my list but the result is same and data drawing go wrong.
in second I try to load all data with my object so that there is no need to check db in adapter. but it problem is still remains...
finally I create the HashMap<key,LinearLayout> and cache every drawn layout with id of its item. So if it's drawn before I just load its view from my HashMap and every dynamic layout will create just once. But it still shows wrong data on fast scrolling! Really I don't know what to do next!
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
final ViewHolder viewHolder;
final MenuStructureCase item = getItem(position);
if (convertView == null) {
viewHolder = new ViewHolder();
convertView = this.mInflater.inflate(R.layout.fragment_menu_item, null);
viewHolder.menu_title = (TextView) convertView.findViewById(R.id.menu_title);
viewHolder.tag_list_in_menu_linear_layout = (LinearLayout) convertView.findViewById(R.id.tag_list_in_menu_linear_layout);
viewHolder.menu_delete = (ImageButton) convertView.findViewById(R.id.image_button_delete);
importMenuTags(viewHolder, getItem(position), viewHolder.tag_list_in_menu_linear_layout);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.menu_title.setText(item.getTitle());
}
return convertView;
}
and this is importMenuTags():
private void importMenuTags(ViewHolder viewHolder, MenuStructureCase item, LinearLayout layout) {
List<String> tags = db.getMenuTags(item.getTitle()); //this present list of string that contain my tags
for (String tag : tags) {
Drawable drawable = getContext().getResources().getDrawable(R.drawable.color_shape);
drawable.setColorFilter(Color.parseColor(each_tag_color), PorterDuff.Mode.SRC_ATOP);
RelativeLayout rl = new RelativeLayout(getContext());
LinearLayout.LayoutParams lparams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
lparams.setMargins(15, 15, 15, 15);
lparams.width = 50;
lparams.height = 50;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
rl.setBackground(drawable);
} else {
rl.setBackgroundDrawable(drawable);
}
rl.setLayoutParams(lparams);
layout.addView(rl);
}
}
You have to select data from db before adapter initialization. So that
getItem(position)
will return already a "ready" item-object.
You shouldn't set the values to Views inside
if (convertView == null) {
...
}
This code is only for a viewHolder initialization. You create a new one, if convertView is null or read it as tag.
Setting of values you have to do after viewHolder initialization, actually where you set the title.
But in order to increase a performance, you shouldn't select the values from db on each step of getView. You have to have everything prepared (already selected).
You can do this way:
First of all create method inside adapter class:
public void updateNewData(List<MenuStructureCase> newList){
this.currentList = newList;
notifyDataSetChanged();
}
Now call above method whenever you want to update ListView.
How to call with object of CustomAdapter:
mAdapter.updateNewData(YourNewListHere);
Hope this will help you.
Rendering of data takes times and may be that's causing the issue when you are scrolling fast.
You can restirct the scrolling ( like Gmail : use a pull to refresh ) so that a less amount to data is processed in a list view at single time .
use RecyclerView instead of listview for better performance
ListView recreates the view on scrolling .
May be you can explain more about your problem , then we can provide the inputs accordingly.

ListView Adapter with arbitrary number of row types (Don't know the number of different row types)

So, I am making this application. The application parses a website, or more specifically a vbulletin-board. When I'm parsing a thread in the forum, I have divided it up so that when I parse each post in that thread, I get the actual content of the post in sections such as this, and I store the sections in the correct order in an array:
[Plain text]
[Quote from somebody]
[Plain text]
[Another quote]
[Another quote again]
[Some more plain text]
However, a post can be arranged in any order as you might know, and can consist of more or fewer sections than in the example, and it doesn't have to have quotes in it either, or it might just be one or several quotes. Anything is possible.
When I list the posts in my application, I am using a ListView. Each row of this listview will then always consist of a header, and any combination of the previously mentioned sections.
The way I was thinking of doing it after googling a bit about it is to have one "Base-layout" with just a layout-tag in one XML-file, and a separate layout for each section, stored in separate XML-files, and at each call to getView() in my adapter, look at the post at that position in my "Post-list", and then loop through the sections in that particular post, and inflate a new "Quote-layout" for each quote-section stored in the post, and inflate a "Plain-text-layout" for each plain-text-section in the post. And for each of those I fill in all the content belonging to that post.
I think this would work, but there might be a performance problem? As I understand it layout inflation is quite expensive, and I won't be able to recycle the View passed in to getView() either, since it might have a bunch of sections added to it that I might not need in another call to getView().. That is, if I understand getView() and the recycling somewhat.
This is a basic example of what I mean with the getView() method of the adapter:
#Override
public View getView(int i, View view, ViewGroup viewGroup) {
// Inflate the base-layout, which the others are added to.
view = mInflater.inflate(R.layout.forum_post,null);
View header = mInflater.inflate(R.layout.post_header_layout, null);
View message = mInflater.inflate(R.layout.post_text_layout, null);
View quote = mInflater.inflate(R.layout.post_quote_layout, null);
((ViewGroup)view).addView(header);
((ViewGroup)view).addView(message);
((ViewGroup)view).addView(quote);
return view;
}
And then inflate more quote-views/message-views as needed when I extract the data from my list of saved posts.
The base-layout is just a LinearLayout-tag
The layouts I inflate are just RelativeLayouts with some TextViews and an ImageView added.
This code produces this result, where I have a Header with
username, picture, etc.., One section of Plain text, and one Quote-section.
This doesn't seem to work properly all the time though, because when I tried it out just now a copy of the list seemed to get stuck on the background and another one scrolled on top of it..
http://s14.postimg.org/rizid8q69/view.png
Is there a better way to do this? Because I imagine this isn't very efficient
You need to override getViewItemType and getViewTypeCount.
getItemViewType(int position) - returns information which layout type you should use based on position
Then you inflate layout only if it's null and determine type using getItemViewType.
Example :
private static final int TYPE_ITEM1 = 0;
private static final int TYPE_ITEM2 = 1;
private static final int TYPE_ITEM3 = 2;
#Override;
public int getItemViewType(int position)
{
int type;
if (position== 0){ // your condition
type = TYPE_ITEM1; //type 0 for header
} else if(position == 1){
type = TYPE_ITEM2; //type 1 for message
}else {
type = TYPE_ITEM3; //type 2 for Quote
}
return type;
}
#Override
public int getViewTypeCount() {
return 3; //three different layouts to be inflated
}
In getView
int type= getItemViewType(i); // determine type using position.
switch (type) {
case TYPE_ITEM1:
view= mInflater.inflate(R.layout.post_header_layout, null); // inflate layout for header
break;
case TYPE_ITEM2:
view = mInflater.inflate(R.layout.post_text_layout, null); // inflate layout for quote
break;
case TYPE_ITEM3:
quote = mInflater.inflate(R.layout.post_quote_layout, null); // inflate layout for message
break;
....
You need to use a View Holder for smooth scrolling and performance.
http://developer.android.com/training/improving-layouts/smooth-scrolling.html
You can check the tutorial below
http://android.amberfog.com/?p=296
First of all you want to reuse convertView that has been passed as one of the argument. This way you can avoid inflating the item View.
Secondly, you could use something as ViewHolder to store references to your inner Views. Using ViewHolder will increase performance whether you are inflating view or finding them by id as both methods are very expensive.
Set the ViewHolder as a Tag on item View.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
ViewHolder viewHolder;
// if possible reuse view
if (convertView == null) {
final LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(resource, parent, false);
viewHolder = new ViewHolder(mInflater.inflate(R.layout.post_header_layout, null));
view.setTag(viewHolder);
} else {
// reuse view
view = convertView;
viewHolder = (ViewHolder) view.getTag();
}
//set text, listeners, icon, etc.
return view;
}
The ViewHolder is just private inner class storing referenced to view.
private static class ViewHolder {
private final View view;
private ViewHolder(View view) {
this.view = view;
}
}
Talk about ListView usage was given at Google IO 2010.
The inflater needs to know the real type of the futur parent ViewGroup, therefore the following code is erroneous:
view = mInflater.inflate(R.layout.forum_post,null);
and instead, you should use this one:
view = mInflater.inflate(R.layout.forum_post,viewGroup,false);
Same thing for the other inflate: use the real parent (view in this case) or another viewGroup which is of the same type as the (futur) parent; otherwise the LayoutParameters will not be set to the right type and the values that you have specified in your XML code will be lost (never used).

Making ListView in SLideMenu like YouTube App

I want to emulate this type of Listview in a SlideMenu. I have the SlideMenu working fine. It is a ListFragment. I want to copy this pattern like the YouTube app on Android:
I essentially have a couple of list items I need to add to the top of the list of categories. And I want a Header to separate.
I want this:
Home
Profile
Top Items
Header that says Categories
And List of Categories
I already have the Categories listed out fine on my SlideMenu. They come from an adapter that populate from a table in MySQL. But the three top items do not come from that same table (or ANY table). Is the top portion a header to a ListView? Is it its OWN ListView? or..?
Keep in mind, I want ability to sort the list (which I already have via a spinner). So Categories must be dynamic. But how to I add a couple of static items above AND make a header?
I don't really need code sample, I just want to know method to implement this.
EDIT: Here is Code in progress
This show the separator like the Channels line in the Youtube example. Need to also figure out how to add those two or three static lines up top.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
holder = new ViewHolder();
View rowView = convertView;
LayoutInflater inflater = ((Activity) context).getLayoutInflater();
int type = getItemViewType(position);
if (rowView == null) {
switch (type) {
case TYPE_ITEM:
rowView = inflater.inflate(R.layout.mastercat_layout, null,
true);
holder.textView = (TextView) rowView.findViewById(R.id.label);
holder.textView.setTypeface(tf);
holder.imageView = (ImageView) rowView.findViewById(R.id.icon);
break;
case TYPE_SEPARATOR:
rowView = inflater.inflate(R.layout.mastercat_layout_separate, null);
break;
}
rowView.setTag(holder);
} else {
holder = (ViewHolder) rowView.getTag();
}
holder.textView.setText(getItem(position));
holder.imageView.setImageResource(R.drawable.ic_launcher);
return rowView;
}
Maybe you need a couple pools of convertView in adapter?
BaseAdapter contains methods
public int getItemViewType (int position)
and
public int getViewTypeCount ()
You can override it to implement 2 pools of views - one for Headers and another one for Items of listView. Also in this case you need to change you getView method according to itemViewType, returned by getItemViewType().

How to keep list items in memory?

I have custom listview. When I scroll my listview, android keeps in memory (as far as I understand) items which is displaying on screen and doesn't keep items which is hidden (not scrolled to).
In my case (I think) keeping all list items would be better than generating hidden items.
So, how to "tell" android to keep all items in memory? (15-20 items). PS: if it's wasting of resources, I'd like just to try.
My adapter (some funcs):
private View newView(Context context, ViewGroup parent) {
LayoutInflater layoutInflater = LayoutInflater.from(context);
return layoutInflater.inflate(R.layout.myl,parent,false);
}
public View getView(int position,View convertView,ViewGroup parent) {
View view=null;
if(convertView!=null) view=convertView; else view=newView(context,parent);
HashMap<String,String> d=new HashMap<String,String>();
d=data.get(position);
String qweqwe=d.get("qweqwe"); //9 more lines like this.
TextView txt=(TextView)view.findViewById(R.id.mfmf); //
txt.setText(qweqwe); //
txt.setTypeface(mlf); //5 more blocks of 3 lines like this.
if (smth.equals("0")){
view.setBackgroundDrawable(context.getResources().getDrawable(R.drawable.mvmv));
} else {
view.setBackgroundDrawable(context.getResources().getDrawable(R.drawable.mvmv2));
}
return view;
}
Ok.. there are some things that can be optimized here, instead of trying to fix lag with workarounds :)
You should implement a static class, where you can store references to the Views in your myl.xml. For each View you want to manipulate in myl.xml, you create a View in this static class. So if you have 10 TextViews, you fill this class with 10 TextViews.
static class AdapterViewsHolder {
TextView txt1;
TextView txt2;
TextView txt3;
...
ImageView img1;
... etc etc.
}
In the adapter, you now only do the findViewById() calls if the convertView is null. findViewById() is not cheap, so limiting the amount of calls increases performance.
private HashMap<String, String> mData;
private LayoutInflater mInflater;
private TypeFace mCustomTypeFace;
// Some contructor for passing data into the Adapter.
public BaseAdapter(HashMap<String, String> data, Context ctx) {
mData = data;
mInflater = LayoutInflater.from(ctx);
mCustomTypeFace = Typeface.createFromAsset(ctx.getAssets(), "yourTypeFace.ttf");
}
public View getView(int position, View convertView, ViewGroup parent) {
// This AdapterViewsHolder will hold references to your views, so you don't have to
// call findViewById() all the time :)
AdapterViewsHolder holder;
// Check if convertView is null
if(convertView == null) {
// If it is, we have to inflate a new view. You can probably use the newView() call here if you want.
convertView = mInflater.inflate(R.layout.myl, null);
// Initialize the holder
holder = new AdapterViewsHolder();
// Now we do the smart thing: We store references to the views we need, in the holder. Just find all the views you need, by id.
holder.txt1 = (TextView) convertView.findViewById(R.id.textview1);
holder.txt2 = (TextView) convertView.findViewById(R.id.textview2);
...
holder.img1 = (ImageView) convertView.findViewById(R.id.imageview1);
// Store the holder in the convertViews tag
convertView.setTag(holder);
}
else {
// If convertView is not null, we can get get the holder we stored in the tag.
// This holder now contains references to all the views we need :)
holder = (AdapterViewsHolder) convertView.getTag();
}
// Now we can start assigning values to the textviews and the imageviews etc etc
holder.txt1.setText(mData.get(position));
...
holder.txt1.setTypeface(mCustomTypeFace);
...
holder.img1.setImageResource("IMAGE RESOURCE HERE");
if(someThing.equals("sometext") {
convertView.setBackgroundDrawable(somedrawable);
}
else {
convertView.setBackgroundDrawable(someotherdrawable);
}
// Finally, we return the convertView
return convertView;
}
I do not know how your data is organized, so you have to change this code a bit.
One more thing that can cause lag is the android:cacheColorHint xml attribute. Usually you set this to either the same color as you application background, or transparent. Setting it transparent have been known to cause rapid Garbage collections on some occasions.
You could override the Adapter and have it inflate all of the views in the constructor, then just return the proper one with getView(). Might make it easy if you store the Views in some data object (array, list etc..)
But really you should let the system use the convertView like it was designed to. Overall you'd get better performance doing it that way I think.

Is this ArrayAdapter suitable for use with CommonsWare MergeAdapter? If so, why is it not working?

I'm attempting to use CommonsWare's MergeAdapter class and having limited success. In particular, I am not sure if 1) my ArrayAdapter is suitable for use, 2) if I am adding it correctly, and 3) if I am doing all that is necessary to wire everything up.
Here is my subclass of ArrayAdapter:
class PDLAdapter extends ArrayAdapter<PartnerDisease> {
public PDLAdapter(final Context context) {
super(context, 0);
}
#Override
public View getView(final int position, View convertView, final ViewGroup parent) {
ViewHolder viewHolder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.partnerdisease_list_item, null);
viewHolder = new ViewHolder(convertView);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.populateViews(getItem(position));
return convertView;
}
}
Here is my object StructuredSubDisease (the name makes no sense if you actually consider it's a top-level object containing sub diseases, but whatever):
class StructuredSubDisease {
public String headingText;
public ArrayList<PartnerDisease> subDiseases;
public View headingView() {
View returnView = mInflater.inflate(R.layout.partnerdisease_list_item, null);
TextView t = (TextView) returnView.findViewById(R.id.tv_displayname);
t.setText(headingText);
return returnView;
}
}
...and here is where the "magic" is supposed to be happening.
for (StructuredSubDisease s : subDiseaseList) {
mMergeAdapter.addView(s.headingView()); // #Alex, <--- thing 1
PartnerDiseaseListAdapter adapter = new PartnerDiseaseListAdapter(this);
for (PartnerDisease p : s.subDiseases) {
adapter.add(p);
}
mMergeAdapter.addAdapter(adapter); // <--- and thing 2
}
I have Logged the count:
Log.i("mergecount", "" + mMergeAdapter.getCount());
This returns 1, where I would expect 2.
EDIT: I forgot to mention, the result of this is that the headingView() is displayed with the proper heading text, but there is no list beneath it.
Where am I going wrong?
my ArrayAdapter is suitable for use
It seems OK.
if I am adding it correctly
It seems OK.
if I am doing all that is necessary to wire everything up
You don't have any diseases, apparently.
because I added two things - the headingView() (which is rendered) and the adapter (which silently fails)
getCount() returns the number of total rows that should be in your ListView, not the number of things added to the MergeAdapter. In your case, it would appear that you have no diseases.
Start by putting your PartnerDiseaseListAdapter directly into your ListView, ignoring the MergeAdapter. Get that working. Then, switch back to the MergeAdapter.

Categories

Resources