如何正确地实现一个自定义列表视图使用毕加索库图像? [英] How to correctly implement a custom listview with images using Picasso library?

查看:156
本文介绍了如何正确地实现一个自定义列表视图使用毕加索库图像?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我创建了图像的自定义列表视图布局这是从网络加载是这样的:

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

它滚动下来时工作正常。但是,当你向下滚动,在previous物品出门屏幕,然后销毁。当您尝试再次向上滚动,它就会再次加载(从高速缓存,速度更快,但不是即时),这会导致延迟,这是不是流利理所应当的。

1.Is那里如何正确地做到这一点的例子吗?
2.Is有没有办法prevent列表视图项目正在当他们过了屏幕的破坏?
3,如果是这样,它会导致问题,以保持太多的项目?

·贝娄是我的code:

MenuAdapter:

 公共类MenuAdapter扩展了BaseAdapter {

    上下文语境;
    名单< MyMenuItem>菜单项;

    MenuAdapter(上下文的背景下,名单,其中,MyMenuItem>菜单项){
        this.context =背景;
        this.menuItems =菜单项;
    }

    @覆盖
    公众诠释getCount将(){
        返回menuItems.size();
    }

    @覆盖
    公共对象的getItem(INT位置){
        返回menuItems.get(位置);
    }

    @覆盖
    众长getItemId(INT位置){
        返回menuItems.indexOf(的getItem(位置));
    }

    私有类ViewHolder {
        ImageView的ivMenu;
        TextView的tvMenuHeader;
    }



    @覆盖
    公共查看getView(INT位置,查看convertView,ViewGroup中父){

        ViewHolder支架=无效;

        LayoutInflater mInflater =(LayoutInflater)context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
        如果(convertView == NULL){
            convertView = mInflater.inflate(R.layout.menu_item,NULL);
            持有人=新ViewHolder();

            holder.tvMenuHeader =(TextView中)convertView.findViewById(R.id.tvMenuHeader);
            holder.ivMenu =(ImageView的)convertView.findViewById(R.id.ivMenuItem);

            convertView.setTag(保持器);
        } 其他 {
            支架=(ViewHolder)convertView.getTag();
        }

        MyMenuItem row_pos = menuItems.get(位置);

        Picasso.with(上下文)
                .load(row_pos.getItem_image_url())
                。走进(holder.ivMenu);

        holder.tvMenuHeader.setText(row_pos.getItem_header());

        Log.e(测试,标题:+ row_pos.getItem_header());
        返回convertView;
    }

}
 

MyMenuItem:

 公共类MyMenuItem {

    私人字符串item_header;
    私人字符串item_image_url;

    公共MyMenuItem(字符串item_header,字符串item_image_url){
        this.item_header = item_header;
        this.item_image_url = item_image_url;
    }

    公共字符串getItem_header(){
        返回item_header;
    }

    公共无效setItem_header(字符串item_header){
        this.item_header = item_header;
    }

    公共字符串getItem_image_url(){
        返回item_image_url;
    }

    公共无效setItem_image_url(字符串item_image_url){
        this.item_image_url = item_image_url;
    }
}
 

MainActivity:

 公共类MyActivity扩展活动实现AdapterView.OnItemClickListener {

    名单< MyMenuItem>菜单项;
    ListView控件myListView;
    JSONArray jsonArray;

    @覆盖
    保护无效的onCreate(包savedInstanceState){
        super.onCreate(savedInstanceState);
        的setContentView(R.layout.activity_my);
        捆绑额外= getIntent()getExtras()。

        如果(临时演员!= NULL){
            尝试{
                jsonArray =新JSONArray(extras.getString(数据));
            }赶上(例外五){
                e.printStackTrace();
            }
        }


        菜单项=新的ArrayList< MyMenuItem>();


        的for(int i = 0; I< jsonArray.length();我++){
            尝试 {
                MyMenuItem项目=新MyMenuItem(jsonArray.getJSONObject(我).getString(标题),jsonArray.getJSONObject(I).getString(IMAGEURL));
                menuItems.add(项目);
            }赶上(例外五){
                e.printStackTrace();
            }

        }

        myListView =(ListView控件)findViewById(R.id.list);
        MenuAdapter适配器=新MenuAdapter(这一点,菜单项);
        myListView.setAdapter(适配器);
        myListView.setOnItemClickListener(本);
    }
}
 

