Android - 使用 LruCache 的图像 GridView 画廊 - 图像不断变化
Android - Image GridView gallery with LruCache - images keep changing
我根据 LruCache 用法示例编写了此适配器 class。我遇到的问题如下:
如果我快速滑动并且 GridView 向下或向上滚动得非常远(位图尚未缓存),图像将使用错误的位图绘制,而不是保持空白。图像在 GridView 静止(不滚动)时不断变化,直到最终加载正确的图像,然后一切正常。我看不出我的代码有什么问题......关于如何避免这种情况有什么想法吗?
public class ImageGalleryAdapter extends BaseAdapter {
@SuppressLint("NewApi")
private MyLruCache mMemoryCache;
int size;
BaseAdapter adapter;
LayoutInflater mInflater;
ArrayList<ImageItem> photos;
Context con;
ContentResolver cr;
@SuppressLint("NewApi")
public ImageGalleryAdapter(ArrayList<ImageItem> photoList,Context con, int size, GridView gridView) {
this.photos=photoList;
this.size=size;
this.con=con;
adapter=this;
mInflater = (LayoutInflater) con.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
cr = con.getContentResolver();
final int memClass = ((ActivityManager)con.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();
// Use 1/8th of the available memory for this memory cache.
final int cacheSize = 1024 * 1024 * memClass / 4;
mMemoryCache = new MyLruCache(cacheSize) {
@SuppressLint("NewApi")
protected int sizeOf(String key, Bitmap bitmap) {
// The cache size will be measured in bytes rather than number of items.
return bitmap.getByteCount();
}
};
}
public int getCount() { return photos.size(); }
public Object getItem(int position) { return photos.get(position); }
public long getItemId(int position) { return position; }
@SuppressLint("InflateParams")
public View getView( final int position, View convertView, ViewGroup parent) {
ImageHolder holder;
if (convertView == null) {
holder = new ImageHolder();
convertView = mInflater.inflate(R.layout.image_gallery_item, null);
holder.imageview = (ImageView) convertView.findViewById(R.id.thumbImage);
holder.checkbox = (CheckBox) convertView.findViewById(R.id.itemCheckBox);
convertView.setTag(holder);
} else { holder = (ImageHolder) convertView.getTag(); }
FrameLayout.LayoutParams params=(FrameLayout.LayoutParams)holder.imageview.getLayoutParams();
params.width=size; params.height=size;
holder.imageview.setLayoutParams(params);
final Bitmap bm = mMemoryCache.getBitmapFromCache(photos.get(position).filename);
if (bm == null){
mMemoryCache.getNewBitmap(holder.imageview, position);
}
if(bm!=null && !bm.isRecycled()){
holder.imageview.setImageBitmap(bm);
holder.imageview.setScaleType(ScaleType.FIT_XY);
}
else{
holder.imageview.setImageResource(R.drawable.loading);
}
return convertView;
}
class ImageHolder {
ImageView imageview;
CheckBox checkbox;
}
class MyLruCache extends LruCache<String, Bitmap>{
public MyLruCache(int maxSize) {
super(maxSize);
}
@SuppressLint("NewApi")
public void addBitmapToCache(String key, Bitmap bitmap) {
if (getBitmapFromCache(key) == null) {
put(key, bitmap);
}
}
@SuppressLint("NewApi")
public Bitmap getBitmapFromCache(String key) {
return (Bitmap) get(key);
}
public void getNewBitmap(ImageView imageview, int position) {
BitmapWorkerTask task = new BitmapWorkerTask(imageview);
task.execute(photos.get(position));
}
class BitmapWorkerTask extends AsyncTask<ImageItem, Void, Bitmap>{
private final WeakReference<ImageView> imageViewReference;
ImageItem item;
public BitmapWorkerTask(ImageView imageView) {
// Use a WeakReference to ensure the ImageView can be garbage collected
imageViewReference = new WeakReference<ImageView>(imageView);
}
@Override
protected Bitmap doInBackground(ImageItem... params) {
ImageItem item = params[0];
this.item=item;
String filename = item.filename;
final Bitmap bitmap = getBitmapForImageItem(item);
mMemoryCache.addBitmapToCache(String.valueOf(filename), bitmap);
return bitmap;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
if (imageViewReference != null && bitmap != null) {
final ImageView imageView = (ImageView)imageViewReference.get();
if (imageView != null) {
imageView.setScaleType(ScaleType.FIT_XY);
imageView.setImageBitmap(bitmap);
}
}
}
}
}
public Bitmap getBitmapForImageItem(ImageItem item){
long imageID=item.imageId;
Bitmap bm = null;
if(!item.isVideo){
bm=MediaStore.Images.Thumbnails.getThumbnail( cr, imageID, MediaStore.Images.Thumbnails.MICRO_KIND, null);
}
else{
bm=MediaStore.Video.Thumbnails.getThumbnail( cr, imageID, MediaStore.Video.Thumbnails.MICRO_KIND, null);
}
if(bm==null){
try{
Bitmap newbitmap=F.bitmapInScreenSizeFromPath(item.filename, con, 70, 70);
bm=F.cropAndScaleBitmap(newbitmap, 70, 70);
newbitmap.recycle();
}catch(Exception e){}
}
return bm;
}
}
问题出在你的 BitmapWorkerTask onPostExecute()
中,它总是将 Bitmap
设置为 ImageView
,而在 GridView
中,视图被重用,所以你不断变化的图像看到的是滚动时图像的更新,由于滚动时间长,相同的 ImageView
用于各种图像。
我的建议是在 ImageHolder
中添加一个位置并更新 getView()
上的值,然后在 BitmapWorkerTask onPostExecute()
上您可以通过以下方式获得 ImageView
的当前位置ImageHolder
,如果它与 Bitmap
的位置不同,则不需要更新它,因为图像已滚出屏幕。
我根据 LruCache 用法示例编写了此适配器 class。我遇到的问题如下:
如果我快速滑动并且 GridView 向下或向上滚动得非常远(位图尚未缓存),图像将使用错误的位图绘制,而不是保持空白。图像在 GridView 静止(不滚动)时不断变化,直到最终加载正确的图像,然后一切正常。我看不出我的代码有什么问题......关于如何避免这种情况有什么想法吗?
public class ImageGalleryAdapter extends BaseAdapter {
@SuppressLint("NewApi")
private MyLruCache mMemoryCache;
int size;
BaseAdapter adapter;
LayoutInflater mInflater;
ArrayList<ImageItem> photos;
Context con;
ContentResolver cr;
@SuppressLint("NewApi")
public ImageGalleryAdapter(ArrayList<ImageItem> photoList,Context con, int size, GridView gridView) {
this.photos=photoList;
this.size=size;
this.con=con;
adapter=this;
mInflater = (LayoutInflater) con.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
cr = con.getContentResolver();
final int memClass = ((ActivityManager)con.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();
// Use 1/8th of the available memory for this memory cache.
final int cacheSize = 1024 * 1024 * memClass / 4;
mMemoryCache = new MyLruCache(cacheSize) {
@SuppressLint("NewApi")
protected int sizeOf(String key, Bitmap bitmap) {
// The cache size will be measured in bytes rather than number of items.
return bitmap.getByteCount();
}
};
}
public int getCount() { return photos.size(); }
public Object getItem(int position) { return photos.get(position); }
public long getItemId(int position) { return position; }
@SuppressLint("InflateParams")
public View getView( final int position, View convertView, ViewGroup parent) {
ImageHolder holder;
if (convertView == null) {
holder = new ImageHolder();
convertView = mInflater.inflate(R.layout.image_gallery_item, null);
holder.imageview = (ImageView) convertView.findViewById(R.id.thumbImage);
holder.checkbox = (CheckBox) convertView.findViewById(R.id.itemCheckBox);
convertView.setTag(holder);
} else { holder = (ImageHolder) convertView.getTag(); }
FrameLayout.LayoutParams params=(FrameLayout.LayoutParams)holder.imageview.getLayoutParams();
params.width=size; params.height=size;
holder.imageview.setLayoutParams(params);
final Bitmap bm = mMemoryCache.getBitmapFromCache(photos.get(position).filename);
if (bm == null){
mMemoryCache.getNewBitmap(holder.imageview, position);
}
if(bm!=null && !bm.isRecycled()){
holder.imageview.setImageBitmap(bm);
holder.imageview.setScaleType(ScaleType.FIT_XY);
}
else{
holder.imageview.setImageResource(R.drawable.loading);
}
return convertView;
}
class ImageHolder {
ImageView imageview;
CheckBox checkbox;
}
class MyLruCache extends LruCache<String, Bitmap>{
public MyLruCache(int maxSize) {
super(maxSize);
}
@SuppressLint("NewApi")
public void addBitmapToCache(String key, Bitmap bitmap) {
if (getBitmapFromCache(key) == null) {
put(key, bitmap);
}
}
@SuppressLint("NewApi")
public Bitmap getBitmapFromCache(String key) {
return (Bitmap) get(key);
}
public void getNewBitmap(ImageView imageview, int position) {
BitmapWorkerTask task = new BitmapWorkerTask(imageview);
task.execute(photos.get(position));
}
class BitmapWorkerTask extends AsyncTask<ImageItem, Void, Bitmap>{
private final WeakReference<ImageView> imageViewReference;
ImageItem item;
public BitmapWorkerTask(ImageView imageView) {
// Use a WeakReference to ensure the ImageView can be garbage collected
imageViewReference = new WeakReference<ImageView>(imageView);
}
@Override
protected Bitmap doInBackground(ImageItem... params) {
ImageItem item = params[0];
this.item=item;
String filename = item.filename;
final Bitmap bitmap = getBitmapForImageItem(item);
mMemoryCache.addBitmapToCache(String.valueOf(filename), bitmap);
return bitmap;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
if (imageViewReference != null && bitmap != null) {
final ImageView imageView = (ImageView)imageViewReference.get();
if (imageView != null) {
imageView.setScaleType(ScaleType.FIT_XY);
imageView.setImageBitmap(bitmap);
}
}
}
}
}
public Bitmap getBitmapForImageItem(ImageItem item){
long imageID=item.imageId;
Bitmap bm = null;
if(!item.isVideo){
bm=MediaStore.Images.Thumbnails.getThumbnail( cr, imageID, MediaStore.Images.Thumbnails.MICRO_KIND, null);
}
else{
bm=MediaStore.Video.Thumbnails.getThumbnail( cr, imageID, MediaStore.Video.Thumbnails.MICRO_KIND, null);
}
if(bm==null){
try{
Bitmap newbitmap=F.bitmapInScreenSizeFromPath(item.filename, con, 70, 70);
bm=F.cropAndScaleBitmap(newbitmap, 70, 70);
newbitmap.recycle();
}catch(Exception e){}
}
return bm;
}
}
问题出在你的 BitmapWorkerTask onPostExecute()
中,它总是将 Bitmap
设置为 ImageView
,而在 GridView
中,视图被重用,所以你不断变化的图像看到的是滚动时图像的更新,由于滚动时间长,相同的 ImageView
用于各种图像。
我的建议是在 ImageHolder
中添加一个位置并更新 getView()
上的值,然后在 BitmapWorkerTask onPostExecute()
上您可以通过以下方式获得 ImageView
的当前位置ImageHolder
,如果它与 Bitmap
的位置不同,则不需要更新它,因为图像已滚出屏幕。