Erratic behaviour of listview (Android) - android

In my listview I have a custom Adapter, which I build using a TreeMap, I defined the getView method which is as follows. I am trying to strike out the text in a certian textview of the listview on click, only those textviews will be striken off which contain a certain pattern of characters (y#y). However on clicking one row in the listview I am getting strike out effect on some other row.
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (convertView == null) {
convertView = inflater.inflate(R.layout.chklistlayout, parent, false);
}
TextView textView = ((TextView) convertView.findViewById(R.id.textView1));
TextView imageview = ((TextView) convertView.findViewById(R.id.textView2));
textView.setText(values[position]);
imageview.setText(mValues[position]);
String s = mValues[position];
if (s.contains("y#y")) {
System.out.println("In if of getview");
System.out.println(s);
imageview.setPaintFlags(imageview.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
} else {
}
return convertView;
}
}
I tried using a holder pattern too, using a static holder class, but the problem seems to persist. Any pointers?

this answer is half mine and half from Muhammad Babar that we both commented on the question making together a quite nice answer:
use else block to handle this, imageview.setPaintFlags() not to strike
that happens
Because of the convertView. When you scroll the list the same view
that was used before is give back to getView method. So the same
imageView that you painted with StrikeThrough before is still painted
with it. So you have to revert that action.
more over, because of this recycling, usually dealing with Adapters you must ALWAYS undo/revert/repaint/change text/change image to all the elements to make sure it will be on screen the way you want.
ps.: now you should apply a Holder pattern to get better performance.

Related

Holder pattern and convert view

I have learned earlier about great approach to increase performance - Holder Pattern. This is good idea to speed up UI and animation.
It is clearly why and how to use it.
I have used it a lot , but now I am little bit confused about this.
When getView method is called it has three arguments one is converView. As I undertand it is previously inflated view of list item, so the are some questions about this.
If it is previously inflated view, why not just to use it, return it from method, of course check to null before.
How does this implemented,listview class has private array or another data structure that holds all inflated views ?
Why this feature is not implemented in adapters ?
Thanks in advance.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder =(ViewHolder) convertView.getTag();
}
If you would just use the convertView, you would need to get hold of your views with findViewById(). This is exactly what the ViewHolder pattern is trying to avoid. findViewById() is a surprisingly expensive method and can slow down your app, especially if you constantly call it when scrolling through lists.
Listviews reuse the layouts of the child items, to avoid having to inflate the same views over and over again.
Most adapters were already available to developers before people came up with the ViewHolder pattern. The latest new list view, RecyclerView, has an adapter that enforces the use of the ViewHolder pattern.
You can't call ViewHolder holder =(ViewHolder) convertView.getTag(); at the first line of getView. Because convertView could be null. Try again like this:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
ViewHoldler holder = null;
if (convertView == null) {
convertView = LayoutInflater.from(ctx).inflate(
R.layout.frag_home_gridview_item, null, false);
holder = new ViewHoldler();
holder.iv = (ImageView) convertView
.findViewById(R.id.gridview_item_label);
holder.tv = (TextView) convertView
.findViewById(R.id.gridview_item_name);
convertView.setTag(holder);
} else {
holder = (ViewHoldler) convertView.getTag();
}
holder.tv.setText(getItem(position));
holder.iv.setImageResource(this.ids[position]);
return convertView;
}
private class ViewHoldler {
ImageView iv;
TextView tv;
}
Because you most likely are not using same views. Say you have a row which has a TextView in it. The convertview is one of the recycled views and it's textview may be displaying different information that it should be.
It does keep as many views as there are visible, once you scroll down the top view get's recycled and you come back to answer 1.
I don't understand, your code is from the BaseAdapter class.

Effective ArrayAdapter

