Does a replacement for Gallery widget with View recycling exist?
Asked Answered
S

5

12

The default Gallery widget on Android does not recycle views - everytime the view for a new position is called the widget always calls the getView method of the adapter with convertView set to null.

As you scroll backwards and forwards this ends up in lots of views being created, which the recycler component that the Gallery stores them in does not seem to recycle them quickly enough leading to an OOM situation.

You can test this easily with a few large-ish images as your gallery items, but just a TextView will cause it in the end. Put a log statement with a counter in the getView method of your adapter also to see how many new views are created.

Does a third-party widget which behaves like a Gallery but that also implements view recycling exist?

Surrebuttal answered 26/4, 2011 at 11:52 Comment(2)
If nobody has another solution, you could grab the source code to Gallery, modify it to suit, and use it. I see in the code what you're referring to. I have no idea why makeAndAddView() is written the way it is.Quenchless
@Quenchless Cheers - I already started doing that, but it's ugly and means copying and editing quite a few classes since it uses a few protected members and methods, so I was just wondering if anyone had any better ideas.Surrebuttal
S
24

My solution was, in the end, going with @CommonsWare's suggestion to modify the Gallery source code. This is also required copying the following files:

  • AdapterView
  • AbsSpinner

but these are pretty simple.

After that I modified code to do the following:

RecycleBin (AbsSpinner)

  • Place objects in the recycler one after another, rather than according to position
  • Retrieve objects from the bottom of the recycler, regardless of the position requested
  • The existing implementation assumed that each different position in the adapter resulted in a unique view. The changes above are only good if your Gallery contains only one type of item, if not you'll need to add some sort of key based on item type and the amount of that type required

Gallery

  • Used reflection (ugh) to modify the private mGroupFlags variable of ViewGroup to allow child re-ordering - I also set a boolean value indicating whether the field access succeeded which I test before using the component.
  • Removed all calls to mRecycler.clear()
  • The number of items the gallery has to display changes as it scrolls and the existing implementation would clear the recycler when (a) setSelection was called (b) a motion scroll occurred

With these modifications my counter in my newView method in my adapter reached... 7.

