Фоновая подгрузка списков

При загрузке больших списков из медленного источника (например HTTP запрос) нет смысла загружать весь список сразу, особенно если пользователю потребуется лишь небольшое количество данных (результаты поиска, новостные статьи). В этом случае целесообразно реализовать постраничную подгрузку списка. В этой статье показан простой способ с использованием footer view контрола ListView. Подразумевается, что у нас уже есть список и его адаптер, который загружает элементы (пусть несколько первых, или весь) используя например ассинхронный HTTP запрос. Удобнее вынести запрос данных в отдельный класс, в котором добавить возможность подгрузки очередной страницы.

План действий


  1. Подготавливаем layout для элемента сообщения о процессе загрузки
  2. Подготавливаем контрол ListView
  3. Добавляем в адаптер списка код запуска загрузки следующей страницы
  4. Обрабатываем результат загрузки очередной страницы


Подготавливаем layout для элемента сообщения о процессе загрузки


Создаем layout xml с двумя элементами ProgressBar с установленным флагом indeterminateOnly и TextView с текстом «Идет загрузка...». Помещаем их в горизонтальный LinearLayout. Можно использовать и уже готовый layout для пустого списка для информации о начальной загрузке.

Подготавливаем ListView


Загружаем подготовленный layout и устанавливаем его подвальным для нашего ListView. Это надо сделать до вызова метода setAdapter. Не забудем сохранить ссылку на элемент, чтобы удалить при достижении конца списка.


	private LinearLayout mLoadingFooter;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
	...
	    LayoutInflater layoutInflater = (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
	    mLoadingFooter = (LinearLayout) layoutInflater.inflate(R.layout.loading, null);

	    mList = (ListView) findViewById(R.id.list);
	    mList.addFooterView(mLoadingFooter);
	...
	}
	


Добавляем в адаптер списка код запуска загрузки следующей страницы


Для запуска загрузки следующей страницы используем метод getView адаптера списка: если затребован последний элемент списка, то время грузить следующую страницу. Процесс загрузки удобно убрать в отдельный тред и затем кинуть Intent в текущую активити. Не забудем вызывать notifyDataSetInvalidated если во время загрузки данные, к которым обращается адаптер будут изменяться.

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
	...
	    if (position == getCount() - 1 && hasNextPage()) {
	        loadNextPage();
	    }
	}
	

UPD: grishkaa рекомендовал более правильный способ с использованием AbsListView.OnScrollListener:

	mList.setOnScrollListener(new OnScrollListener() {

	    @Override
	    public void onScrollStateChanged(AbsListView arg0, int arg1) {}
	        
	        @Override
	        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
	            if (visibleItemCount > 0 && firstVisibleItem + visibleItemCount == totalItemCount &&
                            hasNextPage()) {
	                loadNextPage();
	            }
	        }
	});
	

Обрабатываем результат загрузки очередной страницы


Intent можно перехватить анонимным BroadcastReceiver и передать в Handler (чтобы исполнить в UI треде). Нам остается только вызвать метод notifyDataSetChanged адаптера и удалить подвальный элемент, если загружена последняя страница.

	if (!hasNextPage() && mList.getFooterViewsCount() > 0) {
	     mList.removeFooterView(mLoadingFooter);
	}
	mAdapter.notifyDataSetChanged();
	
  • +20
  • 9,5k
  • 6
Поделиться публикацией

Похожие публикации

Комментарии 6

    +1
    спасибо про footerView, еще не знал раньше.
    Все же, зачем эта возня с интнентами, хэндлерами и рисиверами, если есть AsyncTask?

      +2
      Спасибо за отклик!

      При разработке под несколько платформ удобно API работы с сервером вытаскивать в отдельный пакет. Например у нас хорошо получается использовать большое количество кода и в Android, и вJ2ME. В таких случаях наследование от Thread удобно. В данном случае, да, слишком мощное решение, но в приложении, когда много разных асинхронных процессов, можно реализовать в базовом классе активити обработку интентов, а в наследниках — реальных активити только перехватывать нужное событие.
        0
        если вы пишите это приложение одновременно для Android и J2ME, то согласен, легче использовать одинаковый Thread based код.
        Однако чисто для андроид приложения, советую пользоваться асинктасками. Если взглянуть на код класса AsyncTask, то внутри у него Executor, Future, Handler, то есть работа по общению Activity-поток уже сделана за вас.
      +1
      >Добавляем в адаптер списка код запуска загрузки следующей страницы

      You're doing it wrong!
      ИМХО, правильнее повесить на ListView OnScrollListener, а потом в методе
      public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)
      вызывать подгрузку. Как-то так:

      if(firstVisibleItem+visibleItemCount==totalItemCount && !dataLoading && visibleItemCount!=0 && totalItemCount!=0 && hasNextPage()){
      loadNextPage();
      }

      На какую-то определенную последовательность вызовов методов адаптера полагаться нельзя. Об этом написано в документации.
      p.s. почему у меня теги в комментах перестали работать?
        0
        Спасибо! Не знал об этом методе.
        –1
        Прикольно получилось: «Фоновая подгрузка списков из песочницы» :)

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

        Самое читаемое