MenuItem.xml:

 < RelativeLayout的的xmlns:机器人=htt​​p://schemas.android.com/apk/res/android
    机器人:layout_width =match_parent
    机器人:layout_height =match_parent>


    < ImageView的
        机器人:ID =@ + ID / ivMenuItem
        机器人:layout_width =match_parent
        机器人:layout_height =WRAP_CONTENT
        机器人:scaleType =中心
        机器人:SRC =@可绘制/ EM/>

    <的TextView
        机器人:ID =@ + ID / tvMenuHeader
        机器人:layout_width =match_parent
        机器人:layout_height =WRAP_CONTENT
        机器人:后台=#55000000
        机器人:paddingBottom会=15dp
        机器人:以下属性来=10dp
        机器人:paddingRight =10dp
        机器人:paddingTop =15dp
        机器人:文字颜色=@机器人:彩色/白
        机器人:layout_gravity =左|顶
        机器人:layout_alignBottom =@ + ID / ivMenuItem
        机器人:layout_alignParentLeft =真
        机器人:layout_alignParentStart =真
        机器人:layout_alignParentRight =真
        机器人:layout_alignParentEnd =真/>
< / RelativeLayout的>
 

解决方案

1。是否有如何做到这正常吗?

为例

您code相pretty的接近完美。适配器的 getView 方法通常是优化的关键路径。例如毕加索自己的例子<一个比较href="https://github.com/square/picasso/blob/master/picasso-sample/src/main/java/com/example/picasso/SampleListDetailAdapter.java">SampleListDetailAdapter.java.最重要的点它(以及你的code)有

  • 检查和放大器;再利用已经膨胀的观点,通货膨胀是昂贵的。
  • 使用 ViewHolder ,所以你不必叫 findViewById 每次。不贵的要命简单的看法。同时缓存AFAIK。
  • Picasso.with(上下文).load(URL)... 每次你需要显示的图像。这应该立即结束,但仍使用缓存等神奇功效。

有一些小的优化,你可以添加,但我怀疑,有明显的甚至是可测量的变化:

