How use ArrayAdapter<T> with complex Layout? - android

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).

Related

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.

ListView: how to access Item's elements programmatically from outside?

I have the following situation.
I have a ListView, each item of the ListView is comprised of different widgets (TextViews, ImageViews, etc...) inflated form a Layout in the getView() method of the custom adapter.
Now, I would like to achieve the following:
when a certain event is triggered I want to change the background of a View which is inside the item.
Please how do I do it?
This is the the Item Layout:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/cardlayout"
android:layout_width="320dp"
android:layout_height="130dp"
android:background="#android:color/transparent"
android:orientation="vertical"
android:paddingBottom="5dp"
android:paddingRight="5dp"
android:paddingTop="5dp" >
<FrameLayout
android:layout_width="320dp"
android:layout_height="117dp" >
<View
android:id="#+id/card"
android:layout_width="320dp"
android:layout_height="117dp"
android:background="#drawable/card_selector" />
</FrameLayout>
</LinearLayout>
I need to change the background of card
I have tried doing this:
View v=lv.getAdapter().getView(index, null, lv);
View card =(View)v.findViewById(R.id.card);
card.setBackgroundResource(R.drawable.pressed_background_card);
But no success :-((
When your event is triggered you should just call a notifyDataSetChanged on your adapter so that it will call again getView for all your visible elements.
Your getView method should take into account that some elements may have different background colors (and not forget to set it to normal color if the element doesn't need the changed background, else with recycling you would have many elements with changed background when you scroll)
edit :
I would try something like this :
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null)
{
convertView = LayoutInflater.from(getContext()).inflate(R.layout.card, parent, false);
}
//This part should also be optimised with a ViewHolder
//because findViewById is a costly operation, but that's not the point of this example
CardView cardView =(CardView)convertView .findViewById(R.id.card);
//I suppose your card should be determined by your adapter, not a new one each time
Card card = getItem(position);
//here you should check sthg like the position presence in a map or a special state of your card object
if(mapCardWithSpecialBackground.contains(position))
{
card.setBackgroundResource(specialBackground);
}
else
{
card.setBackgroundResource(normalBackground);
}
cardView.setCard(card);
return convertView;
}
And on the special event i would add the position of the item into the map and call notifyDataSetChanged.
Use the onitemclicklistener which has method onclicksomething..that takes four or five parameters. (View parent, View view, int position, int id). Use the view parameter to customize your background.
Update
Here's some of my code, If you don't understand I recommend to read about recycling and ViewHolder pattern.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
{
ViewHolder viewHolder;
// If convertView isn't a recycled view, create a new.
if(convertView == null){
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.row_gallery_frame, parent, false);
viewHolder = new ViewHolder();
// Here you must be able to find your Widget inside convertView and set a listener to it I guess?
viewHolder.nameHolder = (TextView) convertView.findViewById(R.id.nameTv);
// Set a reference to newly inflated view
convertView.setTag(viewHolder);
}
// If it is, then get the ViewHolder by tag
else{
viewHolder = (ViewHolder)convertView.getTag();
}
// Set the data
GalleryFrame galleryFrame = galleryFrameArrayList.get(position);
viewHolder.nameHolder.setText(galleryFrame.getName());
return convertView;
}
}
// Viewholder pattern which holds all widgets used
public static class ViewHolder{
public TextView nameHolder;
}
I assume you have a model object that you use to "draw" the list item , and for example the background color is determined based on a boolean or something.
All you need to do, is change the value on which you base your decision which background color should that TextView have.
Your getView() method should have code like that
if (myModelObj.isBrown()) {
myTextView.setBackgroundResource(R.drawable.brown_bg);
else
myTextView.setBackgroundResource(R.drawable.not_brown_bg);
All you should do when ur event is triggered, is set the value of the brown boolean in your model
and call notifyDataSetChanged() on your adapter
EDIT
If for some reason you don't wanna call nofitfyDataSetChanged(), althought it won't move the scroll position of your list and with the right recyclying it won't cause bad performance
You can find the View object that represent the list item you want to edit-if it's visisble-, and simply change the background in it, without refreshing the list at all.
int wantedPosition = 10; // Whatever position you're looking for
int firstPosition = listView.getFirstVisiblePosition() - listView.getHeaderViewsCount();
int wantedChild = wantedPosition - firstPosition
if (wantedChild < 0 || wantedChild >= listView.getChildCount()) {
// Wanted item isn't displayed
return;
}
View wantedView = listView.getChildAt(wantedChild);
then use wantedView to edit your background
This answer can be found here
try this one:
View v=lv.getAdapter().getView(index, null, lv);
View card =(View)v.findViewById(R.id.card);
card.setBackgroundResource(R.drawable.pressed_background_card);
card.invalidate();
v.invalidate();
those function force your views to redraw itself and they will render again.
look at invalidate()
What I normally do is this:
public static class EventDetailsRenderer {
private TextView title;
private TextView description;
private Event item;
public EventDetailsRenderer(View view) {
extractFromView(view);
}
private final void extractFromView(View view) {
title = (TextView) view.findViewById(R.id.EventTitle);
description = (TextView) view.findViewById(R.id.Description);
}
public final void render() {
render(item);
}
public final void render(Event item) {
this.item= item;
title.setText(item.getTitle());
description.setText(item.getDescription());
}
}
private class EventsAdapter
extends ArrayAdapter<Event> {
public EventsAdapter(Context context) {
super(context, R.layout.list_node__event_details, 0);
}
public void addAllItems(Event... services) {
for (int i = 0; i < services.length; i++) {
add(services[i]);
}
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
Event event = getItem(position);
EventDetailsRenderer eventRenderer;
if (convertView != null && convertView.getTag() != null) {
eventRenderer = (EventDetailsRenderer) convertView.getTag();
} else {
convertView = getActivity().getLayoutInflater().inflate(R.layout.list_node__event_details, null);
eventRenderer = new EventDetailsRenderer(convertView);
convertView.setTag(eventRenderer);
}
eventRenderer.render(event);
return convertView;
}
}
NOTE: that this example might not compile I pasted it from some code I have and deleted some lines to show an example but the logic it the same.
And then when you want to render it, just get the children from the list, iterate over them, check if the renderer contains the card you want to flip and call its render method... then you render a specific item in the list without effecting the rest of the items.
Let me know if this works...
Adam.
User EasyListViewAdapters library https://github.com/birajpatel/EasyListViewAdapters
Features
Easier than implementing your own Adapter (ie handling
BaseAdaper#getView).Very Easier to provide multi-row support.
Library takes care of recycling all views, that ensures performance
& helps your list view scroll smoothly.
Cleaner code. By keeping different RowViewSetter classes for
different row-types makes your code easy to manage & easy to reuse.
No data browsing, Library takes care of browsing data through
data-structure when View is being drawn or event occurs so that
Users does not have to look for their data to take actions.
Just by passing correct row-types library will Auto-map your
data-types to row-types to render views. Row views can be created by
using XML or Java (doesn't restrict to XML-Only Approach).
Load More callbacks can be registered to implement paginatation
support to your list.
Handling children viewclicks, you can also register for
Children(present inside your rows) view click events.
All these Views are registered with single OnClickListner so that
this mechanism is very memory efficient when click event occurs
users you gets clickedChildView, rowData,int eventId as callback
params.

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

ListView, and ArrayAdapter issue, How do I proceed?

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

Android ListView with different layouts for each row

I am trying to determine the best way to have a single ListView that contains different layouts for each row. I know how to create a custom row + custom array adapter to support a custom row for the entire list view, but how can I implement many different row styles in the ListView?
Since you know how many types of layout you would have - it's possible to use those methods.
getViewTypeCount() - this methods returns information how many types of rows do you have in your list
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.
Look at this tutorial for further information.
To achieve some optimizations in structure that you've described in comment I would suggest:
Storing views in object called ViewHolder. It would increase speed because you won't have to call findViewById() every time in getView method. See List14 in API demos.
Create one generic layout that will conform all combinations of properties and hide some elements if current position doesn't have it.
I hope that will help you. If you could provide some XML stub with your data structure and information how exactly you want to map it into row, I would be able to give you more precise advise. By pixel.
I know how to create a custom row + custom array adapter to support a custom row for the entire list view. But how can one listview support many different row styles?
You already know the basics. You just need to get your custom adapter to return a different layout/view based on the row/cursor information being provided.
A ListView can support multiple row styles because it derives from AdapterView:
An AdapterView is a view whose children are determined by an Adapter.
If you look at the Adapter, you'll see methods that account for using row-specific views:
abstract int getViewTypeCount()
// Returns the number of types of Views that will be created ...
abstract int getItemViewType(int position)
// Get the type of View that will be created ...
abstract View getView(int position, View convertView, ViewGroup parent)
// Get a View that displays the data ...
The latter two methods provide the position so you can use that to determine the type of view you should use for that row.
Of course, you generally don't use AdapterView and Adapter directly, but rather use or derive from one of their subclasses. The subclasses of Adapter may add additional functionality that change how to get custom layouts for different rows. Since the view used for a given row is driven by the adapter, the trick is to get the adapter to return the desired view for a given row. How to do this differs depending on the specific adapter.
For example, to use ArrayAdapter,
override getView() to inflate, populate, and return the desired view for the given position. The getView() method includes an opportunity reuse views via the convertView parameter.
But to use derivatives of CursorAdapter,
override newView() to inflate, populate, and return the desired view for the current cursor state (i.e. the current "row") [you also need to override bindView so that widget can reuse views]
However, to use SimpleCursorAdapter,
define a SimpleCursorAdapter.ViewBinder with a setViewValue() method to inflate, populate, and return the desired view for a given row (current cursor state) and data "column". The method can define just the "special" views and defer to SimpleCursorAdapter's standard behavior for the "normal" bindings.
Look up the specific examples/tutorials for the kind of adapter you end up using.
Take a look in the code below.
First, we create custom layouts. In this case, four types.
even.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:background="#ff500000"
android:layout_height="match_parent">
<TextView
android:id="#+id/text"
android:textColor="#android:color/white"
android:layout_width="match_parent"
android:layout_gravity="center"
android:textSize="24sp"
android:layout_height="wrap_content" />
</LinearLayout>
odd.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:background="#ff001f50"
android:gravity="right"
android:layout_height="match_parent">
<TextView
android:id="#+id/text"
android:textColor="#android:color/white"
android:layout_width="wrap_content"
android:layout_gravity="center"
android:textSize="28sp"
android:layout_height="wrap_content" />
</LinearLayout>
white.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:background="#ffffffff"
android:gravity="right"
android:layout_height="match_parent">
<TextView
android:id="#+id/text"
android:textColor="#android:color/black"
android:layout_width="wrap_content"
android:layout_gravity="center"
android:textSize="28sp"
android:layout_height="wrap_content" />
</LinearLayout>
black.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:background="#ff000000"
android:layout_height="match_parent">
<TextView
android:id="#+id/text"
android:textColor="#android:color/white"
android:layout_width="wrap_content"
android:layout_gravity="center"
android:textSize="33sp"
android:layout_height="wrap_content" />
</LinearLayout>
Then, we create the listview item. In our case, with a string and a type.
public class ListViewItem {
private String text;
private int type;
public ListViewItem(String text, int type) {
this.text = text;
this.type = type;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
}
After that, we create a view holder. It's strongly recommended because Android OS keeps the layout reference to reuse your item when it disappears and appears back on the screen. If you don't use this approach, every single time that your item appears on the screen Android OS will create a new one and causing your app to leak memory.
public class ViewHolder {
TextView text;
public ViewHolder(TextView text) {
this.text = text;
}
public TextView getText() {
return text;
}
public void setText(TextView text) {
this.text = text;
}
}
Finally, we create our custom adapter overriding getViewTypeCount() and getItemViewType(int position).
public class CustomAdapter extends ArrayAdapter {
public static final int TYPE_ODD = 0;
public static final int TYPE_EVEN = 1;
public static final int TYPE_WHITE = 2;
public static final int TYPE_BLACK = 3;
private ListViewItem[] objects;
#Override
public int getViewTypeCount() {
return 4;
}
#Override
public int getItemViewType(int position) {
return objects[position].getType();
}
public CustomAdapter(Context context, int resource, ListViewItem[] objects) {
super(context, resource, objects);
this.objects = objects;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder = null;
ListViewItem listViewItem = objects[position];
int listViewItemType = getItemViewType(position);
if (convertView == null) {
if (listViewItemType == TYPE_EVEN) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.type_even, null);
} else if (listViewItemType == TYPE_ODD) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.type_odd, null);
} else if (listViewItemType == TYPE_WHITE) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.type_white, null);
} else {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.type_black, null);
}
TextView textView = (TextView) convertView.findViewById(R.id.text);
viewHolder = new ViewHolder(textView);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.getText().setText(listViewItem.getText());
return convertView;
}
}
And our activity is something like this:
private ListView listView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); // here, you can create a single layout with a listview
listView = (ListView) findViewById(R.id.listview);
final ListViewItem[] items = new ListViewItem[40];
for (int i = 0; i < items.length; i++) {
if (i == 4) {
items[i] = new ListViewItem("White " + i, CustomAdapter.TYPE_WHITE);
} else if (i == 9) {
items[i] = new ListViewItem("Black " + i, CustomAdapter.TYPE_BLACK);
} else if (i % 2 == 0) {
items[i] = new ListViewItem("EVEN " + i, CustomAdapter.TYPE_EVEN);
} else {
items[i] = new ListViewItem("ODD " + i, CustomAdapter.TYPE_ODD);
}
}
CustomAdapter customAdapter = new CustomAdapter(this, R.id.text, items);
listView.setAdapter(customAdapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView adapterView, View view, int i, long l) {
Toast.makeText(getBaseContext(), items[i].getText(), Toast.LENGTH_SHORT).show();
}
});
}
}
now create a listview inside mainactivity.xml
like this
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context="com.example.shivnandan.gygy.MainActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="#style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
<include layout="#layout/content_main" />
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/listView"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_marginTop="100dp" />
</android.support.design.widget.CoordinatorLayout>
In your custom array adapter, you override the getView() method, as you presumably familiar with. Then all you have to do is use a switch statement or an if statement to return a certain custom View depending on the position argument passed to the getView method. Android is clever in that it will only give you a convertView of the appropriate type for your position/row; you do not need to check it is of the correct type. You can help Android with this by overriding the getItemViewType() and getViewTypeCount() methods appropriately.
If we need to show different type of view in list-view then its good to use getViewTypeCount() and getItemViewType() in adapter instead of toggling a view VIEW.GONE and VIEW.VISIBLE can be very expensive task inside getView() which will affect the list scroll.
Please check this one for use of getViewTypeCount() and getItemViewType() in Adapter.
Link : the-use-of-getviewtypecount
ListView was intended for simple use cases like the same static view for all row items.
Since you have to create ViewHolders and make significant use of getItemViewType(), and dynamically show different row item layout xml's, you should try doing that using the RecyclerView, which is available in Android API 22. It offers better support and structure for multiple view types.
Check out this tutorial on how to use the RecyclerView to do what you are looking for.

Categories

Resources