Как обновить Android ListView с динамическими данными в режиме реального времени? - PullRequest
14 голосов
/ 01 мая 2011

У меня есть данные загрузки фонового потока, которые я хочу отобразить в Android ListView. Данные меняются очень часто (то есть 1-2 раза в секунду). Иногда меняется количество строк в наборе данных (но, конечно, не так часто, как данные в ячейках).

Насколько я могу судить, существует два способа обновления данных в ячейках:

  1. Фоновый поток уведомляет поток пользовательского интерфейса о готовности новых данных, и поток пользовательского интерфейса может затем вызвать BaseAdapter.notifyDataSetChanged (). Тем не менее, я читал в нескольких местах, что если этот метод вызывается часто, он будет медленным, потому что ListView должен реструктурировать все свои представления subviews.

  2. Если количество наборов данных не изменилось, я мог бы найти все видимые ячейки ListView, связанные с измененными данными, и обновить значения вручную, не вызывая notifyDataSetChanged (). Это, вероятно, сработает, но я думаю, к сожалению, мне приходится обновлять представления вручную, когда адаптер списка должен обрабатывать уведомления и механизмы обновления для меня, когда я уведомляю об этом. Этот метод также не будет работать, если количество наборов данных меняется со временем (т.е. изменяются не только данные в каждой ячейке ListView, но и общее количество ячеек в ListView, которое может увеличиваться или уменьшаться в зависимости от фонового потока, предоставляющего данные в реальном времени). ).

Я, конечно, был бы признателен за мысли других людей, которые реализовали этот сценарий, и за то, как они оптимизировали простоту кода и, самое главное, производительность.

Ответы [ 4 ]

7 голосов
/ 10 мая 2011

Я экспериментировал с ListView, и вам необходимо обновить ячейки ListView вручную, не вызывая notifyDataSetChanged(), если у вас есть данные в реальном времени и вы хотите, чтобы ListView обновлялся с лучшей производительностью.

notifyDataSetChanged() заставляет ListView перестраивать всю иерархию View очень медленно, если вы вызываете ее часто (то есть раз в секунду).

Примечание (2014 г.).Это НЕ ПРИМЕНЯЕТСЯ, если вы используете обычный современный ListView с шаблоном ViewHolder.Вы просто вызываете notifyDataSetChanged, и это все, что нужно сделать.Это невероятно эффективно, поскольку Android знает, что нужно обновлять только ячейки на экране.

Я реализовал схему, в которой, если размер моего набора данных изменяется с одного обновления на другое, я называю notifyDataSetChanged().Если размер набора данных оставался постоянным от одного обновления к следующему (так, чтобы количество ячеек в ListView было таким же, как и раньше, когда требуется перерисовка данных), то я перебираю ListView.getFirstVisiblePosition(): getLastVisiblePosition() и обновите только видимые ячейки.

2 голосов
/ 01 мая 2011

Однажды я применил фильтр, подобный коду ниже, используя notifyDataSetChanged(), и у меня не было проблем с ним.

Я также изменил вид списка на ходу вручную. Оба сработали хорошо. В некоторых случаях я боюсь изменять данные вручную, потому что это быстрее и потому что это не влияет на весь список.

В любом случае, представления создаются на ходу, когда они должны появиться на экране, и удаляются, когда они покидают экран, поэтому, если вы измените данные, используемые для создания представлений, если пользователь прокручивает ListView, и представления получают Теоретически, вне экрана представления будут создаваться с новыми данными, как только они снова появятся на экране.

Я бы порекомендовал вам попробовать приведенный ниже код, чтобы понять, как работает notifyDataSetChanged(), и решить, работает ли он для вас.

public class ListText extends Activity {


    private ListView lv1;
    private Followed followedFriends[];
    ListView lst;
    EditText edt;
    FollowedFilterableAdapter arrad;

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

        lv1=(ListView)findViewById(R.id.listView1);
        edt = (EditText) findViewById(R.id.editText1);

        followedFriends = new Followed[10];
        followedFriends[0] = new Followed("Alan Walder", "0123456789", "1");
        followedFriends[1] = new Followed("Alberto Levi", "123456789", "1");
        followedFriends[2] = new Followed("David Rodan", "23456789", "1");
        followedFriends[3] = new Followed("David Stern", "3456789", "1");
        followedFriends[4] = new Followed("Elias Jawa", "456789", "1");
        followedFriends[5] = new Followed("Elian Moreno", "56789", "1");
        followedFriends[6] = new Followed("Jonathan Rodan", "6789", "1");
        followedFriends[7] = new Followed("Klara Rodan", "789", "1");
        followedFriends[8] = new Followed("Willy Rosales", "89", "1");
        followedFriends[9] = new Followed("ZZZ ZZZ", "9", "1");


