I have a similar problem to the one in this question: ListView and rows recycling problem
Above is solved. but same solution does not work for me.
I have a listview consisting of textviews. Each textview can contain variable number of images. I am getting images with html.imagegetter and I am using recycling in getview method of list adapter.
If there is one or more images in a textview, it will have a height to be able to fit its content. When this textview is recycled and new content does not fill the height defined before, then empty space will appear.
The problem is that I can't restore the textview height in getview because I dont know what height the next list item will have.
this is my getview:
public View getView(int position, View convertView, ViewGroup parent) {
final ViewHolder holder;
View rowView = convertView;
if (rowView == null) {
LayoutInflater inflater = context.getLayoutInflater();
rowView = inflater.inflate(resource, parent, false);
holder = new ViewHolder();
holder.imgAvatar = (ImageView) rowView.findViewById(R.id.imgAvatar);
holder.textDetails = (TextView) rowView.findViewById(R.id.textDetails);
holder.textPostTime = (TextView) rowView.findViewById(R.id.textPostTime);
holder.textPost = (TextView) rowView.findViewById(R.id.textPost);
rowView.setTag(holder);
} else {
holder = (ViewHolder) rowView.getTag();
}
holder.textDetails.setText(details.toString());
holder.textPostTime.setText(x.postTime);
holder.textPost.setText(Html.fromHtml(x.postBody, new URLImageGetter(holder.textPost, context), null, context));
return rowView;
}
this is row layout:
<TextView
android:ellipsize="none"
android:autoLink="web"
android:textColor="#000000"
android:id="#+id/textPost"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
THE PROBLEM: The listview recycling mechanism can't calculate the new height for used views according to its new contents.
Without recycling, it is too slow.
So, essentially your problem is with recycling the views. When your views get inflated from XML they look at the images and decide how tall to be. This works fine because the inflater can cause the height to be "wrap_content" aka however tall the tallest child component is - this is dynamic.
However, when your getview recycles the old views it (for some reason) isn't updating the height of the rows, and neither are you (which you never had to do before because the inflater handles it automatically from "wrap_content"). It would appear from what you described that you just get a row with whatever the height of the view happened to be set at previously (this previous value was set by the inflater automatically).
Your two solutions are:
Don't recycle the views - just inflate a new one every time. You will see a performance hit which will probably only be noticeable at lists with ~50 elements or more. If it is a small list this may be the easiest (code-wise) option, although there it will still operate slightly slower.
Keep recycling the views - if you pick this you will have to set the height of the row (or text view, whatever is determining it) yourself based on the image heights. This is doable but will require more work. I'd suggest
secret option 3 - normalize the image heights - If you would a.) like to continue recycling views but b.) don't want to write the code to set the row heights manually and c.) resizing the images wouldn't kill your app, you could use the setMaxHeight(int) method to make sure all of your images are the same height (or use a method like this to resize the images to a predetermined size)
Note: I am pretty sure (almost certain) that, normally, if height is set to "wrap_content" it would wrap to the new content size (since it normally updates the size when the content is updated). I couldn't say why this is not happening for you - it may be that something inside the "wrap_content" has its size set to a concrete value and thus isn't resizing, so the "wrap_content" isn't changing even though there is empty space.
Try using requestLayout on your image and textviews. It will help the listview recompute the height of recycled views.
Related
I working on my project with gridview layout, and the whole code is pretty identical to Android Developers example (I can't post links because of my low reputation, sorry).
Its works fantastic, when I need to fill screen with 3*3 or 4*4 (and etc) grids, but now I have to design my gridview with only 8 imageviews and I want to set the empty cell in the middle of GridView, just like that (click for images):
That is my current position:
This is what I need to do:
I have googled that a lot, but I can't find some example that shows how to implement that.
Have you any idea how can I solve that issue?
Set it to invisible -
imageView.setVisibility(View.INVISIBLE);
not works for me, because then I "lost" one of my imageViews.
Thanks a lot!
Let's consider you will always have an (x*x) number of items in your GridView, where x is an odd number bigger than 1, like 3 or 5. The index of the item that you want to hide is items.size()/2. Note that in Android, 9/2 = 4 and 25/2 = 12. So, we already know the index of the item that should be hidden.
Following, in the code, you just have to make your item View stay invisible when that your adapter.getView() method is called for that index. Here is that getView() portion of the code:
#Override
public View getView(final int position, View convertView, ViewGroup parent)
{
ViewHolder viewHolder;
LayoutInflater inflater = yourContext.getLayoutInflater();
if(convertView==null)
{
viewHolder = new ViewHolder();
convertView = inflater.inflate(R.layout.your_grid_item_layout, null);
convertView.setTag(viewHolder);
}
else
{
viewHolder = (ViewHolder) convertView.getTag();
}
if(position == yourItemsList.size()/2){
convertView.setVisibility(View.INVISIBLE);
}
else{
convertView.setVisibility(View.VISIBLE);
}
return convertView;
}
This will make your middle View invisible. The effect that you wanted.
Also note that your GridView number of lines has to be equal to the number of rows for this method to work.
You can always change the number of rows dynamically, according to the number of items that you have. In your Activity code, before gridView.setAdapter(), like this:
gridView.setNumColumns((int) Math.sqrt(myItemsList.size()));
Again I warn you that this code only works for GridView configurations of 3*3, 5*5, 7*7, and so on.
I have a listview that can have items from 1 to 100, dynamically varying.
However, when I have fewer items, I want the items to appear vertically centered in
the listview layout. By default, Android draws the items form the top. I have attached a picture to illustrate what I need.
Is there any way I can achieve this, either by code or in XML layout ?
Thanks.
Edit: Based on the answers I have received now, I am adding more clarity to my question as the answers are off mark.
The positioning of the Listview in the main layout or positioning of individual item layouts is not an ISSUE here. The listview is positioned properly as shown in the shaded background. The question to which I need answer is:
Assume there are 10 items, the entire shared area of Listview will be used by items,
possibly Items 1 to 5 are shown.
Assume there are 3 items only. The listview is still shown but Items 1,2 and 3 are rendered at the top of the listview (shaded area). However, in this case, I want the items to be drawn at centre of the listview's layout (as shown in the picture).
Anyway this can be done ?
Use this property
mLoginUserNameValueView.setGravity(Gravity.CENTER_HORIZONTAL);
which one you want to center in the List view adapter and which position exactly where you need
e-x you need your 55th position you want center the item means
in getview you will use this line like below.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
convertView = null;
if (convertView == null) {
convertView = mLayoutInflater.inflate(R.layout.navigationlayout,
null);
mHolder.mLayout = (LinearLayout) convertView
.findViewById(R.id.navigationLayout);
mHolder.mNavigationtext = (TextView) convertView
.findViewById(R.id.navigationText);
convertView.setTag(mHolder);
}
if (\\Your desired position checking )
\\ and set the above line here
}
return convertView;
}
I read all the other posts on getView() and didn't find any solutions. I have a GridView with a SimpleCursorAdapter. I log(position); in getView() and I see a pattern like this:
0,0,1,0,0,2,0,0,3,0,0,4,0,0,5 etc. This means I'm having to build 3 views as it scrolls for every new view displayed and it's choppy and laggy. Why does it do this? I don't have anything obvious like setting my gridview to wrap-content or anything else weird. There's nothing strange about my code. One thing that might be a factor is that every item view could have a different height depending on the length of the text I'm displaying.
I'm currently debugging on a 4.2.2 Galaxy Nexus.
Index 0 is requested in gridview measure/layout pass.
The question doesn't have the details, but the following could explain the pattern you're seeing:
The GridView is in a layout that requires two measure/layout passes (e.g. LinearLayout with weights, RelativeLayout with layout dependency rules). This explains the two position 0s.
Each getView() causes the parent to re-layout. This explains the position 0 after each position.
make sure that the height of your listview is not wrap_content
and make it fill_parent .
Hope that helps .
As I wrote in the question's comments, I noticed that getView() is invoked on position 0 even though that position is NOT seen at all. I think the reason is the need to measure the items by the grid.
It doesn't bother me that much that this happens a bit, but it bothers me when it's done a lot and when scrolling down the grid, making the grid leggy. Especially when it repaints elements that aren't even seen.
The way I solved it is that when getView(0) is requested and item 0 should not be seen, meaning a measurement is done, I return an actual view which was already painted (reusing the converted view), avoiding repainting position 0.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// Workaround Android requesting position 0 even though it doesn't draw it.
AbsListView listView = (AbsListView) parent;
int firstVisiblePosition = listView.getFirstVisiblePosition();
if (position == 0 && firstVisiblePosition > 10) {
if (convertView == null) {
convertView = actualPaint(position, convertView, parent);
}
return convertView;
}
convertView = actualPaint(position, convertView, parent);
return convertView;
}
I am trying to improve our list view rendering performance and looking into the fine tuning now. (we use viewHolder, fetch images async, pause image displaying on scroll, disabled scrolling cache already)
Now I was inspecting the layout and came across a setup like the following for the single list item's layout, which gets inflated in getView.
getView() of custom list adapter
if (convertView == null) {
convertView = inflater.inflate(R.layout.zzz_list_item, null);
...save stuff in holder etc.
zzz_list_item.xml
<LinearLayout
android:id="#+id/layout_success"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/base"
android:orientation="vertical"
android:paddingBottom="30dp" >
...many lines of a "success" item layout
</LinearLayout>
<LinearLayout
android:id="#+id/layout_failure"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:visibility="gone" >
...many lines for a failure item layout
</LinearLayout>
The visibility gets controlled further down in the getView method of our custom listview adapter
getView() method - executed every time
if(isSuccessfulItem){
((LinearLayout) convertView.findViewById(R.id.layout_failure)).setVisibility(View.GONE);
((LinearLayout) convertView.findViewById(R.id.layout_success)).setVisibility(View.VISIBLE);
} else {
((LinearLayout) convertView.findViewById(R.id.layout_failure)).setVisibility(View.VISIBLE);
((LinearLayout) convertView.findViewById(R.id.layout_success)).setVisibility(View.GONE);
}
While this also might be a small performance hog (getView is expensive), I wonder if it would make sense to refactor and split the handling for success and failure elements into two different layouts, which would then be inflated respectively using getViewTypeCount() and getItemViewType(int position).
Does the additional failure layout code (and therefore increased file size) for my list item layout affect the performance, even if it is set to visibility=GONE during inflation?
Any insights would be much appreciated, thanks.
Does the additional failure layout code (and therefore increased file
size) for my list item layout affect the performance, even if it is
set to visibility=GONE during inflation?
Either way the performance gain or loss it's minimal. A view with visibility set to gone doesn't need calculation in the layout and measuring phase as it's ignored but it does consume memory(and this is what you could talk about). With your current implementation even if you don't need/use the failure part of the row layout you do have it occupying memory (multiply this by the number of rows visible on the screen). Splitting the current row in two parts will clear that memory need as each row will have only the views it actually uses.
If I were you I would implement two row types as I think it's cleaner(and it also doesn't add useless views in the memory).
You probably know this but use:
convertView = inflater.inflate(R.layout.zzz_list_item, convertView, false);
also, cache the row views in the holder.
I have a HorzontalScrollView with a LinearLayout inside. During Runtime I can add more LinearLayouts to the LinearLayout.
Now I have the problem that the Scrollview only scrolls a little bit and not smooth with one finger slide!
Does anyone have a solution for this problem?
HorizontalScrollView doesn't use an adapter that manages the list's memory, therefore it can't handle heavy (images, custom views, etc) lists.
You can use this Horizontal ListView http://www.dev-smart.com/archives/34 but make sure you don't write the on list item click method inside the getView, it will make the list scroll slow. Other than that, that's a great resource for a smooth horizontal list view.
You can also explore the Android view pager, which is also supported on lower Android versions using the compatibility pack: http://developer.android.com/sdk/compatibility-library.html
Edit - something like that in the adapter that inflates the XML you want (the linearLayout) and then populates every view with the relevant data.
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.chat_friends_list_item, null);
}
ImageView status = (ImageView)convertView.findViewById(R.id.status);
ImageView image = (ImageView)convertView.findViewById(R.id.image);
ImageView imageBorder = (ImageView)convertView.findViewById(R.id.image_border);
TextView title = (TextView)convertView.findViewById(R.id.title);
}
The problem was my parent Viewflow because it has stolen the swipe event! The HorizontalListView is too buggy for me! (Problems with size attributes)
However,thank you for your answer! ;)