Gallery ImageAdapter convertView is always null - android

I am using a Gallery with an ImageAdapter to load it with ImageViews that pull images out of my resources. My problem is that the convertView that gets passed to the getView() method in my adapter is always null. This means that a new ImageView is created each and every time getView() is called. This leads to horrible preformance because the GC is constantly running to wipe away all of these created and no longer used ImageView's.
This is apparently a known bug: Gallery's view cache is broken; never converts views..
My two preferred solutions are either 1. handle a cache of views in the adapter itself and take care of all the logic required to re-use them properly.
or 2. include a my own copy of the Gallery widget and try to fix it so it properly returns recycled views.
I've started implementing option one but am quickly realizing I don't exactly know how to make all of the logic behind that operation. I am begining to think that option two might be easier.
I've found the code for the Gallery widget here: http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.1_r2/android/widget/Gallery.java
I don't fully understand it, but I can see that it is calling
child = mAdapter.getView(position, null, this);
on line 745. My (shot in the dark) guess that this is the root of the problem.
Does anyone have experience with this bug. Or can anyone point me in the right direction for figuring out how the recycler situation works so that I can tweak this widget to work correctly? Or even suggest some alternate option that I may be overlooking.
EDIT: The best solution that I ever found was an implementation called EcoGallery. The only place I can find reference to it online anymore is here. To get it working you have to put each chunk from there in the correct place within your project.

No experience with that bug particularly, but I have done custom caching before with a 3rd party view pager (before the support lib).
It's really not too difficult honestly. In my case, I knew there would be, at most, one item on the screen. But I also wanted the item to the left and right to be preloaded (these were webviews pulling data off the net). So I have a simple array of 3 views.
[V1, V2, V3]
Now the only thing you need to do is correlate a position in your adapter to a position in your cache. There's a variety of ways you could tackle this. When I first spiked it out, I just made whatever my current view was to be V2 and the rotated the items of the array around when flipping the pager. So flipping to the next view would alter my array
[V2, V3, V1]
Was simple to keep up with. Or you could just do the math and calculate a position to a relative position of the cache.
Another approach is to create a last in, first out queue. Recycle views by pushing them into the queue, and when you need a view, just pop one from it.

I do not have experience with Gallery widget, but I'am using a lot ListView with images. According to your problem, and link to Google issue, they still haven't fix this problem.
So, there is solutio with nice library (and examples within it), which solve cache/ajax problems, and much more.
Link to library
or, more concrete, link to image examples
If you download their examples, you will find how they implemented gallery with Gallery widget, using their AQuery utility class in
com.androidquery.test.image.ImageLoadingGalleryActivity class.
Snippet from code:
final List<Photo> entries;// here only to show what enteries are...
listAq = new AQuery(this); //define as Action class member, here only to show what it is
ArrayAdapter<Photo> aa = new ArrayAdapter<Photo>(this, R.layout.gallery_item, entries){
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null){
convertView = getLayoutInflater().inflate(R.layout.gallery_item, parent, false);
}
Photo photo = getItem(position);
AQuery aq = listAq.recycle(convertView);
aq.id(R.id.name).text(photo.title);
String tbUrl = photo.tb;
if(!aq.shouldDelay(position, convertView, parent, tbUrl)){
aq.id(R.id.tb).image(tbUrl);
aq.id(R.id.text).text(photo.title).gone();
}else{
aq.id(R.id.tb).clear();
aq.id(R.id.text).text(photo.title).visible();
}
return convertView;
}
};
aq.id(R.id.gallery).adapter(aa);
Where Photo is just POJO object (fetched from remote):
class Photo {
String tb;
String url;
String title;
String author;
}
R.id.gallery refers to
<Gallery
android:id="#+id/gallery"
android:layout_width="fill_parent"
android:layout_height="200dip" />
And R.layout.gallery_item refers to:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="100dip"
android:layout_height="75dip" >
<ProgressBar
android:id="#+id/progress"
android:layout_width="15dip"
android:layout_height="15dip"
android:layout_centerInParent="true" />
<ImageView
android:id="#+id/tb"
style="#style/GalleryItem"
android:layout_width="100dip"
android:layout_height="75dip" />
<TextView
android:id="#+id/text"
android:layout_width="100dip"
android:layout_height="75dip"
android:gravity="center"
android:maxLines="4"
android:padding="15dip"
android:text="Dummy TextDummy TextDummy TextDummy TextDummy Text"
android:textColor="#FFFFFFFF"
android:textSize="8sp" />
</RelativeLayout>
Hope you'll find this library useful in solving your problem.

