ListView, and ArrayAdapter issue, How do I proceed? - android

I have a Product Class, Which has three fields:-
id
name
price
In my code I create a List<Product> productList = Collections.synchronizedList(new ArrayList<Product>());
This product list is used to create a ArrayAdapter<Product> adapter = ArrayAdapter<Product>(), I fill the productList in a separate thread and adapter is notified accordingly. It is working absolutely fine.
Now,
I want to change the color of the some specific products (say for price < 1000).
Each row of ListView should contain 4 elements product image,name, desc and price.
When User clicks the Product, in a context menu options i.e. buy Product, View Product should be displayed.
I have read few blogs and threads related to that. Still I cant decide where to begin, I read about the customization of the ArrayAdapter, overriding getView(), custom list filters etc. Which way will be the best for my requirement... in other words How can custom adapters and list filters benefit me ?

You should extend BaseAdapter and provide your own layout for each item (getView()). Don't forget to manage the view recycling and maybe use the ViewHolder paradigm.
EDIT
I didn't use a lot the ListAdpater, because it binds to a ListView only. Sometimes I need an adapter for a GridView, and the BaseAdapter gives me enough freedom for all use cases.
Example of BaseAdapter:
public class FanAdapter extends BaseAdapter {
private List<Fan> mFans;
private Activity mContext;
public FanAdapter(Activity context, List<Fan> fans) {
mContext = context;
mFans = fans;
}
private class ViewHolder {
public ImageView image;
public TextView firstName;
public TextView lastName;
}
#Override
public View getView(int position, View view, ViewGroup container) {
if (view == null) {
view = LayoutInflater.from(mContext).inflate(R.layout.fan_item, container, false);
}
ViewHolder viewHolder = (ViewHolder) view.getTag();
if(viewHolder == null){
viewHolder = new ViewHolder();
viewHolder.image = (ImageView) view.findViewById(R.id.image);
viewHolder.firstName = (TextView) view.findViewById(R.id.firstname);
viewHolder.lastName = (TextView) view.findViewById(R.id.lastname);
view.setTag(viewHolder);
}
// setting here values to the fields of my items from my fan object
viewHolder.firstName.setText(fan.getFirstName());
(...)
return view;
}
#Override
public int getCount() {
if (mFans != null) {
return mFans.size();
} else {
return 0;
}
}
#Override
public Object getItem(int position) {
return mFans.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
}
You can use it with an Activity containing a ListView or a ListActivity (having in its layout a ListView with a special id):
<ListView
android:id="#id/android:list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:cacheColorHint="#android:color/transparent" />
This way, your ListActivity that will inflate the view will be able to make a findViewById() call and getListView() will return this internal listView. It's a small hack, you can put your own listView with another id and make the findViewById() yourself. For The ListActivity, there's another hack: if the ListActivity finds an empty view with again a special id, it will be shown when the list is empty:
<include
android:id="#+id/empty"
layout="#layout/empty"
android:visibility="gone"
android:layout_gravity="center" />
Then on your listView, whether you used an Activity or ListActivity, you can set your adapter on the ListView:
getListView().setAdapter(new FanAdapter(this, myFanDataArray)));

in getView(...) method you have to check price and set color of row...
see this customized listview..
http://samir-mangroliya.blogspot.in/p/android-customized-listview.html
i set row color as per odd and even row and
you can set checking price...
if(price < 1000){
row.setBackgroundColor(Color.RED);
}else{
row.setBackgroundColor(Color.Yellow);
}

Related

Recycler View Onclick method in Activity?

