Implement expansion files in Wallpaper Chooser - android

I've an app on Google Play that contains wallpapers (it's a wallpaper chooser) that still uses Gallery (http://developer.android.com/reference/android/widget/Gallery.html, yes, it's an old app). I'm trying to implement apk expansion files to break the 50MB limit.
Right now in-app downloader and .obb download from GP works very well. I can find it in /Android/obb. I still can't ready wallpapers from .obb file. I tried APK Expansion Zip Library but I don't know how to use it with a Gallery...
This is my wallpaper.java file:
public class wallpaper extends Activity implements AdapterView.OnItemSelectedListener,
OnClickListener {
private Gallery mGallery;
private ImageView mImageView;
private TextView mInfoView;
private boolean mIsWallpaperSet;
private Bitmap mBitmap;
private ArrayList<Integer> mThumbs;
private ArrayList<Integer> mImages;
private WallpaperLoader mLoader;
#Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
// Start wallpaper app
requestWindowFeature(Window.FEATURE_NO_TITLE);
findWallpapers();
setContentView(R.layout.wallpaper_chooser);
mGallery = (Gallery) findViewById(R.id.gallery);
mGallery.setAdapter(new ImageAdapter(this));
mGallery.setOnItemSelectedListener(this);
mGallery.setCallbackDuringFling(false);
findViewById(R.id.set).setOnClickListener(this);
mImageView = (ImageView) findViewById(R.id.wallpaper);
mInfoView = (TextView) findViewById(R.id.info);
}
private void findWallpapers() {
mThumbs = new ArrayList<Integer>(24);
mImages = new ArrayList<Integer>(24);
final Resources resources = getResources();
final String packageName = getApplication().getPackageName();
addWallpapers(resources, packageName, R.array.wallpapers);
addWallpapers(resources, packageName, R.array.extra_wallpapers);
}
private void addWallpapers(Resources resources, String packageName, int list) {
final String[] extras = resources.getStringArray(list);
for (String extra : extras) {
int res = resources.getIdentifier(extra, "drawable", packageName);
if (res != 0) {
final int thumbRes = resources.getIdentifier(extra + "_small",
"drawable", packageName);
if (thumbRes != 0) {
mThumbs.add(thumbRes);
mImages.add(res);
}
}
}
}
#Override
protected void onResume() {
super.onResume();
mIsWallpaperSet = false;
}
#Override
protected void onDestroy() {
super.onDestroy();
if (mLoader != null && mLoader.getStatus() != WallpaperLoader.Status.FINISHED) {
mLoader.cancel(true);
mLoader = null;
}
}
public void onItemSelected(AdapterView parent, View v, int position, long id) {
if (mLoader != null && mLoader.getStatus() != WallpaperLoader.Status.FINISHED) {
mLoader.cancel();
}
mLoader = (WallpaperLoader) new WallpaperLoader().execute(position);
}
/*
* When using touch if you tap an image it triggers both the onItemClick and
* the onTouchEvent causing the wallpaper to be set twice. Ensure we only
* set the wallpaper once.
*/
private void selectWallpaper(int position) {
if (mIsWallpaperSet) {
return;
}
mIsWallpaperSet = true;
try {
InputStream stream = getResources().openRawResource(mImages.get(position));
setWallpaper(stream);
setResult(RESULT_OK);
finish();
} catch (IOException e) {
Log.e("Paperless System", "Failed to set wallpaper: " + e);
}
}
public void onNothingSelected(AdapterView parent) {
}
private class ImageAdapter extends BaseAdapter {
private LayoutInflater mLayoutInflater;
ImageAdapter(wallpaper context) {
mLayoutInflater = context.getLayoutInflater();
}
public int getCount() {
return mThumbs.size();
}
public Object getItem(int position) {
return position;
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
ImageView image;
if (convertView == null) {
image = (ImageView) mLayoutInflater.inflate(R.layout.wallpaper_item, parent, false);
} else {
image = (ImageView) convertView;
}
int thumbRes = mThumbs.get(position);
image.setImageResource(thumbRes);
Drawable thumbDrawable = image.getDrawable();
if (thumbDrawable != null) {
thumbDrawable.setDither(true);
} else {
Log.e("Paperless System", String.format(
"Error decoding thumbnail resId=%d for wallpaper #%d",
thumbRes, position));
}
return image;
}
}
public void onClick(View v) {
selectWallpaper(mGallery.getSelectedItemPosition());
}
class WallpaperLoader extends AsyncTask<Integer, Void, Bitmap> {
BitmapFactory.Options mOptions;
WallpaperLoader() {
mOptions = new BitmapFactory.Options();
mOptions.inDither = false;
mOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;
}
protected Bitmap doInBackground(Integer... params) {
if (isCancelled()) return null;
try {
return BitmapFactory.decodeResource(getResources(),
mImages.get(params[0]), mOptions);
} catch (OutOfMemoryError e) {
return null;
}
}
#Override
protected void onPostExecute(Bitmap b) {
if (b == null) return;
if (!isCancelled() && !mOptions.mCancel) {
// Help the GC
if (mBitmap != null) {
mBitmap.recycle();
}
mInfoView.setText(getResources().getStringArray(R.array.info)[mGallery.getSelectedItemPosition()]);
final ImageView view = mImageView;
view.setImageBitmap(b);
mBitmap = b;
final Drawable drawable = view.getDrawable();
drawable.setFilterBitmap(true);
drawable.setDither(true);
view.postInvalidate();
mLoader = null;
} else {
b.recycle();
}
}
void cancel() {
mOptions.requestCancelDecode();
super.cancel(true);
}
}
Wallpapers are defined in res/values/wallpapers.xml:
<resources>
<string-array name="wallpapers" translatable="false">
<item>wallpaper1</item>
<item>wallpaper2</item>
<item>wallpaper3</item>
[...]
</string-array>
<string-array name="info" translatable="false">
<item>wallpaper1 description</item>
<item>wallpaper2 description</item>
<item>wallpaper3 description</item>
[...]
</string-array>
How can I change patch of my wallpapers from /res/drawable to /obb/com.example.app/main.8.com.example.app.obb ?
Thanks in advance for your help!

First read about APK expansion files here: http://developer.android.com/google/play/expansion-files.html
Then, read more about using Gallery widget here: http://developer.android.com/reference/android/widget/Gallery.html
Finally, consider using a ViewPager, referenced from here: http://developer.android.com/training/implementing-navigation/lateral.html
I am not sure if you should ask questions like that, please correct me if I am wrong..

Related

Android App - Selecting Image from Phone Gallery and Adding it to a ListView

I am using a custom adapter in order to add a selected image to the ListView. I get this error:
04-29 18:15:33.582: E/AndroidRuntime(24214): java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.ImageView.setImageBitmap(android.graphics.Bitmap)' on a null object reference
04-29 18:15:33.582: E/AndroidRuntime(24214): at app.photoexpress.ViewAlbumActivity$ImageAdapter.getView(ViewAlbumActivity.java:240)
When a photo is added from the gallery, I create a new Photo object and store the given Bitmap in there. And my getBitmap() method calls that but apparently it is null in this case. Not sure what s wrong here
Here is my Code:
public class ViewAlbumActivity extends AppCompatActivity {
ListView listview;
ImageAdapter adapter;
CharSequence[] images;
int width = 250;
boolean isSearch = false;
// ArrayList<String> listItems = new ArrayList<String>();
TextView title;
Album album;
ArrayList<Album> albumList;
String albumName = "Album";
Album currentlySelectedAlbum;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_main);
// get bundle
Bundle bundle = getIntent().getExtras();
if (bundle != null) {
albumList = (ArrayList<Album>) bundle.getSerializable("albums");
albumName = bundle.getString("albumName");
album = Album.findAlbum(albumName, getBaseContext());
Log.e(".", album.getName());
setTitle(albumName);
} else {
album = new Album("placeholder");
}
currentlySelectedAlbum = Album.findAlbum(albumName, getBaseContext());
ArrayList<Photo> photoList = currentlySelectedAlbum.getPhotos();
// Displays the icon and album name in the action bar
getSupportActionBar().setDisplayShowHomeEnabled(true);
getSupportActionBar().setLogo(R.drawable.ic_launcher);
getSupportActionBar().setDisplayUseLogoEnabled(true);
listview = (ListView) findViewById(R.id.listView1);
adapter = new ImageAdapter(this, photoList);
listview.setAdapter(adapter);
// images = readPhotoList();
// Display display = this.getWindowManager().getDefaultDisplay();
// width = display.getWidth();
// width=width/2-15;
/*listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Intent intent = new Intent(ViewAlbumActivity.this, ViewPhotoActivity.class);
// intent.putExtra(name, value)
intent.putExtra("index", position);
intent.putExtra("albumname", albumName);
intent.putExtra("album", album);
intent.putExtra("isSearch", isSearch);
startActivity(intent);
}
});*/
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
if (!isSearch) {
getMenuInflater().inflate(R.menu.viewalbum, menu);
}
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
if (!isSearch) {
int id = item.getItemId();
if (id == R.id.addPhoto) {
Intent intent = new Intent(Intent.ACTION_PICK,
android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(intent, 1);
// addPhoto();
return true;
}
}
return super.onOptionsItemSelected(item);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
Uri targetUri = data.getData();
// textTargetUri.setText(targetUri.toString());
Bitmap bitmap;
try {
bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(targetUri));
//image.setImageBitmap(bitmap);
Photo photo = new Photo(bitmap);
currentlySelectedAlbum.addPhoto(photo);
Album.updateAlbumList(currentlySelectedAlbum, getBaseContext());
adapter.notifyDataSetChanged();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
/*
* private String[] readPhotoList() { String path =
* Environment.getExternalStorageDirectory().getAbsolutePath() + "/photos/";
*
* File dir = new File(path);
*
* File[] filelist = dir.listFiles();
*
* if (filelist == null) { String[] z = new String[1]; z[0] = "no file in "
* + path; return z; }
*
* if (filelist.length == 0) { String[] s = new String[1]; s[0] =
* "no image file under " + path; return s; } ArrayList<String> result = new
* ArrayList<String>(); for (int i = 0; i < filelist.length; i++) { if
* (BitmapFactory.decodeFile(path + filelist[i].getName()) != null) {
* result.add(filelist[i].getName()); } } String[] convertList = new
* String[result.size()]; for (int i = 0; i < result.size(); i++) {
* convertList[i] = result.get(i); } return convertList;
*
* }
*/
/////////////
class ImageAdapter extends BaseAdapter {
private Context mContext;
public ArrayList<Photo> photoList2;
public ImageAdapter(Context c, ArrayList<Photo> photoList) {
mContext = c;
this.photoList2 = photoList;
}
public int getCount() {
int returnInt = 0;
try {
returnInt = currentlySelectedAlbum.getPhotos().size();
} catch (NullPointerException e) {
Log.e("NullPointerError", ".");
}
return returnInt;
}
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(int position, View convertView, ViewGroup parent) {
ImageView imageView = new ImageView(mContext);
View vi = convertView;
// String fileName="";
if (convertView == null) {
/*imageView = new ImageView(mContext);
imageView.setLayoutParams(new ListView.LayoutParams(width, width));
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setPadding(0, 0, 0, 0);*/
LayoutInflater inflator = (LayoutInflater)ViewAlbumActivity.this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
vi = inflator.inflate(R.layout.customimageview, parent, false);
} /*else {
imageView = (ImageView) convertView;
}
// if there is any photo
if (album.getPhotos().size() != 0) {
// final String fileName =
// album.getPhotos().get(position).getName();
// showImgFromSD("/photos/" + fileName, imageView);
// showImgFromSD("/photos/002.jpg",imageView);
}*/
Bitmap bitmap = photoList2.get(position).getBitmap();
ImageView thumbnail = (ImageView)vi.findViewById(R.id.imageView1);
thumbnail.setImageBitmap(bitmap);
return imageView;
}
/*
* private void showImgFromSD(String fileName, ImageView iv) { File f =
* new File(Environment.getExternalStorageDirectory(), fileName);
* FileInputStream input = null;
*
* try { input = new FileInputStream(f); } catch (FileNotFoundException
* e) { // toastMsg("File: " + fileName + " not found"); return; }
* Bitmap pic = BitmapFactory.decodeStream(input, null, null);
* iv.setImageBitmap(pic);
*
* }
*/
}
/*
* private void addPhoto() { final CharSequence[] options = images;
* AlertDialog.Builder build = new
* AlertDialog.Builder(ViewAlbumActivity.this);
* build.setTitle("../sdcard/photos/"); build.setItems(options, new
* DialogInterface.OnClickListener() { public void onClick(DialogInterface
* dialog, int item) { Album tempAlbum = findAlbum(albumName, albumList);
* Album allPhotos = findAlbum("All Photos", albumList); List<Photo> Photos
* = tempAlbum.getPhotos();
*
* for (Photo p : Photos) { //if (p.getName().compareToIgnoreCase((String)
* options[item]) == 0) { toastMsg(options[item] + " already in album");
* return; } } //Photo tempPhoto = new Photo((String) options[item], "");
* tempAlbum.addPhoto(tempPhoto); allPhotos.addPhoto(tempPhoto);
*
* // SERIALIZE HERE
*
* // listItems.add((String) options[item]); adapter.notifyDataSetChanged();
* listview.invalidateViews(); toastMsg(options[item] + " added.");
*
* } }); AlertDialog alert = build.create(); alert.show(); }
*/
// handles pop up messages
private void toastMsg(String msg) {
Context context = getApplicationContext();
CharSequence text = msg;
int duration = Toast.LENGTH_LONG;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
}
}
Album.java:
public class Album implements Serializable {
/**
*
*/
private static final long serialVersionUID = -7593044560206879666L;
String name;
static ArrayList<Photo> photos;
boolean previouslySelected = false;
public Album() {
photos = new ArrayList<Photo>();
}
public Album(String name) {
this.name = name;
photos = new ArrayList<Photo>();
}
public String toString() {
return name;
}
public String getName() {
return name;
}
public static ArrayList<Album> deleteAlbum(int position, Context context) {
ArrayList<Album> albumList = new ArrayList<Album>();
albumList = SerializableManager.readSerializable(context, "albumList.ser");
albumList.remove(albumList.get(position));
return albumList;
}
public static ArrayList<Album> addAlbum(Album a, Context context) {
ArrayList<Album> albumList = new ArrayList<Album>();
albumList = SerializableManager.readSerializable(context, "albumList.ser");
for (Album i : albumList) {
if (i.getName().equalsIgnoreCase(a.getName())) {
Log.e("Not Added", "Album not added because it already exists.");
CreateNewAlbumActivity.toastMsg("Not added. Album already exists.");
return albumList;
}
}
albumList.add(a);
return albumList;
}
public static void updateAlbumList(Album a, Context context){
ArrayList<Album> albumList = SerializableManager.readSerializable(context, "albumList.ser");
for (int i = 0; i < albumList.size(); i++){
if (albumList.get(i).getName().equals(a.getName())){
albumList.remove(i);
albumList.add(a);
}
}
SerializableManager.saveSerializable(context, albumList, "albumList.ser");
}
public static boolean doesAlbumExist(String name, Context context) {
ArrayList<Album> albumList = SerializableManager.readSerializable(context, "albumList.ser");
for (Album i : albumList) {
if (i.getName().equalsIgnoreCase(name)) {
return true;
}
}
return false;
}
public static Album findAlbum(String name, Context context) {
ArrayList<Album> albumList = SerializableManager.readSerializable(context, "albumList.ser");
for (Album i : albumList) {
if (i.getName().equalsIgnoreCase(name)) {
return i;
}
}
return null;
}
public static ArrayList<Album> renameAlbum(String oldName, String newName, Context context) {
ArrayList<Album> albumList = new ArrayList<Album>();
albumList = SerializableManager.readSerializable(context, "albumList.ser");
for (Album i : albumList) {
if (i.getName().equalsIgnoreCase(oldName)) {
Log.e(".", "match found");
if (!doesAlbumExist(newName, context)) {
i.rename(newName);
}
}
}
return albumList;
}
public void rename(String newName) {
this.name = newName;
}
public ArrayList<Photo> getPhotos() {
return photos;
}
public void addPhoto(Photo p) {
photos.add(p);
return;
}
}
Photo.java
public class Photo implements Serializable{
// The Constant storeDir.
public static final String storeDir = "data\\photo";
int id;
String Caption;
Bitmap bitmap;
List<Tag> Tags = new ArrayList <Tag>();
public Photo(Bitmap bitmap) {
id = 1 + (int)(Math.random() * 5000);
this.bitmap = bitmap;
}
public Photo(String caption, Bitmap bitmap) {
Caption = caption;
id = 1 + (int)(Math.random() * 5000);
this.bitmap = bitmap;
}
public void setCaption(String input)
{
Caption = input;
}
public boolean hasTag( String type, String value)
{
for(Tag s: Tags )
{
if(s.Type.compareToIgnoreCase(type) == 0 && s.value.compareToIgnoreCase(value) == 0 )
{
return true;
}
}
return false;
}
public boolean addTag( String type, String value)
{
//if tag already there error
if(!hasTag(type,value))
{
Tags.add(new Tag(value,type));
return true;
}
else
return false;
}
public boolean deleteTag( String type, String value)
{
int count = 0;
for(Tag s: Tags )
{
if(s.Type.compareToIgnoreCase(type) == 0 && s.value.compareToIgnoreCase(value) == 0 )
{
Tags.remove(count);
return true;
}
count++;
}
return false;
}
#Override
public String toString()
{
return "no";
}
public List<Tag> getTags()
{
return Tags;
}
public String getCaption()
{
return Caption;
}
public Bitmap getBitmap(){
return bitmap;
}
#Override
public boolean equals(Object o)
{
if (o==null)
{
return false;
}
if(!(o instanceof Photo)) {return false;}
Photo other = (Photo)o;
return (true);
//FIX THIS
}
}
customimageview.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<ImageView
android:id="#+id/imageView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
Based upon your stacktrace you start with it looks like the error happens on this line (A bit hard without knowing that this is actually line 240):
thumbnail.setImageBitmap(bitmap);
As your error is a NullPointer it means that thumbnail is null and that tells us that:
ImageView thumbnail = (ImageView)vi.findViewById(R.id.imageView1);
Fails to find an ImageView. So to get to the root we would need to see the xml of:
R.layout.customimageview
But it seems likely that there is no imageView with the ID of imageView1 in there. If there is none adding one should solve the nullpointer.
If this is not the case and there is an ImageView in there we will keep on investigating. :) (but post your xml then as well and please confirm exactly which line 240 is.)
Edit:
Also looking on your code your adapter is not fully implemented and you will probably get other errors later if this is not fixed. For example the methods getItem and getItemId.
getItem should probably look something like this:
public Object getItem(int position) {
return photolist2.get(position);
}
and getItemId:
public long getItemId(int position) {
return position;
}
Edit2:
Also, while Serializable is handy and easy it does come with some negative aspects, for example speed on Android and it could be good to look up Parceble and to help you with the boilderplate code: https://github.com/johncarl81/parceler. But that's for a later time ;)
Edit 3:
Looking more on the Adapter code it contained a few errors, this should be closer to what you want:
class ImageAdapter extends BaseAdapter {
private Context mContext;
public ArrayList<Photo> photoList2;
public ImageAdapter(Context c, ArrayList<Photo> photoList) {
mContext = c;
this.photoList2 = photoList;
}
public int getCount() {
int returnInt = 0;
try {
returnInt = currentlySelectedAlbum.getPhotos().size();
} catch (NullPointerException e) {
Log.e("NullPointerError", ".");
}
return returnInt;
}
public Object getItem(int position) {
return photoList2.get(position);
}
public long getItemId(int position) {
return position;
}
// create a new ImageView for each item referenced by the Adapter
public View getView(int position, View convertView, ViewGroup parent) {
View vi;
// String fileName="";
if (convertView == null) {
/*imageView = new ImageView(mContext);
imageView.setLayoutParams(new ListView.LayoutParams(width, width));
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setPadding(0, 0, 0, 0);*/
LayoutInflater inflator = (LayoutInflater)ViewAlbumActivity.this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
vi = inflator.inflate(R.layout.customimageview, parent, false);
} else {
vi = convertView;
}
Bitmap bitmap = photoList2.get(position).getBitmap();
ImageView thumbnail = (ImageView)vi.findViewById(R.id.imageView1);
thumbnail.setImageBitmap(bitmap);
return vi;
}
So what's the difference? In your adaptor you create and ImageView at the top and return that view at the end. So when Android calls getView again with the convertView, the convertView now holds that ImageView object and not a view of customimageview that you created. So when vi.findViewByid was called it was trying to find an ImageView inside an ImageView, and therefore failed.

ImageViews custom checkboxes repeats itself randomly

I have a gridview with an adapter, which also has a checkbox look to be shown when an imageview has been clicked. But something goes wrong. If an image is checked and you scroll to the bottom, one of the images are also checked. If you scroll up again, another image than the one you clicked to begin with, is checked (in most cases).
So here is the code for my adapter. The code in concern is the getView method:
public class ImageAdapter extends BaseAdapter {
private Context context;
private ArrayList<String> files;
private Bitmap mPlaceHolderBitmap;
private File directory;
private int thumbSize;
private GridImageListener gridImageListener;
private LayoutInflater inflater;
private View rootView;
private final int MAX_PICTURE_LIMIT = 5;
private int picturesChosenCount = 0;
public ImageAdapter(Context context, ArrayList<String> files, File directory, int thumbSize, GridImageListener gridImageListener, LayoutInflater inflater, View rootView) {
super();
this.context = context;
this.files = files;
this.directory = directory;
this.thumbSize = thumbSize;
this.gridImageListener = gridImageListener;
this.inflater = inflater;
this.rootView = rootView;
mPlaceHolderBitmap = drawableToBitmap(context.getResources().getDrawable(R.drawable.logo));
}
public Bitmap drawableToBitmap (Drawable drawable) {
if (drawable instanceof BitmapDrawable) {
return ((BitmapDrawable)drawable).getBitmap();
}
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
#Override
public int getCount() {
return files.size();
}
#Override
public Object getItem(int position) {
return files.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(final int position, View convertView, ViewGroup container) {
final ViewHolder holder;
if (convertView == null) { // if it's not recycled, initialize some attributes
holder = new ViewHolder();
convertView = inflater.inflate(R.layout.component_imageview_checkable, null);
holder.imageview = (ImageView) convertView.findViewById(R.id.checkableImageView);
holder.imageview.getLayoutParams().height = thumbSize;
holder.imageview.getLayoutParams().width = thumbSize;
holder.imageCheckBox = (RelativeLayout) convertView.findViewById(R.id.image_check_view);
loadBitmap(files.get(position), holder.imageview);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.imageCheckBox.setId(position);
holder.imageview.setId(position);
holder.imageview.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (holder.imageCheckBox.getVisibility() == View.VISIBLE) {
holder.imageCheckBox.setVisibility(View.INVISIBLE);
picturesChosenCount--;
} else {
if (picturesChosenCount < 5) {
holder.imageCheckBox.setVisibility(View.VISIBLE);
picturesChosenCount++;
} else {
Toast.makeText(context, "Max number of pictures has been reached", Toast.LENGTH_SHORT).show();
}
}
gridImageListener.onClickImage(files.get(position));
}
});
holder.id = position;
return convertView;
}
public void loadBitmap(String path, ImageView imageView) {
if (cancelPotentialWork(path, imageView)) {
final BitmapWorkerTask task = new BitmapWorkerTask(imageView, context, path, directory, thumbSize);
final AsyncDrawable asyncDrawable =
new AsyncDrawable(context.getResources(), mPlaceHolderBitmap, task);
imageView.setImageDrawable(asyncDrawable);
task.execute(path);
}
}
public static boolean cancelPotentialWork(String path, ImageView imageView) {
final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
if (bitmapWorkerTask != null) {
final String bitmapData = bitmapWorkerTask.path;
// If bitmapData is not yet set or it differs from the new data
if (bitmapData.equals("0") || !bitmapData.equals(path)) {
// Cancel previous task
bitmapWorkerTask.cancel(true);
} else {
// The same work is already in progress
return false;
}
}
// No task associated with the ImageView, or an existing task was cancelled
return true;
}
private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
if (imageView != null) {
final Drawable drawable = imageView.getDrawable();
if (drawable instanceof AsyncDrawable) {
final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
return asyncDrawable.getBitmapWorkerTask();
}
}
return null;
}
static class AsyncDrawable extends BitmapDrawable {
private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
public AsyncDrawable(Resources res, Bitmap bitmap,
BitmapWorkerTask bitmapWorkerTask) {
super(res, bitmap);
bitmapWorkerTaskReference =
new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
}
public BitmapWorkerTask getBitmapWorkerTask() {
return bitmapWorkerTaskReference.get();
}
}
}
class ViewHolder {
int id;
ImageView imageview;
RelativeLayout imageCheckBox;
}
I know it has something to do with the recycling of views in the gridview, but I don't know how to avoid/fix it. Each imageview is loaded separately in an asynctask and is reloaded when you scroll to it. If you need to take a look at it, the code is here: http://pastebin.com/2BcBW7PN
And finally, here is my XML for the item of each image:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="#+id/checkableImageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="centerCrop"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
<RelativeLayout
android:id="#+id/image_check_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="#+id/checkableImageView"
android:layout_alignTop="#+id/checkableImageView"
android:layout_alignRight="#+id/checkableImageView"
android:layout_alignBottom="#+id/checkableImageView"
android:visibility="invisible">
<com.ivankocijan.magicviews.views.MagicTextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textColor="#color/white"
android:background="#drawable/blue_border"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
<com.ivankocijan.magicviews.views.MagicTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/icon_check"
android:textColor="#color/white"
android:textSize="#dimen/txtsize_standard_smaller_icon"
android:padding="4dp"
app:typeFace="#string/icon_font"
android:background="#color/standard_blue"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
</RelativeLayout>
</RelativeLayout>
What might be wrong? I have read all the tutorials I could find, but none of them seemed to do any difference.
You can use : Eg: Android – Select multiple photos from Gallery
Or :
Entity -CustomGallery.class
public class CustomGallery {
public String sdcardPath;
public boolean isSeleted = false;
}
Adapter -GalleryAdapterCustom.class
private Context mContext;
private LayoutInflater infalter;
private ArrayList<CustomGallery> data = new ArrayList<CustomGallery>();
ImageLoader imageLoader;
private boolean isActionMultiplePick;
public GalleryAdapterCustom(Context c, ImageLoader imageLoader) {
infalter = (LayoutInflater) c
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mContext = c;
this.imageLoader = imageLoader;
// clearCache();
}
#Override
public int getCount() {
return data.size();
}
#Override
public CustomGallery getItem(int position) {
return data.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
public void setMultiplePick(boolean isMultiplePick) {
this.isActionMultiplePick = isMultiplePick;
}
public void selectAll(boolean selection) {
for (int i = 0; i < data.size(); i++) {
data.get(i).isSeleted = selection;
}
notifyDataSetChanged();
}
public boolean isAllSelected() {
boolean isAllSelected = true;
for (int i = 0; i < data.size(); i++) {
if (!data.get(i).isSeleted) {
isAllSelected = false;
break;
}
}
return isAllSelected;
}
public boolean isAnySelected() {
boolean isAnySelected = false;
for (int i = 0; i < data.size(); i++) {
if (data.get(i).isSeleted) {
isAnySelected = true;
break;
}
}
return isAnySelected;
}
public ArrayList<CustomGallery> getSelected() {
ArrayList<CustomGallery> dataT = new ArrayList<CustomGallery>();
for (int i = 0; i < data.size(); i++) {
if (data.get(i).isSeleted) {
dataT.add(data.get(i));
}
}
return dataT;
}
public void addAll(ArrayList<CustomGallery> files) {
try {
this.data.clear();
this.data.addAll(files);
} catch (Exception e) {
e.printStackTrace();
}
notifyDataSetChanged();
}
public void changeSelection(View v, int position) {
if (data.get(position).isSeleted) {
data.get(position).isSeleted = false;
} else {
data.get(position).isSeleted = true;
}
ArrayList<CustomGallery> dataT = new ArrayList<CustomGallery>();
for (int i = 0; i < data.size(); i++) {
if (data.get(i).isSeleted) {
dataT.add(data.get(i));
}
}
if (dataT.size() > NewPostActivity.max_image) {
Toast.makeText(mContext, "Bạn chỉ được đăng tối đa 20 ảnh / 1 tin",
Toast.LENGTH_SHORT).show();
data.get(position).isSeleted = false;
}
((ViewHolder) v.getTag()).imgQueueMultiSelected.setSelected(data
.get(position).isSeleted);
// }
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
final ViewHolder holder;
if (convertView == null) {
convertView = infalter.inflate(R.layout.gallery_item, null);
holder = new ViewHolder();
holder.imgQueue = (ImageView) convertView
.findViewById(R.id.imgQueue);
holder.imgQueueMultiSelected = (ImageView) convertView
.findViewById(R.id.imgQueueMultiSelected);
if (isActionMultiplePick) {
holder.imgQueueMultiSelected.setVisibility(View.VISIBLE);
} else {
holder.imgQueueMultiSelected.setVisibility(View.GONE);
}
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.imgQueue.setTag(position);
try {
imageLoader.displayImage("file://" + data.get(position).sdcardPath,
holder.imgQueue, new SimpleImageLoadingListener() {
#Override
public void onLoadingStarted(String imageUri, View view) {
holder.imgQueue
.setImageResource(R.drawable.default_icon2);
super.onLoadingStarted(imageUri, view);
}
});
if (isActionMultiplePick) {
holder.imgQueueMultiSelected
.setSelected(data.get(position).isSeleted);
}
} catch (Exception e) {
e.printStackTrace();
}
return convertView;
}
public class ViewHolder {
ImageView imgQueue;
ImageView imgQueueMultiSelected;
}
public void clearCache() {
imageLoader.clearDiscCache();
imageLoader.clearMemoryCache();
}
public void clear() {
data.clear();
notifyDataSetChanged();
}
}
Activity- CustomGalleryActivity.class
public class CustomGalleryActivity extends Activity {
GridView gridGallery;
Handler handler;
GalleryAdapterCustom adapter;
String count_image;
RelativeLayout layout_select, layout_cancel;
ImageView imgNoMedia;
Button btnGalleryOk;
public static final int REQUEST_CODE_Gallery = 1111;
public static final int REQUEST_CODE_Gallery_OK = 2222;
String action;
private ImageLoader imageLoader;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.gallery);
action = getIntent().getStringExtra("action");
if (action == null) {
finish();
}
initImageLoader();
init();
}
private void initImageLoader() {
try {
String CACHE_DIR = Environment.getExternalStorageDirectory()
.getAbsolutePath() + "/.temp_tmp";
new File(CACHE_DIR).mkdirs();
File cacheDir = StorageUtils.getOwnCacheDirectory(getBaseContext(),
CACHE_DIR);
DisplayImageOptions defaultOptions = new DisplayImageOptions.Builder()
.cacheOnDisc(true).imageScaleType(ImageScaleType.EXACTLY)
.bitmapConfig(Bitmap.Config.RGB_565).build();
ImageLoaderConfiguration.Builder builder = new ImageLoaderConfiguration.Builder(
getBaseContext())
.defaultDisplayImageOptions(defaultOptions)
.discCache(new UnlimitedDiscCache(cacheDir))
.memoryCache(new WeakMemoryCache());
ImageLoaderConfiguration config = builder.build();
imageLoader = ImageLoader.getInstance();
imageLoader.init(config);
} catch (Exception e) {
}
}
private void init() {
handler = new Handler();
gridGallery = (GridView) findViewById(R.id.gridGallery);
gridGallery.setFastScrollEnabled(true);
adapter = new GalleryAdapterCustom(getApplicationContext(), imageLoader);
PauseOnScrollListener listener = new PauseOnScrollListener(imageLoader,
true, true);
gridGallery.setOnScrollListener(listener);
if (action.equalsIgnoreCase(Action.ACTION_MULTIPLE_PICK)) {
findViewById(R.id.layout_select).setVisibility(View.VISIBLE);
gridGallery.setOnItemClickListener(mItemMulClickListener);
adapter.setMultiplePick(true);
} else if (action.equalsIgnoreCase(Action.ACTION_PICK)) {
findViewById(R.id.layout_select).setVisibility(View.GONE);
gridGallery.setOnItemClickListener(mItemSingleClickListener);
adapter.setMultiplePick(false);
}
gridGallery.setAdapter(adapter);
imgNoMedia = (ImageView) findViewById(R.id.imgNoMedia);
layout_cancel = (RelativeLayout) findViewById(R.id.layout_cancel);
layout_cancel.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
finish();
}
});
layout_select = (RelativeLayout) findViewById(R.id.layout_select);
layout_select.setOnClickListener(mOkClickListener);
new Thread() {
#Override
public void run() {
Looper.prepare();
handler.post(new Runnable() {
#Override
public void run() {
adapter.addAll(getGalleryPhotos());
checkImageStatus();
}
});
Looper.loop();
};
}.start();
}
private void checkImageStatus() {
if (adapter.isEmpty()) {
imgNoMedia.setVisibility(View.VISIBLE);
} else {
imgNoMedia.setVisibility(View.GONE);
}
}
View.OnClickListener mOkClickListener = new View.OnClickListener() {
#Override
public void onClick(View v) {
ArrayList<CustomGallery> selected = adapter.getSelected();
String[] allPath = new String[selected.size()];
for (int i = 0; i < allPath.length; i++) {
allPath[i] = selected.get(i).sdcardPath;
}
if (allPath.length == 0) {
Toast.makeText(CustomGalleryActivity.this, "Vui lòng chọn ảnh",
Toast.LENGTH_SHORT).show();
} else {
Intent data = new Intent().putExtra("all_path", allPath);
setResult(REQUEST_CODE_Gallery_OK, data);
finish();
}
}
};
AdapterView.OnItemClickListener mItemMulClickListener = new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> l, View v, int position, long id) {
adapter.changeSelection(v, position);
}
};
AdapterView.OnItemClickListener mItemSingleClickListener = new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> l, View v, int position, long id) {
CustomGallery item = adapter.getItem(position);
Intent data = new Intent().putExtra("single_path", item.sdcardPath);
setResult(REQUEST_CODE_Gallery_OK, data);
finish();
}
};
private ArrayList<CustomGallery> getGalleryPhotos() {
ArrayList<CustomGallery> galleryList = new ArrayList<CustomGallery>();
try {
final String[] columns = { MediaStore.Images.Media.DATA,
MediaStore.Images.Media._ID };
final String orderBy = MediaStore.Images.Media._ID;
Cursor imagecursor = managedQuery(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, columns,
null, null, orderBy);
if (imagecursor != null && imagecursor.getCount() > 0) {
while (imagecursor.moveToNext()) {
CustomGallery item = new CustomGallery();
int dataColumnIndex = imagecursor
.getColumnIndex(MediaStore.Images.Media.DATA);
item.sdcardPath = imagecursor.getString(dataColumnIndex);
galleryList.add(item);
}
}
} catch (Exception e) {
e.printStackTrace();
}
// show newest photo at beginning of the list
Collections.reverse(galleryList);
return galleryList;
}
Use .
Intent i = new Intent(NewPostActivity.this,
CustomGalleryActivity.class);
i.putExtra("action", Action.ACTION_MULTIPLE_PICK);
startActivityForResult(i, REQUEST_CODE_Gallery);