I've gotten around this by using a custom cache as dskinner suggests.
I pre-calculate (screenwidth/minwidth of item) the max # of items that can be show in the gallery on the screen at one time and add a few more (the gallery will need extra to show items on the left and right as you scroll thru it). I create an array of this size - the views get created as requested and placed in the cache. Use position % cachesize to figure out which cached view to return when getView is called.

Related

Nesting custom views

I have a custom view that, on a high level, takes whatever drawable one might pass into it and draws a ring around it, the ring is animated, and comes with a whole set of parameters; things such as ringWidth, gradient colors, etc.
Currently, to show/specify this view in my XML, my markup looks a lot like this:
<RingedImageView
android:id="#+id/ringedImageView"
android:layout_height="#dimen/image_gigantic"
android:layout_width="#dimen/image_gigantic"
app:source="#drawable/ic_icon_1"
app:ringWidth="2dp" />
This is all well and good. However, instead of passing in a drawable as the image in the middle, what I would like is to be able to pass in a whole view, entirely seperate from #id/ringedImageView.
Ideally, I'd like this to look as follows in my XML:
<RingedImageView
android:id="#+id/ringedImageView"
android:layout_height="#dimen/image_gigantic"
android:layout_width="#dimen/image_gigantic" ...>
<ImageView
android:id="#+id/imageView" .../>
</RingedImageView>
My question then is, can I access that nested ImageView in my RingedImageView.java class? How can I specify how to handle this situation?
Some answers point to having to extend ViewGroup instead of View, but as this isn't a layout manager per se, and the component comes with its own rules for displaying content, a View seems more appropriate. Would appreciate a pointer.
Thanks all

Android - Creating an Airbnb-like Horizontal Image Slider

I'm looking to replicate airbnb's app image slider where you have one image at the top of a view that you can horizontally swipe and it automatically goes to the next image upon swipe. I created a horizontalscrollview but it doesn't quite have the right functionality. It doesn't flip from image to image, upon swipe it sort of scrolls along the image and eventually to the next image. I've googled around quite a bit and haven't seen a non deprecated solution.
Any ideas?
EDIT - adding some of my code per request:
Basically at a high level I'm pulling in imageURLs from my AWS database (in a different class not shown below) and then trying to load those URLs into a horizontalscrollview using Picasso.
//generic instructions to allow insertion of pictures into a horizontalimageslider
public View insertPhoto(String path){
LinearLayout layout = new LinearLayout(getActivity());
layout.setGravity(Gravity.CENTER);
ImageView imageView = new ImageView(getActivity());
imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
Picasso.with(getActivity() ).load(path).into(imageView); //tried this but got errors when running > resize(layout.getWidth(), layout.getHeight()), also tried .fit() after .load image wouldn't load
layout.addView(imageView);
return layout;
}
private class GetApartmentTask extends AsyncTask<ApiConnector,Long,Apartment >
//after a doInBackground
#Override
protected void onPostExecute(Apartment apartment) {
//to get image URL from database to then use with Picasso to download the image
mImageURLArraylist = mApartment.getApartmentImageArrayList();
for (int z = 0 ;z<mImageURLArraylist.size(); z++) {
mLinearLayout.addView(insertPhoto("http:" + mImageURLArraylist.get(z)) );
}
}}
in onCreateView method (alongside other things obviously)
mLinearLayout = (LinearLayout) v.findViewById(R.id.details_page_apartment_picture);
MyXML (relevant part)
<HorizontalScrollView
android:id="#+id/horizontal_scroll_view"
android:layout_width="match_parent"
android:layout_height="500dp"
android:fillViewport="true">
<LinearLayout
android:id="#+id/details_page_apartment_picture"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:scaleType="fitCenter"
android:orientation="horizontal">
</LinearLayout>
</HorizontalScrollView>
After many hours of frustration and some help getting over the finish line, the answer to swiping some part of the screen in this way is to use a viewpager.
Some great references:
swipe some part of the screen
http://developer.android.com/training/animation/screen-slide.html
What is the role of " isViewFromObject (View view, Object object)" in FragmentStatePagerAdapter?
my working code is provided in another question I posted that got me over the finish line :) --
Android ViewPager Won't Show Second Page?
I don't know the specifics of the airbnb app image slider, but it sounds like you could use flexslider. It's basically a JQuery toolkit. I have used it before in a website and it seems to have the functionality you are looking for.
https://www.woothemes.com/flexslider/

