Images from URL appear and disappear on ListView Scroll - android

Hello I fill my ListView with a BaseAdapter, here a Download an Image from url, then I set the BitMap result in ImageView inside the xml row.
It works fine, but when I scroll the images appear and disapear in diferent places.
This my Code:
Activity:
public class MyActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity);
lvData = (ListView) findViewById(R.id.listview1);
AdapterClass adapter = new AdapterClass(this, String[] urls);
lvData.setAdapter(adapter);
}
}
Adapter:
public class PopularAdapter extends BaseAdapter {
private String[] mList;
private LayoutInflater mLayotInflalter;
public PopularAdapter(Context context, String[] list) {
mList = list;
mLayotInflalter = ((Activity) context).getLayoutInflater();
}
#Override
public int getCount() {
return mList.size();
}
#Override
public Object getItem(int position) {
return mList.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
WrapperRow wrapper;
if (convertView == null) {
convertView = mLayotInflalter.inflate(R.layout.row, null);
wrapper = new WrapperRow (convertView);
convertView.setTag(wrapper);
} else
wrapper = (WrapperRow ) convertView.getTag();
// //
new DownloadImage(wrapper.getImageView()).execute(mList.[position]);
return convertView;
}
}
Image Downloader Class:
public class DownloadImage extends AsyncTask<String, Void, Bitmap> {
ImageView bmImage;
public DownloadImage(ImageView bmImage) {
this.bmImage = bmImage;
}
protected Bitmap doInBackground(String... urls) {
String urldisplay = urls[0];
Bitmap mIcon11 = null;
try {
InputStream in = new java.net.URL(urldisplay).openStream();
mIcon11 = BitmapFactory.decodeStream(in);
} catch (Exception e) {
Log.e("Error", e.getMessage());
e.printStackTrace();
}
return mIcon11;
}
protected void onPostExecute(Bitmap result) {
bmImage.setImageBitmap(result);
}
}
Can you help me please? :D

This happens because the views get recycled in a ListView. This answer does a good job of explaining how the recycling mechanism works. Now coming on to your case, whenever getView gets called in your adapter, you are kick starting a new AsyncTask that downloads the image at the given url and sets it in the imageView inside WrapperRow.
Let's see why exactly the views flicker. Consider that you have a wrapper rows - wr1, wr2, wr3 .... wr8 and they get recycled by the listView (i arbitrarily chose to explain with 8 rows. the listView could use less or more). As soon as getView gets called the first time, you inflate a wrapper row (wr1) and then pass it to the AsyncTask which downloads the image & then sets it. Now you scroll through the list and the same process takes place for views wr2 to wr8 and on further scrolling it is time to recycle the views and wr1 comes into play again. You kick start another asyncTask to download the image but you never cleared the imageView (it already holds the first image now) and when the task is done downloading it sets the new image in imageview and this is when you an image disappearing and a new image showing up. When you scroll through the list, you see this happening repeatedly for all the rows!!
You could use any of the libraries like Picasso or UIL, but if you wanna spin up your own implementation remember these things
Don't hold on to strong references of views in asyncTasks (this would prevent the activity instance from GC even if it gets destroyed leading to memory leaks)
Whenever you start a new asyncTask make sure to cancel the previous asyncTask (if any) that still works with this view
And it always better to use caches (memory & disk)

Related

Android: Array out of index error while deleting files using AsyncTask

