I got my listview working almost good enough. When I scroll fast up OR down it shows the same 3-4 pictures every time, which slowly turn into the right image.
Code to retrieve bitmap that I think needs fixing
public Bitmap retrieveBitmap(String url) throws Exception {
InputStream inputStream = null;
try {
inputStream = this.retrieveStream(url);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 8;
final Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null, options);
return bitmap;
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
Before I added the options sample size it kept crashing as I scrolled, I just copied this off another stack question but am not sure what exactly it is doing besides reducing the size somehow.
Adapter class
package com.example.jdmb;
import java.util.ArrayList;
import android.app.Activity;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
public class MoviesAdapter extends ArrayAdapter<Movies> {
private ArrayList<Movies> movieData;
private Activity context;
public MoviesAdapter(Context context, int resource,
ArrayList<Movies> objects) {
super(context, resource, objects);
this.context = (Activity) context;
this.movieData = objects;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
ViewHolder holder;
if (view == null) {
LayoutInflater vi = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = vi.inflate(R.layout.list_row, parent, false);
holder = new ViewHolder();
holder.id = (TextView) view.findViewById(R.id.movieId);
holder.title = (TextView) view.findViewById(R.id.movieTitle);
holder.vote_avg = (TextView) view.findViewById(R.id.movieAvg);
holder.backdrop_path = (ImageView) view
.findViewById(R.id.movieBackdrop);
holder.release_date = (TextView) view
.findViewById(R.id.movieRelease);
holder.original_title = (TextView) view
.findViewById(R.id.movieTitle2);
holder.vote_count = (TextView) view.findViewById(R.id.movieCount);
holder.adult = (TextView) view.findViewById(R.id.movieAdult);
holder.poster = (ImageView) view.findViewById(R.id.moviePoster);
holder.popularity = (TextView) view
.findViewById(R.id.moviePopularity);
view.setTag(holder);
} else {
holder = (ViewHolder) view.getTag();
}
Movies movie = movieData.get(position);
if (movie != null) {
// check to see if each individual textview is null.
// if not, assign some text!
holder.id.setText("id: " + movie.getId());
holder.title.setText("title: " + movie.getTitle());
holder.vote_avg.setText("vote_avg: " + movie.getVote_average());
DownloadBitmap.downloadBitmap(holder.backdrop_path,
movie.getBackdrop_path());
holder.release_date.setText("release_date: "
+ movie.getRelease_date());
holder.original_title.setText("original_title: "
+ movie.getOriginal_title());
holder.vote_count.setText("vote_count: " + movie.getVote_count());
holder.adult.setText("adult: " + movie.isAdult());
DownloadBitmap
.downloadBitmap(holder.poster, movie.getPoster_path());
holder.popularity.setText("popularity: " + movie.getPopularity());
}
return view;
}
private static class ViewHolder {
TextView id;
TextView title;
TextView vote_avg;
ImageView backdrop_path;
TextView release_date;
TextView original_title;
TextView vote_count;
TextView adult;
ImageView poster;
TextView popularity;
}
}
Downloader class
package com.example.jdmb;
import java.io.InputStream;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;
import android.os.AsyncTask;
import android.widget.ImageView;
public class DownloadBitmap {
static Bitmap image;
public static void downloadBitmap(final ImageView imageView,
final String url) {
new AsyncTask<Bitmap, Void, Bitmap>() {
#Override
protected Bitmap doInBackground(Bitmap... params) {
HttpRetriever retriever = new HttpRetriever();
try {
image = retriever.retrieveBitmap(url);
} catch (Exception e) {
}
return image;
}
#Override
protected void onPostExecute(Bitmap result) {
super.onPostExecute(result);
if (imageView != null) {
imageView.setImageBitmap(result);
}
}
}.execute();
}
}
What part of my code is making me see the same photos when scrolling, I think it has something to do with recycling the images, but not sure how to get past it.
The recycled ImageView shows that last bitmap you told it to. You either need to set the holder.backdrop_path & holder.poster to a generic resource before returning the view or in onPreExecute of your AsyncTask.
Like this:
package com.example.jdmb;
import java.io.InputStream;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.widget.ImageView;
public class DownloadBitmap {
static Bitmap image;
public static void downloadBitmap(final ImageView imageView,
final String url) {
new AsyncTask<Bitmap, Void, Bitmap>() {
protected void onPreExecute(){
//This could also be an internal resource instead of null
imageView.setImageBitmap(null);
}
#Override
protected Bitmap doInBackground(Bitmap... params) {
HttpRetriever retriever = new HttpRetriever();
InputStream is = retriever.retrieveStream(url);
image = BitmapFactory.decodeStream(is);
return image;
}
#Override
protected void onPostExecute(Bitmap result) {
super.onPostExecute(result);
imageView.setImageBitmap(result);
}
}.execute();
}
}
I would recommend a much more robust way of accomplishing this task. This solution will require you to use the Volley Library and the majority of this code can be found in the Google IO source. Here is a video covering the same topic if it helps.
Download Volley and add it to your build path.
We are going to uses the Google IO super class of the Volley ImageLoader.
*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.apps.iosched.util;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.TransitionDrawable;
import android.net.http.AndroidHttpClient;
import android.os.Build;
import android.os.Environment;
import android.support.v4.app.FragmentActivity;
import android.widget.ImageView;
import com.android.volley.Cache;
import com.android.volley.Network;
import com.android.volley.RequestQueue;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.BasicNetwork;
import com.android.volley.toolbox.DiskBasedCache;
import com.android.volley.toolbox.HttpClientStack;
import com.android.volley.toolbox.HurlStack;
import java.io.File;
import java.util.ArrayList;
/**
* A class that wraps up remote image loading requests using the Volley library combined with a
* memory cache. An single instance of this class should be created once when your Activity or
* Fragment is created, then use {#link #get(String, android.widget.ImageView)} or one of
* the variations to queue the image to be fetched and loaded from the network. Loading images
* in a {#link android.widget.ListView} or {#link android.widget.GridView} is also supported but
* you must store the {#link com.android.volley.Request} in your ViewHolder type class and pass it
* into loadImage to ensure the request is canceled as views are recycled.
*/
public class ImageLoader extends com.android.volley.toolbox.ImageLoader {
private static final ColorDrawable transparentDrawable = new ColorDrawable(
android.R.color.transparent);
private static final int HALF_FADE_IN_TIME = UIUtils.ANIMATION_FADE_IN_TIME / 2;
private static final String CACHE_DIR = "images";
private Resources mResources;
private ArrayList<Drawable> mPlaceHolderDrawables;
private boolean mFadeInImage = true;
private int mMaxImageHeight = 0;
private int mMaxImageWidth = 0;
/**
* Creates an ImageLoader with Bitmap memory cache. No default placeholder image will be shown
* while the image is being fetched and loaded.
*/
public ImageLoader(FragmentActivity activity) {
super(newRequestQueue(activity),
BitmapCache.getInstance(activity.getSupportFragmentManager()));
mResources = activity.getResources();
}
/**
* Creates an ImageLoader with Bitmap memory cache and a default placeholder image while the
* image is being fetched and loaded.
*/
public ImageLoader(FragmentActivity activity, int defaultPlaceHolderResId) {
super(newRequestQueue(activity),
BitmapCache.getInstance(activity.getSupportFragmentManager()));
mResources = activity.getResources();
mPlaceHolderDrawables = new ArrayList<Drawable>(1);
mPlaceHolderDrawables.add(defaultPlaceHolderResId == -1 ?
null : mResources.getDrawable(defaultPlaceHolderResId));
}
/**
* Creates an ImageLoader with Bitmap memory cache and a list of default placeholder drawables.
*/
public ImageLoader(FragmentActivity activity, ArrayList<Drawable> placeHolderDrawables) {
super(newRequestQueue(activity),
BitmapCache.getInstance(activity.getSupportFragmentManager()));
mResources = activity.getResources();
mPlaceHolderDrawables = placeHolderDrawables;
}
/**
* Starts processing requests on the {#link RequestQueue}.
*/
public void startProcessingQueue() {
getRequestQueue().start();
}
/**
* Stops processing requests on the {#link RequestQueue}.
*/
public void stopProcessingQueue() {
getRequestQueue().stop();
}
public ImageLoader setFadeInImage(boolean fadeInImage) {
mFadeInImage = fadeInImage;
return this;
}
public ImageLoader setMaxImageSize(int maxImageWidth, int maxImageHeight) {
mMaxImageWidth = maxImageWidth;
mMaxImageHeight = maxImageHeight;
return this;
}
public ImageLoader setMaxImageSize(int maxImageSize) {
return setMaxImageSize(maxImageSize, maxImageSize);
}
public ImageContainer get(String requestUrl, ImageView imageView) {
return get(requestUrl, imageView, 0);
}
public ImageContainer get(String requestUrl, ImageView imageView, int placeHolderIndex) {
return get(requestUrl, imageView, mPlaceHolderDrawables.get(placeHolderIndex),
mMaxImageWidth, mMaxImageHeight);
}
public ImageContainer get(String requestUrl, ImageView imageView, Drawable placeHolder) {
return get(requestUrl, imageView, placeHolder, mMaxImageWidth, mMaxImageHeight);
}
public ImageContainer get(String requestUrl, ImageView imageView, Drawable placeHolder,
int maxWidth, int maxHeight) {
// Find any old image load request pending on this ImageView (in case this view was
// recycled)
ImageContainer imageContainer = imageView.getTag() != null &&
imageView.getTag() instanceof ImageContainer ?
(ImageContainer) imageView.getTag() : null;
// Find image url from prior request
String recycledImageUrl = imageContainer != null ? imageContainer.getRequestUrl() : null;
// If the new requestUrl is null or the new requestUrl is different to the previous
// recycled requestUrl
if (requestUrl == null || !requestUrl.equals(recycledImageUrl)) {
if (imageContainer != null) {
// Cancel previous image request
imageContainer.cancelRequest();
imageView.setTag(null);
}
if (requestUrl != null) {
// Queue new request to fetch image
imageContainer = get(requestUrl,
getImageListener(mResources, imageView, placeHolder, mFadeInImage),
maxWidth, maxHeight);
// Store request in ImageView tag
imageView.setTag(imageContainer);
} else {
imageView.setImageDrawable(placeHolder);
imageView.setTag(null);
}
}
return imageContainer;
}
private static ImageListener getImageListener(final Resources resources,
final ImageView imageView, final Drawable placeHolder, final boolean fadeInImage) {
return new ImageListener() {
#Override
public void onResponse(ImageContainer response, boolean isImmediate) {
imageView.setTag(null);
if (response.getBitmap() != null) {
setImageBitmap(imageView, response.getBitmap(), resources,
fadeInImage && !isImmediate);
} else {
imageView.setImageDrawable(placeHolder);
}
}
#Override
public void onErrorResponse(VolleyError volleyError) {
}
};
}
private static RequestQueue newRequestQueue(Context context) {
// On HC+ use HurlStack which is based on HttpURLConnection. Otherwise fall back on
// AndroidHttpClient (based on Apache DefaultHttpClient) which should no longer be used
// on newer platform versions where HttpURLConnection is simply better.
Network network = new BasicNetwork(
UIUtils.hasHoneycomb() ?
new HurlStack() :
new HttpClientStack(AndroidHttpClient.newInstance(
NetUtils.getUserAgent(context))));
Cache cache = new DiskBasedCache(getDiskCacheDir(context, CACHE_DIR));
RequestQueue queue = new RequestQueue(cache, network);
queue.start();
return queue;
}
/**
* Sets a {#link android.graphics.Bitmap} to an {#link android.widget.ImageView} using a
* fade-in animation. If there is a {#link android.graphics.drawable.Drawable} already set on
* the ImageView then use that as the image to fade from. Otherwise fade in from a transparent
* Drawable.
*/
#TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
private static void setImageBitmap(final ImageView imageView, final Bitmap bitmap,
Resources resources, boolean fadeIn) {
// If we're fading in and on HC MR1+
if (fadeIn && UIUtils.hasHoneycombMR1()) {
// Use ViewPropertyAnimator to run a simple fade in + fade out animation to update the
// ImageView
imageView.animate()
.scaleY(0.95f)
.scaleX(0.95f)
.alpha(0f)
.setDuration(imageView.getDrawable() == null ? 0 : HALF_FADE_IN_TIME)
.setListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
imageView.setImageBitmap(bitmap);
imageView.animate()
.alpha(1f)
.scaleY(1f)
.scaleX(1f)
.setDuration(HALF_FADE_IN_TIME)
.setListener(null);
}
});
} else if (fadeIn) {
// Otherwise use a TransitionDrawable to fade in
Drawable initialDrawable;
if (imageView.getDrawable() != null) {
initialDrawable = imageView.getDrawable();
} else {
initialDrawable = transparentDrawable;
}
BitmapDrawable bitmapDrawable = new BitmapDrawable(resources, bitmap);
// Use TransitionDrawable to fade in
final TransitionDrawable td =
new TransitionDrawable(new Drawable[] {
initialDrawable,
bitmapDrawable
});
imageView.setImageDrawable(td);
td.startTransition(UIUtils.ANIMATION_FADE_IN_TIME);
} else {
// No fade in, just set bitmap directly
imageView.setImageBitmap(bitmap);
}
}
/**
* Get a usable cache directory (external if available, internal otherwise).
*
* #param context The context to use
* #param uniqueName A unique directory name to append to the cache dir
* #return The cache dir
*/
public static File getDiskCacheDir(Context context, String uniqueName) {
// Check if media is mounted or storage is built-in, if so, try and use external cache dir
// otherwise use internal cache dir
// TODO: getCacheDir() should be moved to a background thread as it attempts to create the
// directory if it does not exist (no disk access should happen on the main/UI thread).
final String cachePath =
Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||
!Environment.isExternalStorageRemovable()
? getExternalCacheDir(context).getPath()
: context.getCacheDir().getPath();
return new File(cachePath + File.separator + uniqueName);
}
/**
* Get the external app cache directory.
*
* #param context The context to use
* #return The external cache dir
*/
private static File getExternalCacheDir(Context context) {
// TODO: This needs to be moved to a background thread to ensure no disk access on the
// main/UI thread as unfortunately getExternalCacheDir() calls mkdirs() for us (even
// though the Volley library will later try and call mkdirs() as well from a background
// thread).
return context.getExternalCacheDir();
}
/**
* Interface an activity can implement to provide an ImageLoader to its children fragments.
*/
public interface ImageLoaderProvider {
public ImageLoader getImageLoaderInstance();
}
}
Now in your Activity Create the ImageLoader and pass it to you adapter when you create your adapter.
#Override
public void onCreate(Bundle savedInstanceState){
...
ImageLoader mImageLoader = new ImageLoader(this, R.drawable.default_image);
MoviesAdapter mMoviesAdapter = new MoviesAdapter(this, R.layout.your_list_layout, moviesList, mImageLoader);
...
}
Adjust your Movie Adapter constructor.
package com.example.jdmb;
import java.util.ArrayList;
import android.app.Activity;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
public class MoviesAdapter extends ArrayAdapter<Movies> {
private ArrayList<Movies> movieData;
private Activity context;
private ImageLoader mImageLoader;
public MoviesAdapter(Context context, int resource,
ArrayList<Movies> objects, ImageLoader loader) {
super(context, resource, objects);
this.context = (Activity) context;
this.movieData = objects;
this.mImageLoader = loader;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
ViewHolder holder;
if (view == null) {
LayoutInflater vi = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = vi.inflate(R.layout.list_row, parent, false);
holder = new ViewHolder();
holder.id = (TextView) view.findViewById(R.id.movieId);
holder.title = (TextView) view.findViewById(R.id.movieTitle);
holder.vote_avg = (TextView) view.findViewById(R.id.movieAvg);
holder.backdrop_path = (ImageView) view
.findViewById(R.id.movieBackdrop);
holder.release_date = (TextView) view
.findViewById(R.id.movieRelease);
holder.original_title = (TextView) view
.findViewById(R.id.movieTitle2);
holder.vote_count = (TextView) view.findViewById(R.id.movieCount);
holder.adult = (TextView) view.findViewById(R.id.movieAdult);
holder.poster = (ImageView) view.findViewById(R.id.moviePoster);
holder.popularity = (TextView) view
.findViewById(R.id.moviePopularity);
view.setTag(holder);
} else {
holder = (ViewHolder) view.getTag();
}
Movies movie = movieData.get(position);
if (movie != null) {
// check to see if each individual textview is null.
// if not, assign some text!
holder.id.setText("id: " + movie.getId());
holder.title.setText("title: " + movie.getTitle());
holder.vote_avg.setText("vote_avg: " + movie.getVote_average());
mImageLoader.get(movie.getBackdrop_path(), holder.backdrop_path);
holder.release_date.setText("release_date: "
+ movie.getRelease_date());
holder.original_title.setText("original_title: "
+ movie.getOriginal_title());
holder.vote_count.setText("vote_count: " + movie.getVote_count());
holder.adult.setText("adult: " + movie.isAdult());
mImageLoader.get(movie.getPoster_path(), holder.poster);
holder.popularity.setText("popularity: " + movie.getPopularity());
}
return view;
}
private static class ViewHolder {
TextView id;
TextView title;
TextView vote_avg;
ImageView backdrop_path;
TextView release_date;
TextView original_title;
TextView vote_count;
TextView adult;
ImageView poster;
TextView popularity;
}
}
I may have missed something or made typing mistakes as I did not directly test this but I am using a similar implementation in one of my own apps.
Related
I want to create a dynamic ImageView which downloads image from online server and loads in app. I am using code from this tutorial
The problem is that code is working on static ImageView I created in layout file but not when I use it for dynamic ImageView. It loads loader image but does not replace with original image from URL after downloading to phone memory. It generates below error -
D/skia: --- decoder->decode returned false
Here is my ImageAdapter.java code -
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.ImageView;
import com.pixelandpublish.shreeshasirestaurant.R;
import com.pixelandpublish.shreeshasirestaurant.activity.ProductsActivity;
import com.pixelandpublish.shreeshasirestaurant.model.Categories;
import java.util.List;
public class ImageAdapter extends BaseAdapter {
private Context mContext;
private DatabaseHandler db;
// Constructor
public ImageAdapter(Context c) {
mContext = c;
}
public int getCount() {
db = new DatabaseHandler(mContext);
List<Categories> categories = db.getAllCategories();
return categories.size();
}
public Object getItem(int position) {
return null;
}
public long getItemId(int position) {
return 0;
}
// create a new ImageView for each item referenced by the Adapter
public View getView(final int position, View convertView, ViewGroup parent) {
ImageView imageView;
if (convertView == null) {
imageView = new ImageView(mContext);
imageView.setLayoutParams(new GridView.LayoutParams(340, 340));
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setPadding(8, 8, 8, 8);
imageView.setId(position);
}
else
{
imageView = (ImageView) convertView;
}
//imageView.setImageResource(mThumbIds[position]);
db = new DatabaseHandler(mContext);
List<Categories> categories = db.getAllCategories();
final String[] log = new String[categories.size()];
final Integer[] id = new Integer[categories.size()];
int i = 0;
for (Categories cn : categories) {
log[i] = cn.getName();
id[i] = cn.getID();
i++;
}
// Loader image - will be shown before loading image
int loader = R.drawable.ic;
// Imageview to show
//ImageView image = (ImageView) findViewById(R.id.image);
// Image url
String image_url = "http://api.androidhive.info/images/sample.jpg";
// ImageLoader class instance
ImageLoader imgLoader = new ImageLoader(mContext);
// whenever you want to load an image from url
// call DisplayImage function
// url - image url to load
// loader - loader image, will be displayed before getting image
// image - ImageView
imgLoader.DisplayImage(image_url, loader, imageView);
//imageView.setImageResource(mThumbIds[position]);
imageView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Intent i = new Intent(mContext, ProductsActivity.class);
//Create the bundle
Bundle bundle = new Bundle();
//Add your data to bundle
bundle.putString("categoryId", String.valueOf(id[position]));
//Add the bundle to the intent
i.putExtras(bundle);
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.getApplicationContext().startActivity(i);
}
});
return imageView;
}
}
Thanx in advance. :)
you can simply use picasso library for downloading images from internet...
http://code.tutsplus.com/tutorials/android-sdk-working-with-picasso--cms-22149
You can use the Univeral Image Loader for it...Refer this link
Universal Image Loader
ImageLoader imageLoader = ImageLoader.getInstance();
ImageLoader.getInstance().init(ImageLoaderConfiguration.createDefault(activity));
DisplayImageOptions options = new DisplayImageOptions.Builder()
.showImageOnLoading(R.drawable.YOUR_DRWABLE)
.showImageForEmptyUri(R.drawable.YOUR_DRWABLE)
.showImageOnFail(R.drawable.YOUR_DRWABLE)
.cacheInMemory(true)
.cacheOnDisk(true)
.considerExifParams(true)
.bitmapConfig(Bitmap.Config.RGB_565)
.build();
imageLoader.DisplayImage("YOUR URL", R.drawable.YOUR_DRWABLE, IMAGEVIEW_HERE);
I am developing an android application, where I suppose to show all the gallery images in a fragment. I have got the fragment working with all gallery images in it as thumbnails. Then I wanted to show all images in Mosaic Format but could not find anything regarding that.
So I decided to use AndroidStaggeredGrid library to show the images in ETsy style.
I have successfully imported the library, I have made some changes to code according to AndroidStaggeredGrid's github page. but the images are still appearing in the same format as normal gallery and columns.
Can someone please help me with it. below is the code.
Adaptor- This is Adaptor for my imageview.
Here i have added background color just to test if its working fine. but its not.
package com.ultimate.camera.adapters;
import android.app.Activity;
import android.content.Context;
import android.util.Log;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.ImageView;
import com.etsy.android.grid.util.DynamicHeightImageView;
import com.ultimate.camera.R;
import com.ultimate.camera.adapters.items.PhotoItem;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class PhotoAdapter extends ArrayAdapter<PhotoItem>{
private static final String TAG = "PhotoAdaptor";
private Context context;
private int resourceId;
private final Random mRandom;
private final ArrayList<Integer> mBackgroundColors;
private static final SparseArray<Double> sPositionHeightRatios = new SparseArray<Double>();
public PhotoAdapter(Context context, int resourceId,
List<PhotoItem> items, boolean useList) {
super(context, resourceId, items);
mRandom = new Random();
this.context = context;
this.resourceId = resourceId;
mBackgroundColors = new ArrayList<Integer>();
mBackgroundColors.add(R.color.orange);
mBackgroundColors.add(R.color.green);
mBackgroundColors.add(R.color.blue);
mBackgroundColors.add(R.color.yellow);
mBackgroundColors.add(R.color.grey);
}
private class ViewHolder {
DynamicHeightImageView photoImageView;
}
/**
* Populate the view holder with data.
* #param position
* #param convertView
* #param parent
* #return
*/
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
PhotoItem photoItem = getItem(position);
View viewToUse = null;
LayoutInflater mInflater = (LayoutInflater) context
.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
if (convertView == null) {
holder = new ViewHolder();
viewToUse = mInflater.inflate(resourceId, null);
holder.photoImageView = (DynamicHeightImageView) viewToUse.findViewById(R.id.imageView);
viewToUse.setTag(holder);
} else {
viewToUse = convertView;
holder = (ViewHolder) viewToUse.getTag();
}
double positionHeight = getPositionRatio(position);
int backgroundIndex = position >= mBackgroundColors.size() ?
position % mBackgroundColors.size() : position;
Log.d(TAG, "getView position:" + position + " h:" + positionHeight);
// Set the thumbnail
holder.photoImageView.setImageURI(photoItem.getThumbnailUri());
holder.photoImageView.setHeightRatio(positionHeight);
viewToUse.setBackgroundResource(mBackgroundColors.get(backgroundIndex));
return viewToUse;
}
private double getPositionRatio(final int position) {
double ratio = sPositionHeightRatios.get(position, 0.0);
if (ratio == 0) {
ratio = getRandomHeightRatio();
sPositionHeightRatios.append(position, ratio);
Log.d(TAG, "getPositionRatio:" + position + " ratio:" + ratio);
}
return ratio;
}
private double getRandomHeightRatio() {
return (mRandom.nextDouble() / 2.0) + 1.0; // height will be 1.0 - 1.5 the width
}
}
PhotoItem java This interface is to get the photoItem for adaptor
package com.ultimate.camera.adapters.items;
import android.net.Uri;
public class PhotoItem {
private Uri thumbnailUri;
private Uri fullImageUri;
public PhotoItem(Uri thumbnailUri,Uri fullImageUri) {
this.thumbnailUri = thumbnailUri;
this.fullImageUri = fullImageUri;
}
/**
* Getters and setters
*/
public Uri getThumbnailUri() {
return thumbnailUri;
}
public void setThumbnailUri(Uri thumbnailUri) {
this.thumbnailUri = thumbnailUri;
}
public Uri getFullImageUri() {
return fullImageUri;
}
public void setFullImageUri(Uri fullImageUri) {
this.fullImageUri = fullImageUri;
}
}
PhotoGalleryFragment XML Layot for Phot Gallery
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<!--<FrameLayout-->
<!--android:layout_width="fill_parent"-->
<!--android:layout_height="fill_parent">-->
<!--<GridView-->
<!--style="#style/GridView.PhotoGallery"-->
<!--android:id="#android:id/list"-->
<!--android:numColumns="3" />-->
<com.etsy.android.grid.StaggeredGridView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#android:id/list"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
app:item_margin="8dp"
app:column_count="2"
></com.etsy.android.grid.StaggeredGridView>
<TextView
android:id="#+id/empty"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center"
android:visibility="invisible"/>
<!--</FrameLayout>-->
</LinearLayout>
Photo Item XML
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<FrameLayout
android:layout_width="#dimen/photo_width"
android:layout_height="#dimen/photo_height">
<com.etsy.android.grid.util.DynamicHeightImageView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="#+id/imageView"
android:adjustViewBounds="false"
android:gravity="center"
android:scaleType="centerCrop"/>
</FrameLayout>
</RelativeLayout>
PhotoGalleryFragment.Java
package com.ultimate.camera.fragments;
import android.app.Activity;
import android.app.LoaderManager;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Loader;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.ListAdapter;
import android.widget.TextView;
import com.etsy.android.grid.StaggeredGridView;
import com.ultimate.camera.R;
import com.ultimate.camera.activities.MainActivity;
import com.ultimate.camera.adapters.PhotoAdapter;
import com.ultimate.camera.adapters.items.PhotoItem;
import com.ultimate.camera.utilities.PhotoGalleryAsyncLoader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class SimplePhotoGalleryListFragment extends BaseFragment implements AbsListView.OnItemClickListener,
LoaderManager.LoaderCallbacks<List<PhotoItem>> {
protected OnFragmentInteractionListener mListener;
// protected AbsListView mListView;
protected PhotoAdapter mAdapter;
protected ArrayList<PhotoItem> mPhotoListItem;
protected TextView mEmptyTextView;
protected ProgressDialog mLoadingProgressDialog;
protected StaggeredGridView mListView;
/**
* Required empty constructor
*/
public SimplePhotoGalleryListFragment() {
super();
}
/**
* Static factory method
* #param sectionNumber
* #return
*/
public static SimplePhotoGalleryListFragment newInstance(int sectionNumber) {
SimplePhotoGalleryListFragment fragment = new SimplePhotoGalleryListFragment();
Bundle args = new Bundle();
args.putInt(ARG_SECTION_NUMBER, sectionNumber);
fragment.setArguments(args);
return fragment;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Create an empty loader and pre-initialize the photo list items as an empty list.
Context context = getActivity().getBaseContext();
// Set up empty mAdapter
mPhotoListItem = new ArrayList<PhotoItem>() ;
mAdapter = new PhotoAdapter(context,
R.layout.photo_item,
mPhotoListItem, false);
// Prepare the loader. Either re-connect with an existing one,
// or start a new one.
getLoaderManager().initLoader(0, null, this);
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container,
Bundle savedInstanceState) {
View view = null;
view = inflater.inflate(R.layout.fragment_photo_gallery, container, false);
// Set the mAdapter
mListView = (StaggeredGridView) view.findViewById(android.R.id.list);
((AdapterView<ListAdapter>) mListView).setAdapter(mAdapter);
mEmptyTextView = (TextView)view.findViewById(R.id.empty);
// Show the empty text / message.
resolveEmptyText();
// Set OnItemClickListener so we can be notified on item clicks
mListView.setOnItemClickListener(this);
return view;
}
protected void resolveEmptyText(){
if(mAdapter.isEmpty()){
mEmptyTextView.setVisibility(View.VISIBLE);
setEmptyText();
} else {
mEmptyTextView.setVisibility(View.INVISIBLE);
}
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mListener = (OnFragmentInteractionListener) activity;
// Show a progress dialog.
mLoadingProgressDialog = new ProgressDialog(getActivity());
mLoadingProgressDialog.setMessage("Loading Photos...");
mLoadingProgressDialog.setCancelable(true);
mLoadingProgressDialog.show();
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnFragmentInteractionListener");
}
}
#Override
public void onDetach() {
super.onDetach();
mListener = null;
cancelProgressDialog();
}
#Override
public void onPause(){
super.onPause();
cancelProgressDialog();
}
#Override
public void onStop(){
super.onStop();
cancelProgressDialog();
}
/**
* This is only triggered when the user selects a single photo.
* #param parent
* #param view
* #param position
* #param id
*/
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (null != mListener) {
// Tell the share builder to add the photo to the share operation.
PhotoItem photoListItem = (PhotoItem)this.mAdapter.getItem(position);
String imagePath = photoListItem.getThumbnailUri().getPath();
mListener.onFragmentInteraction(MainActivity.SELECT_PHOTO_ACTION);
resetFragmentState();
}
}
/**
* Used when hitting the back button to reset the mFragment UI state
*/
protected void resetFragmentState(){
// Clear view state
getActivity().invalidateOptionsMenu();
((BaseAdapter) mListView.getAdapter()).notifyDataSetChanged();
}
public void setEmptyText() {
mEmptyTextView.setText("No Photos!");
}
/**
* Loader Handlers for loading the photos in the background.
*/
#Override
public Loader<List<PhotoItem>> onCreateLoader(int id, Bundle args) {
// This is called when a new Loader needs to be created. This
// sample only has one Loader with no arguments, so it is simple.
return new PhotoGalleryAsyncLoader(getActivity());
}
#Override
public void onLoadFinished(Loader<List<PhotoItem>> loader, List<PhotoItem> data) {
// Set the new data in the mAdapter.
mPhotoListItem.clear();
for(int i = 0; i < data.size();i++){
PhotoItem item = data.get(i);
mPhotoListItem.add(item);
}
mAdapter.notifyDataSetChanged();
resolveEmptyText();
cancelProgressDialog();
}
#Override
public void onLoaderReset(Loader<List<PhotoItem>> loader) {
// Clear the data in the mAdapter.
mPhotoListItem.clear();
mAdapter.notifyDataSetChanged();
resolveEmptyText();
cancelProgressDialog();
}
/**
* Save cancel for the progress loader
*/
private void cancelProgressDialog(){
if(mLoadingProgressDialog != null){
if(mLoadingProgressDialog.isShowing()){
mLoadingProgressDialog.cancel();
}
}
}
}
My Output
You can see in my output images are of the same size thumbnails, I dont have text though. but i expect all images to be of different size thumbnails.
I would really appriciate if someone help me with this.
Also if anyone know how to create a Mosaic Formation of the images I have given the mosaic formate image as well
After scratching my head again for few more time, Finally I have figured out the answer.
The problem was in PhotoItem Layout file (PhotoItem.XML) where i have defined layout for each photoItem thats gonna show up in fragment.
There was a FrameLayout as a parent of DynamicImageView section, Which had its own width and height due to which, its not allowing to set a dynamic hight holder.photoImageView.setHeightRation(positionHeight) to a PhotoItem.
So, the only change was PhotoItem.XML, I have to keep rest of the code as it is.
So I had to remove the FrameLayout section and keep only DynamicImageView as a PhotoItem Layout.
PhotoItem.XML
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<com.etsy.android.grid.util.DynamicHeightImageView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="#+id/imageView"
android:adjustViewBounds="false"
android:gravity="center"
android:scaleType="centerCrop"/>
</RelativeLayout>
Then It worked fine and done for now, I have attached the screenshot below :)
I would still be looking for Mosaic View Solution though
Thanks
You got all images of the same size because of holder.photoImageView.setHeightRatio(positionHeight); you sat heightRatio for your images so remove this line and all its effect.. try to remove holder.photoImageView.setHeightRatio(positionHeight);
The images loaded by this custom adapter placed at wrong positions i.e correct movie banner is not placed at correct list view item. and keeps on changing for a while.
here is my custom adapter with ASYNCTASK which is loading images from URL
import java.net.URL;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.AsyncTask;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import androlizer.yify.torrent.R;
import androlizer.yify.torrents.models.UpcomingMovieListModel;
public class UpcomingMoviesCustomAdapter extends ArrayAdapter<UpcomingMovieListModel> {
Context context;
public UpcomingMoviesCustomAdapter(
Context context, int resource, List<UpcomingMovieListModel> objects) {
super(context, resource, objects);
this.context = context;
}
static class ViewHolder
{
TextView movieTitle_textView;
TextView uploader_textView;
TextView date_textView;
ImageView movie_icon_imageView;
ImageView imdb_url_imageView;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
final ViewHolder holder;
// getting data
final UpcomingMovieListModel movie = getItem(position);
if (convertView == null)
{
convertView = View.inflate(context, R.layout.movie_upcoming_row, null);
holder = new ViewHolder();
holder.movieTitle_textView = (TextView) convertView.findViewById(R.id.movie_upcoming_movie_title);
holder.uploader_textView = (TextView) convertView.findViewById(R.id.movie_upcoming_uploader);
holder.date_textView = (TextView) convertView.findViewById(R.id.movie_upcoming_date);
holder.imdb_url_imageView = (ImageView)convertView.findViewById(R.id.movie_upcoming_imageView_imdblink);
holder.movie_icon_imageView = (ImageView)convertView.findViewById(R.id.movie_upcoming_movie_image_view);
convertView.setTag(holder);
}
else
{
holder = (ViewHolder)convertView.getTag();
}
if (movie != null)
{
holder.movieTitle_textView.setText(movie.getM_title());
holder.uploader_textView.setText(movie.getUploader());
SimpleDateFormat origFormat= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//Store it as a date object
Date date = null;
try {
date = origFormat.parse(movie.getDate_added());
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//Output it as a string that uses the new format
SimpleDateFormat newFormat= new SimpleDateFormat("MMMMMMMMM dd, yyyy 'at' hh:mm a");
String desiredDateFormat = newFormat.format(date);
holder.date_textView.setText(desiredDateFormat);
holder.imdb_url_imageView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(movie.getImdb_url())));
}
});
}
new ImageLoader().execute(convertView.g, movie.getM_cover());
return convertView;
}
public class ImageLoader extends AsyncTask<Object, String, Bitmap> {
private View view;
private Bitmap bitmap = null;
#Override
protected Bitmap doInBackground(Object... parameters) {
// Get the passed arguments here
view = (View) parameters[0];
String uri = (String)parameters[1];
// Create bitmap from passed in Uri here
// ...
try {
URL req = new URL(uri);
bitmap = BitmapFactory.decodeStream(req.openConnection()
.getInputStream());
} catch (Exception e) {
// TODO: handle exception
}
return bitmap;
}
#Override
protected void onPostExecute(Bitmap bitmap) {
if (bitmap != null && view != null) {
ImageView splash = (ImageView) view.findViewById(R.id.movie_upcoming_movie_image_view);
splash.setImageBitmap(bitmap);
}
}
}
}
I think the problem is that you don't stop a previous ImageLoader when a list item is reused: when a list item is reused another ImageLoader is attached to it but without removing the previous that was attached to the same list item instance.
Because of this can happen that the first ImageLoader could finish its job after the last one that sets the wrong image. Also you need to cache the downloaded images otherwise already downloaded images will downloaded again.
The right thing to do(TM) should be to set the image bitmap in the holder instance related to the list item and instead to stop the loader, make it acts on the holder.
What is happening here is that you are loading your images and setting them to the ImageView for your current list item, but not storing that Bitmap anywhere else. ListItems in Android are reused so that when you scroll, the device doesn't have to store many ListItems in memory. As a result, the OS will set new images to your list items as you scroll, which won't be the ones that you want.
The solution is to store your Bitmaps in the ArrayList that backs your ListView. In this case, it is List<UpcomingMovieListModel> objects that holds your data for the list view. There are several changes that need to be made here, so I'm not going to provide the full code, but I will describe the process.
Extend your UpcomingMovieListModel object to have a field called movieIcon.
Pass the current UpcomingMovieListModel object to your AsyncTask and have the AsyncTask set movieIcon to the downloaded image.
In your getView method, set the ImageView to the value of movie.movieIcon if it is not null.
In onPostExecute for your AsyncTask call this.notifyDataSetChanged() after you set movieIcon to the newly downloaded image. This tells the ListView that you have changed the underlying data, and it should refresh to show the icon for the current movie in each item on the screen.
Do not call setImageBitmap directly in onPostExecute. This will be taken care of in getView when you notify that the dataset has changed.
If you change these things, you should see all the right images, because you'll be storing them correctly for the underlying data.
Although there are many tutorials out there, I've found it really difficult to implement an AsyncTask to load images from URI's (obtained from a content provider) into a custom adapter.
I get the basic gist, which is to have a class containing an AsyncTask, do the bitmap creation in the 'doInBackground', and then set the ImageView in the onPostExecute.
The problem for me, being new to android & programming, is that I don't know how to pass in my Uri's to the AsyncTask for each item, how to create the bitmap, and how to return it to the adapter.
I've only gotten this far with the actual AsyncTask class (ImageLoader):
package another.music.player;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.widget.ImageView;
public class ImageLoader extends AsyncTask<String, String, Bitmap> {
private ImageView imageView;
private Bitmap bitmap = null;
#Override
protected Bitmap doInBackground(String... uri) {
// Create bitmap from passed in Uri here
return bitmap;
}
#Override
protected void onPostExecute(Bitmap bitmap) {
if (bitmap != null && imageView != null) {
imageView.setImageBitmap(bitmap);
}
}
}
And my custom adapter looks like this:
package another.music.player;
import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.provider.BaseColumns;
import android.provider.MediaStore.Audio.AlbumColumns;
import android.provider.MediaStore.Audio.AudioColumns;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import android.widget.ImageView;
import android.widget.TextView;
class AlbumAdapter extends CursorAdapter {
public AlbumAdapter(Context context, Cursor c, int flags) {
super(context, c, flags);
}
private static Uri currentSongUri;
#Override
public void bindView(View view, Context context, Cursor cursor) {
ImageView albumArt = (ImageView) view.getTag(R.id.albumArt);
TextView text1 = (TextView) view.getTag(R.id.artistTitle);
TextView text2 = (TextView) view.getTag(R.id.albumTitle);
TextView text3 = (TextView) view.getTag(R.id.totalSongs);
albumArt.setImageBitmap(null);
text1.setText(cursor.getString(cursor
.getColumnIndex(AudioColumns.ARTIST)));
text2.setText(cursor.getString(cursor
.getColumnIndex(AudioColumns.ALBUM)));
text3.setText(cursor.getString(cursor
.getColumnIndex(AlbumColumns.NUMBER_OF_SONGS)));
String currentAlbumId = cursor.getString(cursor
.getColumnIndex(BaseColumns._ID));
Integer currentAlbumIdLong = Integer.parseInt(currentAlbumId);
Uri artworkUri = Uri.parse("content://media/external/audio/albumart");
currentSongUri = ContentUris.withAppendedId(artworkUri,
currentAlbumIdLong);
//Run ImageLoader AsyncTask here, and somehow retrieve the ImageView & set it.
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
LayoutInflater inflater = LayoutInflater.from(context);
View view = inflater.inflate(R.layout.albumitem, null);
view.setTag(R.id.albumArt, view.findViewById(R.id.albumArt));
view.setTag(R.id.artistTitle, view.findViewById(R.id.artistTitle));
view.setTag(R.id.albumTitle, view.findViewById(R.id.albumTitle));
view.setTag(R.id.totalSongs, view.findViewById(R.id.totalSongs));
return view;
}
}
I would be very grateful if somebody could show me how to proceed with this.
Thanks.
You need to pass your AsyncTask the view so that it can update it when it completes:
//Run ImageLoader AsyncTask here, and let it set the ImageView when it is done.
new ImageLoader().execute(view, uri);
And modify AsyncTask so that it can handle mixed parameter types:
public class ImageLoader extends AsyncTask<Object, String, Bitmap> {
private View view;
private Bitmap bitmap = null;
#Override
protected Bitmap doInBackground(Object... parameters) {
// Get the passed arguments here
view = (View) parameters[0];
String uri = (String)parameters[1];
// Create bitmap from passed in Uri here
// ...
return bitmap;
}
#Override
protected void onPostExecute(Bitmap bitmap) {
if (bitmap != null && view != null) {
ImageView albumArt = (ImageView) view.getTag(R.id.albumArt);
albumArt.setImageBitmap(bitmap);
}
}
}
I haven't tested this code but it should give you an idea.
Why are you doing setImage in AsyncTask? You can do it in a thread. I don't think in this condition AsyncTask would be good. Better you do it in different thread.
I have a ListView that has one image and two lines of texts for each element (organized by a RelativeLayout). It works ok, but it's too slow and I know where the problem comes from!
This is the getView() method for the custom adapter that I'm using:
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = mLayoutInflater.inflate(R.layout.list_view_item, parent, false);
mViewHolder = new ViewHolder();
mViewHolder.cover = (ImageView) convertView.findViewById(R.id.app_icon);
mViewHolder.title = (TextView) convertView.findViewById(R.id.selection);
mViewHolder.description = (TextView) convertView.findViewById(R.id.app_short_description);
convertView.setTag(mViewHolder);
} else {
mViewHolder = (ViewHolder) convertView.getTag();
}
// Here is the origin of the issue !
final Feed currentFeed = getItem(position);
mViewHolder.title.setText(currentFeed.getTitle());
mViewHolder.description.setText(currentFeed.getDescription());
try {
if(currentFeed.getThumbnailUrl() != null) {
downloadThumbnail(mViewHolder.cover, currentFeed.getThumbnailUrl());
}
} catch(Exception e) {
e.printStackTrace();
}
return convertView;
}
private static class ViewHolder {
TextView title;
TextView description;
ImageView cover;
}
So I have done some manual benchmarking and it appears that allocating an instance of Feed is the source of this slowness:
final Feed currentFeed = getItem(position);
I know this because I have written another version of this to compare the two:
// Here is the origin of the issue !
//final Feed currentFeed = getItem(position);
mViewHolder.title.setText("Title");
mViewHolder.description.setText("Description");
try {
if(currentFeed.getThumbnailUrl() != null) {
downloadThumbnail(mViewHolder.cover, "some url");
}
} catch(Exception e) {
e.printStackTrace();
}
This one was way smoother (even with the downloadThumbnail() method working).
I also precise that there are only 15 items on my ListView.
I know that allocating objects is very expensive because of garbage collection but I can't any other way to do it!
Any idea?
Thanks!
EDIT
Don't mind too much about the downloadThumbnail() method, it already does some caching. And actually even without any picture, it's still slow.
When user scrolls the list, getView gets called on the adapter. Make sure that you dont do same things repeatedly, for example generating thumbnail. If number of items is limited (for example video content), then you can create all views and keep it ready for get view. Otherwise you may have to implement cacheing.
Below code shows an adapter and listView implementation, where in all listviews are created and stored in memory. Since this is meant for video browsing, memory does not pose any issue. (limited number of content, max 100)
Video List Adapter
import java.util.ArrayList;
import java.util.Formatter;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.provider.MediaStore;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.LinearLayout.LayoutParams;
public class VideoListAdapter extends BaseAdapter {
private Context mContext = null;
private HashMap<String, VideoListItem> mHashedItems = new HashMap<String, VideoListItem>();
private static final String TAG = "VideoListAdapter";
public static final int VIDEO_CONTENT_ID = 0;
public static final int VIDEO_CONTENT_TITLE = 1;
public static final int VIDEO_CONTENT_DURATION = 2;
public static final int VIDEO_CONTENT_RESOLUTION = 3;
public static final int VIDEO_CONTENT_MIME = 4;
private Cursor mCursorForVideoList = null;
private ContentResolver mContentResolver = null;
private int mListCount = 0;
VideoListAdapter(Context context, ContentResolver cr) {
mContext = context;
mContentResolver = cr;
Log.i(TAG, "In the Constructor");
mCursorForVideoList =
mContentResolver.query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
new String[] { MediaStore.MediaColumns._ID,
MediaStore.MediaColumns.TITLE,
MediaStore.Video.VideoColumns.DURATION,
MediaStore.Video.VideoColumns.RESOLUTION
},
null,
null,
null);
mListCount = mCursorForVideoList.getCount();
}
#Override
public int getCount() {
return mListCount;
}
#Override
public Object getItem(int arg0) {
return getVideoListItem(arg0);
}
#Override
public long getItemId(int position) {
//Log.i(TAG, "position : " + position);
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
//Log.i(TAG, "GetView :: Position : " + position);
return getVideoListItem(position);
}
private VideoListItem getVideoListItem(int position)
{
//Log.i(TAG, "getVideoListItem :: Position : " + position);
String key = Integer.toString(position);
VideoListItem item = mHashedItems.get(key);
if(item == null)
{
//Log.i(TAG, "New getVideoListItem :: Position : " + position);
mCursorForVideoList.moveToPosition(position);
mHashedItems.put(key, new VideoListItem(mContext, mContentResolver, mCursorForVideoList));
}
return mHashedItems.get(key);
}
};
Video List View
import java.util.Formatter;
import java.util.Locale;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Typeface;
import android.provider.MediaStore;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TableLayout;
import android.widget.TextView;
import android.widget.LinearLayout.LayoutParams;
class VideoListItem extends LinearLayout
{
private static final String TAG = "VideoListAdapter";
private ImageView mThumbnail = null;
private TextView mDuration = null;
private TextView mTitle = null;
private TextView mResolution = null;
private LayoutInflater mLayoutFactory = null;
private long mContentId = 0;
public VideoListItem(Context context, ContentResolver cr, Cursor cursor) {
super(context);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
params.setMargins(10, 10, 10, 10);
mLayoutFactory = LayoutInflater.from(context);
View thisView = mLayoutFactory.inflate(R.layout.videolistitem, null);
addView(thisView);
mThumbnail = (ImageView) findViewById(R.id.thumbnail);
mDuration = (TextView) findViewById(R.id.DDuration);
mTitle = (TextView) findViewById(R.id.DTitle);
mResolution = (TextView) findViewById(R.id.DResolution);
mThumbnail.setLayoutParams(new LinearLayout.LayoutParams(144, 144));
Resources r = this.getResources();
Bitmap bMap = MediaStore.Video.Thumbnails.getThumbnail(cr, cursor.getLong(VideoListAdapter.VIDEO_CONTENT_ID), MediaStore.Video.Thumbnails.MINI_KIND, null);
if(bMap != null)
{
mThumbnail.setImageBitmap(Bitmap.createScaledBitmap(bMap, 128, 128, true));
}
else
{
mThumbnail.setImageDrawable(r.getDrawable(R.drawable.error));
}
mThumbnail.setPadding(16, 16, 16, 16);
mTitle.setText(cursor.getString(VideoListAdapter.VIDEO_CONTENT_TITLE));
mTitle.setSingleLine();
mTitle.setTextColor(Color.GREEN);
mResolution.setText(cursor.getString(VideoListAdapter.VIDEO_CONTENT_RESOLUTION));
mResolution.setSingleLine();
mResolution.setTextColor(Color.RED);
mDuration.setText(stringForTime(cursor.getInt(VideoListAdapter.VIDEO_CONTENT_DURATION)));
mDuration.setSingleLine();
mDuration.setTextColor(Color.CYAN);
mContentId = cursor.getLong(VideoListAdapter.VIDEO_CONTENT_ID);
}
public long getContentId()
{
return mContentId;
}
private StringBuilder mFormatBuilder = null;
private Formatter mFormatter = null;
private String stringForTime(int timeMs) {
int totalSeconds = timeMs / 1000;
mFormatBuilder = new StringBuilder();
mFormatter = new Formatter(mFormatBuilder, Locale.getDefault());
int seconds = totalSeconds % 60;
int minutes = (totalSeconds / 60) % 60;
int hours = totalSeconds / 3600;
mFormatBuilder.setLength(0);
if (hours > 0) {
return mFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString();
} else {
return mFormatter.format("%02d:%02d", minutes, seconds).toString();
}
}
};
Shash
Don't allocate or store the Feed object in your View holder and instead only store the position (position). When you need to reference the object then grab the reference index from the ViewHolder and act accordingly.
Edit
Of course I missed that you're using the object later on... You might also create a number of minimal, static methods for your Feed object that only return specific things, such as the title, etc. Then call these methods in your getView method to set the UI elements without full creation of the Feed itself.