I'm trying to create a UI similar to Google Keep. I know how to create a staggered View using a Recycler View. If i click a specific Card. Then it has to open a activity.
I can achieve this using onclick method.
This same scenario happens in atleast 5 different Activities in my App.
My question is that
Can I use this single Adapter in all those 5 places ?
If yes, where should i place the onclick actions ?
If no, How can I Create a staggered layout like Keep?
Thanks in Advance!
(See application for RecyclerView below in edits)
Like I mentioned in my comment, it's certainly fine to have separate adapters for all your Activities which use different data and views. As your app data and layouts get more complex, so does your code...that's just the way it is.
But if some of your Activities used similar data in their ListViews -- maybe, for example, two TextViews and an ImageButton -- you could save some effort by defining a single adapter that can be used for multiple Activities. You would then instantiate separate objects for each Activity, similar to the way you would create several ArrayAdapter<String> objects to populate multiple ListViews.
The BaseAdapter is a great class to extend when writing a custom adapter. It's flexible and allows you complete control over the data that's getting shown in your ListView. Here's a minimal example:
public class CustomBaseAdapter extends BaseAdapter {
private Context context;
private ArrayList<String> listData;
public CustomBaseAdapter(Context context, ArrayList<String> listData) {
this.context = context;
this.listData = listData;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.your_list_item_layout, parent, false);
//populate the view with your data -- some examples...
TextView textData = (TextView) convertView.findViewById(R.id.yourTextView);
textData.setText(listData.get(position));
ImageButton button = (ImageButton) convertView.findViewById(R.id.yourImageButton);
button.setOnClickListener(new View.OnClickListener() {
//...
//...
});
}
return convertView;
}
#Override
public Object getItem(int position) {
return 0;
}
#Override
public long getItemId(int position) {
return 0;
}
#Override
public int getCount() {
return listData.size();
}
}
So the key part of this code is obviously the getView() method, which is called every time the ListView needs some data to display. For efficiency, views are stored in something called a convertView so they may be re-used and not have to be inflated every time a view appears on the screen.
So what we do in getView() is first find out if the convertView exists. If it does, we just pass that back to the calling ListView because everything should already be instantiated and ready to go. If the convertView is null, it means the data hasn't been instantiated (or needs to be re-instantiated for whatever reason), and so we inflate a brand new view and populate it with our data.
So in the case of this example adapter above, if several of your Activities all displayed a single list of Strings, you could reuse this adapter for each one, passing in a different ArrayList<String> through the constructor each time you created a new object. But obviously you could pass in more than just Strings if you had more data to show. The level of complexity is up to you. And if the difference among your Activities was too great, I would just create custom versions of this class for all of them and just instantiate them and populate them however you'd like. It will keep all your data very organized and encapsulated.
Hope this helps! Feel free to ask questions for more clarification if you need it.
EDIT IN RESPONSE TO COMMENTS
Since you are using a RecyclerView instead of just plain ListViews (which I, for some reason, totally forgot) you could still do something very similar using a RecyclerView.Adapter<YourViewHolder> instead. The difference would be that instead of inflating the views in a getView() method, they are inflated inside your custom ViewHolder, which I assume you already have. The code might look something like this:
public class CustomRecyclerViewAdapter extends RecyclerView.Adapter<StringViewHolder> {
private final List<String> items;
public CustomRecyclerViewAdapter(ArrayList<String> items) {
this.items = items;
}
#Override
public StringViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//So instead of inflating the views here or in a getView() like in
//in the BaseAdapter, you would instead inflate them in your custom
//ViewHolder.
return new StringViewHolder(parent);
}
#Override
public void onBindViewHolder(StringViewHolder holder, int position) {
holder.setModel(items.get(position));
}
#Override
public long getItemId(int position) {
return items.get(position).hashCode();
}
#Override
public int getItemCount() {
return items.size();
}
}

Correct way to implement onMeasure() and onLayout() in custom AdapterView

