How to properly implement a custom list with images using the Picasso library?

I have created a custom list layout with images downloaded from the web, for example:

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

It works fine when scrolling down. However, when scrolling down, the previous items exit the screen and then are destroyed. When you try to scroll the page again, it loads again (from the cache, faster but not instantaneously) which causes a delay and is not as fluent as it should be.

1. Is there an example of how to do this correctly?
2.Is there a way to prevent the list items from being destroyed when they are off screen?
3.If so, would it cause problems with saving too many items?

Below is my code:

MenuAdapter:

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;
    }

}

      

MyMenuItem:

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;
    }
}

      

MainActivity:

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);
    }
}

      

MenuItem.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>

      

+3


source to share


1 answer


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

Your code looks pretty close to perfect. The Adapter method is getView

usually the critical path for optimization. Compare, for example, Picasso's SampleListDetailAdapter.java example . Important points it (as well as your code) does

  • check and reuse already inflated types, inflation is expensive.
  • use ViewHolder

    so you don't have to call findViewById

    every time. Not very expensive for simple looks. Also cached afike.
  • Picasso.with(context).load(url)...

    every time you need to display an image. This should complete instantly, but still use caches and other magic.

There are a few small optimizations you can add, but I doubt there are noticeable or even measurable changes:

net style change: use BaseAdapter#getItem(position)

. This method is only for you. The framework doesn't 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 sane identifier method

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

      

equivalent to

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

      

but now infinitely faster. indexOf(Object)

scales very poorly with the number of objects.

Cache 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 the list items from being destroyed when they are off screen?

Not (*).

.. (*), you can essentially cache the result getView

, eg. in LruCache(position, View)

or LruCache(MyMenuItem, View)

, then do not touch convertView

- they must remain unturned or you will kill these views in the cache. Also

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

      

seemed to be required because the standard adapter using code assumes that the views it removes from view are gone. They are not and mess with them mess with your cache and caused weird display problems for me.

3. If so, will too many elements cause problems?

Yes. This behavior is not expected or expected. There is also more or less nothing that you get. You may be able to save the call to holder.tvMenuHeader.setText()

. In the same way as Picasso

, but both of them should complete instantly. Picasso

should already cache the image. By caching everything Views

, you are essentially adding another cache that contains all the images as well. I would rather check that the picasso cache is working as intended and contains most of the items. The only reason you can do this with views caching is for cases that require complex view customization, so it's worth caching the fully rendered view, not just some pieces of content.

Profile

Profiling can really tell you where you can / should / should improve. The first to look at IMO is tracking. You will see that the code is blocking the main thread, which causes the scroll list to hang. If you are doing complex views and you see drawing methods run most of the time, project them as well.

+9


source







All Articles