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.
Related
I'm testing a simple grid adapter with a custom object, the app runs on the device without a problem but stays blank instead of inflating the activity with the specified grid items. This is my code.
Main Activity
package com.example.nobodyme.errorrepository;
import android.app.Activity;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.GridView;
import android.widget.TextView;
import android.widget.Toast;
import android.view.View;
import android.widget.AdapterView.OnItemClickListener;
import java.util.ArrayList;
public class MainActivity extends ActionBarActivity {
GridView gridView;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ArrayList<GridItems> gridItems = new ArrayList<>();
gridItems.add(new GridItems("Android", 2));
gridItems.add(new GridItems("Java", 3));
gridView = (GridView) findViewById(R.id.gridView1);
gridView.setAdapter(new GridViewAdapter(this, gridItems));
}
}
GridViewAdapter
package com.example.nobodyme.errorrepository;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import java.util.List;
public class GridViewAdapter extends ArrayAdapter<GridItems> {
private Context context;
int b;
public GridViewAdapter(Context context, List<GridItems> gridItems) {
super(context, 0, gridItems);
b=gridItems.size();
}
public View getView(int position, View convertView, ViewGroup parent) {
View gridView = convertView;
if(gridView == null)
{
gridView = LayoutInflater.from(getContext()).inflate(
R.layout.grid_view, parent, false);
}
GridItems currentgriditem = getItem(position);
TextView mMaintextView = (TextView) gridView.findViewById(R.id.grid_item_main);
mMaintextView.setText(currentgriditem.getTitle());
TextView mUnansweredtextView = (TextView) gridView.findViewById(R.id.grid_item_unanswered);
mUnansweredtextView.setText(currentgriditem.getUnansweredNo());
return gridView;
}
#Override
public int getCount() {
return b;
}
#Override
public long getItemId(int position) {
return 0;
}
}
GridItems
package com.example.nobodyme.errorrepository;
/**
* Created by nobodyme on 15/1/17.
*/
public class GridItems {
/** Grid title*/
private String mTitle;
/** NO of unanswered items in the gird **/
private Integer mUnansweredNo;
public GridItems(String Title, Integer UnansweredNo) {
mTitle = Title;
mUnansweredNo = UnansweredNo;
}
public String getTitle() {
return mTitle;
}
public Integer getUnansweredNo() {
return mUnansweredNo;
}
}
I have edited the code as per the comments and the app still crashes.
You're overriding getCount and returning zero in the adapter. Don't override this, or return the correct number of items.
Please check below code :
#Override
public int getCount() {
return gridItems.size();
}
Instead of 0 returning, You should return your list size.
You need define gridItems List on your arrayAdapter and set it on the constructor
and override getCount to return the gridItems (adapterGridItems) size
private List<GridItems> adapterGridItems;
public GridViewAdapter(Context context, List<GridItems> gridItems) {
super(context, 0, gridItems);
//set adapterGridItems
this.adapterGridItems=gridItems;
}
#Override
public int getCount() {
return adapterGridItems.size();
}
please, check
mMaintextView.setText(currentgriditem.getUnansweredNo());
you are passing a integer so mMainTextView.setText will find a resource with currentgriditem.getUnansweredNo() id, i think you are trying to set currentgriditem.getUnansweredNo() as string, so this will work
mMaintextView.setText(currentgriditem.getUnansweredNo()+"");
are you sure that is mMaintextView.setText(currentgriditem.getUnansweredNo()+"");
and not
mUnansweredtextView.setText(currentgriditem.getUnansweredNo()+"");
?
In order to tell the adapter how many GridItems to show we need to override the getCount method as shown above by #samsad.
The reason the compiler complains that it can't recognize symbol griditems is because you haven't created a field to store a reference to the GridItems in your adapter.
Try creating a field for the ArrayList of GridItems in your adapter to fix the issue.
I am trying to learn to create a meme generator app. First I had a problem with my app crashing when I clicked on the images in the grid view as the images were too big. I was advised to use image id to pass the image to the second activity. I (thought, I) changed the code accordingly. Now when clicked on the image, I can see the next activity with enter top and bottom text options but still image does not appear. I know the problem is the way I am passing image id but don't know what. I hope you can give me specific code as I am very new to programming.
I know using uri also is an option but being a beginner I am not sure how I would execute that here. If you think it is a better (in terms of speed of the app and memory usage / easier to learn and execute), you would be kind enough to help me out with the code too, would be appreciated.
Thank You in advance!
Here is my code:
Main Activity.java
package com.javatechig.gridviewexample;
import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.GridView;
import java.util.ArrayList;
public class MainActivity extends ActionBarActivity {
private GridView gridView;
private GridViewAdapter gridAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
gridView = (GridView) findViewById(R.id.gridView);
gridAdapter = new GridViewAdapter(this, R.layout.grid_item_layout, getData());
gridView.setAdapter(gridAdapter);
gridView.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
ImageItem item = (ImageItem) parent.getItemAtPosition(position);
//Create intent
Intent intent = new Intent(MainActivity.this, DetailsActivity.class);
intent.putExtra("id", item.getId());
//Start details activity
startActivity(intent);
}
});
}
/**
* Prepare some dummy data for gridview
*/
private ArrayList<ImageItem> getData() {
final ArrayList<ImageItem> imageItems = new ArrayList<>();
TypedArray imgs = getResources().obtainTypedArray(R.array.image_ids);
for (int i = 0; i < imgs.length(); i++) {
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), imgs.getResourceId(i, -1));
imageItems.add(new ImageItem(bitmap, R.array.image_ids));
}
return imageItems;
}
}
DetailsActivity.java
package com.javatechig.gridviewexample;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.widget.ImageView;
public class DetailsActivity extends ActionBarActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.details_activity);
//imageUri = Uri.parse(extras.getString("imageUri"));
Bitmap bitmap = getIntent().getParcelableExtra("id");
ImageView imageView = (ImageView) findViewById(R.id.image);
imageView.setImageBitmap(bitmap);
}
}
ImageItem.java
package com.javatechig.gridviewexample;
import android.graphics.Bitmap;
public class ImageItem {
private Bitmap image;
private int id;
public ImageItem(Bitmap image, int id) {
super();
this.image = image;
this.id = id;
}
public Bitmap getImage() {
return image;
}
public void setImage(Bitmap image) {
this.image = image;
}
public int getId(){
return id;
}
public void setId(int id){
this.id = id;
}
}
GridViewAdapter.java
package com.javatechig.gridviewexample;
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 java.util.ArrayList;
public class GridViewAdapter extends ArrayAdapter<ImageItem> {
private Context context;
private int layoutResourceId;
private ArrayList<ImageItem> data = new ArrayList<ImageItem>();
public GridViewAdapter(Context context, int layoutResourceId, ArrayList<ImageItem> data) {
super(context, layoutResourceId, data);
this.layoutResourceId = layoutResourceId;
this.context = context;
this.data = data;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
ViewHolder holder;
if (row == null) {
LayoutInflater inflater = ((Activity) context).getLayoutInflater();
row = inflater.inflate(layoutResourceId, parent, false);
holder = new ViewHolder();
holder.image = (ImageView) row.findViewById(R.id.image);
row.setTag(holder);
} else {
holder = (ViewHolder) row.getTag();
}
ImageItem item = data.get(position);
holder.image.setImageBitmap(item.getImage());
return row;
}
static class ViewHolder {
ImageView image;
}
}
There are a few issues with your code. To start, you are obtaining your images from the resources but when you create a new ImageItem and add it to the ArrayList you are not passing in the individual resource ID. Here is the relevant part of your getData() method:
TypedArray imgs = getResources().obtainTypedArray(R.array.image_ids);
for (int i = 0; i < imgs.length(); i++) {
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), imgs.getResourceId(i, -1));
//Here you are putting in the generic R.array.image_ids
//Instead you should put in the images specific id: imgs.getResourceId(i, -1));
imageItems.add(new ImageItem(bitmap, R.array.image_ids));
}
Next, you are incorrectly passing and retrieving data between Activities. Here's your relevant code:
Intent intent = new Intent(MainActivity.this, DetailsActivity.class);
intent.putExtra("id", item.getId()); //You are putting in an int extra
...
//You are attempting to retrieve a Parcelable extra even though you only
//passed in an int
Bitmap bitmap = getIntent().getParcelableExtra("id");
And finally, I want to make it clear that you are not storing these ImageItems anywhere, so, you wouldn't be able to retrieve the correct ImageItem with the ID to get the image.
So, what's the solution?
As you may know Bitmaps can be quite large and passing them between Activities isn't the best approach as there can be many issues in doing so. One way of getting around this is storing them in a File and retrieving that same File in the other Activity. Fortunately, all of the Bitmaps you need are already stored in your resources, so, you can just get them from there.
First, fix your getData() method:
private ArrayList<ImageItem> getData() {
final ArrayList<ImageItem> imageItems = new ArrayList<>();
TypedArray imgs = getResources().obtainTypedArray(R.array.image_ids);
try{
for (int i = 0; i < imgs.length(); i++) {
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), imgs.getResourceId(i, -1));
imageItems.add(new ImageItem(bitmap, imgs.getResourceId(i, -1)));
}
}finally{
imgs.recycle(); //When done recycle your TypedArray
}
return imageItems;
}
Then, your onCreate() method in DetailsActivity:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.details_activity);
Intent intent = getIntent();
int id = intent.getIntExtra("id", -1);
if(id != -1){
//We have the resource ID so we can retreive the item
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), id);
ImageView imageView = (ImageView) findViewById(R.id.image);
imageView.setImageBitmap(bitmap);
}else{
//We don't have the resource ID so show default View or perform task
}
}
item.getId() is the unique ID in memory , not the image's resource ID, you should use resource ID.
the simple way , you can set resource ID as imageItems ID in setgetData().
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.
I have a ListFragment that contains a list of items. I would like to load say 9 items at a time and when i scroll and reach the bottom of the listview i want to load another 9 items in background.
I make 2 request to my web server:
1) to get all the item id's of the items, by a searh() method
2) to get all the item details of a specific item though its id, by getId(id) method
The version i have implemented gets all the ids and then loads all the items at once in the doInBackground method of AsyncTask and it works. and it takes very long (i dont want a button because its really ugly).
I'd like to introduce this thing about the onScrollListener so that when i first open my app, in background i get all the ids, and then i get the first 9 items and show them. then when i scroll to the end i want to load the next 9 items. How do i do this?
I have read a few posts but it not clear to me, especially due to the fact that i have 2 functions that need to be run in background, 1 function needs to be run once while the other many times and i need to keep track of which id's i getting.
I would also if possible like to add the function that if i pull the ListView a little then it should update my view.
Here is my code:
import java.util.ArrayList;
import java.util.HashMap;
import android.app.ListFragment;
import android.app.ProgressDialog;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ListView;
import android.widget.Toast;
import com.prjma.lovertech.R;
import com.prjma.lovertech.adapter.ListViewAdapter;
import com.prjma.lovertech.util.MVPFunctions;
public class CompraFragment extends ListFragment {
public ListView listView;
public ListViewAdapter adapter;
/**
* Keep track of the login task to ensure we can cancel it if requested.
*/
private DownloadTask mDownloadTask = null;
public ArrayList<HashMap<String, Object>> items;
public Bitmap icon;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
//View rootView = inflater.inflate(R.layout.fragment_compra, false);
View rootView = inflater.inflate(R.layout.fragment_compra, container, false);
// now you must initialize your list view
listView = (ListView) rootView.findViewById(android.R.id.list);
mDownloadTask = new DownloadTask();
mDownloadTask.execute((Void) null);
return rootView;
}
/**
* Represents an asynchronous login/registration task used to authenticate
* the user.
*/
public class DownloadTask extends AsyncTask<Void, Void, Boolean> {
private ProgressDialog progressDialog;
#Override
protected Boolean doInBackground(Void... params) {
// TODO: attempt authentication against a network service.
//Here i get all the id's
ArrayList<Long> ids = MVPFunctions.getMioSingolo().search();
//for each id get all its details and put it in a map
items = new ArrayList<HashMap<String, Object>>();
for(int i=0; i < ids.size(); i++){
items.add(MVPFunctions.getMioSingolo().getItem(ids.get(i)));
}
return true;
}
#Override
protected void onPreExecute(){
/*
* This is executed on UI thread before doInBackground(). It is
* the perfect place to show the progress dialog.
*/
progressDialog = ProgressDialog.show(getActivity(), "", "Downloading Content...");
}
#Override
protected void onPostExecute(final Boolean success) {
mDownloadTask = null;
// dismiss the dialog after getting all products
progressDialog.dismiss();
//showProgress(false);
if (items.get(0).get("status error")!= null){
Toast.makeText(getActivity(), "status error = " + items.get(0).get("status error"), Toast.LENGTH_LONG).show();
Log.i("status error put toast", (String) items.get(0).get("status error"));
//fai qualcosa, tipo torna indietro, ecc
}
// updating UI from Background Thread
ListViewAdapter adapter = new ListViewAdapter(getActivity(),R.layout.listview_item_row, items, icon);
// updating listview
listView.setAdapter(adapter);
}
#Override
protected void onCancelled() {
mDownloadTask = null;
//showProgress(false);
}
}
}
Adapter class:
import java.util.ArrayList;
import java.util.HashMap;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import com.prjma.lovertech.R;
import com.prjma.lovertech.activity.DettagliActivity;
import com.prjma.lovertech.model.Item;
public class ListViewAdapter extends ArrayAdapter<String> {
private static LayoutInflater inflater = null;
public Context context;
public int layoutResourceId;
public ArrayList<HashMap<String, Object>> items;
public Bitmap icon;
//public ImageLoader imageLoader;
public ListViewAdapter(Context context, int listviewItemRow, ArrayList<HashMap<String, Object>> items, Bitmap icon) {
// TODO Auto-generated constructor stub
super(context, listviewItemRow);
this.items = items;
this.context = context;
this.icon = icon;
}
public int getCount() {
return items.size();
}
public Item getItem(Item position) {
return position;
}
public long getItemId(int position) {
return position;
}
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
View row = convertView;
ViewHolder viewHolder = new ViewHolder();
if (row == null) {
inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
row = inflater.inflate(R.layout.listview_item_row, null);
viewHolder.ic_thumbnail = (ImageView)row.findViewById(R.id.ic_thumbnail);
viewHolder.scadenza = (TextView)row.findViewById(R.id.tvScadenza);
viewHolder.prezzo = (TextView)row.findViewById(R.id.tvPrezzo);
viewHolder.followers = (TextView)row.findViewById(R.id.tvFollowers);
viewHolder.hProgressBar = (ProgressBar)row.findViewById(R.id.hProgressBar);
row.setTag(viewHolder);
} else {
viewHolder = (ViewHolder)row.getTag();
}
HashMap<String, Object> item = items.get(position);
viewHolder.ic_thumbnail.setImageBitmap((Bitmap) item.get("pic1m"));
viewHolder.scadenza.setText((CharSequence) item.get("scadenza"));
viewHolder.prezzo.setText((CharSequence) item.get("prezzo"));
viewHolder.followers.setText((CharSequence) item.get("followers"));
viewHolder.hProgressBar.setProgress((Integer) item.get("coefficient"));
//row.onListItemClick(new OnItemClickListener1());
row.setOnClickListener(new OnItemClickListener(position));
return row;
}
private class OnItemClickListener implements OnClickListener {
private int mPosition;
private OnItemClickListener(int position){
mPosition = position;
}
#Override
public void onClick(View arg0) {
Log.i("onListItemClickList", "Item clicked: " + mPosition);
Toast.makeText(context, "Message " + Integer.toString(mPosition), Toast.LENGTH_SHORT).show();
Intent intent = new Intent(context, DettagliActivity.class);
Bundle bundle = new Bundle();
bundle.putInt("id", mPosition);
intent.putExtras(bundle);
context.startActivity(intent);
}
}
static class ViewHolder {
public TextView prezzo;
public TextView scadenza;
public TextView followers;
public ImageView ic_thumbnail;
public ProgressBar hProgressBar;
}
}
In your adapter, check how close the user is from the bottom of the data set. When they get to the end, call a method that fetches more items from the network. I normally use a "REFRESH_THRESHOLD" integer to prefetch items before they're needed.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
Item current = getItem(position);
//Pre-fetch
if(getCount() - position <= REFRESH_THRESHOLD){
//If there are more items to fetch, and a network request isn't already underway
if(is_loading == false && has_remaining_items == true){
getItemsFromNetwork();
}
}
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.