I know that I should measure children in onMeasure() and layout them in onLayout(). The question is in what of these methods should I add/recycle views so I could measure all children together with an eye on how they are mutually positioned (i.e. grid, list or whatever)?
My first approach was to add/recycle views in onLayout() but from that point I can't measure my children because they aren't added to AdapterView yet and getChildCount() returns 0 in onMeasure(). And I can't measure AdapterView itself without children being already layouted because it really depends upon their mutual positions, right?
I'm really confused with android layouting process in AdapterView when childrens are added/removed dynamically.
I can't post a comment because I'm a new user, but can you describe WHAT you're trying to do, as opposed to HOW you're trying to do it? Frequently, you will find that this is an issue of design as opposed to coding. Especially if you're coming from a different platform (example, iOS). From experience, I found that measuring and manual layouts in Android is mostly unnecessary if you design your layout properly in light of your business need.
EDIT:
As I mentioned this can be solved using some design decisions. I will use your Nodes/List example (hoping that this is your actual use case, but the solution can be expanded for a more general problem).
So if we think about your Header as a comment in a forum, and the List as replies to your comment, we can make the following assumption:
One list is enough, not two. Each item in the list can either be a header (comment) or a list item (reply). Each reply is a comment, but not all comments are replies.
For item n, I know if it's a comment or a reply (i.e. is it a header or an item in your list).
For item n, I have a boolean member isVisible (default false; View.GONE).
Now, you can use the following components:
One extended adapter class
Two Layout XMLs: One for your Comment, one for your reply. You can have unlimited comments and each comment can have unlimited replies. Both those satisfy your requirements.
Your fragment or activity container class that implements OnItemClickListener to show/hide your list.
So let's look at some code, shall we?
First, your XML files:
Comment row (your header)
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/overall"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true">
<TextView
android:id="#+id/comment_row_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>
Now your reply row (an element in your list)
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/overall"
android:layout_width="match_parent"
android:layout_height="wrap_content"> <!-- this is important -->
<TextView
android:id="#+id/reply_row_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"/> <!-- important -->
</RelativeLayout>
Ok, now your adapter class
public class CommentsListAdapter extends BaseAdapter implements OnClickListener
{
public static String TAG = "CommentsListAdapter";
private final int NORMAL_COMMENT_TYPE = 0;
private final int REPLY_COMMENT_TYPE = 1;
private Context context = null;
private List<Comment> commentEntries = null;
private LayoutInflater inflater = null;
//All replies are comments, but not all comments are replies. The commentsList includes all your data. (Remember that the refresh method allows you to add items to the list at runtime.
public CommentsListAdapter(Context context, List<Comment> commentsList)
{
super();
this.context = context;
this.inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
this.commentEntries = commentsList;
}
//For our first XML layout file
public static class CommentViewHolder
{
public RelativeLayout overall;
public TextView label;
}
//For our second XML
public static class ReplyViewHolder
{
public RelativeView replyOverall;
public TextView replyLabel;
}
#Override
public int getViewTypeCount()
{
return 2; //Important. We have two views, Comment and reply.
}
//Change the following method to determine if the current item is a header or a list item.
#Override
public int getItemViewType(int position)
{
int type = -1;
if(commentEntries.get(position).getParentKey() == null)
type = NORMAL_COMMENT_TYPE;
else if(commentEntries.get(position).getParentKey() == 0L)
type = NORMAL_COMMENT_TYPE;
else
type = REPLY_COMMENT_TYPE;
return type;
}
#Override
public int getCount()
{
return this.commentEntries.size(); //all data
}
#Override
public Object getItem(int position)
{
return this.commentEntries.get(position);
}
#Override
public long getItemId(int position)
{
return this.commentEntries.indexOf(this.commentEntries.get(position));
}
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
CommentViewHolder holder = null;
ReplyViewHolder replyHolder = null;
int type = getItemViewType(position);
if(convertView == null)
{
if(type == NORMAL_COMMENT_TYPE)
{
convertView = inflater.inflate(R.layout.row_comment_entry, null);
holder = new CommentViewHolder();
holder.label =(TextView)convertView.findViewById(R.id.comment_row_label);
convertView.setTag(holder);
}
else if(type == REPLY_COMMENT_TYPE)
{
convertView = inflater.inflate(R.layout.row_comment_reply_entry, null);
replyHolder = new ReplyViewHolder();
replyHolder.replyLable = (TextView)convertView.findViewById(R.id.reply_row_label);
convertView.setTag(replyHolder);
}
}
else
{
if(type == NORMAL_COMMENT_TYPE)
{
holder = (CommentViewHolder)convertView.getTag();
}
else if(type == REPLY_COMMENT_TYPE)
{
replyHolder = (ReplyViewHolder)convertView.getTag();
}
}
//Now, set the values of your labels
if(type == NORMAL_COMMENT_TYPE)
{
holder.label.setTag((Integer)position); //Important for onClick handling
//your data model object
Comment entry = (Comment)getItem(position);
holder.label.setText(entry.getLabel());
}
else if(type == REPLY_COMMENT_TYPE)
{
replyHolder = (ReplyViewHolder)convertView.getTag(); //if you want to implement onClick for list items.
//Or another data model if you decide to use multiple Lists
Comment entry = (Comment)getItem(position);
replyHolder.replyLabel.setText(entry.getLabel()));
//This is the key
if(entry.getVisible() == true)
replyHolder.replyLabel.setVisibility(View.VISIBLE);
else
replyHolder.replyLabel.setVisibility(View.GONE);
}
return convertView;
}
//You can use this method to add items to your list. Remember that if you are using two data models, then you will have to send the correct model list here and create another refresh method for the other list.
public void refresh(List<Comment> commentsList)
{
try
{
this.commentEntries = commentsList;
notifyDataSetChanged();
}
catch(Exception e)
{
e.printStackTrace();
Log.d(TAG, "::Error refreshing comments list.");
}
}
//Utility method to show/hide your list items
public void changeVisibility(int position)
{
if(this.commentEntries == null || this.commentEntries.size() == 0)
return;
Comment parent = (Comment)getItem(position);
for(Comment entry : this.commentEntries)
{
if(entry.getParent().isEqual(parent))
entry.setVisible(!entry.getVisible()); //if it's shown, hide it. Show it otherwise.
}
notifyDataSetChanged(); //redraw
}
}
Ok great, now we have a list of headers with hidden children (remember, we set the default visibility of children to 'gone'). Not what we wanted, so let's fix that.
Your container class (fragment or activity) you will have the following XML definition
<!-- the #null divider means transparent -->
<ListView
android:id="#+id/comments_entries_list"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:divider="#null"
android:dividerHeight="5dp" />
And your onCreateView will implement OnItemClickListener and have the following
private ListView commentsListView = null;
private List<Comment>comments = null;
private static CommentsListAdapter adapter = null;
....
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
...
//comments list can be null here, and you can use adapter.refresh(data) to set the data
adapter = new CommentsListAdapter(getActivity(), comments);
this.commentsListView.setAdapter(adapter);
this.commentsListView.setOnClickListener(this); //to show your list
}
Now to show your list when you click a header
#Override
public void onItemClick(AdapterView<?> parent, View view, int position,
long id)
{
adapter.changeVisibility(position);
}
Now, if an item is clicked and that item has a parent (i.e. list item), it will be shown/hidden according to its current state.
Some comments about the code:
I wrote this on WordPad as I don't have a dev environment handy. Sorry for any compilation errors.
This code can be optimized: If you have a very large data set, this code would be slow since you're redrawing the entire list on every call to changeVisibility(). You can maintain two lists (one for headers, one for list items) and in changeVisibility you can query over the list items only).
I re-enforce that idea that some design decisions would make your life a lot easier. For example, if your list items were actually just a list of labels, then you can have one custom XML file (for your header) and a ListView view within it that you can set to View.GONE. This will make all other views pretend that it's not even there and your layout will work properly.
Hope this helps.