First of all, my problem:
My ListView woun't scroll smoothly.
Now a bunch of details:
I'm currently using an ArrayAdapter<CustomClass> in my App for displaying Text and and Image in each element of the ListView. I've been trying to make the ListView to scroll as smooth as possible. But as soon as the text becomes longer (about 40 characters), the ListView starts to stutter when scrolling.
I am displaying about 9 rows at the same time. If I make the ListView smaller (about 6 rows) it works fine..
I am not implementing onScrollListener and I am not running big background tasks.
This is the code I'm currently using (only getView and Holder):
#Override
public View getView(int position, View convertView, ViewGroup parent) {
//View row = convertView;
Holder holder = null;
if(convertView == null){
//Log.e("adapter", "convertview == null");
LayoutInflater inflater = ((Activity)context).getLayoutInflater();
convertView = inflater.inflate(layoutResourceId, parent, false);
holder = new Holder();
holder.imgIcon = (ImageView)convertView.findViewById(R.id.icon);
holder.txtTitle = (TextView)convertView.findViewById(R.id.folder_name);
holder.txtInfo = (TextView)convertView.findViewById(R.id.info_text);
holder.pBar = (ProgressBar)convertView.findViewById(R.id.pBar);
convertView.setTag(holder);
}else{
holder = (Holder)convertView.getTag();
}
TrackInfo tInfo = data.get(position);
if(tInfo == null){
return convertView;
}
holder.imgIcon.setImageResource(icon);
holder.txtTitle.setText(tInfo.getTitle());
return convertView;
}
static class Holder
{
ImageView imgIcon;
TextView txtTitle;
TextView txtInfo;
ProgressBar pBar;
}
You may notice there are more elements than I actively use. This is due to the reason that I normally use the others, too, but I am currently ignoring them since I was trying to find out why it's not scrolling smoothly.
As already mentioned, it seems to be the length of the string tInfo.getTitle(). I can't change the length of the strings, since those are filenames I can't influence.
Now my QUESTION:
What's the problem? Is it that much data to handle? Or is my code bad?
I'm testing on a Moto G (1.2GHz Quad-Core, more details here).
Thank you for your attention, have a good flight!
I was working with marquee effect in the ListView. I always thought that as long as I don't call TextView.setSelected(true), this wouldn't cause any further processing. Therefore, I had android:ellipsize="marquee" as a parameter for my TextView in the layout-file of the ListView-element, while only one to-be-highlighted element was set selected.
Apparently, I was wrong.
As long as the text wasn't too long for the given space (about 40 characters), there was no problem. But if the size of the text exceeded the given space, the problems started.
I am not sure what exactly the problem is, but after having a look into the source of the TextView, I recognized that there is a lot more to do when marquee is enabled:
The TextView is faded out on the right side (instead of ...)
A Spannable is used as a CharSequence
It needs to be checked if marquee should start
...
So long story short:
I removed marquee and ListView scrolls very smoothly.

Colouring a listview custom item element basing on value

I have a simple (?) problem that I can't seem able to fix.
I have to populate a listview with two columns - two string arrays.
I managed to do so, after searching a lot (see here).
Now, what I need to do is to colour the background of the second item red, purple or blue depending on its value.
Is it possible?
I know that many things can be done with custom listviews, even assigning different images based on a particular value.
Thank you in advance.
In your listadapter (for example see http://www.vogella.com/articles/AndroidListView/article.html#adapterown_example) override getView() and when setting the value of the second textview set its background color. e.g.:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View rowView = inflater.inflate(R.layout.rowlayout, parent, false);
TextView textView = (TextView) rowView.findViewById(R.id.label);
textView.setText(values[position]);
String s = values[position];
if (s.startsWith("red")) {
//BACKGROUND COLOR CHANGE
textView.setBackgroundColor(getResources().getColor(R.color.your_red));**
}
return rowView;
}

Android ListView Layout inflater

We are working with list views in college at the moment. My lecturer gave us a simple application that displays mail messages in a list and when the user selects one it displays the content of the message in a new activity. I understand pretty much all of what is going on but there are a few grey areas I want to clear up!
Basically I am wondering what this section of code does?
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v == null) {
LayoutInflater vi = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.inbox_row, null);
}
This method is located within a class that extends ArrayAdapter. Am I right in thinking that it is some form of recycling? for when views go on and off the screen?....
Any help is much appreciated. thanks.
it's exactly what you said, a form of recycling.
Inflating a layout takes a lot of memory and a lot of time, so for the efficiency sake, the system passes to you that just went off the screen and you can simply update its text and images and give them back to the UI.
So for example, if your list view is showing 6 items on its list (due to the height of it), it will only inflate 6 items and during scroll it just keeps recycling them.
there's some extra optimisations tricks that you should use and I'm sure that the video link that the commenter posted will explain them.
edit
that example is an ArrayAdapter of Store items, but you can make it to whatever you need.
the adapter does the match and separation layer between UI and data.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null)
convertView = newView();
// Store is the type of this ArrayAdapter
Store store = getItem(position);
Holder h = (Holder) convertView.getTag();
// And here I get the data and address them to the UI
// as you can see, if the convertView is not null,
// I'm not creating a new one, I'm just changing text, images, etc
h.storeName.setText(store.getStoreName());
h.address.setText(store.getAddressLine1());
h.postcode.setText(store.getPostCode());
h.distance.setText(store.getDistance());
return convertView;
}
// I like to separate in a different method when the convertView is null
// but that's me being organisation obsessive
// but it also makes easy to see which methods are only being called the 1st time
private View newView() {
LayoutInflater inf = LayoutInflater.from(getContext());
View v = inf.inflate(R.layout.map_result_list, null);
Holder h = new Holder();
// here we store that holder inside the view itself
v.setTag(h);
// and only call those findById on this first start
h.storeName = (TextView) v.findViewById(R.id.txtLine1);
h.address = (TextView) v.findViewById(R.id.txtLine2);
h.postcode = (TextView) v.findViewById(R.id.txtLine3);
h.distance = (TextView) v.findViewById(R.id.txtDistance);
return v;
}
// this class is here just to hold reference to the UI elements
// findViewById is a lengthy operation so this is one of the optimisations
private class Holder {
TextView storeName;
TextView address;
TextView postcode;
TextView distance;
}