        arrad =  new FollowedFilterableAdapter(followedFriends);
        lv1.setAdapter(arrad);

        addTextChangeList();
    }

    private void addTextChangeList()
    {
        edt.addTextChangedListener(new TextWatcher()
        {


            public void onTextChanged( CharSequence arg0, int arg1, int arg2, int arg3)
            {
                // TODO Auto-generated method stub

            }

            public void beforeTextChanged( CharSequence arg0, int arg1, int arg2, int arg3)
            {
                // TODO Auto-generated method stub

            }



            public void afterTextChanged( Editable arg0)
            {
                // TODO Auto-generated method stub
                ListText.this.arrad.getFilter().filter(arg0);
            }
        });

    }


    ///////////////////////////////////Internal classes ////////////////////////

    private class Followed
    {
        private String _name;
        private String _id;
        private boolean _followed;
        private String _picToDelete = "http://images.wikia.com/tibia/en/images/7/72/Skeleton.gif";

        private Followed(String name, String id, String followed)
        {
            this._name = name;
            this._id = id;
            this._followed = followed.equals("1");
        }

        public String toString(){return _name;}
        public String getName(){return _name;}
        public String getId(){return _id;}
        public boolean idFollowed(){return _followed;}
        public String getURL(){return _picToDelete;}
    }

    /////////////////////////////////////////Adapter//////////////////////////////////

    private class FollowedFilterableAdapter extends BaseAdapter implements Filterable
    {
        /**
         * Lock used to modify the content of {@link #mObjects}. Any write operation
         * performed on the array should be synchronized on this lock. This lock is also
         * used by the filter (see {@link #getFilter()} to make a synchronized copy of
         * the original array of data.
         */
        private final Object _Lock = new Object();

        /*/**
         * Indicates whether or not {@link #notifyDataSetChanged()} must be called whenever
         * {@link #mObjects} is modified.
         */
        //private boolean _NotifyOnChange = true;

        private List<Followed> _Objects;

        private ArrayList<Followed> _followedFriends;
        private ArrayFilter _Filter;

        public FollowedFilterableAdapter(Followed[] followedFriends)
        {
            _Objects = Arrays.asList(followedFriends);
        }

        public int getCount() {
            return _Objects.size();
        }

        public Followed getItem(int position) {
            return _Objects.get(position);
        }

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

        public View getView(int position, View convertView, ViewGroup parent) {
            int px = 2;

            //Creating the CategoryRow that represents the row
            LinearLayout lstItem = new LinearLayout(ListText.this);
            lstItem.setLayoutParams(new ListView.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT,1));
            lstItem.setOrientation(LinearLayout.HORIZONTAL);
            //lstItem.setPadding(px,px,px,px);
            lstItem.setGravity(Gravity.CENTER_VERTICAL);
            /*<LinearLayout android:layout_width="fill_parent"
                            android:layout_height="wrap_content" android:orientation="horizontal"
                            android:padding="2dp" android:gravity="center_vertical">*/

            //Adding the Image
            ImageView icon = new ImageView(ListText.this);
            icon.setLayoutParams(new LayoutParams(50,50));
            icon.setImageResource(R.drawable.icon);
            icon.setScaleType(ScaleType.CENTER_CROP);
            //icon.setImage(tag.getId());
            /*<ImageView android:layout_width="50dp" android:id="@+id/iconFollList"
                                android:src="@drawable/icon" android:layout_height="40dp"></ImageView>*/

            //Adding the Linear Layout for the text
            RelativeLayout lstTextx = new RelativeLayout(ListText.this);
            lstTextx.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT,1));
            lstTextx.setGravity(Gravity.CENTER_VERTICAL);
            lstTextx.setPadding(5, px, px, px);
            /*<LinearLayout android:layout_width="fill_parent"
                                android:layout_height="wrap_content" android:orientation="vertical"
                                android:padding="2dp" android:paddingLeft="5dp">*/

            //Adding the Name of the person who commented
            TextView txtCommenter = new TextView(ListText.this);
            txtCommenter.setLayoutParams(
                    new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
                    LayoutParams.WRAP_CONTENT));
            txtCommenter.setTextSize(10);
            txtCommenter.setTypeface(Typeface.create("Sans Serif", Typeface.BOLD));
            txtCommenter.setText(_Objects.get(position).getName());
            /*<TextView android:text="TextView" android:id="@+id/FollListCategory"
                                    android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>*/


            ImageView btnFollowed = new ImageView(ListText.this);
            RelativeLayout.LayoutParams params = 
                new RelativeLayout.LayoutParams(80,30);
            params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
            btnFollowed.setLayoutParams(params);
            btnFollowed.setImageResource(R.drawable.icon);
            btnFollowed.setScaleType(ScaleType.FIT_XY);

            //Arming the View
            lstItem.addView(icon, 0);
            lstTextx.addView(txtCommenter, 0);
            lstTextx.addView(btnFollowed,1);
            lstItem.addView(lstTextx,1);

            return lstItem;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void notifyDataSetChanged() {
            super.notifyDataSetChanged();
            //_NotifyOnChange = true;
        }

        /*public void setNotifyOnChange(boolean notifyOnChange) {
            _NotifyOnChange = notifyOnChange;
        }*/

        public Filter getFilter() {
            if (_Filter == null) {
                _Filter = new ArrayFilter();
            }
            return _Filter;
        }

        /////////////////////Second Level Internal classes //////////////////////////

        private class ArrayFilter extends Filter {
            @Override
            protected FilterResults performFiltering(CharSequence prefix) {
                FilterResults results = new FilterResults();

                if (_followedFriends == null) {
                    synchronized (_Lock) {
                        _followedFriends = new ArrayList<Followed>(_Objects);
                    }
                }

                if (prefix == null || prefix.length() == 0) {
                    synchronized (_Lock) {
                        ArrayList<Followed> list = new ArrayList<Followed>(_followedFriends);
                        results.values = list;
                        results.count = list.size();
                    }
                } else {
                    String prefixString = prefix.toString().toLowerCase();

                    final ArrayList<Followed> values = _followedFriends;
                    final int count = values.size();

                    final ArrayList<Followed> newValues = new ArrayList<Followed>(count);

                    for (int i = 0; i < count; i++) {
                        final Followed value = values.get(i);
                        final String valueText = value.toString().toLowerCase();

                        // First match against the whole, non-splitted value
                        if (valueText.startsWith(prefixString)) {
                            newValues.add(value);
                        } else {
                            final String[] words = valueText.split(" ");
                            final int wordCount = words.length;

                            for (int k = 0; k < wordCount; k++) {
                                if (words[k].startsWith(prefixString)) {
                                    newValues.add(value);
                                    break;
                                }
                            }
                        }
                    }

                    results.values = newValues;
                    results.count = newValues.size();
                }

                return results;
            }

            @Override
            protected void publishResults(CharSequence constraint, FilterResults results) {
                //no inspection unchecked
                _Objects = (List<Followed>) results.values;
                if (results.count > 0) {
                    notifyDataSetChanged();
                } else {
                    notifyDataSetInvalidated();
                }
            }
        }
    }

