【问题标题】:How to correctly implement a custom listview with images using Picasso library?如何使用 Picasso 库正确实现带有图像的自定义列表视图?
【发布时间】:2014-11-07 00:14:04
【问题描述】:

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

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

向下滚动时效果很好。但是,当您向下滚动时,以前的项目会离开屏幕然后被销毁。当您尝试再次向上滚动时,它会再次加载(从缓存中加载,更快但不是即时),这会导致延迟并且不流畅。

1.是否有正确执行此操作的示例?
2.有没有办法防止listview项目在屏幕外被破坏?
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;
    }

}

我的菜单项:

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>

【问题讨论】:

    标签: java android listview adapter picasso


    【解决方案1】:

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

    您的代码看起来非常接近完美。 Adapter 的getView 方法通常是优化的关键路径。例如,比较毕加索自己的例子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);
    

    使用理智的识别方法

    @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。您将看到代码是否阻塞了导致列表滚动不稳定的主线程。如果您正在处理复杂的视图,并且您发现大多数时间都在执行绘制方法,那么也要对它们进行分析。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-09-08
      相关资源
      最近更新 更多