0

This seems to be a common question but I haven't been able to implement any of the solutions I have found. I have a Listview with a custom adapter that displays a thumbnail and text field in a row. The thumbnails are from a folder that I previously created and put the pictures I take from my app.

Here is my list activity:

private LayoutInflater mInflater;
private Vector<RowData> data;
private CustomAdapter adapter;
private RowData rd;

static File path = Environment.getExternalStorageDirectory();
static File fnames = new File(path, "MyImages");

static String[] title = fnames.list();

@Override
public void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_blog);

    mInflater = (LayoutInflater) getSystemService(
    Activity.LAYOUT_INFLATER_SERVICE);
    setListName();
    data = new Vector<RowData>();
    for(int i=0;i<title.length;i++){
        try {
            rd = new RowData(i,title[i]);
        } catch (ParseException e) {
            e.printStackTrace();
        }

        data.add(rd);
    }

    getListView().setTextFilterEnabled(true);
    getListView().setScrollingCacheEnabled(false);
}

public void onRestart()
{
    super.onRestart();
    setListName();
}

private Vector<RowData> setListName()
{
    data = new Vector<RowData>();
    String[] title = fnames.list();

    //get the databases textblog
    DatabaseHandler db = new DatabaseHandler(this);
    List<TextBlog> textBlogs = db.getAllText();
    int positionRaw = textBlogs.size();

    for (int i=0;i<textBlogs.size(); i++) {
        rd = new RowData(i, textBlogs.get(i).getText());
        data.add(rd);
    }

    for(int i=0;i<title.length;i++) {
        try {
            rd = new RowData(positionRaw,title[i]);
            positionRaw++;
        } catch (ParseException e) {
            e.printStackTrace();
        }

        data.add(rd);
    }

    adapter = new CustomAdapter(this, R.layout.list,R.id.title, data);
    setListAdapter(adapter);
    getListView().setTextFilterEnabled(true);
    adapter.notifyDataSetChanged();

    return data;
}


//Create thumbnail from file picture
private Bitmap decodeFile(File f) {
    try {
        //Decode image size
        BitmapFactory.Options o = new BitmapFactory.Options();
        o.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(new FileInputStream(f),null,o);

        //The new size we want to scale to
        final int REQUIRED_SIZE=70;

        //Find the correct scale value. It should be the power of 2.
        int scale=1;
        while (o.outWidth/scale/2>=REQUIRED_SIZE && o.outHeight/scale/2>=REQUIRED_SIZE)
            scale*=2;

        //Decode with inSampleSize
        BitmapFactory.Options o2 = new BitmapFactory.Options();
        o2.inSampleSize=scale;
        return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
    } catch (FileNotFoundException e) {

    }

    return null;
}

//Set row object
private class RowData 
{
    protected int mId;
    protected String mTitle;
    RowData(int id,String title){
    mId=id;
    mTitle = title;
}

@Override
public String toString() {
    return mId+" "+mTitle+" ";
}

and here is my custom adaptor:

public class CustomAdapter extends ArrayAdapter<RowData>
{
    public CustomAdapter(Context context, int resource, int textViewResourceId, 
                                        List<RowData> objects) 
    {               
        super(context, resource, textViewResourceId, objects);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent)
    {   
        ViewHolder holder = null;
        TextView title = null;
        ImageView thumb=null;
        RowData rowData= getItem(position);

        if(null == convertView) {
            convertView = mInflater.inflate(R.layout.list, null);
            holder = new ViewHolder(convertView);
            convertView.setTag(holder);
        }

        holder = (ViewHolder) convertView.getTag();
        title = holder.gettitle();
        title.setText(rowData.mTitle);

        thumb=holder.getImage();
        File file = new File(path + "/MyImages/" + rowData.mTitle);

        //Check what kind of file is it to add thumbnail
        //Way too slow use asynchronous task

        if (rowData.mTitle.substring(rowData.mTitle.lastIndexOf('.') + 1).equalsIgnoreCase("mp4") == true)
        {    
            Bitmap thumbVideo = ThumbnailUtils.createVideoThumbnail(file.getAbsolutePath(), MediaStore.Video.Thumbnails.MICRO_KIND);
            thumb.setImageBitmap(thumbVideo);
        }
        else if (rowData.mTitle.substring(rowData.mTitle.lastIndexOf('.') + 1).equalsIgnoreCase("3gpp") == true)
        {
            thumb.setImageDrawable(getResources().getDrawable(R.drawable.voice));
        }
        else
        {
            thumb.setImageBitmap(decodeFile(file));
        }

        return convertView;
    }