Android - Drag on LongClick with GridView - Unable to initiate drag

I'm developping a thumbnail mosaic of all pictures on SdCard.
If the user longclick on a picture, i want to start drag and drop process.
I clearly manage to get all images files and showing them with my own BaseAdapter in a GridView, but the process is long because I create previews of each image this way :
uri = Uri.fromFile(file);
try {
bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(uri));
if (bitmap != null) {
newBitmap = Bitmap.createScaledBitmap(bitmap, 200, 200, true);
bitmap.recycle();
// here code to add the bitmap to the adapter (see after)
}
} catch (IOException e) {
//Error fetching image, try to recover
}
So in order to avoid long wait, I'm loading the images in a AsyncTask.
The process work well and the images appears one by one, but now an error appear on LongClick, in this code :
mGrid.setOnItemLongClickListener(new OnItemLongClickListener()
{
public boolean onItemLongClick(AdapterView<?> adapter, View view, int position, long id)
{
ClipData data = ClipData.newPlainText("AdapterPosition", String.valueOf(position) );
view.startDrag(data, new DragShadowBuilder(view), null, 0);
return true;
}
});
and the error :
12-27 16:54:14.980: E/View(8089): Unable to initiate drag
12-27 16:54:14.980: E/View(8089): java.lang.NullPointerException
12-27 16:54:14.980: E/View(8089): at android.view.View.startDrag(View.java:11470)
12-27 16:54:14.980: E/View(8089): at renault.limandroid.widget.PartageWidgetActivity$9.onItemLongClick(PartageWidgetActivity.java:547)
Is someone have a idea?
Here is a large part of my code :
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_partage_widget);
// ... //
mImageAdapter = new ImageAdapter(this);
loadImages();
mGrid.setOnItemLongClickListener(new OnItemLongClickListener()
{
public boolean onItemLongClick(AdapterView<?> adapter, View view, int position, long id)
{
ClipData data = ClipData.newPlainText("AdapterPosition", String.valueOf(position) );
view.startDrag(data, new DragShadowBuilder(view), null, 0);
return true;
}
});
mGrid.setAdapter(mImageAdapter);
}
private void loadImages()
{
// si c'est la première fois qu'on passe ici
if (mLoadImageTask.getStatus() == AsyncTask.Status.PENDING)
{
mLoadImageTask.execute();
}
// si le scan est terminé mais qu'on veut le remettre à jour
// on va recréer la tâche
else if (mLoadImageTask.getStatus() == AsyncTask.Status.FINISHED)
{
mLoadImageTask = new LoadImagesFromSDCard();
mLoadImageTask.execute();
}
}
#Override
protected Object doInBackground(Object... params) {
setProgressBarIndeterminateVisibility(true);
Log.d(TAG,"LoadImages - do in background");
Bitmap bitmap = null;
Bitmap newBitmap = null;
Uri uri = null;
List<File> listFiles = new ArrayList<File>();
listFiles = StorageManager.findImageFiles();
for (File file : listFiles)
{
// mImageListFile contains the files that have already been loaded
if (!mImageListFile.contains(file))
{
uri = Uri.fromFile(file);
try {
bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(uri));
if (bitmap != null) {
newBitmap = Bitmap.createScaledBitmap(bitmap, 200, 200, true);
bitmap.recycle();
if (newBitmap != null) {
publishProgress(new LoadedImage(newBitmap));
}
}
} catch (IOException e) {
//Error fetching image, try to recover
}
mImageListFile.add(file);
}
}
return null;
}
/**
* Add a new LoadedImage in the images grid.
*
* #param value The image.
*/
#Override
public void onProgressUpdate(LoadedImage... value)
{
mImageAdapter.addPhoto(value[0]);
mImageAdapter.notifyDataSetChanged();
}
/**
* Set the visibility of the progress bar to false.
*
* #see android.os.AsyncTask#onPostExecute(java.lang.Object)
*/
#Override
protected void onPostExecute(Object result)
{
setProgressBarIndeterminateVisibility(false);
mGrid.invalidate();
}
}
class ImageAdapter extends BaseAdapter {
private Context mContext;
private ArrayList<LoadedImage> photos = new ArrayList<LoadedImage>();
public ImageAdapter(Context context) {
mContext = context;
photos.add(new LoadedImage(BitmapFactory.decodeResource(context.getResources(),
R.drawable.click)));
}
public void addPhoto(LoadedImage photo)
{
photos.add(photo);
}
public int getCount() {
return photos.size();
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent)
{
final ImageView imageView;
final int pos = position;
if (convertView == null) {
imageView = new ImageView(mContext);
} else {
imageView = (ImageView) convertView;
}
imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
imageView.setPadding(2, 2, 2, 2);
imageView.setLayoutParams(new GridView.LayoutParams(parent.getWidth()/4, parent.getWidth()/4));
imageView.setImageBitmap(photos.get(position).getBitmap());
return imageView;
}
public Object getItem(int position)
{
return photos.get(position);
}
}
/**
* A LoadedImage contains the Bitmap loaded for the image.
*/
private static class LoadedImage {
Bitmap mBitmap;
LoadedImage(Bitmap bitmap) {
mBitmap = bitmap;
}
public Bitmap getBitmap() {
return mBitmap;
}
}