XML

    <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    <EditText android:text="" android:layout_height="wrap_content"
    android:id="@+id/editText1" android:layout_width="match_parent"></EditText>
    <ListView android:id="@+id/listView1" android:layout_height="fill_parent"
    android:layout_width="match_parent" android:layout_weight="1"></ListView>

</LinearLayout>

Надеюсь, это поможет.

1 голос
/ 01 мая 2011

Может быть, вы можете установить обработчик в потоке пользовательского интерфейса.Вам нужно создать класс, который реализует Runnable.Передайте ArrayList этому классу.В методе run () создайте адаптер с ArrayList в качестве параметра, затем выполните setAapter в ListView.Вот и все.Вы сделалиЧтобы запустить ваш обработчик: просто из рабочего потока: handler.Post (new MyUpdateToUI ());Вот и все.Надеюсь, это достаточно эффективно для вас?

0 голосов
/ 02 июля 2018

Я столкнулся с очень похожей ситуацией, когда я сохранял текстовые представления представления списка в хэш-карте, где у каждого текстового просмотра был тег для его идентификации.Когда поступили потоковые данные, все, что я сделал, это опубликовал работоспособный текст в просмотре текста после того, как нашел правильный и получил их в кратчайшие сроки.Однако, хотя обновления были мгновенными, пользовательский интерфейс столкнулся с трудностями и стал вялым, когда я обновлял около 30 просмотров текста в секунду.У меня был Listview во фрагменте внутри ViewPager, и казалось, что поток пользовательского интерфейса был забит и заблокирован, когда потоковая передача была включена.Следовательно, я настоятельно рекомендую не обновлять текстовые представления в представлении Listview вручную, если это касается взаимодействия с пользователем и производительности потока пользовательского интерфейса

...