Ленивый ListView

SQLite, Preferences, файлы, SD, Content Provider, XML, JSON
Ответить
apal
Сообщения: 14
Зарегистрирован: 12 авг 2014, 14:29

Ленивый ListView

Сообщение apal » 20 окт 2014, 16:41

Всем доброго времени суток.

Имеется сервер приложения который находится "где-то" в Интернет. Приложение соответственно работает с этим сервером. На сервере есть список объектов, на клиенте есть ListView, который призван отображать этот список. Сервер на запрос клиента возвращает список элементов в JSon.
Задача:
- обеспечить заполнение ListView порционно, т.е. сразу не грузить весь список объектов с сервера, а только видимые элементы и при прокрутке списка догружать

Сделал наследника от BaseAdapter:
(код на C#, но я думаю никого это не смутит)

Код: Выделить всё

public class FacilityListViewAdapter : BaseAdapter
    {
        private readonly LayoutInflater _inflater;
        private readonly int _typeId;

        private List<Facility> list = new List<Facility>();

        public FacilityListViewAdapter(int countItems,LayoutInflater inflater, int typeId)
        {
            _inflater = inflater;
            _typeId = typeId;
            for (int i = 0; i < countItems; i++)
            {
                list.Add(new Facility());
            }
        }

        public override Java.Lang.Object GetItem(int position)
        {
            return null;
        }

        public override long GetItemId(int position)
        {
            return position;
        }
        
        public override View GetView(int position, View convertView, ViewGroup parent)
        {
            View vi = convertView;
            if (vi == null)
            {
                vi = _inflater.Inflate(Resource.Layout.FacilityItem, null);
            }
            else
            {
                return vi;
            }
            

            LinearLayout llData = vi.FindViewById<LinearLayout>(Resource.Id.llData);
            llData.Visibility = ViewStates.Invisible;

            LinearLayout llLoading = vi.FindViewById<LinearLayout>(Resource.Id.llLoading);
            llLoading.Visibility = ViewStates.Visible;

            TextView tvFacilityTitle = vi.FindViewById<TextView>(Resource.Id.tvFacilityTitle);
            TextView tvFacilityId = vi.FindViewById<TextView>(Resource.Id.tvFacilityId);

            ImageView ivLogo = vi.FindViewById<ImageView>(Resource.Id.ivLogo);

            TextView tvRating = vi.FindViewById<TextView>(Resource.Id.tvRating);
            TextView tvWorkTime = vi.FindViewById<TextView>(Resource.Id.tvWorkTime);
            TextView tvWorkTimeState = vi.FindViewById<TextView>(Resource.Id.tvWorkTimeState);

            BackgroundWorker background = new BackgroundWorker();
            background.DoWork += (sender, args) =>
            {
                if (list[position].Id == 0)
                {
                ResponseObject responseObject =
                    FacilityProvider.Instance.GetFacilitiesByType(App.Instance.UserId, _typeId,
                        string.Empty, list[position].Id, 1);

                if (responseObject != null)
                {
                    if (responseObject.State != ResponseObject.StateResponse.Fail)
                    {
                        var facilities = ((List<Facility>) responseObject.Data);
                        if (facilities.Count > 0)
                        {
                            list[position] = facilities[0];
                        }
                    }
                    else
                    {
                        return;
                    }
                }
                }

                if (list[position].Id > 0)
                {
                int rating = RatingProvider.Instance.GetRating(App.Instance.UserId, list[position].Id,
                    OwnerTypes.Facility);

                args.Result = rating;
                }
            };

            background.RunWorkerCompleted += (sender, args) =>
            {
                tvFacilityTitle.Text = list[position].Title;
                tvFacilityId.Text = list[position].Id.ToString();
                UrlImageViewHelper.UrlImageViewHelper.SetUrlDrawable((ivLogo),
                    ServerProvider.App.Instance.ServerImageUrl + list[position].IconURL);

                if (args.Result != null)
                {
                    tvRating.Text = args.Result.ToString();
                }
                else
                {
                    tvRating.Text = "0";
                }

                tvWorkTime.Text =
                    string.Format("{0}-{1}", list[position].WorkTimeStart.ToString("t"),
                        list[position].WorkTimeEnd.ToString("t"));

                var now = DateTime.Now;
                if (now.TimeOfDay >= list[position].WorkTimeStart.TimeOfDay &&
                    now.TimeOfDay <= list[position].WorkTimeEnd.TimeOfDay)
                {
                    tvWorkTimeState.Text = "Открыто";
                    tvWorkTimeState.SetTextColor(Android.Graphics.Color.Green);
                }
                else
                {
                    tvWorkTimeState.Text = "Закрыто";
                    tvWorkTimeState.SetTextColor(Android.Graphics.Color.Red);
                }

                llLoading.Visibility = ViewStates.Invisible;
                llData.Visibility = ViewStates.Visible;
            };
            background.RunWorkerAsync(position);
            return vi;
        }

        public override int Count
        {
            get { return list.Count; }
        }

        public void SetNewCount(int count)
        {
            if (count > list.Count)
            {
                for (int i = 0; i < count - list.Count; i++)
                {
                    list.Add(new Facility());
                }
            }

            this.NotifyDataSetChanged();
        }
    }
В методе GetView, если convertView не известен, у сервера запрашивается объект основываясь на параметре position, делается это асинхронно и пока результат не получен пользователь видит ProgressBar и как только загрузка завершена, ProgressBar скрывается. А если convertView известен - отдается как есть. Метод SetNewCount вызывается когда пользователь прокручивает список, новое количество элементов сообщается списку и он его грузит.

В принципе код задачу свою выполняет, НО по какой-то неизвестной мне причине, в списке дублируются элементы, хаотичный порядок и при прокрутке списка "туда - сюда" порядок элементов меняется. Я подозреваю что дело в параметре position метода GetView, но что конкретно не могу разобраться.

Подскажите пожалуйста что я делаю не так, может использую не тот адаптер или не понимаю принцип работы метода GetView, а может вообще "изобретаю велосипед" и все давно уже сделано?

Аватара пользователя
altwin
Сообщения: 1951
Зарегистрирован: 13 ноя 2013, 14:46

Re: Ленивый ListView

Сообщение altwin » 20 окт 2014, 18:55

Изображение

apal
Сообщения: 14
Зарегистрирован: 12 авг 2014, 14:29

Re: Ленивый ListView

Сообщение apal » 20 окт 2014, 19:30

Спасибо. Но там объясняется как грузить картинки, у меня с ними проблем нет.У меня проблемы с порядком отображения и дублированием объектов в списке.

apal
Сообщения: 14
Зарегистрирован: 12 авг 2014, 14:29

Re: Ленивый ListView

Сообщение apal » 21 окт 2014, 14:44

Решение найдено.
Во первых я переделал адаптер используя паттерн ViewHolder, но это не дало нужного результата.
Вот код последней реализации:

Код: Выделить всё

public class FacilityListViewAdapter : BaseAdapter<Facility>
    {
        private class ViewHolder : Java.Lang.Object
        {
            public LinearLayout _llData;
            public LinearLayout _llLoading;
            public TextView _tvFacilityTitle;
            public TextView _tvFacilityId;
            public ImageView _ivLogo;
            public TextView _tvRating;
            public TextView _tvWorkTime;
            public TextView _tvWorkTimeState;
        }

        private class AsyncLoadFacilityItem : AsyncTask<int,int,Facility>
        {
            private readonly FacilityListViewAdapter _adapter;
            private readonly int _typeId;
            private readonly int _position;
            private readonly LinearLayout _llData;
            private readonly LinearLayout _llLoading;
            private readonly TextView _tvFacilityTitle;
            private readonly TextView _tvFacilityId;
            private readonly ImageView _ivLogo;
            private readonly TextView _tvRating;
            private readonly TextView _tvWorkTime;
            private readonly TextView _tvWorkTimeState;

            public AsyncLoadFacilityItem(FacilityListViewAdapter adapter, int typeId, int position, LinearLayout llData, LinearLayout llLoading, TextView tvFacilityTitle, TextView tvFacilityId, 
                ImageView ivLogo, TextView tvRating, TextView tvWorkTime, TextView tvWorkTimeState)
            {
                _adapter = adapter;
                _typeId = typeId;
                _position = position;
                _llData = llData;
                _llLoading = llLoading;
                _tvFacilityTitle = tvFacilityTitle;
                _tvFacilityId = tvFacilityId;
                _ivLogo = ivLogo;
                _tvRating = tvRating;
                _tvWorkTime = tvWorkTime;
                _tvWorkTimeState = tvWorkTimeState;
            }

            protected override Facility RunInBackground(params int[] @params)
            {
                ResponseObject responseObject =
                    FacilityProvider.Instance.GetFacilitiesByType(App.Instance.UserId, _typeId,
                        string.Empty, _position, 1);

                if (responseObject != null)
                {
                    if (responseObject.State != ResponseObject.StateResponse.Fail)
                    {
                        var facilities = ((List<Facility>)responseObject.Data);
                        if (facilities.Count > 0)
                        {
                            return facilities[0];
                        }
                    }
                }

                return null;
            }

            protected override void OnPostExecute(Facility result)
            {
                if (result == null)
                {
                    return;
                }

                _adapter.list[_position] = result;
                _tvFacilityTitle.Text = result.Title;
                _tvFacilityId.Text = result.Id.ToString();
                UrlImageViewHelper.UrlImageViewHelper.SetUrlDrawable(_ivLogo,
                    ServerProvider.App.Instance.ServerImageUrl + result.IconURL);

                //if (args.Result != null)
                //{
                //    tvRating.Text = args.Result.ToString();
                //}
                //else
                //{
                //    tvRating.Text = "0";
                //}

                _tvWorkTime.Text =
                    string.Format("{0}-{1}", result.WorkTimeStart.ToString("t"),
                        result.WorkTimeEnd.ToString("t"));

                var now = DateTime.Now;
                if (now.TimeOfDay >= result.WorkTimeStart.TimeOfDay &&
                    now.TimeOfDay <= result.WorkTimeEnd.TimeOfDay)
                {
                    _tvWorkTimeState.Text = "Открыто";
                    _tvWorkTimeState.SetTextColor(Android.Graphics.Color.Green);
                }
                else
                {
                    _tvWorkTimeState.Text = "Закрыто";
                    _tvWorkTimeState.SetTextColor(Android.Graphics.Color.Red);
                }

                _llLoading.Visibility = ViewStates.Invisible;
                _llData.Visibility = ViewStates.Visible;
            }
        }

        private readonly LayoutInflater _inflater;
        private readonly int _typeId;

        public Facility[] list;

        public FacilityListViewAdapter(int countItems,LayoutInflater inflater, int typeId)
        {
            _inflater = inflater;
            _typeId = typeId;
            list = new Facility[countItems];
        }

        public override Facility this[int position]
        {
            get { return list[position]; }
        }

        public override long GetItemId(int position)
        {
            return position;
        }
        
        public override View GetView(int position, View convertView, ViewGroup parent)
        {
            Facility cahedFacility = list[position];
            View vi = convertView;
            ViewHolder viewHolder;
            if (vi == null)
            {
                vi = _inflater.Inflate(Resource.Layout.FacilityItem, null);
                viewHolder = new ViewHolder();

                viewHolder._llData = vi.FindViewById<LinearLayout>(Resource.Id.llData);
                viewHolder._llLoading = vi.FindViewById<LinearLayout>(Resource.Id.llLoading);

                viewHolder._ivLogo = vi.FindViewById<ImageView>(Resource.Id.ivLogo);

                viewHolder._tvFacilityTitle = vi.FindViewById<TextView>(Resource.Id.tvFacilityTitle);
                viewHolder._tvFacilityId = vi.FindViewById<TextView>(Resource.Id.tvFacilityId);
                viewHolder._tvRating = vi.FindViewById<TextView>(Resource.Id.tvRating);
                viewHolder._tvWorkTime = vi.FindViewById<TextView>(Resource.Id.tvWorkTime);
                viewHolder._tvWorkTimeState = vi.FindViewById<TextView>(Resource.Id.tvWorkTimeState);
                vi.Tag = viewHolder;
            }
            else
            {
                viewHolder = (ViewHolder)vi.Tag;
            }


            if (cahedFacility == null)
            {
                viewHolder._llData.Visibility = ViewStates.Invisible;
                viewHolder._llLoading.Visibility = ViewStates.Visible;
                new AsyncLoadFacilityItem(this, _typeId, position, viewHolder._llData, viewHolder._llLoading, viewHolder._tvFacilityTitle, viewHolder._tvFacilityId, viewHolder._ivLogo,
                viewHolder._tvRating, viewHolder._tvWorkTime, viewHolder._tvWorkTimeState).Execute(0);
            }
            else
            {
                UrlImageViewHelper.UrlImageViewHelper.SetUrlDrawable(viewHolder._ivLogo,
                    ServerProvider.App.Instance.ServerImageUrl + cahedFacility.IconURL);

                viewHolder._tvFacilityTitle.Text = cahedFacility.Title;
                viewHolder._tvFacilityId.Text = cahedFacility.Id.ToString();
                //viewHolder._tvRating.Text
                viewHolder._tvWorkTime.Text = string.Format("{0}-{1}", cahedFacility.WorkTimeStart.ToString("t"),
                        cahedFacility.WorkTimeEnd.ToString("t"));
                
                    var now = DateTime.Now;
                    if (now.TimeOfDay >= cahedFacility.WorkTimeStart.TimeOfDay &&
                    now.TimeOfDay <= cahedFacility.WorkTimeEnd.TimeOfDay)
                {
                    viewHolder._tvWorkTimeState.Text = "Открыто";
                    viewHolder._tvWorkTimeState.SetTextColor(Android.Graphics.Color.Green);
                }
                else
                {
                    viewHolder._tvWorkTimeState.Text = "Закрыто";
                    viewHolder._tvWorkTimeState.SetTextColor(Android.Graphics.Color.Red);
                }
            }
            return vi;
        }

        public override int Count
        {
            get { return list.Length; }
        }
    }
А во вторых, и это решило мою проблему, перед созданием адаптера я выяснил количество элементов списка, передал их адаптеру и в нем создал массив по заданному размеру. После этого все взлетело. Это конечно не очень хорошо, т.к. не понятно как быть с бесконечным ListView, но пока мне этого достаточно.
Всем спасибо.

ihitmani
Сообщения: 8
Зарегистрирован: 26 ноя 2014, 01:04

Re: Ленивый ListView

Сообщение ihitmani » 26 ноя 2014, 11:10

при чем тут адаптер скажи плиз?? нужно ScrollListener наследовать, и в onScroll дергать лоадер. ха) это вечная проблема с этими ленивыми списками))то тормозит то еще что то ))