纯粹的风格变化:使用 BaseAdapter#的getItem(位置)。这种方法    存在只为你。该框架不使用它。

  @覆盖
   公共MyMenuItem的getItem(INT位置){//&LT;&LT;子类可以使用亚型覆盖的方法!
       返回menuItems.get(位置);
   }

   @覆盖
   公共查看getView(INT位置,查看convertView,ViewGroup中父){
       ...
       MyMenuItem row_pos =的getItem(位置);
 

使用一个健全的标识方法

  @覆盖
众长getItemId(INT位置){
    返回menuItems.indexOf(的getItem(位置));
}
 

等同于

  @覆盖
众长getItemId(INT位置){
    返回的位置;
}
 

但现在无限更快。 的indexOf(对象)尺度实在太差了与对象的数量。

缓存对象不改变:

  MenuAdapter(上下文的背景下,名单,其中,MyMenuItem&GT;菜单项){
    this.mLayoutInflater = LayoutInflater.from(内容);
    this.mPicasso = Picasso.with(上下文);
}
..
@覆盖
公共查看getView(INT位置,查看convertView,ViewGroup中父){
    如果(convertView == NULL){
        convertView = mInflater.inflate(R.layout.menu_item,NULL);
    ...
    mPicasso
            .load(row_pos.getItem_image_url())
            。走进(holder.ivMenu);
 

2。有没有一种方法,以prevent列表视图项目正在当他们过了屏幕的破坏?

没有(*)。

。(*),那么你基本上可以缓存 getView 如结果在 LruCache(位置,查看) LruCache(MyMenuItem,查看),然后不要触摸 convertView - 他们需要保持未转化的,或者你会杀了你的缓存这些看法。此外

  @覆盖
公众诠释getItemViewType(INT位置){
    返回Adapter.IGNORE_ITEM_VIEW_TYPE;
}
 

似乎是必需的,因为使用code标准的适配器假定的观点它会从知名度都没有了。他们不是与他们搞乱弄乱你的缓存并造成怪异的显示问题对我来说。

3。如果是这样,这将导致问题,以保持过多项目?

是的。不intendend /预计此行为。也有或多或少的什么你获得。你也许可以救你调用 holder.tvMenuHeader.setText()。同样,一个毕加索,但他们都应该立即完成。 毕加索必须有自己的图像缓存了。通过缓存所有的浏览你基本上是添加另一个缓存也包含了所有的图像。我宁愿检查毕加索缓存按预期工作,并持有大部分项目。你可能想用视图缓存做到这一点的唯一原因是需要复杂的设置视图的情况下,因此它成为值得缓存完全构建视图,而不是仅仅是一些内容的部分。

简介

剖析其实可以告诉你,你可以/需要/应有所改善。先来看看在国际海事组织是traceview。如果code块你会看到主线程导致波涛汹涌的滚动列表。如果你正在做复杂的意见,你看到抽签方式执行大部分的时间,配置文件他们。

  • <一个href="http://www.curious-creature.org/docs/android-performance-case-study-1.html">http://www.curious-creature.org/docs/android-performance-case-study-1.html
  • <一个href="http://blog.venmo.com/hf2t3h4x98p5e13z82pl8j66ngcmry/performance-tuning-on-android">http://blog.venmo.com/hf2t3h4x98p5e13z82pl8j66ngcmry/performance-tuning-on-android
  • <一个href="http://www.vogella.com/tutorials/AndroidTools/article.html">http://www.vogella.com/tutorials/AndroidTools/article.html

I created a custom listview layout with images which are loaded from web like this:

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

It works fine when scrolling down. However, when you scroll down, the previous items go out of screen then destroyed. When you try to scroll up again, it gets loaded again (from cache, faster but not instant) which causes a delay and it is not fluent as it should be.

1.Is there an example of how to do this properly?
2.Is there a way to prevent listview items being destroyed when they are out of screen?
3.If so, will it cause problems to keep too many items?

Bellow 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>

解决方案

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

Your code looks pretty close to perfect. The Adapter's getView method is usually the critical path to optimize. Compare for example Picasso's own example SampleListDetailAdapter.java. The important points it (as well as your code) does

  • check for & re-use already inflated views, inflation is expensive.
  • use ViewHolder so you don't have to call findViewById every time. Not terribly expensive on simple views. Also cached afaik.
  • Picasso.with(context).load(url)... each time you need to display an image. This should finish instantly but still use caches and other magic.

There are some minor optimizations you can add, but I doubt that there are noticeable or even measurable changes:

pure style change: use BaseAdapter#getItem(position). This method exists for you only. 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 a sane id method

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

is equivalent to

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

but now infinitely faster. indexOf(Object) scales really badly with the number of objects.

Cache objects that don't 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 listview items being destroyed when they are out of screen?

No(*).

..(*) well you can essentially cache the result of getView e.g. in LruCache(position, View) or LruCache(MyMenuItem, View), then don't touch the convertView - they need to remain unconverted or you would kill those views in your 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 views it removes from visibility are gone. They are not and messing with them messes with your cache and caused weird display problems for me.

3. If so, will it cause problems to keep too many items?

Yes. This behavior is not intendend / expected. There is also more or less nothing you gain. You might be able to save you the call to holder.tvMenuHeader.setText(). Likewise the one to Picasso but both of them should complete instantly. Picasso should have your image cached already. By caching all Views you essentially add another cache that also contains all the images. I would rather check that the picasso cache works as intended and holds most items. The only reason you may want to do it with view caching is for cases that require complicated setup of the view, so it becomes worth caching the completely constructed view rather than just some content parts.

Profile

Profiling can actually tell you where you can / need / should improve. The first to look at IMO is traceview. You'll see if code blocks the main thread which results in choppy list scrolling. If you're doing complicated views and you see that the draw methods are executed most of the time, profile them as well.

这篇关于如何正确地实现一个自定义列表视图使用毕加索库图像?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