ListView in ArrayAdapter order get's mixed up when scrolling

I have a ListView in a custom ArrayAdapter that displays an icon ImageView and a TextView in each row. When I make the list long enough to let you scroll through it, the order starts out right, but when I start to scroll down, some of the earlier entries start re-appearing. If I scroll back up, the old order changes. Doing this repeatedly eventually causes the entire list order to be seemingly random. So scrolling the list is either causing the child order to change, or the drawing is not refreshing correctly.
What could cause something like this to happen? I need the order the items are displayed to the user to be the same order they are added to the ArrayList, or at LEAST to remain in one static order. If I need to provide more detailed information, please let me know. Any help is appreciated. Thanks.
I was having similar issues, but when clicking an item in the custom list, the items on the screen would reverse in sequence. If I clicked again, they'd reverse back to where they were originally.
After reading this, I checked my code where I overload the getView method. I was getting the view from the convertedView, and if it was null, that's when I'd build my stuff. However, after placing a breakpoint, I found that it was calling this method on every click and on subsequent clicks, the convertedView was not null therefore the items weren't being set.
Here is an example of what it was:
public View getView(int position, View convertView, ViewGroup parent)
{
View view = convertView;
if (view == null)
{
LayoutInflater vi = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = vi.inflate(R.layout.listitemrow, null);
RssItem rssItem = (RssItem) super.getItem(position);
if (rssItem != null)
{
TextView title = (TextView) view.findViewById(R.id.rowtitle);
if (title != null)
{
title.setText(rssItem.getTitle());
}
}
}
return view;
}
The subtle change is moving the close brace for the null check on the view to just after inflating:
public View getView(int position, View convertView, ViewGroup parent)
{
View view = convertView;
if (view == null)
{
LayoutInflater vi = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = vi.inflate(R.layout.listitemrow, null);
}
RssItem rssItem = (RssItem) super.getItem(position);
if (rssItem != null)
{
TextView title = (TextView) view.findViewById(R.id.rowtitle);
if (title != null)
{
title.setText(rssItem.getTitle());
}
}
return view;
}
I hope this helps others who experience this same problem.
To further clarify the answer of farcats below in more general way, here is my explanation:
The vi.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 (view == 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 (view == null) statement.
Similarily, in other common implementation of this method, some textView, 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.
Another good example where this solution is applied for BaseAdapter appears in BaseAdapter causing ListView to go out of order when scrolled
The ListView reuses view objects when you scroll. Are you overriding the getView method? You need to make sure you set each property for every view, don't assume that it will remember what you had before. If you post that method, someone can probably point you at the part that is incorrect.
I have a ListView, AdapterView and a View (search_options) that contains EditText and 3 Spinners. ListView items are multiple copies of (search_options) layout, where user can add more options in ListView then click search to send sql query built according to users options.
I found that convertView mixing indecies so I added a global list (myViews) in activity and passed it to ArrayAdapter. Then in ArrayAdapter (getView) I add every newly added view to it (myViews).
Also on getView instead of checking if convertView is null, I check if the global list (myViews) has a view on the selected (position).. It totally solved problems after losing 3 days reading the internet!!
1- on Activity add this:
Map<Integer, View> myViews = new HashMap<>();
and then pass it to ArrayAdapter using adapter constructor.
mSOAdapter = new SearchOptionsAdapter(getActivity(), resultStrs, myViews);
2- on getView:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
ViewHolder viewHolder;
if (!myViews.containsKey(position)) {
viewHolder = new ViewHolder();
LayoutInflater inflater = LayoutInflater.from(getContext());
view = inflater.inflate(R.layout.search_options, parent, false);
/// ...... YOUR CODE
myViews.put(position, view);
FontUtils.setCustomFontsIn(view, getContext().getAssets());
}else {
view = myViews.get(position);
}
return view;
}
Finally no more mixing items...

Categories

Resources