Java – How can I properly implement a custom ListView with images using the Picasso library?

How can I properly implement a custom ListView with images using the Picasso library?… here is a solution to the problem.

How can I properly implement a custom ListView with images using the Picasso library?

I created a custom ListView layout using an image loaded from the network like this:

http://i.stack.imgur.com/l8ZOc.png

Works well when scrolling down. However, when you scroll down, the previous item leaves the screen and is destroyed. When you try to scroll up again, it loads again (from cache, faster but not instantaneous), which causes a delay and it shouldn’t be smooth.

1. Is there an example of doing this correctly?
2. Is there a way to prevent the ListView item from being destroyed when it is off-screen?
3. If so, will there be a problem if there are too many elements?

Here’s my code:

Menu adapter:

public class MenuAdapter extends BaseAdapter{

Context context;
    List<MyMenuItem> menuItems;

MenuAdapter(Context context, List<MyMenuItem> menuItems) {
        this.context = context;
        this.menuItems = menuItems;
    }

@Override
    public int getCount() {
        return menuItems.size();
    }

@Override
    public Object getItem(int position) {
        return menuItems.get(position);
    }

@Override
    public long getItemId(int position) {
        return menuItems.indexOf(getItem(position));
    }

private class ViewHolder {
        ImageView ivMenu;
        TextView tvMenuHeader;
    }

@Override
    public View getView(int position, View convertView, ViewGroup parent) {

ViewHolder holder = null;

LayoutInflater mInflater = (LayoutInflater) context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
        if (convertView == null) {
            convertView = mInflater.inflate(R.layout.menu_item, null);
            holder = new ViewHolder();

holder.tvMenuHeader = (TextView) convertView.findViewById(R.id.tvMenuHeader);
            holder.ivMenu = (ImageView) convertView.findViewById(R.id.ivMenuItem);

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

MyMenuItem row_pos = menuItems.get(position);

Picasso.with(context)
                .load(row_pos.getItem_image_url())
                .into(holder.ivMenu);

holder.tvMenuHeader.setText(row_pos.getItem_header());

Log.e("Test", "headers:" + row_pos.getItem_header());
        return convertView;
    }

}

My Menu Item:

public class MyMenuItem {

private String item_header;
    private String item_image_url;

public MyMenuItem(String item_header, String item_image_url){
        this.item_header=item_header;
        this.item_image_url=item_image_url;
    }

public String getItem_header(){
        return item_header;
    }

public void setItem_header(String item_header){
        this.item_header=item_header;
    }

public String getItem_image_url(){
        return item_image_url;
    }

public void setItem_image_url(String item_image_url){
        this.item_image_url=item_image_url;
    }
}

Main Activity:

public class MyActivity extends Activity implements AdapterView.OnItemClickListener {

List<MyMenuItem> menuItems;
    ListView myListView;
    JSONArray jsonArray;

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my);
        Bundle extras = getIntent().getExtras();

if(extras!=null){
            try{
                jsonArray = new JSONArray(extras.getString("Data"));
            }catch (Exception e){
                e.printStackTrace();
            }
        }

menuItems = new ArrayList<MyMenuItem>();

for (int i = 0; i < jsonArray.length(); i++) {
            try {
                MyMenuItem item = new MyMenuItem(jsonArray.getJSONObject(i).getString("title"), jsonArray.getJSONObject(i).getString("imageURL"));
                menuItems.add(item);
            }catch (Exception e){
                e.printStackTrace();
            }

}

myListView = (ListView) findViewById(R.id.list);
        MenuAdapter adapter = new MenuAdapter(this, menuItems);
        myListView.setAdapter(adapter);
        myListView.setOnItemClickListener(this);
    }
}

The menu item .xml:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

<ImageView
        android:id="@+id/ivMenuItem"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:scaleType="center"
        android:src="@drawable/em" />

<TextView
        android:id="@+id/tvMenuHeader"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#55000000"
        android:paddingBottom="15dp"
        android:paddingLeft="10dp"
        android:paddingRight="10dp"
        android:paddingTop="15dp"
        android:textColor="@android:color/white"
        android:layout_gravity="left|top"
        android:layout_alignBottom="@+id/ivMenuItem"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="true" />
</RelativeLayout>

Solution

1。 Is there an example of how to do this correctly?

Your code looks very close to perfect. The getView method of the adapter is usually the optimized critical path. Compare Picasso’s own example< a href="https://github.com/square/picasso/blob/master/picasso-sample/src/main/java/com/example/picasso/SampleListDetailAdapter.java" rel="noreferrer." noopener nofollow">SampleListDetailAdapter.java The gist of it (and your code).

  • Check and reuse a view that has been inflated, which is expensive.
  • Use ViewHolder so that you don’t have to call findViewById every time. Not expensive on a simple View. AFAIK is also cached.
  • Picasso.with(context).load(url)... Every time you need to display an image. This should be done immediately, but caching and other magic will still be used.

You can add some small optimizations, but I doubt there are noticeable or even measurable changes :

Pure style changes: Use BaseAdapter#getItem(position). This method
Exist only for you. The framework does not use it.

   @Override
   public MyMenuItem getItem(int position) { // << subclasses can use subtypes in overridden methods!
       return menuItems.get(position);
   }

@Override
   public View getView(int position, View convertView, ViewGroup parent) {
       ...
       MyMenuItem row_pos = getItem(position);

Use a reasonable id approach

@Override
public long getItemId(int position) {
    return menuItems.indexOf(getItem(position));
}

Equivalent

@Override
public long getItemId(int position) {
    return position;
}

But now it’s infinitely fast. indexOf(Object) has a very bad relationship with the number of objects.

Cached objects that do not change:

MenuAdapter(Context context, List<MyMenuItem> menuItems) {
    this.mLayoutInflater = LayoutInflater.from(content);
    this.mPicasso = Picasso.with(context);
}
..
@Override
public View getView(int position, View convertView, ViewGroup parent) {
    if (convertView == null) {
        convertView = mInflater.inflate(R.layout.menu_item, null);
    ...
    mPicasso
            .load(row_pos.getItem_image_url())
            .into(holder.ivMenu);

2。 Is there a way to prevent ListView items from being destroyed when off-screen?

No (*).

.. You can basically cache the results of a getView, e.g. in LruCache(position, View) or LruCache(MyMenuItem, View), and then don’t touch convertView – they need to remain unconverted or you would kill those in the cache View 。 Also

@Override
public int getItemViewType(int position) {
    return Adapter.IGNORE_ITEM_VIEW_TYPE;
}

Seems necessary because a standard adapter that uses code assumes that the View it removed from visibility has disappeared. They’re not, messing with them would mess up your cache and give me weird display issues.

3。 If so, does keeping too many elements cause problems?

Yes. This behavior is not expected/expected. You also have more or less nothing to gain. You may be able to omit the call to holder.tvMenuHeader.setText(). Similar to Picasso, but they should all be done immediately. Picasso should already cache your images. By caching all Views, you are actually adding another cache that also contains all images. I’d rather check that the picasso cache works as expected and save most projects. The only reason you might want to do this with a View cache is when complex View settings are required, so it’s worth caching a fully built View instead of just caching parts of the content.

Introduction

Analytics can actually tell you where you can/need/should improve. The first thing to look at IMO is traceview. You will see if the code is blocking the main thread that is causing the list scrolling to be unstable. If you are working with complex Views and you find that drawing methods execute most of the time, analyze them as well.

Related Problems and Solutions