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.
Related
I have a custom ArrayAdapter for a ListView that uses a custom row layout, defined separately in XML. The layout is just three TextViews and an ImageView, put together in a RelativeLayout. To improve performance, the adapter uses a ViewHolder system like the one described here to convert existing Views instead of inflating new ones. In ArrayAdapter.getView(), the adapter is supposed to bold or unbold the first TextView, depending on a boolean.
When I first open the app, all of the TextViews are properly bolded or unbolded. However, if I scroll to the bottom of the ListView, then scroll back to the top, all of the title TextViews are bold, even if they aren't supposed to be. I think it must have something to do with converting existing views that are already bold, but I can't figure out what it is. I've debugged the app with Android Studio, and it runs just like I think it should -- when I scroll back up, the adapter properly bolds/unbolds things in the debug window, but they all seem to be bold on the app.
One thing I have noticed is that if I change the textStyle attribute of the TextView to "bold," all the title TextViews are bold from the beginning, and never change. It's only if I remove textStyle or set it to "normal" that the TextViews are normal at the start.
Here's getView() in the ArrayAdapter:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
PostShell shell = postShellList.get(getCount() - 1 - position); //I stack my ListView backwards, so index 0 is at the bottom
ViewHolder holder;
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.row_layout, parent, false);
holder = new ViewHolder();
holder.firstLine = (TextView) convertView.findViewById(R.id.firstLine);
holder.secondLine = (TextView) convertView.findViewById(R.id.secondLine);
holder.thirdLine = (TextView) convertView.findViewById(R.id.thirdLine);
holder.image = (ImageView) convertView.findViewById(R.id.image);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.firstLine.setText(shell.postTitle);
if (shell.unread) {
holder.firstLine.setTypeface(holder.firstLine.getTypeface(), Typeface.BOLD);
} else {
holder.firstLine.setTypeface(holder.firstLine.getTypeface(), Typeface.NORMAL);
}
//convert other TextViews
}
My ViewHolder class is just a static class with a few TextViews and an ImageView.
And here's the relevant part of the code for the row layout I'm using:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="88dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="84dp"
android:paddingRight="16dp"
android:paddingTop="6dp"
android:id="#+id/firstLine"
android:ellipsize="end"
android:text="First"
android:textStyle="normal"
android:singleLine="true" />
<!-- other views -->
</RelativeLayout>
The problem seems to be that "unboldening" the text does not work with the following statement:
holder.firstLine.setTypeface(holder.firstLine.getTypeface(), Typeface.NORMAL);
The following snippet leaves out the holder.firstLine.getTypeface() and just uses a simpler variety of setTypeface(). Worked for me.
if (shell.unread) {
holder.firstLine.setTypeface(holder.firstLine.getTypeface(), Typeface.BOLD);
} else {
holder.firstLine.setTypeface(Typeface.DEFAULT);
}
PostShell shell = postShellList.get(getCount() - 1 - position); //I stack my ListView backwards, so index 0 is at the bottom
If the ListView asks for the element at a particular position, you'd better give it the element at that position or it's going to be confused. IF you want to alter the order of the items in the list, change the order of the list, don't try to trick ListView into rendering it in a different order.
You should store your views in the normal order and use android:stackFromBottom or setStackFromBottom().
I am using StaggeredGridView currently in the project I am working on. But what is happening is the GridView layout is not getting inflated in a correct way. when my layout is loaded the GridView is rendering with let's say 4 column in my tablet(10" Sony Xperia Z) in landscape mode, but sometime somewhere at any random row number the items are not rendering sequentially but at any random position with uneven void space like the following image:
The same type of situation arises when the tablet is in the portrait mode. but surprisingly when the orientation changes then this issue is getting resolved automatically. It's kind of weird problem I am facing right now. I've gone through some research but with no luck.
I've also tried This StaggeredGridView, but the same issue.
Can anyone please help me out there? Thanks in advance.
FYI I am using Viewholder pattern in my Gridview Adapter.
PS:
Is the fix Fixed whitespace related to my concern? I've tried it but no improvement.
I had the same problem in StaggeredGridView during loading images via picasso from network.
When you use:
Picasso.with(mContext).load(getItem(position)).into(holder.imageView);
in your adapter, picasso reserve one grid for your image and SGV make rendering for the content. Then image is loaded at last and SGV makes rendering one more time and images change their position in SGV.
Solution: I check size of the image needed for rendering (I my situation I read it from json) in order to reserve demanding space for the image at the beginning. Please take a look in example:
public class StaggeredAdapter extends ArrayAdapter<String> {
private Context mContext;
public StaggeredAdapter(Context context, int textViewResourceId,
String[] objects) {
super(context, textViewResourceId, objects);
mContext = context;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
LayoutInflater layoutInflator = LayoutInflater.from(getContext());
convertView = layoutInflator.inflate(R.layout.row_staggered_demo, null);
holder = new ViewHolder();
holder.imageView = (ImageView) convertView .findViewById(R.id.imageView1);
convertView.setTag(holder);
}
holder = (ViewHolder) convertView.getTag();
// Calculate size of the element.
int height = (position % 3 == 0) ? 600 : (1200 / (position % 3));
convertView.setLayoutParams(new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, height));
Picasso.with(mContext).load(getItem(position)).into(holder.imageView);
return convertView;
}
static class ViewHolder {
ImageView imageView;
}
}
It may be caused by the item layout. Does it contain image? When items are inflated, they may have different size. So the layouting process is computed using different item size. Try to use stub images which are used before image loading.
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.
I'm working on a ListView based app and I have a very weird problem, my ListItems are reappering and the correct item is not shown in the correct spot. For the sake of making this easy to understand I've set the text on each ListItem to be the same as it's position. I'm doing this in my adapters getView() call. If I have my Nexus 7 4 ListItems are visible. If I have a total of 10 ListItems then it will go like 0, 1, 3, 4, 0, 1, 2, 3, 4. This goes for all devices meaining that the number of items initially on screen + 1 will be correct while all other ListItems are rearrenged.
In which part of my code do you guys think my problem lies because right now I've been trying to fix this for hours and I'm clueless. All help is very much appreciated.
EDIT:
Here's my getView():
#Override
public View getView(int position, View convertView, ViewGroup parent) {
CountdownItem ci = mTitle.get(position);
if (convertView == null) {
convertView = mInflater.inflate(R.layout.list_item, parent, false);
holder = new CountdownViewHolder();
holder.mTitle = (TextView) convertView.findViewById(R.id.textPrim);
holder.mSubtitle = (TextView) convertView
.findViewById(R.id.textSec);
holder.mDayProgress = (ProgressBar) convertView
.findViewById(R.id.day_progress);
holder.mMonthProgress = (ProgressBar) convertView
.findViewById(R.id.month_progress);
holder.mYearText = (TextView) convertView
.findViewById(R.id.year_text);
holder.day_help = (TextView) convertView
.findViewById(R.id.day_help);
holder.month_help = (TextView) convertView
.findViewById(R.id.month_help);
holder.setTitle(Integer.toString(position) + " Title");
holder.setSubtitle(ci.getSubtitle());
holder.fixImageAndText(position);
convertView.setTag(holder);
} else {
holder = (CountdownViewHolder) convertView.getTag();
}
return convertView;
}
You aren't using the ViewHolder pattern correctly. The following code needs to be moved outside the if/else clause and before return convertView:
holder.setTitle(Integer.toString(position) + " Title");
holder.setSubtitle(ci.getSubtitle());
holder.fixImageAndText(position);
This is the correct behaviour for the listview when it is reusing cells, the problem is that you only set the values when the cell is first created.
When convertView == null the listview has no cell to recycle. However, once it has created a few it can reuse them to display as you scroll.
What you need to do is set the title and subtitle even when convertView is not null. That way you're setting them for each new list position.
Yes, this is because android reuses views in lists, to increment performance and rendering speed.
The holder pattern is used to store views ids. After you retrieve them, you have to set the text you want to see inside.
For example, you retrieve your data (e.g. myDataArray[position]), and if it's all ok, you proceed setting title, subtitle, dayprogress, etc. with TextView's setText().
I have a straight forward BaseAdapter for my ListView. It downloads a JSON feed and displays the data in the rows. There is a ViewHolder which contains the views and a data object called "Story". Everything works just fine.
However, after some scrolling of longer lists, I notice two things.
1) My log shows that the adapter is reloading the feed when scrolling further down. This is strange, as I put the whole JSON array into a variable, so why does it have to reload?
2) More importantly, after some scrolling back and forth, the rows contain the wrong "Story" objects. Here are the relevant parts of the getView routine:
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
Story story = stories.get(position);
if (convertView == null) {
//create holder
holder = new ViewHolder();
convertView = inflator.inflate(R.layout.story_list_item, parent, false);
holder.titleView = (TextView) convertView.findViewById(R.id.story_list_title);
holder.dateView = (TextView) convertView.findViewById(R.id.story_list_date);
holder.story = story;
holder.imageView = (ImageView) convertView.findViewById(R.id.story_list_image);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
// configure the view
holder.titleView.setText(story.title);
return convertView;
}
Simple enough. Now the strange thing is that I can fix the problem by eliminating the if statement if (convertView == null) (and, I presume, eliminating the row recycling as well).
But will I not run into memory problems this way? Why does the plain vanilla version not work?
Thanks for your help.
Regards,
S
You are aware that you're only assigning
holder.story = story
when convertView == null ? Consider moving holder.story = story to just after your convertView if-case and it should work a lot better. Btw, do you even need to store the "story" inside your view holder? Typically that pattern should only be used to store Views and view state information, not the data of the actual position.