I have built a gallery which has options to select multiple items and delete. I am loading images to GridView using custom BaseAdapter and while deleting I am using AsyncTask. But if try to delete multiple items getting array out of bound exception.
java.lang.IndexOutOfBoundsException: Invalid index x, size is x
at java.util.ArrayList.throwIndexOutOfBoundsException(ArrayList.java:255)
at java.util.ArrayList.get(ArrayList.java:308)
at com.android.Example.Adapters.ImageAdapter.getView(ImageAdapter.java:94)
I am getting this error only when if I use AsynTask, Deleting works fine if I do it in main thread.
I have no clue at which point my ArrayList is going out of bound.
This my Custom BaseAdapter
public class ImageAdapter extends BaseAdapter {
private com.nostra13.universalimageloader.core.ImageLoader imageLoader;
private Context mContext;
private int displayWidth;
private int imageWidth;
private ArrayList<String> f = new ArrayList<String>();// list of file paths
public ImageAdapter(Context c, ArrayList<String> f) {
mContext = c;
this.f=f;
imageLoader = ImageLoader.getInstance();
imageLoader.init(ImageLoaderConfiguration.createDefault(mContext));
DisplayMetrics metrics = new DisplayMetrics();
((Activity) c).getWindowManager().getDefaultDisplay().getMetrics(metrics);
displayWidth = metrics.widthPixels;
imageWidth=(displayWidth/3);
}
#Override
public int getCount() {
return this.f.size();
}
#Override
public Object getItem(int position) {
return this.f.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
final ImageView imageView;
if (convertView == null) {
imageView = new ImageView(mContext);
imageView.setLayoutParams(new GridView.LayoutParams(imageWidth,imageWidth));
imageView.setPadding(Utils.dpToPx(2), Utils.dpToPx(2), Utils.dpToPx(2), Utils.dpToPx(2));
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setCropToPadding(true);
imageView.setBackground(mContext.getResources().getDrawable(R.drawable.gridview_selector));
} else {
imageView = (ImageView) convertView;
}
//imageView.setAdjustViewBounds(true);
imageLoader.displayImage("file:///"+this.f.get(position),imageView,); //This is line number 94
return imageView;
}
}
And this how I am deleting.
#Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
ArrayList<Uri> imageUris = new ArrayList<Uri>();
SparseBooleanArray checked = gridView.getCheckedItemPositions();
int checkedItemCount = gridView.getCheckedItemCount();
switch (item.getItemId()) {
case R.id.delete:
new DeleteAsync(checked, checkedItemCount).execute();
return true;
And this my AsyncTask
private class DeleteAsync extends AsyncTask<Void, Void, Void> {
SparseBooleanArray _checked;
int _checkedItemCount;
private DeleteAsync(SparseBooleanArray _checked, int _checkedItemCount) {
this._checked = _checked;
this._checkedItemCount = _checkedItemCount;
}
#Override
protected Void doInBackground(Void... params) {
for (int i = (_checkedItemCount - 1); i >= 0; i--) {
if (_checked.valueAt(i)) {
File file = new File(files.get(_checked.keyAt(i)));
if (file.delete()) {
MediaScannerConnection.scanFile(getActivity(), new String[]{file.toString()}, null, null);
files.remove(_checked.keyAt(i));
} else
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
Toast.makeText(rootView.getContext(), getResources().getString(R.string.delete_error), Toast.LENGTH_SHORT).show();
}
});
}
}
}
#Override
protected void onPostExecute(Void void) {
super.onPostExecute(void);
imageAdapter.notifyDataSetChanged();
progress.setVisibility(View.INVISIBLE);
}
}
Setting adapter to GridView
ArrayList<String> files=getListOfImagFiles();
imageAdapter= new ImageAdapter(rootView.getContext(), files);
gridView.setAdapter(imageAdapter);
Check the size of array before deleting.
Something like this may work.
if(items.size())>0
for (int i = _checkedItemCount; i > 0; i--)
{
//do your stuff
}
This is a common problem in getView of gridview adapters. For safe side I have followed like this.
put a condition check in getView()
if(position<this.f.size())
{
imageLoader.displayImage("file:///"+this.f.get(position),imageView,); //This is line number 94
}
else
{
//Display some default img or error img.
}
Suggestion:
Do not delete items of array based on the position of item. Delete based on the item model object.
Follow MVC guidelines.
Currently it is giving issue as
When you start you have total 10 items in files list. So index will be 0-9 and your for loop runs for this index range.
Now when you delete entry from files arraylist your count decreases by one i.e. it becomes Now you index range will be 0-8.
So as you go on deleting items from files arraylist your this line of code
imageLoader.displayImage("file:///"+this.f.get(position),imageView,);
inside baseadapter will loose that position.
So you are left with two options:
Do not delete from files arraylist just keep track of filenames and in onpostexecute delete those items from files arraylist and use notifydatasetchanged on your baseadapter.
Delete item from files arraylist but on each deletion use onprogressupdate to call notifydatasetchanged on your baseadapter.
Weirdly I solved my problem just putting safe guard before loading image.
if(position<getCount())
imageLoader.displayImage("file:///"+this.f.get(position),imageView,options);
I am not accepting my answer as I think this is not the perfect solution for this problem and also I have no idea why this is happening only when I am using AsyncTask
Do setAdapter again instead of NotifyDataSetChanged, it will reload the list and position will be correct.