How can I solve memory leak gridview into listview? - Android

I trying to make a listview which contains a view in every row. This view contains 2 textviews and 1 gridview which is 2 columns. In every column I use a basic layout which is consist of 2 textviews.
This is preview of basic layout which is used in every block of gridview.
Here is its xml; -First view-
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/listview_item">
<TextView
android:layout_width="220dp"
android:layout_height="wrap_content"
android:textSize="18dp"
android:text="Item Name"
android:id="#+id/list_item"
android:maxLines="1"
android:layout_marginLeft="5dp"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Price"
android:textSize="18dp"
android:id="#+id/item_price"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_marginRight="5dp"/>
</RelativeLayout>
Here is my second view which contains 2 textview and 1 gridview.
Here its xml; -Second view-
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/listview_item"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="40dp"
android:text="Kategori"
android:id="#+id/categories_title_list_layout"/>
<GridView
android:layout_width="match_parent"
android:layout_height="0dp"
android:id="#+id/gridView_list_layout"
android:numColumns="2"
android:layout_weight="1"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Not: Lorem ipsum dolor sit amet.."
android:textSize="18dp"
android:id="#+id/categories_note"/>
</LinearLayout>
Here is my last view; -Third view-
This listview's every row takes shape of my second view which takes shape of first view.
Here is my adapter which are create this views.
For first view I use this adapter;
public class ItemListAdapter extends BaseAdapter {
private Context context;
private ArrayList<Item> items;
public ItemListAdapter(Context context, ArrayList<Item> items) {
super();
this.context = context;
this.items = items;
}
public int getCount() {
return items.size();
}
public Object getItem(int i) {
return items.get(i);
}
public long getItemId(int i) {
return i;
}
public View getView(final int i, View view, ViewGroup viewGroup) {
ViewHolder holder;
if(view == null) {
holder = new ViewHolder();
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.listview_item_content, null);
holder.title = (TextView) view.findViewById(R.id.list_item);
holder.price = (TextView) view.findViewById(R.id.item_price);
view.setTag(holder);
}
holder = (ViewHolder) view.getTag();
Typeface face=Typeface.createFromAsset(context.getAssets(), "fonts/roboto.ttf");
holder.title.setTypeface(face, Typeface.BOLD);
holder.title.setText(items.get(i).getName());
holder.title.setTextColor(Color.parseColor(Shop.getInstance().getItemGridTextColor()));
holder.price.setText(Global.getLocalizedPriceStringByLocale(Shop.getLocale(), items.get(i).getPrice()));
holder.price.setTextColor(Color.parseColor(Shop.getInstance().getItemGridTextColor()));
return view;
}
public class ViewHolder {
public TextView title;
public TextView price;
}
}
(This adapter takes item's title and price and puts them in first view.)
My second adapter which create second view is here;
public class CategoryListAdapter extends BaseAdapter {
private Context context;
//private ArrayList<Item> items;
private ArrayList<Category> currentCategory;
public CategoryListAdapter(Context context, ArrayList<Category> category) {
super();
this.context = context;
currentCategory = category;
}
public int getCount() {
return currentCategory.size();
}
public Object getItem(int i) {
return currentCategory.get(i);
}
public long getItemId(int i) {
return i;
}
public View getView(final int i, View view, ViewGroup viewGroup) {
ViewHolder holder;
if (view == null) {
holder = new ViewHolder();
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.grid_list_layout, null);
holder.title = (TextView) view.findViewById(R.id.categories_title_list_layout);
holder.gridView = (GridView) view.findViewById(R.id.gridView_list_layout);
holder.note = (TextView) view.findViewById(R.id.categories_note);
view.setTag(holder);
}
holder = (ViewHolder) view.getTag();
Typeface face = Typeface.createFromAsset(context.getAssets(), "fonts/roboto.ttf");
holder.title.setTypeface(face, Typeface.BOLD);
if(currentCategory.get(i).getName().equals("")){
holder.title.setText("Diğer");
}else{
holder.title.setText(currentCategory.get(i).getName());
}
holder.title.setTextColor(Color.parseColor(Shop.getInstance().getItemGridTextColor()));
holder.note.setText(currentCategory.get(i).getDeepNote());
holder.note.setTextColor(Color.parseColor(Shop.getInstance().getItemGridTextColor()));
holder.gridView.setAdapter(new ItemListAdapter(context, currentCategory.get(i).getItems()));
holder.gridView.setBackgroundColor(Color.parseColor(Shop.getInstance().getItemGridBackgroundColor()));
holder.gridView.getBackground().setAlpha(180);
return view;
}
public class ViewHolder {
public TextView title;
public GridView gridView;
public TextView note;
}
}
And I use this adapter to create ListView.
Here is my problem. This works really slow. I mean ListView freezes for a moment then slide down when I try to move down.
And there is another problem about GridView's height. My GridView's height is wrap_content but it doesn't behave like wrap_content. It shows bigger or smaller GridView.
For example; under "Diğer" title there should be a GridView which contains only 1 item as you can see, but it can not show the complete text. And under "Adet Ürünler" there should be 190 items but it only views 20 of them.
These are my problems. Sorry for my coding. If you can not understand my code, please ask me.
Thanks for your helps.
This answer won't give you a definitive solution, not because I'm not willing, but because it's impossible (and even harder without not just viewing your code, but knowing it very well). But from my experience I can tell you that those kind of memory leaks doesn't occur just due to directly referenced objects - objects you declare (and keep referencing another classes/objects) in turn depends on many other classes and so on, and probably you're seeing a memory leak due to an incorrect handling of any of your instances which at the same time reference other instances.
Debugging memory leaks is a often a very hard work, not just because as I said above it sometimes doesn't depend directly on what you've declared, but also because finding a solution might not be trivial. The best thing you can do is what you already seem to be doing: DDMS + HPROF. I don't know how much knowledge you have, but although it's not a universal method, this link helped me so much to find memory leaks in my code.
Although it seems trivial, the best way to debug those kind of things is progresively remove portions of your code (overall, those which implies working with instances of other classes) and see how the HPROF report change.
as far as i understand your problem, it's low performance during scrolling the list you've shown in the screenshot?
a solution to your problem would need you to rewrite your adapter and layouts. Your performance problem is due to the rather large list-items you use (e.g. a grid inside a single list item with 190 items), that have to be loaded during scrolling the list, together with a rather low re-usability of list-items.
in addition, i prefer not to use view-holders.
To get rid of the grids, you could use a list of objects (or wrappers like shown below), that contains the 'title', 'note' and the single grid rows in between. you would have to overwrite some of the adapters methods, to use multiple viewtypes inside one listview (like shown below).
perhaps you'll also need some more code, to map your model into the new list, but after all, your performance should be back to normal.
only disadvantage i know of (and have no fast solution for) is due to the different height in single list items, the scrollbar of the whole list shows sometimes a strange behaviour (like: height of scrollbar indicator changes during scroll)
wrapper
public class ListItemWrapper {
ListItemType type;
Object content;
public ListItemWrapper(ListItemType type, Object content) {
this.type = type;
this.content = content;
}
public enum ListItemType { Title,Note, Content;}
}
ListAdapter
public class ListAdapter extends ArrayAdapter<ListItemWrapper> {
private LayoutInflater inflater;
public ListAdapter(Context context, int resource) {
super(context, resource);
inflater = LayoutInflater.from(context);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ListItemWrapper item = getItem(position);
switch (item.type) {
case Content: return getViewForGridRow((GridRowContent)item.content, convertView);
case Note: return getViewForNote((String)item.content, convertView);
case Title: return getViewForTitle((String) item.content, convertView);
}
return convertView; //this case should never happen
}
private View getViewForTitle(String content, View convertView) {
if (convertView == null) {
//TODO inflate a new view for the title
convertView = inflater.inflate(R.layout.titleRowLayout, null);
}
//TODO set textview value for the title
return convertView;
}
private View getViewForNote(String content, View convertView) {
if (convertView == null) {
//TODO inflate a new view for the note
convertView = inflater.inflate(R.layout.noteRowLayout, null);
}
//TODO set textview value for the note
return convertView;
}
private View getViewForGridRow(GridRowContent item, View convertView) {
if (convertView == null) {
//TODO inflate a new view for a single grid row
convertView = inflater.inflate(R.layout.gridRowLayout, null);
}
//TODO set values of the grid row (e.g. textview items)
return convertView;
}
#Override
public int getItemViewType(int position) {
return getItem(position).type.ordinal();
}
#Override
public int getViewTypeCount() {
return ListItemType.values().length;
}
}
I simplified parts of the model, but i hope you'll get the point.
as you can see, i don't use a custom BaseAdapter, since the arrayadapter already manages my collection. i only need to tell the list, that there are different item view types and which item in the collection uses which view type.
Additionally, there's no need to use any holders, since all holding and managing the different views is already done by the adapter - e.g. we only need to inflate views if the given convertView is not already cached within the adapter.
using this way should be much more memory efficient and performance should increase, since much less views have to be inflated in a single step.
I hope this helps,
Christian
Edit
can't explain the disapearing gridview in the center, but that shouldn't happen anymore

