RJB RJB - 5 days ago 6
Android Question

RecyclerView jerky, with only small png icons, Android

I have a RecyclerView, with a simple TextView and an ImageView, which is a little icon (a flag of the country). Each icon is between 2kb and 10kb, which seems pretty small to me. The icons are all stored in the drawable folder.

However, when scrolling, it isn't very smooth. It jerks.

I have this code:

search = (EditText) findViewById( R.id.search);
mRecyclerView = (RecyclerView) findViewById(R.id.recyclerview);
mRecyclerView.setHasFixedSize(true);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mRecyclerView.setNestedScrollingEnabled(false);


and here is my adapter:

public class CountryAdapter extends RecyclerView.Adapter<CountryViewHolder> {

private ArrayList<Country> countryArrayList;
private Context context;

public CountryAdapter(ArrayList<Country> countryArrayList, Context context) {
this.countryArrayList = countryArrayList;
this.context = context;
}

@Override
public CountryViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.country_item, parent, false);

return new CountryViewHolder(itemView);

}

@Override
public void onBindViewHolder(CountryViewHolder holder, int position) {


final Country currentCountry = countryArrayList.get(position);

holder.countryItemLinearLayout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(context, currentCountry.getCountry().toString(), Toast.LENGTH_SHORT).show();
}
});

holder.countryTextView.setText(currentCountry.getCountry());
holder.countryFlagImageView.setImageResource(currentCountry.getFlag());


}

@Override
public int getItemCount() {
return countryArrayList.size();
}
}

Answer

I've had this issue before as well. The way to fix this is to load the Bitmap from the resource asynchronously, and then set it on the image when it's done loading. Here's basically my system.

public class LoadImage extends AsyncTask<Void, Void, Bitmap> {

    private Context context;

    private ImageView imageView;
    private int imageResource;

    public LoadImage(Context context, ImageView imageView, int imageResource) {
        this.context = context;
        this.imageView = imageView;
        this.imageResource = imageResource;
    }

    @Override
    protected Bitmap doInBackground(Void... params) {
        return BitmapFactory.decodeResource(context.getResources(), imageResource);
    }

    @Override
    protected void onPostExecute(Bitmap bitmap) {
        super.onPostExecute(bitmap);

        imageView.setImageBitmap(bitmap);
    }
}

And then you call it like this:

new LoadImage(context, holder.countryFlagImageView, currentCountry.getFlag()).execute();

This will move the decoding of the bitmap into a separate thread, so that it's not holding up your RecyclerView.

If you put this inside your holder class, and when you call it, you set it to a variable, you can then cancel the process if that view gets recycled before the task has finished. Like this:

Holder class

public class CountryViewHolder extends RecyclerView.ViewHolder {

    public AsyncTask imageLoadTask;

    public void setImage(Context context, ImageView imageView, int resource){
        imageLoadTask = new LoadImage(context, imageView, resource);
        imageLoadTask.execute();
    }

}

Calling it from above

@Override
public void onBindViewHolder(CountryViewHolder holder, int position) {

    final Country currentCountry = countryArrayList.get(position);

    holder.setImage(holder.countryFlagImageView.getContext(), holder.countryFlagImageView, currentCountry.getFlag());

}

and then in your adapter, you add this override:

@Override
public void onViewRecycled(FeedHolder holder) {
    super.onViewRecycled(holder);
    holder.imageLoadTask.cancel(true);
}

This basically causes it only to decode the resources for the views currently on screen.

Hope it helps!

Comments