laggy Listview with ImageView and Executor Framework

I have a ListView with custom items, like this one:
The grey square is an ImageView. The data to fill the ListView comes from a database in the form of a Cursor. But the images are not directly stored in the database, but in the SDCard, the database only holds a String reference to them.
In the beginning I was decoding the Image into a Bitmap from the overriden CursorAdapter's bindView() callback method:
Bitmap bmp = BitmapFactory.decodeFile(imageLocation);
holder.imageHolder.setImageBitmap(bmp);
But the ListView Scrolling was very laggy. So i read about Executor framework and implemented it, replacing the previous code with the following:
ImageView imageView = holder.imageHolder;
asyncImageLoader.DisplayImage(imageLocation, imageView);
And creating the AsyncImageLoader class. Which creates, in its constructor, a thread pool with maximum of 5 worker threads to take care of the Runnables sent to the work queue. Then, when I call the DisplayImage() method from my custom CursorAdapter, it checks if the location String contains a url. If it does, an ImageLoader Runnable is sent to the thread pool's work queue. If the location contains "N/A", a default image is set to the ImageView.
When an available worker thread takes care of the ImageLoader Runnable, the image in the SDCard is decoded into a Bitmap, and a ImageDisplayer Runnable is sent to the Main Thread's message queue, to show the image in the UI:
public class AsyncImageLoader {
ExecutorService executorService;
Handler handler = new Handler();
public AsyncImageLoader() {
this.executorService = Executors.newFixedThreadPool(5);
}
public void DisplayImage(String location, ImageView imageView) {
if(!location.matches("N/A")) {
queueImageDecoding(location, imageView);
} else {
imageView.setImageDrawable(imageView.getContext().getResources().getDrawable(R.drawable.not_available));
}
}
private void queueImageDecoding(String location, ImageView imageView) {
executorService.execute(new ImageLoader(location, imageView));
}
class ImageLoader implements Runnable {
private String location;
private ImageView imageView;
public ImageLoader(String location, ImageView imageView) {
this.location = location;
this.imageView = imageView;
}
#Override
public void run() {
Bitmap bmp = BitmapFactory.decodeFile(location);
handler.post(new ImageDisplayer(bmp, imageView));
}
}
class ImageDisplayer implements Runnable {
private Bitmap bitmap;
private ImageView imageView;
public ImageDisplayer(Bitmap bitmap, ImageView imageView) {
this.bitmap = bitmap;
this.imageView = imageView;
}
#Override
public void run() {
if(bitmap != null) {
imageView.setImageBitmap(bitmap);
}
}
}
}
The problem is that I am still getting the Laggy scrolling. If I get rid of the code inside ImageLoader.run() method, the scrolling is perfect. Isn't that code supposed to be processed in a worker thread? What am i missing here?
UPDATE
Since the Views in the ListView are reused when the scrolling happens, the Bitmaps returned from the worker thread, are set several times in a single ImageView.
So the possible solutions are:
To avoid setting the old Bitmap when the ListView item has already been reused.
Or even better, cancel the task.
I am cancelling the tasks using a Future object. Which is stored in the holder tagged to the item View inside the custom CursorAdapter:
public class MyCustomAdapter extends CursorAdapter {
...
public AsyncImageLoader asyncImageLoader;
private static class ViewHolder {
ImageView imageHolder;
TextView text1Holder;
TextView text2Holder;
TextView text3Holder;
Button buttonHolder;
Future<?> futureHolder;
}
public MyCustomAdapter(Context context, Cursor c, int flags) {
...
this.asyncImageLoader = new AsyncImageLoader();
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
...
return view;
}
#Override
public void bindView(View view, Context context, Cursor cursor) {
ViewHolder holder = (ViewHolder)view.getTag();
String location = ...;
ImageView imageView = holder.imageHolder;
if(holder.futureHolder == null) {
holder.futureHolder = asyncImageLoader.DisplayImage(location, imageView);
} else {
if(!holder.futureHolder.isDone())
holder.futureHolder.cancel(true);
holder.futureHolder = asyncImageLoader.DisplayImage(location, imageView);
}
...
}
}
Each time an item view is reused, I check if the holder's future object isDone(). If it is not, I cancel the task with Future.cancel(true). But now, the problem is that the tasks complete too fast to be cancelled. if I put the worker thread to sleep, for let's say 1 second, then the task lasts long enough to be cancelled and the ListView scrolling works better. But i have to wait 1 second for the images to appear and I don't want that.
public class AsyncImageLoader {
....
public Future<?> DisplayImage(String location, ImageView imageView) {
if(!location.matches("N/A")) {
return executorService.submit(new ImageLoader(location, imageView));
} else {
imageView.setImageDrawable(imageView.getContext().getResources().getDrawable(R.drawable.not_available));
return null;
}
}
class ImageLoader implements Runnable {
private String location;
private ImageView imageView;
public ImageLoader(String location, ImageView imageView) {
this.location = location;
this.imageView = imageView;
}
#Override
public void run() {
boolean interrupted = false;
try {
if(!Thread.currentThread().isInterrupted()) {
Thread.sleep(1000);
Bitmap bmp = BitmapFactory.decodeFile(location);
handler.post(new ImageDisplayer(bmp, imageView));
}
} catch (InterruptedException consumed) {
interrupted = true;
} finally {
if(interrupted)
Thread.currentThread().interrupt();
}
}
}
...
}
The second solution would be to let the tasks complete, but prevent setting the old Bitmap when the ListView item has already been reused. But i can't figure out how to do it. Any suggestions?
Ok, originally I get the images from a Web Service, and store them in the SDCard. From the samples I downloaded, I trusted the service was returning all the images with the same dimensions. WRONG! some of them are bigger than expected and were causing the lag when were set in the ImageView. I just had to scale them down. Load a Scaled Bitmap Version into Memory