    private class ViewHolder {
        private View mRow;
        private TextView title = null;
        private ImageView thumb=null; 

        public ViewHolder(View row) {
            mRow = row;
        }

        public TextView gettitle() {
            if(null == title) {
                title = (TextView) mRow.findViewById(R.id.title);
            }
                return title;
        }     

        public ImageView getImage() {
            if (null == thumb) {
                thumb = (ImageView) mRow.findViewById(R.id.img);
                                      }
                return thumb;
            }
        }
    }
}

I am quiet sure it's because of the thumbnails creation and I have to implement it in a AsynchTask, but I tried witout any success.

Can anyone suggest where I'm going wrong, or at list give me a tips?

Chilledrat
  • 2,593
  • 3
  • 28
  • 38
Kalimero95
  • 43
  • 1
  • 9
  • You can see similar issue [How do I do a lazy load of images in ListView](http://stackoverflow.com/q/541966/1050058) – Trung Nguyen Aug 09 '12 at 09:41
  • 1
    Here is a little OT tip: You mix indendation, whitespaces and bracket styles a lot which makes your code somewhat hard to read. You can format it easily by pressing `Ctrl+Shift+F` in Eclipse before copying it over into a question. Better formatted code is easier to read and will improve your chances of getting an answer. *(other IDEs may have a similar feature, but I don't know that from the top of my head)* –  Aug 09 '12 at 09:48
  • @Yul thanks i gonna take a look – Kalimero95 Aug 09 '12 at 10:08
  • title = holder.gettitle(); and title = holder.gettitle(); uses findViewById() inside them. findViewById is pretty expensivee, try to use it once. Otherwise your HolderView has not point. Also use ImageCache, like in lazy load of images. You have already the link. – Lazy Ninja Aug 09 '12 at 10:11

1 Answers1

1

You are trying to implement here the ViewHolder pattern, but your implementation looks wrong.

The idea of this pattern is to reduce the call to findViewById()which has an impact on your performance. Only if the row is null (convertView) you should call findViewById(), otherwise reuse the previous view saved with the setTag()

Lets take a look at your code:

if(null == convertView){
    convertView = mInflater.inflate(R.layout.list, null);
    holder = new ViewHolder();
    convertView.setTag(holder);
}

// .......          
title = holder.gettitle();
// .........
thumb=holder.getImage();

Notice that holder.getTitle(), and holder.getImage() are called after the if statement. This means they will be called every time regardless if the convertView is null or not.
Now, taking a look at these getters we see that they contain code that calls findViewById();

Ex. for the getTitle()

public TextView gettitle() {
     if(null == title){
          title = (TextView) mRow.findViewById(R.id.title);
     }
     return title;
} 

So, basically, you don't use here ViewHolder pattern, just some mixed code, and in the end the findViewById() is called every time which reduces the performance of ListView.

To do it correctly you should call findViewById() only when the convertView is null. Ex:

if(null==convertView){
    convertView = mInflater.inflate(R.layout.list, null);
    holder = new ViewHolder();

    // Getting a refernce to the views with findViewById()
    title = holder.gettitle();
    thumb=holder.getImage();

    convertView.setTag(holder);
}else{
     holder = (ViewHolder) convertView.getTag();
}

// Then you set the appropriate values to your views through the holder:
holder.title.setText("");
holder.thumb.setImageBitmap(...);

This is the correct way to implement ViewHolder pattern

(PS: You'll need to change the access modifier for the title, thumb,... to public.)

Andy Res
  • 15,963
  • 5
  • 60
  • 96
  • I think problem is get bitmap in `Adapter`. He should use `Asynctask` – Trung Nguyen Aug 09 '12 at 10:13
  • @Yul, quite possible, another factor influencing ListView performance – Andy Res Aug 09 '12 at 10:20
  • @AndyRes Thank you so much for your time and the explanation. I tried it but sadly it didn't change anything then i try without loading the thumbnails and it works perfectly so i really need to get those thumbs by an asyncTask – Kalimero95 Aug 09 '12 at 12:22
  • Some references in regards to AsyncTask: http://androidresearch.wordpress.com/2012/03/17/understanding-asynctask-once-and-forever/, http://developer.android.com/reference/android/os/AsyncTask.html – Andy Res Aug 09 '12 at 12:38