add button to only one row in android listView - android

I am trying to add a button in the middle of my listView. Ideally The button will split the listView and it will continue afterward, but if this is not possible I will be ok with a button inside a row in the listView.
For example. My list view will have line one (image + text) , line two ( image + text) , button, and go on with the list view.
I have wrote the following code. This adds a button to a row in listView, but on the way it also adds an empty button (a button with now text) to every row in my listView. In addition the gravity setting for center is not working.
My xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal" >
<ImageView android:id="#+id/imgUserIcon"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_marginRight="10dp"
android:layout_marginTop="5dp"
android:gravity="center_vertical"
android:scaleType="fitStart" />
<Button
android:id="#+id/buttonShowHide"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="#string/showHide" />
<TextView android:id="#+id/txtTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginTop="5dp"
/>
</LinearLayout>
my adapter
public class UserAdapter extends ArrayAdapter<UserAccountData> {
Context context;
int layoutResourceId;
User data[] = null;
public UserAdapter(Context context, int layoutResourceId,
UserAccountData[] data) {
super(context, layoutResourceId, data);
this.layoutResourceId = layoutResourceId;
this.context = context;
this.data = data;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
UserAccountDataHolder holder = null;
if (row == null) {
LayoutInflater inflater = ((Activity) context).getLayoutInflater();
row = inflater.inflate(layoutResourceId, parent, false);
holder = new UserAccountDataHolder();
holder.imgIcon = (ImageView) row.findViewById(R.id.imgUserIcon);
holder.txtTitle = (TextView) row.findViewById(R.id.txtTitle);
holder.showHide = (Button) row.findViewById(R.id.buttonShowHide);
row.setTag(holder);
} else {
holder = (UserHolder) row.getTag();
}
User user = data[position];
holder.txtTitle.setText(user.title);
holder.imgIcon.setImageResource(user.icon);
holder.showHide.setText(user.buttonName);
return row;
}
static class UserHolder {
ImageView imgIcon;
TextView txtTitle;
Button showHide;
}
}
My Java object for the row. I have created two constructors one for the button and one for the image and text.
public class UserAccountData {
public int icon;
public String type;
public String title;
public CharSequence buttonName;
public UserAccountData(){
super();
}
// for image and text
public UserAccountData(int icon, String title, String type) {
super();
this.icon = icon;
this.title = title;
this.type = type;
}
// for button
public UserData(CharSequence buttonName, String type) {
super();
this.buttonName = buttonName;
this.type = type;
}
public void setType(String type){
this.type = type;
}
}
In my activity I am adding the following two rows to the array , that later my adapter will use to create the listView ( I am passing it an ArrayList that being changed into an Array)
user_data.add(new UserAccountData(icon, "title,"type"));
user_data.add(new UserAccountData("show Password","button"));
a) is there a way to split the listView and the middle and just add a button? and continue the same listView? Because my current solution tries to add a button to a row.
b) any ideas why I am actually also adding an empty button to the icon, title type row?
I am getting icon, title, empty button on my actual listView
Thank you very much
UPDATE:
Found two blogs
http://logc.at/2011/10/10/handling-listviews-with-multiple-row-types/
and
http://android.amberfog.com/?p=296
, but still don't have any luck. Would appreciate some more in depth help

is there a way to split the listView and the middle and just add a button? and continue the same listView? Because my current solution tries to add a button to a row.
If I understand your question you want something like:
My list view will have:
image + text
image + text
button
image + text
etc...
You can have more than one type of row layout if you override getViewTypeCount() and getItemViewType().
getViewTypeCount() should return the number of types, in this case 2.
getItemViewType(int position) will return which type the row at position is, in this case either 0 or 1.
Addition
I don't really know how to make the distinction between the image text row and the button. I tried to find a way to see if my image is null (using the 2nd constructor) , but this does not seems to work
This sounds like a good approach, but since icon is an int it will never be null, the default value for an uninitialized integer is 0. Try:
#Override
public int getItemViewType(int position) {
UserAccountData data = getItem(position);
if(data.icon == 0)
return 1;
return 0;
// The same thing in one line:
//return getItem(position).icon == 0 ? 1 : 0;
}

