如何使用Picasso库正确实现带图像的自定义列表视图?

Eve*_*Guy 3 java android listview adapter picasso

我创建了一个自定义列表视图布局,其中包含从Web加载的图像,如下所示:

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

向下滚动时工作正常.但是,当您向下滚动时,之前的项目将从屏幕中移出然后销毁.当你再次尝试向上滚动时,它会再次被加载(从缓存,更快但不是即时),这会导致延迟并且它不应该流畅.

1.有一个如何正确做到这一点的例子吗?
2.有没有办法阻止listview项目在屏幕外被销毁?
3.如果是这样,是否会因为保留太多物品而导致问题?

贝娄是我的代码:

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

}
Run Code Online (Sandbox Code Playgroud)

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

主要活动:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

zap*_*apl 9

1.有没有一个如何正确做到这一点的例子?

您的代码看起来非常接近完美.适配器的getView方法通常是优化的关键路径.比较Picasso自己的示例SampleListDetailAdapter.java.重要的一点(以及你的代码)

  • 检查和重新使用已经膨胀的观点,通货膨胀是昂贵的.
  • 使用,ViewHolder所以你不必findViewById每次都打电话.在简单的视图上并不是非常昂贵.也缓存了afaik.
  • 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);
Run Code Online (Sandbox Code Playgroud)

使用理智的id方法

@Override
public long getItemId(int position) {
    return menuItems.indexOf(getItem(position));
}
Run Code Online (Sandbox Code Playgroud)

相当于

@Override
public long getItemId(int position) {
    return position;
}
Run Code Online (Sandbox Code Playgroud)

但现在无限快.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);
Run Code Online (Sandbox Code Playgroud)

2.有没有办法阻止列表视图项目在屏幕外被销毁?

没有(*).

..(*),那么你基本上可以缓存的结果,getView例如,在LruCache(position, View)LruCache(MyMenuItem, View),然后不要接触convertView-他们需要保持未转换,或者你会杀了你的缓存这些看法.也

@Override
public int getItemViewType(int position) {
    return Adapter.IGNORE_ITEM_VIEW_TYPE;
}
Run Code Online (Sandbox Code Playgroud)

似乎是必需的,因为使用代码的标准适配器假定它从可见性中删除的视图消失了.他们不是和他们搞乱你的缓存混乱,并为我造成了奇怪的显示问题.

3.如果是这样,是否会因为保留太多物品而导致问题?

是.此行为不是意图/预期.你或多或少都没有获得.您可以将通话保存到holder.tvMenuHeader.setText().同样地Picasso,他们应该立即完成.Picasso应该已经缓存了您的图像.通过缓存所有Views你基本上添加另一个包含所有图像的缓存.我宁愿检查毕加索缓存是否按预期工作并保存大部分项目.您可能希望使用视图缓存执行此操作的唯一原因是需要复杂设置视图的情况,因此值得缓存完全构造的视图而不仅仅是某些内容部分.

轮廓

分析实际上可以告诉您可以/需要/应该改进的地方.第一个看IMO的是traceview.您将看到代码是否阻止主线程导致乱序列表滚动.如果您正在执行复杂的视图,并且您发现绘制方法大多数时间都在执行,那么也要对它们进行概要分析.