Here is the code (Placed in the public domain 2013/08/07 under http://en.wikipedia.org/wiki/WTFPL)

Surrebuttal answered 4/5, 2011 at 11:54 Comment(8)
would you mind posting a link to the source files for Gallery and the other two you mention?Artful
Too long to post all the classes in one answer so I put them here: pastebin.com/FWyYTt4DSurrebuttal
Thanks a ton for posting this! It works great compared to the built in Gallery Widget. It is a shame that we have to resort to such extreme measures just to get a functioning recycler. If I would've realized what a pain it was going to be I would've chosen a different widget instead of Gallery. Your modified gallery works and solved my issue though Thanks Again!Gavelkind
i cant use the code provided by you...there are multiple class cast exceptions and also onItemclickListener is also creating problems... 03-05 12:16:00.545: E/AndroidRuntime(30246): java.lang.ClassCastException: android.widget.Gallery$LayoutParams 03-05 12:16:00.545: E/AndroidRuntime(30246): at .EcoGallery.setUpChild(EcoGallery.java:773) 03-05 12:16:00.545: E/AndroidRuntime(30246): at com.exiticlabs.arsenallwp.EcoGallery.makeAndAddView(EcoGallery.java:752) 03-05 12:16:00.545: E/AndroidRuntime(30246): at .EcoGallery.layout(EcoGallery.java:646) .EcoGallery.onLayout(EcoGallery.java:362)Romeliaromelle
I got this problem: 05-27 10:23:02.650: E/AndroidRuntime(2679): java.lang.ClassCastException: android.widget.Gallery$LayoutParams cannot be cast to com.designfuture.music.widget.gallery.EcoGallery$LayoutParams do you know how to solve?Mcmichael
@Joseph Earl. It works great. Any suggestions on how to implement touch events for child view's inside the ImageView in eco gallery?Varityper
@JosephEarl What is the license is your code distributed under? I would like to include it in a project I'm working onEarwig
@Akos Since you asked, I am placing the code in the public domain under en.wikipedia.org/wiki/WTFPLSurrebuttal
M
1

Actually there is an alternative, though I haven't personally tested it:

https://github.com/falnatsheh/EcoGallery

Miun answered 26/11, 2013 at 13:7 Comment(14)
EcoGallery is based upon the (deprecated) Android Gallery and it's basically the Android Gallery source code with some changes (but it exhibits the same bugs the old Gallery had) albeit it better recicles view. But the "jumping" gallery problem is still there if you download images in the background.Lamarre
not sure what is the "jumping" bug, but I don't like the gallery view anyway, and you are correct that this project is based on the original code of the gallery, but they claim to try to fix this view. i think that at least the view-recycling mechanism is fixed there.Miun
Yes, this version of the gallery does a better (Albeit not 'as good as a listview') job recycling views. The biggest problem (that the original gallery had and was never fixed even after it was deprecated) is that when you load your images in the background (like you should), they will trigger a layout, and this causes the gallery to snap the "closest image" to the center. this causes a jumping all the time as you scroll and items are loaded (and put in the gallery). There are some workarounds that fix the issue but it's hacky. Once imgs are loaded gallery works fine :)Lamarre
I'm still not sure what you are talking about. If images are being loaded and views' sizes are based on them, of course they will cause weird things to happen, as sizes of the views get changed. this will happen for listViews too. you should use a fixed size on the items in order to avoid such a thing. Anyway, about the library, I've tried it out about a month ago and ditched it for a crash I've found (happens only after a lot of fast scrolling). This is why i think that if you must use a gallery, use this one instead.Miun
"this will happen for listViews too. you should use a fixed size on the items in order to avoid such a thing." Hmmm no, this doesn't happen on list views. Where did you get that idea? This happens because the original gallery is snapping the item to center onLayout (which gets called when the ImageView does a setXXXXBackground(). It has been a bug in the gallery forever and possibly one of the many reasons why google decided to just ditch the gallery and start from scratch with (ok but not 100% similar alternatives), the ViewPager and HorizontalScrollableView).Lamarre
And yes, I've seen the crash, but I solved it by modifying the velocity factor by an order of 3 (so the scroll is a lot slower, which is what I needed anyway) so a fast fling will scroll 3-5 items only. it's crappy, but there's no real easy alternative that doesn't require you to reinvent the wheel. Some day, Google will understand that the UI is one of the most important parts of an App and will hopefully copy Apple in that front. (Not aesthetically, but from a developer's point of view).Lamarre
@MartínMarconcini you probably mean HorizontalScrollView (but it's from API 3...) . Anyway, i still don't understand which bug you are talking about. the bug i talked about is setting the items' views to be wrap content, so if you get different sizes of images when the loading is finished, you cause the items' size to change. it makes sense and will happen in both listView and gridView (and some ViewGroups).Miun
Ok, the original gallery exhibited a stutter effect when images were loaded in the background. For more information check this: #5758906 the reasons behind this behavior are known (and maybe irrelevant since it was deprecated) but it was likely one of the reasons why google ditched the gallery, bad and hacky design. It hasn't really been replaced with a similar alternative, neither ViewPager nor HorizontalScrollView do the same thing the old Gallery did.Lamarre
ok, anyway, if you wish, you can make an alternative of the gallery view by using a horizontalListView (link here: github.com/sephiroth74/HorizontalVariableListView/tree/master/… ), and add some scrolling listeners to mimic its behavior. you could even make it a coverflow by adding things like on the circly launcher sample made by samsung (link here: developer.samsung.com/android/samples/Circle-Launcher ) .Miun
Will check the link, thanks. I hacked EcoGallery a lot and made specific changes that work for me, but I have added a huge //TODO to replace it with either alternative in the near future. I just want to finish other stuff first. :)Lamarre
if you make a library, can you please share it?Miun
For the record, the last answer in that stackoverflow question, solves the problem if you have a fixed size imageview. Basically overloading public void requestLayout() in a custom imageview does the trick (but only for the gallery of course). I will share it, because nothing I've done is crazy, but for some apps it may work :) I'll do it in a few days (maybe over the weekend).Lamarre
Thanks. if you wish, you can also check out my humble tiny library: github.com/AndroidDeveloperLB/AndroidJniBitmapOperationsMiun
There: github.com/Gryzor/TimelineGallerySample (I've added some perks too)Lamarre
B
0

I used patch from http://code.google.com/p/android/issues/detail?id=3376#c19

Branch answered 3/5, 2012 at 19:32 Comment(0)
L
0

Super late to the party, but I've modified EcoGallery to do a few more things (and avoid some crashes).

I've called it TimelineGallery and it's the same crap as the Gallery, but it can do smooth scroll and doesn't do weird stuff when images are loaded asynchronously.

To demonstrate it, the sample uses Picasso and PullToRefresh.

The original code, copyright and such belongs to Google so blame them for making such a crappy widget.

Final note: I do not recommend using the gallery, it's old, buggy, hacky and will probably never be maintained. The problem is not about fixing its bugs, the problem is that the whole architecture of the Gallery is wrong and as such, fixing it is not possible without introducing more hacks.

Google realized this and deprecated it. Use a ViewPager or a HorizontalScrollList and deal with the limitations of each.

If you still want to go ahead and use this "gallery", feel free, it works, but it may crash your app and may frustrate you.

Lamarre answered 13/1, 2014 at 23:25 Comment(0)
S
-1

Another quicker WorkAround for the OutOfMemory Issues, is to try/catch the code where you decode the image and if the OutOfMemory-exception is thrown, you try to decode it with smaller Resolution again..

something like this:

private static Bitmap decodeFile(File f, int size, int suggestedScale) {

    int scale = 1;
    Bitmap bmp = null;
    try {
        // Decode image size
        BitmapFactory.Options o = new BitmapFactory.Options();
        o.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(new FileInputStream(f), null, o);

        // Find the correct scale value. It should be the power of 2.
        int width_tmp = o.outWidth, height_tmp = o.outHeight;

        if(suggestedScale > 0)
            scale = suggestedScale;
        else {
            if (width_tmp >= height_tmp) {
                scale = Math.round((float)(width_tmp) / size);
            } else {
                scale = Math.round((float)(height_tmp) / size);
            }
        }

        if(scale < 2)
            return BitmapFactory.decodeFile(f.getPath()); 

        Debug.i(TAG, "width: " + width_tmp + "  height: " + height_tmp + "  scale: " + scale);


        // Decode with inSampleSize
        BitmapFactory.Options o2 = new BitmapFactory.Options();
        o2.inSampleSize = scale;
        bmp = BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
    } catch (FileNotFoundException e) {

    } catch(OutOfMemoryError e) {
        Debug.i(TAG, "we retry it cause of an OutOfMemoryException");
        return decodeFile(f, size, scale+1);
    } catch(Exception e){
        Debug.w(TAG, e);
    }
    return bmp;
}

Ofcourse it is now possible, that you will see different resolutions of the same picture at different times - But at least your Gallery will not crash anymore and you allways show the highest resolution you can.

Swot answered 30/5, 2011 at 11:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.