Related

RecyclerView onBindViewHolder position not as expected

I know this problem has being asked numerous times relating to ListViews, However I have never been able to understand the solution truly. I am aware that the position parameter of onBindViewHolder() in RecyclerViewAdapter is similar to the position parameter in listAdapter getView(). I also am aware that the position is only relative to visible views. So how can I go about loading an Image for each row of the RecyclerView? The image may be different depending on position. The RecyclerView has a known number of rows (57)
I intend to have an array list of 'favourites'for example int[] fav = {2,6,30,40}. So if position equals one of these elements then ImageView sets one drawble, else it sets another.Could anyone help me with this?
At the moment position repeats itself and many extra rows are setting the drawable reserved for row IDs in the fav array
Thank You,
public class RecyclerViewAdapter extends ...... {
private List<StockItemRow> stockItems;
private Context mContext;
private int[] favourites = {2,3};
RecyclerViewAdapter(Context context, List modelData) {
if (modelData == null) {
throw new IllegalArgumentException("modelData must not be null");
}
this.stockItems = modelData;
this.mContext = context;
}
#Override
public ListItemViewHolder onCreateViewHolder(...)
{
View itemView =
LayoutInflater.fromLayoutInflater.
from(viewGroup.getContext()).
inflate(R.layout.listitem_row, viewGroup, false);
return new ListItemViewHolder(itemView);
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
#Override
public void onBindViewHolder(ListItemViewHolder viewHolder, int position) {
StockItemRow stockModel = stockItems.get(position);
for(int i = 0;i<favourites.length;i++) {
if (position == favourites[i]) {
viewHolder.star.setImageResource(R.drawable.star_pressed);
}
}
#Override
public int getItemCount() {
return stockItems.size();
}
public final static class ListItemViewHolder extends....
ImageView star;
public ListItemViewHolder(final View itemView) {
super(itemView);
star= (ImageView) itemView.findViewById(R.id.star);
}
}
}
XML
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<android.support.v7.widget.RecyclerView
android:layout_height="match_parent"
android:id="#+id/recyclerView"
android:divider="#drawable/list_selector"
android:dividerHeight="1dip"
android:layout_width="match_parent">
</android.support.v7.widget.RecyclerView>
</LinearLayout>
#Deev is correct: the position is the position within the adapter, not on screen. The problem you are running into is because your onBindViewHolder() method is only handling the favorite case, but not the non-favorite case. The ViewHolder pattern is used for efficiency and it's re-using views. Change the code to be this:
int fav_res = R.drawable.star;
for(int i = 0;i<favourites.length;i++) {
if (position == favourites[i]) {
fav_res = R.drawable.star_presed;
break;
}
}
viewHolder.star.setImageResource(fav_res);

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

EditText view not remaining in focus when in ListFragment

When I tap on an EditText (see the "Your answer here ..." field is the screen-cap below) which is inside a ListFragment it does not remain in focus and this makes it impossible to use. As soon as the text cursor appears on the EditText field it quickly disappears and the EditText field looses focus.
I have found few related stack posts and suspect something might be stealing the focus from the EditText. See related post here. I have also read posts on forcing focus but nothing makes sense.
I could build the view programmatically however it is much easier to use the standard ListFragment and ArrayAdapter code. The list will grow with user input and it's easier to manage with the infrastructure that Android provides.
See my screen below. On the right hand side of the screen is a ListFragment which contains custom layouts for the rows. Each row contains an EditText and a Button to submit the text.
Here is the code for the ListFragment:
public class ItemDetailFragment extends ListFragment {
/**
* The fragment argument representing the item ID that this fragment
* represents.
*/
public static final String ARG_ITEM_ID = "item_id";
private static final String STATE_ACTIVATED_POSITION = "activated_position";
/**
* The dummy content this fragment is presenting.
*/
private static DetailArrayAdapter mAdapter;
/**
* Mandatory empty constructor for the fragment manager to instantiate the
* fragment (e.g. upon screen orientation changes).
*/
public ItemDetailFragment() {
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ArrayList<DetailData> items = new ArrayList<DetailData>();
items.add(new DetailData("1"));
mAdapter = new DetailArrayAdapter(getActivity(), R.layout.detail_layout, items);
// TODO: replace with a real list adapter.
setListAdapter(mAdapter);
}
class DetailArrayAdapter extends ArrayAdapter<DetailData> {
Context context = null;
int layoutResourceId;
ArrayList<DetailData> data;
public DetailArrayAdapter(Context context, int layoutResourceId, ArrayList<DetailData> data){
super(context, layoutResourceId, data);
this.context = context;
this.layoutResourceId = layoutResourceId;
this.data = data;
}
public View getView(int position, View convertView, ViewGroup parent){
View row = convertView;
LayoutInflater inflater = ((Activity)context).getLayoutInflater();
row = inflater.inflate(layoutResourceId, parent, false);
DetailData data = this.data.get(position);
EditText text = (EditText) row.findViewById(R.id.answer);
text.setText(data.detail_number + "You answer here ...");
Button submit_button = (Button) row.findViewById(R.id.answer_submit);
submit_button.setOnClickListener(new OnClickListener(){
#Override
public void onClick(View v) {
int count = mAdapter.getCount();
int item_num = count + 1;
DetailData d = new DetailData(""+item_num);
mAdapter.add(d);
}
});
return row;
}
}
class DetailData {
public String detail_number;
public DetailData(String num){
this.detail_number = num;
}
}
}
Here is the layout code for the custom row:
<?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:orientation="horizontal" >
<EditText
android:id="#+id/answer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="Your answer here..."
android:maxLines="1"
android:singleLine="true"/>
<Button
android:id="#+id/answer_submit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Go" />
</LinearLayout>
Adding the following line to ItemDetailFragment.DetailArrayAdapter.getView() after the creation of the EditText object fixes the problem:
text.requestFocus();
I don't understand it, but having the EditText request focus when the view is retrieved enables that EditText (as well as the others) on the screen to be focused.
requestFocus() only works for the last edittext added, if you have several editext on the listfragment, it doesn't work

Making one row in a ListView to a password (show and hide) row

I have created a customized listView using the following tutorial http://www.ezzylearning.com/tutorial.aspx?tid=1763429.
My list view includes two row (each with an image and a TextView). The first row is user, and the second is password.
I am looking for a way to make the password row to masked, something like ****, and to add another row that will enable the user to set it to visible/ mask.
I found the following examples,
How to show hidden password in textview?
How to switch between hide and view password
but I have no idea how to implement this on a specific row.
my rows xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal" >
<ImageView android:id="#+id/imgUserAccountIcon"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_marginRight="10dp"
android:layout_marginTop="5dp"
android:gravity="center_vertical"
android:scaleType="fitStart" />
<TextView android:id="#+id/txtTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginTop="5dp"
/>
</LinearLayout>
My user class
public class UserAccountData {
public int icon;
public String title;
public UserAccountData(){
super();
}
public UserAccountData(int icon, String title) {
super();
this.icon = icon;
this.title = title;
}
}
My adapter class
public class UserAccountAdapter extends ArrayAdapter<UserAccountData> {
Context context;
int layoutResourceId;
UserAccountData data[] = null;
public UserAccountAdapter(Context context, int layoutResourceId,
UserAccountData[] data) {
super(context, layoutResourceId, data);
this.layoutResourceId = layoutResourceId;
this.context = context;
this.data = data;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
UserAccountDataHolder holder = null;
if (row == null) {
LayoutInflater inflater = ((Activity) context).getLayoutInflater();
row = inflater.inflate(layoutResourceId, parent, false);
holder = new UserAccountDataHolder();
holder.imgIcon = (ImageView) row.findViewById(R.id.imgUserAccountIcon);
holder.txtTitle = (TextView) row.findViewById(R.id.txtTitle);
row.setTag(holder);
} else {
holder = (UserAccountDataHolder) row.getTag();
}
UserAccountData userAccountData = data[position];
holder.txtTitle.setText(userAccountData.title);
holder.imgIcon.setImageResource(userAccountData.icon);
return row;
}
static class UserAccountDataHolder {
ImageView imgIcon;
TextView txtTitle;
}
}
and the appropriate list view snippet of the activity method
List<UserAccountData> user_data = new ArrayList<UserAccountData>();
user_data.add(new UserAccountData(R.drawable.username_icon,"userName");
user_data.add(new UserAccountData(R.drawable.password_icon,"password");
usersArray = new UserAccountData[user_data.size()];
user_data.toArray(usersArray);
UserAccountAdapter adapter = new UserAccountAdapter(this, R.layout.user_accounts_row, usersArray);
userAccountsListView = (ListView) findViewById(R.id.userAccounts);
userAccountsListView.setAdapter(adapter);
Attaching a picture of what I would like to accomplish
Before click:
after click
thanks
You could fill up a list of TextViews in your adapter while you inflated. After inflation, you'd have access to all the TextViews, and then in your onItemClickListener, when someone clicked the item in in Position 2 (in this case at least), you simply reference your list of TextViews, get the appropriate one, and change the parameters on the fly.
If you have a set, small number of rows like this a TableLayout is a better fit than a ListView. Then you could easily customize the xml of the two TextViews. Another option is to call setRawInputType to set the password on just the second TextView. You'd need to do that in the adapter's getView function based on the view's position.
Well I got around this problem by displaying the password as plain-text in an AlertDialog when the user clicks the ListView entry.

Android: What to do if performance of ListView is still not enough?

Well this topic was and still is debated really a lot and I already read many tutorials, hints and saw talks about it. But I still have problems with my implementation of a custom BaseAdapter for a ListView whenever I reach a certain complexity of my rows.
So what I basically have are some entities I'm getting by parsing xml coming from the network. In addition I fetch some Images, etc. and all this is done in an AsyncTask.
I use the performance optimizing ViewHandler approach within my getView() method and reuse convertView as suggested by everyone. I.e. I hope that I'm using ListView as it's supposed to be and it really works fine when I'm just displaying a single ImageView and two TextViews, which are styled with a SpannableStringBuilder (I don't use any HTML.fromHTML whatsoever).
And now here it comes. Whenever I extend my row layout with multiple small ImageViews, a Button and some more TextViews all differently styled with SpannableStringBuilder, I get a ceasing scroll performance. The row consists of a RelativeLayout as a parent and all other elements are arranged with layout parameters, so I can't get the row to be more simple in its layout. I must admit that I never saw any example of a ListView implementation with rows containing that many UI elements.
However, when I'm using a TableLayout within a ScrollView and filling it by hand with an AsyncTask (new rows added steadily by onProgressUpdate() ), it behaves perfectly smooth even with hundreds of rows in it. It just stumbles a little bit when new rows are added if scrolled to the end of the list. Otherwise it's much smoother than with the ListView, where it's always stumbling when scrolled.
Are there any suggestions what to do when a ListView just doesn't want to perform well? Should I stay with the TableLayout approach or is it advised to fiddle with a ListView to optimize the performance a bit?
Here is the implementation of my adapter:
protected class BlogsSeparatorAdapter extends BaseAdapter {
private LayoutInflater inflater;
private final int SEPERATOR = 0;
private final int BLOGELEMENT = 1;
public BlogsSeparatorAdapter(Context context) {
inflater = LayoutInflater.from(context);
}
#Override
public int getCount() {
return blogs.size();
}
#Override
public Object getItem(int position) {
return position;
}
#Override
public int getViewTypeCount() {
return 2;
}
#Override
public int getItemViewType(int position) {
int type = BLOGELEMENT;
if (position == 0) {
type = SEPERATOR;
} else if (isSeparator(position)) {
type = SEPERATOR;
}
return type;
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
UIBlog blog = getItem(position);
ViewHolder holder;
if (convertView == null) {
holder = new ViewHolder();
convertView = inflater.inflate(R.layout.blogs_row_layout, null);
holder.usericon = (ImageView) convertView.findViewById(R.id.blogs_row_user_icon);
holder.title = (TextView) convertView.findViewById(R.id.blogs_row_title);
holder.date = (TextView) convertView.findViewById(R.id.blogs_row_date);
holder.amount = (TextView) convertView.findViewById(R.id.blogs_row_cmmts_amount);
holder.author = (TextView) convertView.findViewById(R.id.blogs_row_author);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.usericon.setImageBitmap(blog.icon);
holder.title.setText(blog.titleTxt);
holder.date.setText(blog.dateTxt);
holder.amount.setText(blog.amountTxt);
holder.author.setText(blog.authorTxt);
return convertView;
}
class ViewHolder {
TextView separator;
ImageView usericon;
TextView title;
TextView date;
TextView amount;
TextView author;
}
/**
* Check if the blog on the given position must be separated from the last blogs.
*
* #param position
* #return
*/
private boolean isSeparator(int position) {
boolean separator = false;
// check if the last blog was created on the same date as the current blog
if (DateUtility.getDay(
DateUtility.createCalendarFromUnixtime(blogs.get(position - 1).getUnixtime() * 1000L), 0)
.getTimeInMillis() > blogs.get(position).getUnixtime() * 1000L) {
// current blog was not created on the same date as the last blog --> separator necessary
separator = true;
}
return separator;
}
}
This is the xml for the row (no button, still stumbling):
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:background="#drawable/listview_selector">
<ImageView
android:id="#+id/blogs_row_user_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:paddingTop="#dimen/blogs_row_icon_padding_top"
android:paddingLeft="#dimen/blogs_row_icon_padding_left"/>
<TextView
android:id="#+id/blogs_row_title"
android:layout_toRightOf="#id/blogs_row_user_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="#dimen/blogs_row_title_padding"
android:textColor="#color/blogs_table_text_title"/>
<TextView
android:id="#+id/blogs_row_date"
android:layout_below="#id/blogs_row_title"
android:layout_toRightOf="#id/blogs_row_user_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="#dimen/blogs_row_date_padding_left"
android:textColor="#color/blogs_table_text_date"/>
<ImageView
android:id="#+id/blogs_row_cmmts_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#id/blogs_row_title"
android:layout_toRightOf="#id/blogs_row_date"
android:layout_margin="#dimen/blogs_row_cmmts_icon_margin"
android:src="#drawable/comments"/>
<TextView
android:id="#+id/blogs_row_cmmts_amount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#id/blogs_row_title"
android:layout_toRightOf="#id/blogs_row_cmmts_icon"
android:layout_margin="#dimen/blogs_row_author_margin"/>
<TextView
android:id="#+id/blogs_row_author"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#id/blogs_row_title"
android:layout_toRightOf="#id/blogs_row_cmmts_amount"
android:marqueeRepeatLimit="marquee_forever"
android:singleLine="true"
android:ellipsize="marquee"
android:layout_margin="#dimen/blogs_row_author_margin"/>
</RelativeLayout>
********** UPDATE *************
As it turned out the problem was simply solved by using ArrayAdapter instead of a BaseAdapter. I used the exact same code with an ArrayAdapter and the performance difference is GIGANTIC! It runs just as smooth as with a TableLayout.
So whenever I'm using ListView, I will definitely avoid using BaseAdapter as it is significantly slower and less optimized for complex layouts. This is a rather interesting conclusion because I hadn't read a word about it in examples and tutorials. Or perhaps I wasn't reading it accurately. ;-)
Well however this is the code that is working smoothly (as you can see my solution is using seperators to group the list):
protected class BlogsSeparatorAdapter extends ArrayAdapter<UIBlog> {
private LayoutInflater inflater;
private final int SEPERATOR = 0;
private final int BLOGELEMENT = 1;
public BlogsSeparatorAdapter(Context context, List<UIBlog> rows) {
super(context, R.layout.blogs_row_layout, rows);
inflater = LayoutInflater.from(context);
}
#Override
public int getViewTypeCount() {
return 2;
}
#Override
public int getItemViewType(int position) {
int type = BLOGELEMENT;
if (position == 0) {
type = SEPERATOR;
} else if (isSeparator(position)) {
type = SEPERATOR;
}
return type;
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
final UIBlog blog = uiblogs.get(position);
int type = getItemViewType(position);
ViewHolder holder;
if (convertView == null) {
holder = new ViewHolder();
if (type == SEPERATOR) {
convertView = inflater.inflate(R.layout.blogs_row_day_separator_item_layout, null);
View separator = convertView.findViewById(R.id.blogs_separator);
separator.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
// do nothing
}
});
holder.separator = (TextView) separator.findViewById(R.id.blogs_row_day_separator_text);
} else {
convertView = inflater.inflate(R.layout.blogs_row_layout, null);
}
holder.usericon = (ImageView) convertView.findViewById(R.id.blogs_row_user_icon);
holder.title = (TextView) convertView.findViewById(R.id.blogs_row_title);
holder.date = (TextView) convertView.findViewById(R.id.blogs_row_date);
holder.amount = (TextView) convertView.findViewById(R.id.blogs_row_author);
holder.author = (TextView) convertView.findViewById(R.id.blogs_row_author);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
if (holder.separator != null) {
holder.separator
.setText(DateUtility.createDate(blog.blog.getUnixtime() * 1000L, "EEEE, dd. MMMMM yyyy"));
}
holder.usericon.setImageBitmap(blog.icon);
holder.title.setText(createTitle(blog.blog.getTitle()));
holder.date.setText(DateUtility.createDate(blog.blog.getUnixtime() * 1000L, "'um' HH:mm'Uhr'"));
holder.amount.setText(createCommentsAmount(blog.blog.getComments()));
holder.author.setText(createAuthor(blog.blog.getAuthor()));
return convertView;
}
class ViewHolder {
TextView separator;
ImageView usericon;
TextView title;
TextView date;
TextView amount;
TextView author;
}
/**
* Check if the blog on the given position must be separated from the last blogs.
*
* #param position
* #return
*/
private boolean isSeparator(int position) {
boolean separator = false;
// check if the last blog was created on the same date as the current blog
if (DateUtility.getDay(
DateUtility.createCalendarFromUnixtime(blogs.get(position - 1).getUnixtime() * 1000L), 0)
.getTimeInMillis() > blogs.get(position).getUnixtime() * 1000L) {
// current blog was not created on the same date as the last blog --> separator necessary
separator = true;
}
return separator;
}
}
+++++++++++++++++ SECOND EDIT WITH TRACES +++++++++++++++++++++
Just to show that BaseAdapter DOES something different than the ArrayAdapter. This is just the whole trace coming from the getView() method with the EXACT same code in both adapters.
First the amount of calls http://img845.imageshack.us/img845/5463/tracearrayadaptercalls.png
http://img847.imageshack.us/img847/7955/tracebaseadaptercalls.png
Exclusive time consumption
http://img823.imageshack.us/img823/6541/tracearrayadapterexclus.png
http://img695.imageshack.us/img695/3613/tracebaseadapterexclusi.png
Inclusive time consumption
http://img13.imageshack.us/img13/4403/tracearrayadapterinclus.png
http://img831.imageshack.us/img831/1383/tracebaseadapterinclusi.png
As you can see there is a HUGE difference (ArrayAdapter is four times faster in the getView() method) between those two adapters. And I really don't have any idea why this is so dramatic. I can only assume that ArrayAdapter has some sort of better caching or further optimizations.
++++++++++++++++++++++++++JUST ANOTHER UPDATE+++++++++++++++++
To show you how my current UIBlog class is built:
private class UIBlog {
Blog blog;
CharSequence seperatorTxt;
Bitmap icon;
CharSequence titleTxt;
CharSequence dateTxt;
CharSequence amountTxt;
CharSequence authorTxt;
}
Just to make it clear, I'm using this for BOTH adapters.
You should use DDMS' profiler to see exactly where time is spent. I suspect that what you are doing inside getView() is expensive. For instance, does viewUtility.setUserIcon(holder.usericon, blogs.get(position).getUid(), 30); create a new icon each time? Decoding images all the time would create hiccups.
Quite a lot to read ;)
I couldn't see anything wrong in your layout.
You could optimize
- your first if with a ||
- cache blogs.get( position ) in a variable
- déclare you constant static.
- why do you use a Calendar and finery it back to ms ? You seem to already have your ms ?
But I fear it won't be enough.
Regards,
Stéphane

Categories

Resources