Как правильно реализовать собственный список с изображениями с помощью библиотеки Picasso?

Я создал собственный макет списка с изображениями, которые загружаются из Интернета следующим образом:

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

Он отлично работает при прокрутке вниз. Однако при прокрутке вниз предыдущие элементы исчезают с экрана, а затем уничтожаются. Когда вы снова пытаетесь прокрутить вверх, он снова загружается (из кеша, быстрее, но не мгновенно), что вызывает задержку и не работает так, как должно быть.

1.Есть ли пример того, как это сделать правильно?
2.Есть ли способ предотвратить уничтожение элементов списка, когда они находятся за пределами экрана?
3.Если да, то не вызовет ли это проблем с сохранением много предметов?

Ниже мой код:

Адаптер меню:

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

Основная деятельность:

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>

person Everyday Ordinary Android Guy    schedule 07.11.2014    source источник


Ответы (1)


1. Есть ли пример того, как это сделать правильно?

Ваш код выглядит довольно близко к идеальному. Метод адаптера getView обычно является критическим путем для оптимизации. Сравните, например, собственный пример Пикассо SampleListDetailAdapter.java. Важные моменты, которые он (а также ваш код) делает

  • проверить и повторно использовать уже завышенные представления, инфляция стоит дорого.
  • используйте ViewHolder, чтобы вам не приходилось каждый раз вызывать findViewById. Не очень дорого на простых представлениях. Также кешировал афаик.
  • Picasso.with(context).load(url)... каждый раз, когда вам нужно отобразить изображение. Это должно завершиться мгновенно, но по-прежнему использовать кеши и другую магию.

Есть некоторые незначительные оптимизации, которые вы можете добавить, но я сомневаюсь, что есть заметные или даже измеримые изменения:

чистое изменение стиля: используйте BaseAdapter#getItem(position). Этот метод существует только для вас. Фреймворк его не использует.

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

Используйте разумный метод id

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

эквивалентно

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

но теперь бесконечно быстрее. indexOf(Object) очень плохо масштабируется с количеством объектов.

Кэшировать объекты, которые не изменяются:

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. Есть ли способ предотвратить уничтожение элементов списка, когда они находятся вне экрана?

No(*).

..(*) ну, вы можете по существу кэшировать результат getView, например. в LruCache(position, View) или LruCache(MyMenuItem, View), то не трогайте convertView — они должны оставаться неконвертированными, иначе вы убьете эти представления в своем кеше. Также

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

казался необходимым, потому что стандартный адаптер, использующий код, предполагает, что представления, которые он удаляет из видимости, исчезли. Это не так, и возня с ними портит ваш кеш и вызывает у меня странные проблемы с отображением.

3. Если да, не вызовет ли проблем хранение слишком большого количества предметов?

Да. Такое поведение не является предполагаемым/ожидаемым. Вы также более или менее ничего не получаете. Возможно, вам удастся сэкономить на звонке holder.tvMenuHeader.setText(). Аналогично с Picasso, но оба они должны завершиться мгновенно. Picasso ваше изображение уже должно быть кэшировано. Кэшируя все Views, вы, по сути, добавляете еще один кеш, который также содержит все изображения. Я бы предпочел проверить, что кеш Пикассо работает по назначению и содержит большинство элементов. Единственная причина, по которой вы можете захотеть сделать это с кэшированием представления, — это случаи, когда требуется сложная настройка представления, поэтому имеет смысл кэшировать полностью построенное представление, а не только некоторые части содержимого.

Профиль

Профилирование на самом деле может сказать вам, что вы можете/нужно/должны улучшить. Первым, кто смотрит на IMO, является traceview. Вы увидите, блокирует ли код основной поток, что приводит к прерывистой прокрутке списка. Если вы делаете сложные представления и видите, что методы рисования выполняются большую часть времени, профилируйте и их.

person zapl    schedule 07.11.2014