滚动时ListView非常慢(使用ViewHolder / recycle)

时间:2011-08-28 21:57:20

标签: android listview

更新2011-08-29 如果我删除NodePickup中的图像,则拉扯会消失。

问题是 - 为什么?

----

我又回来尝试一些Android开发了。我有一个“旧的”HTC Hero手机,所以我启动了一个,做了一些更新,现在再次运行Eclipse和其他。

我在设备上运行了Android 2.1。

我做了一个非常简单的测试应用程序,除了显示一些活动等之外什么都不做。即使没有数据库连接,也没有从任何网络获取数据的应用程序非常慢。非常慢。

例如,我有一个带有一些自定义布局项的ListView。当只添加6-7个项目(这样我滚动)时,滚动时速度非常慢。此外,我有一些按钮可以更改活动,而且非常慢:

mButtonListenerUPP = new OnClickListener() {
    @Override
    public void onClick(View v)
    {
        Intent myIntent = new Intent(BaseActivity.this, ActivityMain.class);
        BaseActivity.this.startActivity(myIntent);
    }
};

我无法弄清楚为什么,所以我只是在这里发布代码并希望有人给我一些提示=)

THX!

适配器,NodeRowAdapter

import java.util.ArrayList;
import java.util.List;

import android.app.Activity;
import android.content.Context;
import android.view.*;
import android.widget.ArrayAdapter;

import android.widget.TextView;

public class NodeRowAdapter extends ArrayAdapter 
{
    private Activity context;
    private ArrayList<Node> mList;
    private LayoutInflater inflater;

    NodeRowAdapter(Activity context, ArrayList<Node> objects) 
    {
        super(context, R.layout.nodepickup, objects);
        this.context=context;
        mList = objects;
        inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }

    class ViewHolder 
    {
        TextView name;
        TextView time;
        TextView road;
        Node node;
    }

    public Node getNode(int position)
    {
        if (mList != null && mList.size() > position)
            return mList.get(position);
        else
            return null;
    }
    public View getView(int position, View convertView, ViewGroup parent) 
    {
        View view = convertView;
        ViewHolder holder;
        if (view == null)
        {
            view = inflater.inflate(R.layout.nodepickup, parent, false);
            holder = new ViewHolder();
            holder.name =(TextView)view.findViewById(R.id.name);
            holder.time =(TextView)view.findViewById(R.id.time);
            holder.road =(TextView)view.findViewById(R.id.road);
            view.setTag(holder);
        }
        else
        {
            holder = (ViewHolder) convertView.getTag();
        }

        Node node = mList.get(position);
        holder.name.setText(node.name);
        holder.time.setText(node.time);
        holder.road.setText(node.road);

        return(view);
    }
}

主要活动,ActivityMain

public class ActivityMain extends BaseActivity 
{
    private NodeRowAdapter _nodeRowAdapter;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) 
    {
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        SICApplication._myContext = this;
        SICApplication._myContext = this;

        _nodeRowAdapter = new NodeRowAdapter(this, SICApplication.dataHolder_activityMain._nodes);
        ListView listView1 = (ListView) findViewById(R.id.ListViewNodes);
        listView1.setOnItemClickListener(new OnItemClickListener() 
        {
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) 
            {
                Node node = _nodeRowAdapter.getNode(position);
                Log.v("MyApp", "Node=" + node.name);
            } 
        });
        listView1.setAdapter(_nodeRowAdapter);  

    }

    /* Handles item selections */
    public boolean onOptionsItemSelected(MenuItem item) 
    {
        switch (item.getItemId()) 
        {
            case R.id.add_item:
                addNodeItem();
                return true;
        }
        return false;
    }



    private void addNodeItem()
    {
        _nodeRowAdapter.add(new Node("Test", "asd asd ", "14:00", 1));

    }
}

自定义列表项NodePickup

public class NodePickup extends LinearLayout
{
    public NodePickup(Context context, AttributeSet attributeSet)
    {
        super(context, attributeSet);

        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        inflater.inflate(R.layout.nodepickup, this);

        this.setOnClickListener(new OnClickListener() 
        {
            @Override
            public void onClick(View v)
            {
                AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
                builder.setMessage("Ajabaja!")
                .setCancelable(true)
                .setPositiveButton("JA!", new DialogInterface.OnClickListener() 
                {
                   public void onClick(DialogInterface dialog, int id) 
                   {
                       dialog.cancel();
                   }
                });
                builder.show();
            }

        });
    }
}

最后,NodePickup XML布局

