Android load only visible elements of ListView
I'm using Lazy Loading Li开发者_如何学编程st in my application and I need to load only the visible elements of ListView.I've search over the internet,tried a lot of things but still cannot get the things to work.Can you give me a suggestion how to do it?
Here is the ImageLoader class :
package com.stampii.stampii.tableview;
import java.io.File;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Stack;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import com.stampii.stampii.R;
import android.app.Activity;
import android.content.Context;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;
import android.widget.ImageView;
public class ImageLoader {
//the simplest in-memory cache implementation. This should be replaced with something like SoftReference or BitmapOptions.inPurgeable(since 1.6)
private HashMap<String, Bitmap> cache=new HashMap<String, Bitmap>();
private File cacheDir;
private AssetManager mAssetManager;
public ImageLoader(Context context){
//Make the background thead low priority. This way it will not affect the UI performance
photoLoaderThread.setPriority(Thread.NORM_PRIORITY-1);
mAssetManager = context.getAssets();
//Find the dir to save cached images
if (android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED))
cacheDir=new File(android.os.Environment.getExternalStorageDirectory(),"Stampii");
else
cacheDir=context.getCacheDir();
if(!cacheDir.exists())
cacheDir.mkdirs();
}
final int stub_id=R.drawable.default_img;
public void DisplayImage(String url, Activity activity, ImageView imageView)
{
if(cache.containsKey(url))
imageView.setImageBitmap(cache.get(url));
else
{
queuePhoto(url, activity, imageView);
imageView.setImageResource(stub_id);
}
}
private void queuePhoto(String url, Activity activity, ImageView imageView) //opashka
{
//This ImageView may be used for other images before. So there may be some old tasks in the queue. We need to discard them.
photosQueue.Clean(imageView);
PhotoToLoad p=new PhotoToLoad(url, imageView);
synchronized(photosQueue.photosToLoad){
photosQueue.photosToLoad.push(p);
photosQueue.photosToLoad.notifyAll();
}
//start thread if it's not started yet
if(photoLoaderThread.getState()==Thread.State.NEW)
photoLoaderThread.start();
}
private Bitmap getBitmap(String src) {
Bitmap myBitmap = null;
//Decryption
try {
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
SecretKeySpec keySpec = new SecretKeySpec("01234567890abcde".getBytes(), "AES");
IvParameterSpec ivSpec = new IvParameterSpec("fedcba9876543210".getBytes());
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
InputStream input = mAssetManager.open(src);
CipherInputStream cis = new CipherInputStream(input, cipher);
myBitmap = BitmapFactory.decodeStream(cis);
}
catch(Exception e){
e.printStackTrace();
Log.v("ERROR","Error : "+e);
}
return myBitmap;
}
//Task for the queue
private class PhotoToLoad
{
public String url;
public ImageView imageView;
public PhotoToLoad(String u, ImageView i){
url=u;
imageView=i;
}
}
PhotosQueue photosQueue=new PhotosQueue();
public void stopThread()
{
photoLoaderThread.interrupt();
}
//stores list of photos to download
class PhotosQueue
{
private Stack<PhotoToLoad> photosToLoad=new Stack<PhotoToLoad>();
//removes all instances of this ImageView
public void Clean(ImageView image)
{
for(int j=0 ;j<photosToLoad.size();){
if(photosToLoad.get(j).imageView==image)
photosToLoad.remove(j);
else
++j;
}
}
}
class PhotosLoader extends Thread {
public void run() {
try {
while(true)
{
//thread waits until there are any images to load in the queue
if(photosQueue.photosToLoad.size()==0)
synchronized(photosQueue.photosToLoad){
photosQueue.photosToLoad.wait();
}
if(photosQueue.photosToLoad.size()!=0)
{
PhotoToLoad photoToLoad;
synchronized(photosQueue.photosToLoad){
photoToLoad=photosQueue.photosToLoad.pop();
}
Bitmap bmp=getBitmap(photoToLoad.url);
cache.put(photoToLoad.url, bmp);
Object tag=photoToLoad.imageView.getTag();
if(tag!=null && ((String)tag).equals(photoToLoad.url)){
BitmapDisplayer bd=new BitmapDisplayer(bmp, photoToLoad.imageView);
Activity a=(Activity)photoToLoad.imageView.getContext();
a.runOnUiThread(bd);
}
}
if(Thread.interrupted())
break;
}
} catch (InterruptedException e) {
//allow thread to exit
}
}
}
PhotosLoader photoLoaderThread=new PhotosLoader();
//Used to display bitmap in the UI thread
class BitmapDisplayer implements Runnable
{
Bitmap bitmap;
ImageView imageView;
public BitmapDisplayer(Bitmap b, ImageView i){bitmap=b;imageView=i;}
public void run()
{
if(bitmap!=null)
imageView.setImageBitmap(bitmap);
else
imageView.setImageResource(stub_id);
}
}
public void clearCache() {
//clear memory cache
long size=0;
cache.clear();
//clear SD cache
File[] files=cacheDir.listFiles();
for(File f:files){
size=size+f.length();
f.delete();
}
}
}
EDIT1:
getView method in my LazyAdapter class :
public View getView(int position, View convertView, ViewGroup parent) {
View vi=convertView;
ViewHolder holder;
if(convertView==null){
vi = inflater.inflate(R.layout.item, null);
holder=new ViewHolder();
holder.name=(TextView)vi.findViewById(R.id.name);
holder.info=(TextView)vi.findViewById(R.id.info);
holder.image=(ImageView)vi.findViewById(R.id.image);
vi.setTag(holder);
}
else
holder=(ViewHolder)vi.getTag();
holder.name.setText("MotoGP Rider");
holder.info.setText("Category");
holder.image.setTag(data[position]);
imageLoader.DisplayImage(data[position], activity, holder.image);
return vi;
}
Any ideas?
After checking the code: I am sure that ONLY the images that are visible are being loaded!
BUT because some of the images down have the same url! You will think that they are loading in a different order. However they are loaded based on the images visible but any other listitem with the same url will show the image.
I'm using Lazy Loading List in my application and I need to load only the visible elements of ListView.
getView
is called for each position that is visible on the ListView
The pictures you need to load must be added to the queue from the getView
of the Adapter
So you should Override
public View getView (int position, View convertView, ViewGroup parent){
super(position,convertView,parent);
//now add the picture at position to the queue
}
Note: Be careful
Say you added photo 1 to the queue: This does not mean that getView
will not be called again with position 1.
In other words, account for the fact that a position may be called many times using getView
.
To load only visible elements you need to implements a interface of AbsListView.OnScrollListener. From this interface you can find out the "scroll event" and "scrolleventstate" change from its methods.
ListView list = (ListView) findViewById(R.id.listview);
MyAdapter adapter = new MyAdapter(context, items);
list.setAdapter(adapter);
list.setOnScrollListener(new OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view,
int scrollState) {
// Do nothing
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
// threshold being indicator if bottom of list is hit
if (firstVisibleItem = threshold) {
pullMoreData();
}
}
});
For more explanation about this. you can follow this tutorials. smart loading listview, dynamic data listview, detect end scroll, get visible data index
精彩评论