How use ArrayAdapter<T> with complex Layout?

This is the first time I need to use the ArrayAdapter<T> to show a multi-item Row-Layout. After a lot of successful work with different adapters this one is driving me crazy.
getView(..., position, ...) always returns 0-7[EDIT] so I never see elements in my ArrayList that are on position >= 7[/EDIT]. I know, this is the visible position, but how do I select the correct object in the ArrayList?
EDIT: Currently I only get the first 8 elements out of my array because position only comes in from 0-7 - even on a 50 element ArrayList. I don't see a way to position within the ArrayList without a "real" position.
The docs say the following - but I don't get it. Did somebody successfully implement an ArrayAdapter<T> with a complex layout? What do the doc mean and how should I implement it?
If you want to use a more complex
layout, use the constructors that also
takes a field id. That field id should
reference a TextView in the larger
layout resource. However the TextView
is referenced, it will be filled with
the toString() of each object in the
array. You can add lists or arrays of
custom objects. Override the
toString() method of your objects to
determine what text will be displayed
for the item in the list. To use
something other than TextViews for the
array display, for instance,
ImageViews, or to have some of data
besides toString() results fill the
views, override getView(int, View,
ViewGroup) to return the type of view
you want.
Many thanks in advance
hjw
Here's the code so far:
public class HappyAdapter extends ArrayAdapter<Happy> {
static class ViewHolder {
private ImageView imageView;
private TextView textViewBottom;
private TextView textViewTop;
}
private ArrayList<Happy> arrayListHappy;
private DrawableCache drawableCache = DrawableCache.getInstance();
private int layout;
#Override
public int getCount() {
return arrayListHappy.size();
}
#Override
public View getView(int position, View contentView, ViewGroup viewGroup) {
// position always 0-7
View view = null;
ViewHolder viewHolder = null;
if (contentView == null) {
LayoutInflater layoutInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = layoutInflater.inflate(layout, null);
if (view != null) {
viewHolder = new ViewHolder();
viewHolder.imageView = (ImageView) view.findViewById(R.id.happyactivity_row_image_left);
viewHolder.textViewBottom = (TextView) view.findViewById(R.id.happyactivity_row_text_bottom);
viewHolder.textViewTop = (TextView) view.findViewById(R.id.happyactivity_row_text_top);
view.setTag(viewHolder);
}
} else {
view = contentView;
viewHolder = (ViewHolder) contentView.getTag();
}
if (viewHolder != null) {
Happy happy = arrayListHappy.get(position);
if (happy != null) {
viewHolder.imageView.setUrl(happy.getImageThumbnail());
drawableCache.fetchDrawable(happy.getImageThumbnail(), viewHolder.imageView);
viewHolder.textViewBottom.setText(String.valueOf(position));
viewHolder.textViewTop.setText(String.valueOf(viewHolder.position));
}
}
return view;
}
public HappyAdapter(Context context, int layout, ArrayList<Happy> arrayListHappy) {
super(context, layout, arrayListHappy);
this.arrayListHappy = arrayListHappy;
this.layout = layout;
}
}
This is part of the Row-Layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:orientation="horizontal" >
<ImageView
android:id="#+id/happyactivity_row_image_left"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_width="fill_parent" />
<TextView
style="#style/TextViewStandard"
android:id="#+id/happyactivity_row_text_top"
android:layout_weight="1" />
<TextView
style="#style/TextViewStandard"
android:id="#+id/happyactivity_row_text_bottom"
android:layout_weight="1" />
</LinearLayout>
</LinearLayout>
I can't add comments yet so have to answer. As others have noted it isn't really clear what's not working. However, you are only seeing positions 0-7 being inflated as this is the only portion of the list that is currently visible, as you noted yourself. Other rows (with higher position numbers) won't be inflated until you scroll down the list.
Although it uses the BaseAdapter class, rather than ArrayAdapter that you are using, you could look at List14.java in the ApiDemos sample code (ApiDemos/src/com/example/android/apis/view/List14.java) as the principle is the same. When using ArrayAdapter though, you don't need to override all the methods that this sample code does (just getView).

