ListView очень медленно работает при прокрутке (используя ViewHolder/ утилизацию)
ОБНОВЛЕНИЕ 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);
}
};
Я не могу понять, почему, поэтому я просто выкладываю код здесь и надеюсь, что у кого-то есть совет для меня =)
Спасибо!
Адаптер, 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 ответов
ОБНОВЛЕНИЕ 2011-08-29 Если я удалю изображение в NodePickup, отставание исчезнет.
Представлению трудно понять, как должен быть представлен макет. Размещенный вами xml не очень помогает. Если вы удалите ImageView, то LinearLayout02 примет всю ширину родительского элемента. Но наличие imageView со стандартными размерами и компоновкой справа будет fill_parent, который сильно запутывает вид. Снова запрашивает размер imageView, чтобы "сдвинуть поля вправо" (вид). Посмотрите на мои предложения ниже
Tip1
используйте вес свойства LinearLayout. Сделайте imageView fill_parent и LinearLayout тоже (для ширины) и поиграйте со свойствами веса. Сделайте это также для вертикального расположения с TextViews. Лучшее решение - поместить фиксированный размер в высоту мысли TextViews. Также рассмотрите возможность изменить вид сверху на RelativeLayout. Сделайте изображение со стандартными размерами, AlignParentLeft и поместите LinearLayout02 в RightOf imageView. Вы значительно облегчите работу с ViewGroup.
Tip2
Кажется, что когда текст меняет высоту, весь вид должен быть перекачан. Обычная техника, чтобы избежать того, чтобы сделать элемент списка фиксированным по высоте. Таким образом, просмотр списка может повторно использовать переработанные представления без повторного заполнения.
Tip3
Дайте вашему LinearLayout02 фиксированную высоту 64dp или Fill_Parent, так как у вас нет свободного места, но Layout не знает об этом и пытается переставлять его самостоятельно каждый раз, так как текст также является 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 >
После оптимизации моего метода getView() для использования держателя и повторного использования convertView, если он не нулевой, мой просмотр списка все еще был довольно медленным. Затем я попробовал
listView.setScrollingCacheEnabled(false);
и это решило это сразу.
Надеюсь, это кому-нибудь поможет.
Я только что обнаружил это, и я хочу поделиться этим с вами, ребята.
Я пробовал ВСЕ решения, но ничего не получалось. Причиной проблемы была длина текста, который я передаю одному из моих представлений TextView, потому что я использую
mTextView.SetText(theLongString)
в моем адаптере в моем методе getView. Теперь, когда я сжал свою строку, список выглядит очень гладко:)
Для повышения производительности просмотра списка используйте оба или один из двух - (Простая реализация)
- 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 немного сложнее, но проверьте ссылку, если вы хотите реализовать AsyncTask вместе с ViewHolder для более плавной прокрутки. Удачи!
Это заняло некоторое время! Я перепробовал все. Отключение кеша прокрутки, viewHolder, cacheColorHint ... но ничего не получалось!
После долгих поисков я нашел корень всего зла!
В моем themes.xml у меня было масштабное фоновое изображение:
<item name="android:windowBackground">@drawable/window_bg</item>
После снятия фона все стало гладким на сливочном масле.
Я надеюсь, что это помогает кому-то!
Попробуйте использовать android:cacheColorHint="#00000000"
для вашего списка. Чтобы улучшить производительность рисования во время операций прокрутки, платформа Android использует подсказку цвета кэша.
Ссылка: статья developer.android.com.
Это может помочь кому-то
Если у вас есть изображение в вашем элементе списка, вы должны помнить, чтобы снизить качество этого изображения. Он загружается за несколько килобайт быстрее, чем несколько мегабайт.
Это помогло мне
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;
}
Вы можете использовать listview.setScrollingCacheEnabled(false). Когда прокручивается область удержания приложения просмотра списка, а затем выбрасывается исключение Out Of Memory(OOM). Мое решение работает для меня.
Загрузите изображение один раз как растровое изображение и примените его к ImageView программно после его надувания. Если вам нужны разные изображения для каждого элемента, вы должны создавать битовые карты асинхронно, как описано в Android: Как оптимизировать ListView с ImageView + 3 TextViews?
У меня была та же проблема раньше, когда я использовал другой макет, такой как 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>