getView is called several times for the first position in GridView

I have an adapter with this getView:
public View getView(int position, View convertView, ViewGroup parent) {
Log.d("getView gv", position+"");
NewsLine holder = null;
if (convertView == null) {
LayoutInflater inflater = ((Activity) context).getLayoutInflater();
convertView = inflater.inflate(R.layout.grid_entry, parent, false);
holder = new NewsLine();
holder.iv= (ImageView) convertView.findViewById(R.id.photo);
convertView.setTag(holder);
} else {
holder = (NewsLine) convertView.getTag();
}
NewsItem item=news.ITEMS.get(position);
//---------
if(item.imgurl!=null && item.imgurl.compareToIgnoreCase("null")!=0)
{
holder.iv.setVisibility(View.VISIBLE);
mMemoryCache.loadBitmap(item.imgurl, holder.iv,position);
}
else
holder.iv.setVisibility(View.INVISIBLE);
//-------------
return convertView;
}
I have two problems:
the getView is called several times for position 0 (the bitmap is downloaded with an AsyncTask if its missed in a LruCache). I have an animation (alpha from 0-1) that restarts several times for that position.
because I'm recycling the view sometimes you can see the old imageView content for a fraction of a second.
//----
And here is the cache class (only heap):
public class SetImgAT extends LruCache<String, Bitmap> {
private static SetImgAT instance;
private Animation FadeInAnimation;
private SetImgAT(int size, Context context) {
super(size);
FadeInAnimation = AnimationUtils.loadAnimation(context, R.anim.fadein);
}
public static synchronized SetImgAT getInstance(int size, Context context) {
if (instance == null) {
instance = new SetImgAT(size, context);
}
return instance;
}
#Override
protected int sizeOf(String key, Bitmap value) {
return (value.getRowBytes() * value.getHeight());
}
public void loadBitmap(String url, ImageView imageView,int pos) {
Bitmap bitmap = instance.get(url.hashCode() + "");
if (bitmap != null) {
Log.d("ImageCache", "hit - "+url.hashCode()+"pos:"+pos);
imageView.setImageBitmap(bitmap);
imageView.invalidate();
} else {
Log.d("ImageCache", "miss");
BitmapWorkerTask task = new BitmapWorkerTask(imageView);
task.execute(url);
}
}
class BitmapWorkerTask extends AsyncTask<String, Void, Bitmap> {
ImageView mImageView;
public BitmapWorkerTask(ImageView imageView) {
mImageView = imageView;
}
#Override
protected Bitmap doInBackground(String... url) {
Bitmap Picture = null;
if (url[0] != null && url[0].compareToIgnoreCase("null") != 0) {
Log.d("GetBMP from", url[0]);
URL img_value = null;
try {
img_value = new URL(url[0]);
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
Picture = BitmapFactory.decodeStream(img_value
.openConnection().getInputStream());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (Picture == null) {
Log.d("deb", "no bitmap");
} else {
Log.d("got deb", "got bitmap to "+url[0].hashCode());
instance.put(url[0].hashCode()+"", Picture);
}
}
return Picture;
}
#Override
protected void onPostExecute(Bitmap result) {
// super.onPostExecute(result);
if (result != null) {
Log.d("deb", "set bitmap");
mImageView.setImageBitmap(result);
//mImageView.startAnimation(FadeInAnimation);
}
}
}
//----------------
}
Thank you! :)
I've seen similar behavior when scrolling back and forth or haphazardly calling notifyDataSetChanged().
As an enhancement to what you are doing now I would suggest using Picasso instead since it handles this case very well, in addition to the fade in animation.
A one liner in your getView():
Picasso.with(context).load(urlToLoad).into(imageView);
See:
http://square.github.io/picasso/