BaseAdapter causing ListView to go out of order when scrolled

I'm having problems with some BaseAdapter code that I adapted from a book. I've been using variations of this code all over the place in my application, but only just realized when scrolling a long list the items in the ListView become jumbled and not all of the elements are displayed.
It's very hard to describe the exact behavior, but it's easy to see if you take a sorted list of 50 items and start scrolling up and down.
class ContactAdapter extends BaseAdapter {
ArrayList<Contact> mContacts;
public ContactAdapter(ArrayList<Contact> contacts) {
mContacts = contacts;
}
#Override
public int getCount() {
return mContacts.size();
}
#Override
public Object getItem(int position) {
return mContacts.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
if(convertView == null){
LayoutInflater li = getLayoutInflater();
view = li.inflate(R.layout.groups_item, null);
TextView label = (TextView)view.findViewById(R.id.groups_item_title);
label.setText(mContacts.get(position).getName());
label = (TextView)view.findViewById(R.id.groups_item_subtitle);
label.setText(mContacts.get(position).getNumber());
}
else
{
view = convertView;
}
return view;
}
}
You are only putting data in the TextView widgets when they are first created. You need to move these four lines:
TextView label = (TextView)view.findViewById(R.id.groups_item_title);
label.setText(mContacts.get(position).getName());
label = (TextView)view.findViewById(R.id.groups_item_subtitle);
label.setText(mContacts.get(position).getNumber());
to be after the if/else block and before the method return, so you update the TextView widgets whether you are recycling the row or creating a fresh one.
To further clarify the answer of CommonsWare, here is some more info:
The li.inflate operation (needed here for parsing of the layout of a row from XML and creating the appropriate View object) is wrapped by an if (convertView == null) statement for efficiency, so the inflation of the same object will not happen again and again every time it pops into view.
HOWEVER, the other parts of the getView method are used to set other parameters and therefore should NOT be included within the if (convertView == null){ }... else{ } statement.
In many common implementation of this method, some textView label, ImageView or ImageButton elements need to be populated by values from the list[position], using findViewById and after that .setText or .setImageBitmap operations.
These operations must come after both creating a view from scratch by inflation and getting an existing view if not null (e.g. on a refresh).
Another good example where this solution is applied for a ListView ArrayAdapter appears in https://stackoverflow.com/a/3874639/978329

Categories

Resources