<LinearLayout 
    android:id="@+id/LinearLayout01" 
    android:layout_width="fill_parent" 
    android:layout_height="64dip"
    android:orientation="horizontal" 
    android:background="@drawable/stateful_background"
    xmlns:android="http://schemas.android.com/apk/res/android">

    <ImageView 
        android:id="@+id/ImageView01" 
        android:layout_width="40dip" 
        android:layout_height="40dip"
        android:src="@drawable/arrow_up_green"
        android:background="@android:color/transparent">
    </ImageView>

    <LinearLayout 
        android:id="@+id/LinearLayout02"
        android:background="@android:color/transparent"
        android:layout_width="fill_parent" 
        android:layout_height="wrap_content" 
        android:orientation="vertical"
        xmlns:android="http://schemas.android.com/apk/res/android">

        <TextView
            android:text="14:46 (15 min)"
            android:id="@+id/time"
            android:textSize="12dip"
            android:textColor = "#000000"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:background="@android:color/transparent">
        </TextView>

        <TextView
            android:text="test"
            android:id="@+id/road"
            android:textSize="12dip"
            android:textColor = "#000000"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:background="@android:color/transparent">
        </TextView>

        <TextView
            android:text="test test"
            android:id="@+id/name"
            android:textSize="12dip"
            android:textColor = "#000000"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:background="@android:color/transparent">
        </TextView>
    </LinearLayout>
</LinearLayout>

10 个答案:

答案 0 :(得分:17)

  

更新2011-08-29如果我删除NodePickup中的图像,那么懒散就会消失。

该视图很难确定应如何呈现布局。您发布的xml没有多大帮助。如果删除ImageView,则LinearLayout02将占用父级的所有宽度。但是使用带有标准尺寸的imageView和右边的布局会使fill_parent混淆视图。再次请求imageView的大小“将边距向右推”(种类)。看看下面的建议

<强>提示1

使用LinearLayout属性权重。同时制作imageView fill_parent和LinearLayout(宽度)并使用权重属性。 也可以使用TextViews进行垂直布局。最好的解决方案是将固定大小放在TextViews思想的高度上。 还要考虑将顶视图更改为RelativeLayout。使用标准尺寸,AlignParentLeft制作图像,并将LinearLayout02设置为RightOf imageView。你将大量放宽ViewGroup的onMeasure。

<强>提示2

似乎当文本改变高度时,整个视图需要重新充气。一种常见的技术,以避免它使列表项固定在高度。因此列表视图可以重用循环视图而不重新填充。

<强>提示3

为你的LinearLayout02提供64dp或Fill_Parent的固定高度,因为你没有任何剩余空间,但是布局不知道并且每次都尝试重新排列它,因为文本也是Wrap_content。

另外你说如果你删除了ImageView,一切都很快。如果上面没有任何效果你能试试吗?因为您知道imageView大小是固定的。

扩展imageView并覆盖requestLayout()方法。

public class MyImageView extends ImageView {

public PImageView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);

}

public PImageView(Context context, AttributeSet attrs) {
    super(context, attrs);

}

public PImageView(Context context) {
    super(context);

}

@Override
    public void requestLayout() {
        /*
         * Do nothing here
         */
    }
}

现在将MyImageView小部件包含在您的XML中。

<com.package_name.MyImageView 
        android:id="@+id/ImageView01" 
        android:layout_width="40dip" 
        android:layout_height="40dip"
        android:src="@drawable/arrow_up_green"
        android:background="@android:color/transparent">
 </com.package_name.MyImageView >

答案 1 :(得分:15)

优化我的getView()方法以使用持有者并重用convertView,如果它不为null,我的listview仍然相当滞后。 然后,我试过了

listView.setScrollingCacheEnabled(false);

它立即解决了。

希望这有助于某人。

答案 2 :(得分:6)

我刚刚发现了这个,我想与你们分享。

我尝试了所有提供的解决方案但没有任何效果。导致问题的原因是我正在为我的一个TextView视图提供文本的长度,因为我正在使用

mTextView.SetText(theLongString)

在我的getView方法的适配器中。现在我收缩了我的String,listview非常顺利:)

答案 3 :(得分:1)

花了一段时间!我尝试了一切。禁用滚动缓存,viewHolder,cacheColorHint ......但没有任何效果!

经过几个小时的搜索,我找到了万恶之源!

在我的themes.xml中,我有一个缩放背景图片:

<item name="android:windowBackground">@drawable/window_bg</item>

去除beackground后,一切都是黄油光滑的。

我希望这有助于某人!

答案 4 :(得分:1)

要提高listview的性能,请使用两者之一或两者中的任何一个 - (简单实现)

  • ViewHolder
  • AsyncTask(一个单独的线程)

如果您有适度长的列表,我推荐使用ViewHolder,对于大型列表(如音乐播放器),我建议使用ViewHolder和AsyncTask。平滑ListView滚动的关键是尽可能减少内存消耗。

要实现ViewHolder,很简单。只需在自定义适配器中创建一个静态类,它包含通过findViewById找到的所有视图。所以它应该是这样的 -

static class ViewHolder
{
TextView text;
TextView timestamp;
ImageView icon;
ProgressBar progress;
int position;
}