Код: Выделить всё

 @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        // If the total item count is zero and the previous isn't, assume the
        // list is invalidated and should be reset back to initial state
        if (totalItemCount < previousTotalItemCount) {
            this.currentPage = this.startingPageIndex;
            this.previousTotalItemCount = totalItemCount;
            if (totalItemCount == 0) {
                this.loading = true;
            }
        }

        // If it’s still loading, we check to see if the dataset count has
        // changed, if so we conclude it has finished loading and update the current page
        // number and total item count.
        if (loading && (totalItemCount > previousTotalItemCount)) {
            loading = false;
            previousTotalItemCount = totalItemCount;
            currentPage++;
        }

        // If it isn’t currently loading, we check to see if we have breached
        // the visibleThreshold and need to reload more data.
        // If we do need to reload some more data, we execute onLoadMore to fetch the data.
        if (!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold)) {
            onLoadMore(currentPage + 1, totalItemCount);
            loading = true;
        }
    }

    // Defines the processMini for actually loading more data based on page
    public abstract void onLoadMore(int page, int totalItemsCount);
както так. нашел когдато на https://stackoverflow.com/

Mykola
Сообщения: 132
Зарегистрирован: 26 июл 2013, 12:06

Re: Ленивый ListView

Сообщение Mykola » 27 ноя 2014, 12:00

https://github.com/commonsguy/cwac-endless
если будет нужен курсор адаптер, обращайтесь

Ответить