Chris Banes PullToRefreshListView with Custom Adapter Error

I'm Settings up a Custom ListView.
The pull-to-refresh feature comes straight from https://github.com/chrisbanes/Android-PullToRefresh
The ListView displayes Images, so i created a custom Adapter:
class mAdapter extends BaseAdapter{
public mAdapter(Context context){
// nothing to do
}
#Override
public int getCount() {
return mValues.size();
}
#Override
public Object getItem(int position) {
return mValues.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public boolean areAllItemsEnabled()
{
return false;
}
#Override
public boolean isEnabled(int position)
{
return false;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
if(v == null){
LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = inflater.inflate(R.layout.list_item, null);
}
ImageView iv = (ImageView) v.findViewById(R.id.imageView);
if(iv != null){
displayImageInView(iv);
iv.setClickable(true);
iv.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(context, "ImageView", Toast.LENGTH_SHORT).show();
}
});
}
return v;
}
}
in onCreate(), i get the listView and assign the adapter:
mListView = (PullToRefreshListView) findViewById(R.id.listView);
mListView.setAdapter(new mAdapter(context));
After that i add an image to mValues (url for image to load from web) and call notifiyDataSetChanged on the adapter.
in mListView.onRefresh(), i add an image to mValues.
This works smoothly for adding the first image, or even the first bunch of images (before calling mAdapter.notifyDataSetChanged()).
The refresh indicator shows and hides as intended.
The weird things start happening when i try to add another image (or bunch) after that.
The refresh indicator shows, the image is displayed in the list view.
BUT : the refresh indicator never hides again after that. "onRefreshComplete()" gets called, but seems not to work properly the second time.
The UI Thread is not blocking, so operation is still possible.
If i delete all items in mValues, notify the adapter and pull to refresh again, the image is added properly, and the refresh indicator is hidden properly.
Conclusion: The pull-to-refresh only hides properly if the list was empty before refreshing.
I really don't know where to look for a solution for this weird error.
Maybe someone familiar with the Pull-To-Refresh Library from Chirs Banes can help me out here.
Thank You !
I just figured it out myself -.-
For anyone interested:
You have to set onRefreshComplete from the UI Thread.
Use a Handler to .post it from inside onRefresh(). <- which by the way runs on a separate thread.
Have a nice day.
I've found 2 ways:
Dynamically, when you need pulltorefreshview to stop do task on pull up, you can set a custom AsyncTask, for example:
private class GetDataTask extends AsyncTask<Void, Void, String[]> {
#Override
protected String[] doInBackground(Void... params) {
return null;
}
#Override
protected void onPostExecute(String[] result) {
lv.onRefreshComplete();
showToast(getResources().getString(R.string.no_more));
super.onPostExecute(result);
}
}
Dynamically call setMode to the pulltorefreshView
ptrlv.setMode(Mode.Both); // both direction can be used
ptrlv.setMpde(Mode.PULL_FROM_START); // only pull down can be used.