how to make spinner without title(value selected) shown

i'm trying to make a spinner with just arrow but without title, which looks like google maps using to get user's origin or destination for directions
and after you click on the arrow(triangle), start a new map as background.
i have searched for quite a time, found most relate topic are about remove that arrow with a new background , or how to set the prompt/hint/text before selecting any items in the list.so far i got no luck
is that really a spinner? or just a button+popwindow ?or it's related to the adapter?
thank you very much ...
by
LayoutInflater inflater = getLayoutInflater();
View empty = inflater.inflate(R.layout.empty, parent, false);
return empty;
in getView of class that extends ArrayAdapter, now there is
what ever which item is selected,but how to remove the underline and the space it takes, change background?
another solution is as #codeMagic said, with a image button and on click method. and by the way i found
<EditText
android:id="#+id/date"
style="#android:style/Widget.Holo.Spinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/hello_world" />
as mentioned in here
and look like
a little weird.

Performance impact of inflating layouts which have elements with visibility=gone

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.

Unable to make the ImageView of one item in ListView invisible

I have a row for a ListView defined as :
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="#+id/menutext"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textSize="24sp"
android:layout_alignParentLeft="true"/>
<ImageView
android:id="#+id/icon"
android:layout_width="40dip"
android:layout_height="40dip"
android:src="#drawable/lock"
android:layout_gravity="right"
android:layout_alignParentRight="true"/>
</LinearLayout>
My adapter for the ListView is set in onCreate()
listView.setAdapter(new ArrayAdapter<String>(this, R.layout.send_menu_row, R.id.menutext, items));
I have 5 rows with a text and image on each row. In onResume(), I want to make the first row's ImageView invisible.
#Override
protected void onResume()
{
super.onResume();
LinearLayout linLayout = (LinearLayout) listView.getAdapter().getView(0, null, null);
ImageView v = (ImageView) linLayout.getChildAt(1);
v.setVisibility(View.INVISIBLE);
}
But it doesn't change the visibility. Can someone help me on this?
Probably what is happening is that you are not doing that in the right method.
Try to switch the device orientation from horizontal to vertical (or vice-versa). This should trigger onResume method to be invoked and it could work.
Anyway, hiding an image shouldn't be done that way. Perhaps you should use an empty image or override the getView method (in the adapter).
UPDATE - why do I say you shoudn't use this method to do that
The thing is adapter.getView is used to get a view that will be drawn. The OS calls this method when he needs to draw that item on the screen.
This method could be overriden by the developer to draw custom/complex views but it should be used (as in, invoked) exclusively by the system.
For example, when we are talking about long lists, if you scroll, you'll have the getView method invoked and it will receive a view to be reused (which is a lot more efficient). This means that if you hard-core that the first view will be invisible, when you scroll and the first view is reused to display the 20th item (for example), now the 20th item would be invisible because probably you would just update the label and image source.
Note:
When I say first view, I'm referring to the view where the first item is initially drawn. Later, the view that was used to accommodate the first item is going to be reused to accommodate another item.
What is happening:
I think I got it now. What I believe to be happening is the following:
When the activity is initially drawn, you'll have the getView method invoked 5 times (one for each item that you are displaying). Each time the OS collects the view returned and adds it to the listview.
Latter, you'll call getView by yourself. As you pass no view to be reused, the method will create another view and return it. What is different this time? You are not adding this view to the listview. (Also, this isn't what you what to do.)
what you wanted to do is get the view that was used to draw the first item. But in this case you are just getting another view that could be used to draw the first item.
The solution is overriding getView or using a transparent image (easier).
Here's a link for the first result on google:
http://www.softwarepassion.com/android-series-custom-listview-items-and-adapters/
How about override getView and then do setVisibility when you return the first row in getView()?
It's not a great idea to be modifying rows outside of the list adapter. Since everytime the user scrolls you will lose the changes.
A workaround maybe ?
Try making a transparent image (png) and put that in as first item :-)

Categories

Resources