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

时间:2014-11-07 00:14:04

标签: java android listview adapter picasso

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

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

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

1.是否有一个如何正确做到这一点的例子?
2.有没有办法防止列表视图项目在屏幕外被销毁? 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;
    }

}

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>

1 个答案:

答案 0 :(得分:9)

1。有没有一个如何正确执行此操作的示例?

您的代码看起来非常接近完美。 Adapter getView方法通常是优化的关键途径。比较毕加索自己的例子SampleListDetailAdapter.java。重要的一点(以及你的代码)

  • 检查&amp;重新使用已经膨胀的观点,通货膨胀是昂贵的。
  • 使用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);

使用合理的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。有没有办法防止列表视图项目在屏幕外被销毁?

没有(*)。

..(*)你可以基本上缓存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。您将看到代码是否阻止导致乱序列表滚动的主线程。如果您正在执行复杂的视图,并且您发现绘制方法大部分时间都在执行,那么也可以对它们进行分析。