Unable to load thumbnails into ListView

I am currently attempting to make a simple image gallery like the now deprecated Android Gallery. From this gallery the user can also navigate to a simple image editor by selecting an image form the gallery. After a lot of Googling I managed to find a HorizontalListView here which is exactly what I need. Initially I had a lot of success with this by just inserting the images in my folder as compressed bitmaps. Since then however I have found this video from Google I/O in which they create an image gallery and an image editor; similar to what I am attempting to create. There are however two main differences between my app and theirs:
they use a GridView for their gallery and I use the aforementioned HorizontalListView
I am attempting to only load images from a specified target path rather than just all images on the SD card.
So far I am unable to adapt their code to mine as none of the images are loading into my gallery. As with the video I use an AsyncTask to load my thumbnails:
private class ThumbnailAsyncTask extends AsyncTask<Long, Void, Bitmap>
{
//The ImageView we will be adding the thumbnail to
private final ImageView mTarget;
public ThumbnailAsyncTask(ImageView target)
{
mTarget = target;
}
#Override
protected Bitmap doInBackground(Long... params)
{
final long photoID = params[0];
final Bitmap result = MediaStore.Images.Thumbnails.getThumbnail(
getContentResolver(), photoID, MediaStore.Images.Thumbnails.MINI_KIND, null);
return result;
}
#Override
protected void onPreExecute()
{
mTarget.setTag(this);
}
#Override
protected void onPostExecute(Bitmap result)
{
if (mTarget.getTag() == this)
{
mTarget.setImageBitmap(result);
mTarget.setTag(null);
}
}
}
and I am using a CursorAdapter for the images in the gallery:
private class PhotoAdapter extends CursorAdapter
{
public PhotoAdapter(Context context)
{
super(context, null, false);
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent)
{
return LayoutInflater.from(context).inflate(R.layout.list_item, parent, false);
}
#Override
public void bindView(View view, Context context, Cursor cursor)
{
final long photoId = cursor.getLong(cursor.getColumnIndex(BaseColumns._ID));
final ImageView imageView = (ImageView) view.findViewById(android.R.id.icon);
/*
* Cancel any pending thumbnail task, since this view is now bound
* to new thumbnail
*/
final ThumbnailAsyncTask oldTask = (ThumbnailAsyncTask) imageView.getTag();
if (oldTask != null)
oldTask.cancel(false);
/*
* If we arrived here, either cache is disabled or cache miss, so we
* need to kick task to load manually
*/
final ThumbnailAsyncTask task = new ThumbnailAsyncTask(imageView);
imageView.setImageBitmap(null);
imageView.setTag(task);
task.execute(photoId);
}
}
With the following CursorLoader
final LoaderCallbacks<Cursor> mCursorCallbacks = new LoaderCallbacks<Cursor>()
{
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args)
{
final String[] columns = {BaseColumns._ID};
return new CursorLoader(NewProjectActivity.this,
Uri.fromFile(new File(mTargetPath)), columns, null, null,
MediaStore.Images.Media.DATE_ADDED + " DESC");
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data)
{
mAdapter.swapCursor(data);
}
#Override
public void onLoaderReset(Loader<Cursor> loader)
{
mAdapter.swapCursor(null);
}
};
getLoaderManager().initLoader(LOADER_CURSOR, null, mCursorCallbacks);
Any ideas on why none of my images are loading?
The basic problem here is, bindView() method is not waiting for asynctask's result. You need to notify your list view when some of its content get changed.
You can do the following changes.
1) Change task.execute(photoId); to task.execute(photoId).get(); this will force your bindView() method to wait till you get your image. This approach is not advisable but it will help you to understand the exact problem.
OR
2) In onPost() of asynctask, invalidate your list view's old content and try to reload it with new content.
mAdapter.notifyDataSetChanged();
mAdapter.notifyDataSetInvalidated();
OR
3) Personally i will suggest you to finish all network operation first (i.e. fetching images ) by changing your code structure and then after try to set your adapter with the prefetched data. This is not a perfect solution but it worked in my case.