然后,下次你需要查找ViewById中的任何一个视图时,只需像这样引用ViewHolder -

ViewHolder holder = new ViewHolder();
holder.icon = (ImageView) yourView.findViewById(R.id.listitem_image);
holder.text = (TextView) yourView.findViewById(R.id.listitem_text);
holder.timestamp = (TextView) yourView.findViewById(R.id.listitem_timestamp);
holder.progress = (ProgressBar) yourView.findViewById(R.id.progress_spinner);

这是经过测试的代码,取自我的一个项目。但是,原始来源位于此处 - http://developer.android.com/training/improving-layouts/smooth-scrolling.html

使用ViewHolder,列表变得更加流畅。使用AsyncTask有点复杂,但如果您希望与ViewHolder一起实现AsyncTask以获得更流畅的滚动体验,请查看链接。祝你好运!

答案 5 :(得分:0)

将图像作为位图加载一次,然后在充气后以编程方式将其应用于ImageView。如果每个项目需要不同的图像,则应该像Android: How to optimize ListView with ImageView + 3 TextViews?

中所述异步创建位图。

答案 6 :(得分:0)

尝试使用android:cacheColorHint="#00000000"作为列表视图。为了提高滚动操作期间的绘图性能,Android框架重用了缓存颜色提示。

参考:developer.android.com文章。

答案 7 :(得分:0)

这可能会帮助一些人

如果列表项中有图像,则必须记住降低图像的质量。加载几Kb而不是几兆字节的速度更快。

这对我有帮助

 public Bitmap MakeFileSmaller_ToBitmap(File f) {
        try {
            // Decode image size
            BitmapFactory.Options o = new BitmapFactory.Options();
            o.inJustDecodeBounds = true;
            BitmapFactory.decodeStream(new FileInputStream(f), null, o);

            // The new size we want to scale to
            final int REQUIRED_SIZE=200;

            // Find the correct scale value. It should be the power of 2.
            int scale = 1;
            while(o.outWidth / scale / 2 >= REQUIRED_SIZE &&
                    o.outHeight / scale / 2 >= REQUIRED_SIZE) {
                scale *= 2;
            }

            // Decode with inSampleSize
            BitmapFactory.Options o2 = new BitmapFactory.Options();
            o2.inSampleSize = scale;
            return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
        } catch (FileNotFoundException e) {
            Log.d(TAG, "FILE NOT FOUND " );
        }
        Log.d(TAG, "OTHER EXCEPTION");

        return null;
    }

答案 8 :(得分:0)

之前我遇到了同样的问题,而我使用不同的布局,如LinearLayout,RelativeLayout,CardView作为同一列表视图项目中不同子项的父级。我通过更改RelativeLayout中的所有视图解决了这个问题。

使用RelativeLayout作为主要内容,它的子布局可能会提高加载每个项目的速度。所以滚动会很顺利。

<RelativeLayout
android:id="@+id/LinearLayout01"
android:layout_width="fill_parent"
android:layout_height="64dip"
android:orientation="horizontal"
android:background="@drawable/stateful_background"
xmlns:android="http://schemas.android.com/apk/res/android">
<RelativeLayout
    android:layout_width="40dip"
    android:layout_height="40dip"
    android:layout_alignParentTop="true"
    android:layout_alignParentLeft="true"
    android:layout_alignParentStart="true"
    android:id="@+id/relativeLayout">
    <ImageView
        android:id="@+id/ImageView01"
        android:layout_width="40dip"
        android:layout_height="40dip"
        android:src="@drawable/arrow_up_green"
        android:background="@android:color/transparent">
    </ImageView>
</RelativeLayout>
<TextView
    android:text="14:46 (15 min)"
    android:id="@+id/time"
    android:textSize="12dip"
    android:textColor = "#000000"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:background="@android:color/transparent"
    android:layout_alignParentTop="true"
    android:layout_toRightOf="@+id/relativeLayout"
    android:layout_toEndOf="@+id/relativeLayout" />
<TextView
    android:text="test"
    android:id="@+id/road"
    android:textSize="12dip"
    android:textColor = "#000000"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:background="@android:color/transparent"
    android:layout_below="@+id/time"
    android:layout_toRightOf="@+id/relativeLayout"
    android:layout_toEndOf="@+id/relativeLayout" />
<TextView
    android:text="test test"
    android:id="@+id/name"
    android:textSize="12dip"
    android:textColor = "#000000"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:background="@android:color/transparent"
    android:layout_below="@+id/road"
    android:layout_toRightOf="@+id/relativeLayout"
    android:layout_toEndOf="@+id/relativeLayout"/></RelativeLayout>

答案 9 :(得分:0)

你可以使用 listview.setScrollingCacheEnabled(false)。当滚动listview应用程序保持区域然后抛出内存(OOM)异常。我的解决方案对我有效。