GridView + CursorAdapter + AsyncTask load image shifting randomly

I use SimpleCursorAdapter and GridActivity (extended Activity written by me based on ListActivity) to load music albums from MediaStore, and use AsyncTask load each album art .
I tried this in bindView or getView, like this:
new AsyncAlbumArtLoader(viewholder.album_art, mShowFadeAnimation).execute(aid, width, height);
class AsyncAlbumArtLoader:
private class AsyncAlbumArtLoader extends AsyncTask<Object, Void, Bitmap> {
boolean enable_animation = false;
private ImageView imageview;
public AsyncAlbumArtLoader(ImageView imageview, Boolean animation) {
enable_animation = animation;
this.imageview = imageview;
}
#Override
protected void onPreExecute() {
if (enable_animation) {
imageview.startAnimation(AnimationUtils.loadAnimation(getApplicationContext(),
android.R.anim.fade_out));
imageview.setVisibility(View.INVISIBLE);
}
}
#Override
protected Bitmap doInBackground(Object... params) {
return MusicUtils.getCachedArtwork(getApplicationContext(), (Long) params[0],
(Integer) params[1], (Integer) params[2]);
}
#Override
protected void onPostExecute(Bitmap result) {
if (result != null) {
imageview.setImageBitmap(result);
} else {
imageview.setImageResource(R.drawable.albumart_mp_unknown_list);
}
if (enable_animation) {
imageview.setVisibility(View.VISIBLE);
imageview.startAnimation(AnimationUtils.loadAnimation(getApplicationContext(),
android.R.anim.fade_in));
}
}
}
But images shifting between gridview items randomly.
You can see screen record video here.
edited prevent this error by setTag() and getTag() is also no effect.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = super.getView(position, convertView, parent);
mAlbumCursor.moveToPosition(position);
ViewHolder viewholder = (ViewHolder) view.getTag();
String album_name = mAlbumCursor.getString(mAlbumIndex);
if (album_name == null || MediaStore.UNKNOWN_STRING.equals(album_name)) {
viewholder.album_name.setText(R.string.unknown_album_name);
} else {
viewholder.album_name.setText(album_name);
}
String artist_name = mAlbumCursor.getString(mArtistIndex);
if (album_name == null || MediaStore.UNKNOWN_STRING.equals(album_name)) {
viewholder.artist_name.setText(R.string.unknown_artist_name);
} else {
viewholder.artist_name.setText(artist_name);
}
// We don't actually need the path to the thumbnail file,
// we just use it to see if there is album art or not
long aid = mAlbumCursor.getLong(mAlbumIdIndex);
int width = getResources().getDimensionPixelSize(R.dimen.gridview_bitmap_width);
int height = getResources().getDimensionPixelSize(R.dimen.gridview_bitmap_height);
viewholder.album_art.setTag(aid);
new AsyncAlbumArtLoader(viewholder.album_art, mShowFadeAnimation, aid, width, height).execute();
long currentalbumid = MusicUtils.getCurrentAlbumId();
if (currentalbumid == aid) {
viewholder.album_name.setCompoundDrawablesWithIntrinsicBounds(0, 0,
R.drawable.ic_indicator_nowplaying_small, 0);
} else {
viewholder.album_name.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
}
return view;
}
// FIXME image loaded some times incorrect
private class AsyncAlbumArtLoader extends AsyncTask<Object, Void, Bitmap> {
boolean enable_animation = false;
private ImageView imageview;
private long album_id;
private int width,height;
public AsyncAlbumArtLoader(ImageView imageview, Boolean animation, long album_id, int width, int height) {
enable_animation = animation;
this.imageview = imageview;
this.album_id = album_id;
this.width = width;
this.height = height;
}
#Override
protected void onPreExecute() {
if (imageview.getTag() == null || (Long)imageview.getTag() != album_id) {
return;
}
if (enable_animation) {
imageview.startAnimation(AnimationUtils.loadAnimation(getApplicationContext(),
android.R.anim.fade_out));
imageview.setVisibility(View.INVISIBLE);
}
}
#Override
protected Bitmap doInBackground(Object... params) {
if (imageview.getTag() == null || (Long)imageview.getTag() != album_id) {
return null;
}
return MusicUtils.getCachedArtwork(getApplicationContext(), album_id,
width, height);
}
#Override
protected void onPostExecute(Bitmap result) {
if (imageview.getTag() == null || (Long)imageview.getTag() != album_id) {
return;
}
if (result != null) {
imageview.setImageBitmap(result);
} else {
imageview.setImageResource(R.drawable.albumart_mp_unknown_list);
}
if (enable_animation) {
imageview.setVisibility(View.VISIBLE);
imageview.startAnimation(AnimationUtils.loadAnimation(getApplicationContext(),
android.R.anim.fade_in));
}
}
}
The Problem is, that the AsyncTask don't know für which ImageView they where started, respectivley they overlap.
To prevent this you need to do the following:
In your getView Method (before calling the AsyncTask-Constructor you need so set a Tag to your ImageView: myImageView.setTag(object). The best choice is, if you use the object from which getView gets its information. In you case i think it is the ArrayList with the Album-Information. Let' say myImageView.setTag(myAlbumArray.get(position)) THE TAG MUST BE UNIQUE
Now add a new String 'tag' to your AsyncTask class and add this.tag = imageview.getTag().toString().
Now finally add the test in your onPostExecute:
if (imageview.getTag().toString().equals(tag)) {
// you got the right imageView, *your PostExecute Code* }
else {// wrong one, do nothing
}

Categories

Resources