Have the following asynctask that i'm using to download some images. Works fine except for the very first image, which doesn't always appears unless I do something like move to the next image and then back again.
public class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
static ImageView _imageView=null;
public DownloadImageTask(ImageView ctl){
_imageView=ctl;
}
protected void onPostExecute(Bitmap result) {
_imageView.setImageBitmap(result);
}
...
}
I thought this might be an issue with updating the UI on a background thread, so I reworked this using an abstract class that invokes a method on the UI thread that calls .setImageBitmap() but I still get the same behaviour - works fine for all images except the first, unless I move to the next image and back again.
Is there a way to force a redraw on the imageview after i set the image?
I had a similar issue, and what I did in my Activity.onCreate() was something like this:
ListView listView = (ListView) findViewById(R.id.myListView);
adapter = new MyArrayAdapter(this, myArray, ...);
listView.setAdapter(adapter);
if (myArray.size() > 0)
{
// Fake a click on the first item
onItemClick(listView, myArray, 0, 0);
}
Then, onItemClick() does what is required to find the ImageView for the current row. Of course, without seeing your code, the above is just a wild guess...
Related
I implemented ListView to display some items from the SQLite Database. Each of which contains an Image and some Data.
I want my ListView to work faster even if it contains thousand rows of data. So i tried to implement some optimizations that i have noticed. Here is the basic structure of my CustomCursorAdapter:
Class CustomCursorAdapter extends CursorAdapter
{
Cursor cursor;
public CustomCursorAdapter(..., Cursor _cursor)
{
cursor = _cursor;
}
public void bindView(View _view, Context _context, Cursor _cursor)
{
if( view.getTage() == null)
{
//create and initialize a new holder and set it to view tag.
holder = new Holder();
...
...
holder.imageView = (ImageView) view.findViewById(R.id.image);
holder.imageView.setImageDrawable(defaultDrawable);
view.setTag(holder);
}
String mediaID = _cursor.getString("media_id");
//create an asyncTask to load the image from database
new MediaLoader(context, holder.imageView).execute(mediaID);
}
private class MediaLoader extends AsyncTask<String, Void, Media>
{
private Context context;
private final WeakReference <ImageView> imageViewReference;
public MediaLoader(Context _context, ImageView _imageView)
{
context = _context;
imageViewReference = new WeakReference<ImageView>(_imageView);
}
protected Media doInBackground(String... args)
{
String _mediaID = args[0];
return MediaDataManager.getInstance(_context).getMediaObjectForListAdapter(_mediaID);
}
protected void onPostExecute(final Media _media)
{
super.onPostExecute(_media);
if( imageViewReference != null )
{
ImageView _imageView = imageViewReference.get();
if(_imageView != null)
{
if( _media != null && _media.getImage() != null )
{
_imageView.setImageBitmap(_media.getImage());
}
else
{
_imageView.setImageDrawable(defaultDrawable);
}
}
}
}
}//Media Loader ends.
}//Custom Cursor Adapter Ends.
Using this approach loading time of image seemed ok to me.
But in some android devices (low configuration ones), i am experiencing image flickering. For some reason during scrolling or even loading i noticed images keeps changing throughout the list. But the final image that remains in every row is always the correct one.
Edit:
Loading of images one by one is not a problem for me. But showing some irrelevant images before showing the correct one is my only concern.
I couldn't find any helpful resource by searching. Any kind of help is very much appreciated.
Let's imagine that you have 100 rows in your Cursor. And, let's suppose that this is a really short ListView, where only 2 rows are visible. And, let's suppose that after you load up the adapter in the ListView, the user flings through the whole list.
What your code will do is:
Fork 100 AsyncTask instances, where on Android 3.2+ they will only execute one at a time (unless your targetSdkVersion is fairly low)
Download 100 images
Put each of those 100 images into the rows as they come in
All of this, for a case where you only need 2 images, the ones at the end.
This is why you really should consider using an existing library for this sort of thing, like Picasso, where you could plug in logic to pull values out of... well, wherever your images are actually stored. These sorts of libraries already handle these sorts of situations.
If you insist upon implementing this yourself, you will need to add in the smarts to realize that if the user scrolled the list and we are recycling a row, that we no longer need previous tasks that are tied to that row. Cancel those and queue up a task to download what you need. Also, consider using executeOnExecutor() on API Level 11+, so some of these will run in parallel.
I am using a SimpleCursorTreeAdapter to display data in database. I am using loaders to manage all the cursors. Everything works fine. But there are some images in each of the child views. This causes a noticeable stutter when scrolling. So I want to use an asynctask to decode the images in background. Something like (pseudocode):
#Override
protected void bindChildView(View view, Context context, Cursor cursor, boolean isLastChild) {
super.bindChildView(view, context, cursor, isLastChild);
String imgFile = cursor.getString(MyDatabaseHelper.FILE_INDEX);
new asyncLoadImage().execute(imgFile);
}
private class asyncLoadImage extends AsyncTask<String, Void, Bitmap> {
#Override
protected Bitmap doInBackground(String... arg0) {
String imgFile = arg0[0];
return Utils.getBitMap(imgFile);//psuedocode
}
#Override
protected void onPostExecute(Bitmap bm) {
ImageView imageView = new ImageView(mCtx);
imageView.setTag(mID);
imageView.setImageBitmap(bm);
//ok got the imageview. where do I append it ??
}
}
By the time the imageview is ready in onPostExecute() function, the view provided in bindChildView might have been recycled and pointing to some different child element. How do I determine where to append the imageview?
First, don't append. You need the ImageView there anyway. Have the ImageView use a placeholder image, that you replace with the final image when it is ready.
Second, consider using an existing library that knows about Adapter recycling, such as Picasso. Admittedly, I don't know if Picasso has support for ExpandableListAdapter, as I rarely use ExpandableListView.
If you determine that there is no suitable library, you'll need some logic in your getChildView() and/or getGroupView() methods that can deal with recycling and your background work. One fairly simple approach is to tuck the URL of the image needed by the ImageView in the tag of the ImageView via setTag(). Your onPostExecute() can then compare the URL it just downloaded with the URL from the ImageView tag, and if they do not match, do not update the ImageView. This means that you will download some images unnecessarily, so a more sophisticated approach will arrange to cancel the AsyncTask that is downloading the image. And I am sure that there are other approaches as well.
I've been fooling around with Android but I'm stuck. I have a adapter class that
sets an image in an imageview for each item in my gridView. I use the observer pattern to notify the activity that calls the update method and it refreshes the adapter with notifydatasetchanged and that invalidates the gridview itself and the imageView.
The problem is that when i change an object (which has a reference to a drawable), and try to update, nothing happens in the imageview. I've tried to debug and the objects are changed so i don't understand why it doesn't update...
Also, with this it works perfect to make the images disappear, so that's pretty akward..: shape.setImage(android.R.color.transparent)
#Override
public void update(Observable arg0, Object arg1)
{
// Toast.makeText(this, "I am notified",0).show();
adapter.notifyDataSetChanged();
gridView.findViewById(R.id.picture).invalidate();
gridView.invalidate();
}
This is an example of an object that needs to be showed:
public class Square extends Shape
{
public Square()
{
setImage(R.drawable.square);
}
public String print()
{
return "s ";
}
public String getName()
{
return "Square";
}
}
my adapter:
public class GridAdapter extends ArrayAdapter<Shape>
{
Context context;
int layoutResourceId;
List<Shape> data = null;
public GridAdapter(Context context, int layoutResourceId, List<Shape> data)
{
super(context, layoutResourceId, data);
this.layoutResourceId = layoutResourceId;
this.context = context;
this.data = data;
}
public View getView(int position, View convertView,ViewGroup parent)
{
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View rowView = inflater.inflate(R.layout.list_item, parent, false);
rowView.setBackgroundResource(R.drawable.customshape);
ImageView imgView =(ImageView)rowView.findViewById(R.id.picture);
imgView.setImageResource(data.get(position).getImage());
return rowView;
}
I think the actual problem here is updating the drawable from outside the UI thread. notifyDataSetChanged() isn't the answer. The dataset didn't change, in my case at least. The dataset changed and I set the drawable to null (which worked). Later, when the image finished loading, I wasn't updating from the UI thread, so the GridView didn't know it was supposed to learn about the invalidation of the ImageView.
The answer in my case was to use AsyncTask which automatically uses the UI thread and thread syncs.
Note that notifyDatasetChanged() is for when the getViewId() changes in the adapter — which isn't the same as and actually has nothing to do with when the drawable changes. Also note that setImageDrawable() automatically invalidates the ImageView. There's actually no need to call ImageView.invalidate() or any other invalidate(). You just have to update the drawable in the UI thread, and AsyncTask comes with all the right baggage to not only do it at the right time, but in such a way that the GridView can learn about it before it updates the various canvases and drawable caches.
I learned most of the above from the Google Android dev pages and from source diving for hours on end. Curiously, source diving in Android Studio (ctrl-B) is actually easier than slogging through all the docs hoping to stumble on the right paragraph that explains the problem. Though, the problem is explained nicely here:
http://developer.android.com/training/displaying-bitmaps/process-bitmap.html
I cannot comment, so I am going to share my thoughts here, let me know in the comments if I'm missing something.
Seems like you need to add the following method in your GridAdapter:
public void setData(List<Shape> stuff) {
data.clear();
data.addAll(stuff);
notifyDataSetChanged();
}
Now, wherever in your activity you created the adapter, you should have a List that was passed to create the GridAdapter. Let's call that initial list 'data1'.
Assuming by 'refresh' you mean that you want to modify an existing item. You need to find that item in data1 and make the changes. Then you need to find the GridAdapter instance that you have already created and then use the setData method and pass data1.
Hope this is not totally useless info for you.
Note: it would be helpful if you posted more of your code.
The problem is that when i change an object (which has a reference to
a drawable), and try to update, nothing happens in the imageview.
Lets have a look at the doc:
public void notifyDataSetChanged()
Notifies the attached observers that the underlying data has been
changed and any View reflecting the data set should refresh itself.
the only reason that when you call notifyDataSetChanged() and it dose not update your gridview is that your adapter data set and your object are two different things. that means your gridview data has not changed. you must change those objects that you passed to your gridView constructor (List data). if you change any of them and then you call notifyDataSetChanged() it will work. The references of your object and the adapter data must be the same.
another things that I have seen on the net is you must change the data set of adapter by manipulating it with functions like (add(), insert(), remove(), clear(), etc.).
Problem solved lol, i just made a new adapter instead and it worked
I am not a pro of android developing. Please forgive me for asking such a noob question.
So far, I used BaseAdapter and ArrayList<Map<String,String>> to handle the row text view.
My question is what happens if I want to use two ArrayList such as ArrayList<Map<String,String>> and ArrayList<Bitmap> to handle row text and image?
Can I still using notifydatasetchanged to update my ListView like what I did so far.
I want to ask the listView to update the list after the images is finished downloaded by using notifydatasetchanged
In both cases which you might want check this good tutorial.
Make your own custom class that includes both Map and Bitmap.. And define the arrayList of this class.. something like this:
class MyCustomClass{
private Map<String, String> myMap;
private Bitmap myBitmap;
public MyCustomClass(Map<String,String> map, Bitmap bitmap)
{
this.myMap = map;
this.myBitmap = bitmap;
}
}
And in your activity:
ArrayList<MyCustomClass> myArrayList = new ArrayList<MyCustomClass>();
I'm working on an application that primarily consists of a list view. It's backed up by my own custom array adapter whose size changes every 5 seconds. If I scroll through the list view as the array adapter changes from a greater size to a lesser size, I get an out of bounds exception. It makes sense to me why this occurs (since I'm scrolling at a position beyond the new array size), but I was wondering if there was a good way to debug it. I can't seem to come to a clear conclusion, and I was wondering if I could get some help.
I update the adapter using the following asyncTask...
public class myTask extends AsyncTask<Void, Void, Void>{
#Override
protected Void doInBackground(Void... params) {
while(isRunning){
myData.clear();
getData();
publishProgress();
SystemClock.sleep(5000);
}
return null;
}
protected void onProgressUpdate(Void...progress){
listAdapter.notifyDataSetChanged();
}
}
myData is the ArrayList that supports listAdapter and getData() is the function that populates myData with the relevant info that will eventually be displayed in my list view.
Is there a good way to tackle this problem?
Regards
Are you using a custom adapter?
Perhaps in your getViewTypeCount() and getItemViewType(int position) it is going out of bounds there. The view type count should be from (0, n] but the item view type should be [0, n).
E.g. view type count should be 2
But the view types should be 0 and 1, not 1 and 2.
try to override the getCount() function of the adapter class:
#Override
public int getCount() {
return [list that contains data].size();
}
Have you tried resetting the user to either the top of the list or as close as possible when the list changed sizes?
Sorry, maybe is not the answer for the question, but having that infinite loop seems for me that is not the correct way, even if it´s an async task.
I would try to use the an AlarmManager or a Timer.
With that said, seems what you have is a race condition, take into account that it´s not immediately when you ask for data, or a specific position and rebuild the views.
Cheers,
Francisco.