In the getView() method of my GridView, I am doing the following:
public View getView(int position, View convertView, ViewGroup parent) {
convertView = this.inflater.inflate(..);
ImageView image = (ImageView) convertView
.findViewById(R.id.imageButton1);
imageLoader.displayImage("URL/"
+ sampleArray.get(position), image);
sampleArray here is an array I've loaded when the Adapter is created. It is used when lazy-loading (with this tool: https://github.com/nostra13/Android-Universal-Image-Loader) these images and basically is a part of the image URL that determines the picture to load. Now, as far as I know, best practice is to do only inflate the view if it's null:
if(convertView == null){
this.inflater.inflate(...);
When I do this, it is infact faster, but the image loading is weird. If I scroll down and scroll back up, the images switch within the row, meaning in the top row, the 1st column image might randomly switch with the second column image. The array I am using (sampleArray) doesn't change. I know it has something to do with position and getView() being called, but I'm not sure why this behavior happens.
If I do it the way I am doing now (the first block of code), meaning I inflate it every time, it works perfectly but loads slowly. Why does this behavior work like this?
Thanks in advance.
EDIT:
Here's what I think is happening. GetView() is being called multiple times, which is somehow not in sync with the "position" argument, causing the wrong element to be picked out of the array. I'm still puzzled by it.
You should use
.resetViewBeforeLoading()
and probably
.showStubImage(R.drawable.empty_image)
on your DisplayImageOptions when you initialize the UIL.
This will reset the view before the loading.
This is needed because you are reusing the views on gridview.
Have you tried to clean up the original image:
image.setImageBitmap(null);
I check the source code of the image loader, one of the methods is like:
public void displayImage(String uri, ImageView imageView, DisplayImageOptions options)
I think you can also try to set the option as "resetViewBeforeLoading = true"
the reason I think that the weird thing happens is, the view is recycled by the gridview, which is still with the original bitmap when showing up, before the loader attach it with the new bitmap.
if it still doesn't work, please lemme know.
EDIT
I did more investigation, the reason I think is from the recycling.
Let me explain more clearly:
reason:
suppose we have total 12 cells in our data
on screen, there are only 9 views visible
at very beginning, the gridview will create and show 9 on screen:
view_1_1 view_1_2 view_1_3
view_2_1 view_2_2 view_2_3
view_3_1 view_3_2 view_3_3
after scrolling(scrolling up, to show the row 4), at the moment, grid view will put view_1_1, view_1_2, view_1_3 in recycle bin.
and then will get 3 views for row 4 from recycle-bin, which are put in the getView as "convertView"
so, the view_1_1, view_1_2, view_1_3 will be reused.
suppose at the beginning, the views are attached with bitmap
view_1_1 : bitmap_1_1
view_1_2 : bitmap_1_2
view_1_3 : bitmap_1_3
when these view are recycled and show at the row 4, since the bitmaps are loaded asynchronously, the bitmap_1_1, bitmap_1_2, bitmap_1_3 will show up on the row 4, and laterly be replaced by those new bitmap.
how to confirm it
1.one way for debugging grid view I like is put some "id" on the image, the simple way is just put "ImageView" toString()
put an overlay view above each imageView, and write id in it, like:
String s = viewInfoHolder.imageView.toString();
viewInfoHolder.overlay.setText(s.substring(s.length() -3));//last 3 chars are enough and cleanup
2.for making the replacement slow, modify the
com.nostra13.universalimageloader.core.LoadAndDisplayImageTask
the last row in method:
public void run()
from:
handler.post(displayBitmapTask);
to:
handler.postDelayed(displayBitmapTask, 2000);
and you could find out exactly how the grid view works.
Hope it helpful :)
Related
I am a beginner and I am having trouble with understanding a piece of code. Can someone please explain me when this function evoke and what is it for?
Here is my code :
public void onBindViewHolder(myViewHolder holder, int position) {
RecViewHolder currentdata = data.get(position);
holder.favChecker = currentdata.getFavChecker();
holder.serialID = currentdata.getSerialID();
holder.theClassName = currentdata.getTheClassName();
}
Let me start with just a little bit of background (which you may already understand, but it's needed to explain onBindViewHolder()).
RecyclerView is designed to display long lists (or grids) of items. Say you want to display 100 rows of something. A simple approach would be to just create 100 views, one for each row and lay all of them out. But that would be wasteful, because most of them would be off screen, because lets say only 10 of them fit on screen.
So RecyclerView instead creates only the 10 views that are on screen. This way you get 10x better speed and memory usage. But what happens when you start scrolling and need to start showing next views?
Again a simple approach would be to create a new view for each new row that you need to show. But this way by the time you reach the end of the list you will have created 100 views and your memory usage would be the same as in the first approach. And creating views takes time, so your scrolling most probably wouldn't be smooth.
This is why RecyclerView takes advantage of the fact that as you scroll and new rows come on screen also old rows disappear off screen. Instead of creating new view for each new row, an old view is recycled and reused by binding new data to it.
This happens exactly in onBindViewHolder(). Initially you will get new unused view holders and you have to fill them with data you want to display. But as you scroll you'll start getting view holders that were used for rows that went off screen and you have to replace old data that they held with new data.
It is called by RecyclerView to display the data at the specified position. This method is used to update the contents of the itemView to reflect the item at the given position.
for more info check RecyclerView.Adapter#onBindViewHolder
I have a very frustrating problem and I don't have an idea what is wrong.
I build simple XML which contains only GridView. This gridview should show images (ImageView) downloaded from specific urls which I retreive with my code.
So the idea of this APP is only to download images from URLS and show those images in GridView. First I must say that I'm using Universal Image Loader library. First please look at my getView code:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ImageView iv;
if(convertView == null) {
iv = new ImageView(MyActivity.this);
iv.setLayoutParams(new GridView.LayoutParams(size,size));
iv.setScaleType(ImageView.ScaleType.CENTER_CROP);
Log.d("APP", "convertView is NULL");
}
else {
iv = (ImageView) convertView;
Log.d("APP", "convertView is NOT null");
}
loader.displayImage(URLS.get(position), iv);
return iv;
}
PROBLEM:
Code above works, and it shows images in GridView. But when I scroll down, I see that:
All images were downloaded (Even those images who are not visible )
When i scroll back up I see that image which was in place 0, is in place 1 and it has been downloaded again. When I scroll back down, images switched positions again. Scrolling back up, I see again images are switching places again ( and downloading again). This goes infinite.
HOW I SOLVED THIS PROBLEM:
I solved this problem by NOT checking if convertView variable is null. Actually I ommited if/else clause.
I used cache in memory = true. So downloading stops and images are in place for ever.
So why I'm asking for help? Mainly because i know that i should not ommit if/else clause for performance reasons. I don't want to mess with users memory and I would like to bring them fast and reliable user experience. Also i would like to know what could be wrong.
Thank you for help. Much appriciated.
Loader needed some time to load picure in ImageView. Because you reuse view for different images you can see a previous image in the view while new image is loading. You can set resetViewBeforeLoading(true) in DisplayImageOptions for avoid this effect.
Also you can use disk cache to avoid downloading images every time from the network. Also limit size of memory cache and set other settings, but I think memory cache is useful, it improves user experience.
And don't forget to use setOnScrollListener(new PauseOnScrollListener(loader, true, true)) to avoid lags on scrolling.
GridView recycles list items for performance purposes. So when you are scrolling, the list items are getting recycled in different places and then your code is re-populating them. Sometimes this lags for images.
I'd recommend using a library that handles this sort of thing like Picasso.
I was having the exactly same problem as you, and didn't want to disable convertView checking either.
My solution was to increase the memory cache size in ImageLoaderConfiguration.
Before, i used to use it like this:
.memoryCache(new UsingFreqLimitedMemoryCache(2 * 1024 * 1024))
So I changed it to:
.memoryCache(new UsingFreqLimitedMemoryCache(10 * 1024 * 1024))
I don't know if 10*1024*1024 is too much or it will cause any problems, but it seems to have fixed the problem to me and i haven't had any problems until now.
I need to add a switch (Android 4.0+) to my ListView , just a single switch with an ImageView Button , I have seen soo many tutorials that teach of adding several views in the list activities but what I need is just a single option of the list to have this switch . I know if I can put a button or textView I can put a switch in there , but I can't understand how is putting a button or any view there without repeating the whole same scenario on all the options , as for me , I am new to the ArrayAdapters and ListViews , but I need to learn these two so I can use them later on ! what I need is like this for example:
______________________
Image | Switch
______________________
Normal option
______________________
Button | Whatever view
_______________________
Normal option
_______________________
Checkable Option
_______________________
and so on ... So if you clearly , I want full control over every single option in there , thats what I need and want . Thanks for any help :)
if you're gonna do it by listview, then you can do it by being explicit about each position in the getView method of the adapter. here's an example:
public View getView(int position, View convertView, ViewGroup parent) {
switch (position) {
case 0:
// do image switch option
// convertView = layout 0
break;
case 3:
// do button whatever option
// convertView = layout 3
break;
case 5:
// do button whatever option
// convertView = layout 5
break;
default:
// do normal option
// convertView = layout 1
break;
}
return convertView;
}
getView is a method in many android adapters that returns a view. When hooked up to a listView, it will be recognized as the method to return a view for each one of its rows. Each row in a listview that you see is the product of one executed instance of this method. You don't call it: it is called automatically whenever rows of a listview need to appear, or whenever a row position moves off-screen and comes back, or whenever the listView is being refreshed, in general whenever listview rows must be "made". It is there for you to override so that you can customize how ever for the specific look that you want.
convertView is a view as well. It is the product of a concurrent mechanism running in the listview adapter called view-recycling. Imagine that you have a listview that needs to display 1000 rows for your game's shopping menu. a conventional approach would be that you simply make an inflate command for your in getView for a vibrant xml and edit the components to show the item differences. So, that's inflating a 1000 times (and inflating is a expensive step, mind you), plus all that has to be in memory and is just clogging up your garbage collector. Plus, what if you need to refresh something to show changes? Get outta here! There are quad-core phones now, but there is a better way.
listviews done right don't actually make 1000 views at once but only the 7 or 8 that are on the screen at a time. When a listview row view is scrolled beyond the boundaries of the phone's screen, it isn't thrown out just yet. This concurrent mechanism runs and this view is saved and held under the title of convertView and passed in as a parameter for the next getView call. Not to (re-)use it is a terrible waste, because essentially you have a view that looks like the next row in the next getView call, perhaps with only the text in a textview changed or something like that, but you're gonna do another expensive inflation anyway. Then the unused convertView knows that it's second chance has been disregarded, into the GC, and it's business as usual.
The above code doesn't make use of convertView, because frankly it wasn't asked in the question, but it's very simple to utilize.
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
// inflation step
// convertView = inflation
}
// rest of code involving components of inflated view
return convertView;
}
First you check if there's nothing there, which you can imagine is when a listview is first launched and nothing has had the chance to be on deck to be thrown out. And only then do your inflation then. Next time around, getView will have it's mouth full with used view and will plug it in (if it's being returned). This is the single most beneficial action for your listview performance-wise that you can do right off the bat.
But you should know this, about half the questions tagged android on this site are on this alone, there are no guarantees that convertView will plop in the position that you want, as in right below if you're scrolling up and right above if scrolling down. Just make sure that you're being explicit about what goes at what position and you'll be fine. For more information on this and other listview know-how watch this: http://www.youtube.com/watch?v=wDBM6wVEO70.
I am working on a app that shows some places in a listview. In every listview item there is a arrow pointing towards the place(hotel, bar etc).
The problem is I don't know how to keep this arrow updated efficiently, without cashing views locally( which, according to a Google I/O video, is something i should never ever do).
The arrow needs to be updated on every phone orientation sensor event, which is many times a second.
So is there a better approach than calling notifyDataSetChanged() on every event and refiling every list item data?
UPDATE (for #dharan and anyone interested):
I have stopped working on this project because I have a full-time job now, but I thought of a solution (unimplemented/untested).
First limit the angle of rotation to a fixed step, (eg: 5, 10, 15, 20, ... 355, 360) then cache the rotated image for each angle of rotation in order to avoid expensive image rotation calculations (but at a higher memory cost).
After that I would make my getView() method know when to only update the image instead of all the data. In my case this is as easy as:
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView != null && place[position].id == (Integer)convertView.getTag()){
//the place information is already set correctly so just update the image
}
...
}
After these modifications I believe that calling notifyDataSetChanged() should not cause serious performance issues.
If you don't have too many items in the ListView you could convert to using a plain LinearLayout and control those items individually. The problem with LinearLayout though is if an item changes its size then everything (ie all children) has to be relayed out. So triggering a change in one item can re-trigger other things to layout as well. Now because you're changing a compass needle you might be able to skirt around it because that shouldn't cause each row to change its overall size. You just need to repaint that item.
The other option is to write your own layout that takes some short cuts making some assumptions the general purpose layout managers can't. The last thing I might look at is what does notifyDataSetChanged() does under the covers. You might be able to extend ListView and write a method that only redraws the row that changed (of course this assumes the height of the row hasn't changed). If the row height changes then everything after that row has to be relayed out.
We are creating an app with two main views: sView and sViewSettings. If the Android Back button is pressed we want an if statment to check if the current view is set to sView settings, if it is then call the sView.
Already have a listener setup for the back button just need it to call the if statement to check the current view.
Have already tried
if (this.findViewById(android.R.id.content) == sViewSettings)
Any ideas on this?
Thank you for Reading,
Travis
The view with id android.R.id.content is a FrameLayout holding your content view. Try this:
ViewGroup contentFrame = (ViewGroup) findViewById(android.R.id.content);
if (contentFrame.getChild(0) == sViewSettings) { ... }
However, I suggest a slightly different approach: use a ViewSwitcher (or any kind of ViewAnimator) to flip between the two main views and keep track in your code of which one is on display.
EDIT: If you want to keep your layouts loaded separately, you can assign an id (the same one) to the root view of each layout and then retrieve the content view directly using findViewById.