Adding default view in listview in android

I'm using a listview in my android application in which the listitems(in my case it is bitmap images) are loaded dynamically. Actually i'm creating the bitmap images and then it is loaded one by one into the list. what i want is to show all the list items with some default image and update them correspondingly when the bitmap image is created. My code is given below,
public class BitmapDemoActivity extends Activity {
HorizontalListView listview;
Vector<Bitmap> thumbImg;
BitmapCreator creator;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.listviewdemo);
creator=new BitmapCreator();
thumbImg= new Vector<Bitmap>(97);
listview = (HorizontalListView)findViewById(R.id.listview);
listview.setAdapter(new BitmapAdapter());
new AsyncBitmapCreate().execute();
}
private class AsyncBitmapCreate extends AsyncTask<Void, Bitmap, Void>{
//Bitmap[] temp=new Bitmap[44];
#Override
protected Void doInBackground(Void... params) {
// TODO Auto-generated method stub
for(int i=0;i<97;i++){
publishProgress(creator.generateBitmap(i+1));
}
return null;
}
#Override
protected void onProgressUpdate(Bitmap... values) {
// TODO Auto-generated method stub
super.onProgressUpdate(values);
new BitmapAdapter().add(values[0]);
new BitmapAdapter().notifyDataSetChanged();
}
}
class BitmapAdapter extends BaseAdapter{
public void add(Bitmap bitmap)
{
Log.w("My adapter","add");
thumbImg.add(bitmap);
}
#Override
public int getCount() {
return thumbImg.capacity();
}
#Override
public Object getItem(int position) {
return null;
}
#Override
public long getItemId(int position) {
return 0;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater=(LayoutInflater)getSystemService(LAYOUT_INFLATER_SERVICE);
View retval = inflater.inflate(R.layout.listitem, null);
ImageView img = (ImageView) retval.findViewById(R.id.tImage);
img.setImageBitmap(thumbImg.get(position));
return retval;
}
};
}
Here i'm using a vector in which after creating each bitmap, it is inserted into that vector. I'm using an asynctask to create the bitmap. After each bitmap is created i'm calling notifydatasetchanged() method to update the listview. But now in the output whenever each bitmap image is created it is adding one item in the listview with that image. But my requirement is to show all the 97 items in my list with some default image and whenever bitmap is created update the corresponding listitem.
can anyone help me?? Thanks in advance....
The simplest would be to include the default image as the src for the ImageView with id tImage in your layout listitem.xml. And in your getView method, replace the default image if the Bitmap for that position is available.
ImageView img = (ImageView) retval.findViewById(R.id.tImage);
Bitmap bmp = null;
if(position < thumbImg.size()){
thumbImg.get(position);
}
if(null != bmp){
img.setImageBitmap(bmp);
}

Categories

Resources