Работающий Pull to refresh

Это рассказ о том, как получилась работающая реализация Pull to refresh под Android. Совсем не будет примеров кода. Немного картинок и в конце статьи ссылка на архив проекта.

Недавно встала необходимость встроить в текущее приложение Pull to refresh. Это сейчас модно, пользователи привыкли, и для твиттер-клиентов уже стало стандартом. Поиск готовых решений в сети вывел на единственный вариант — open-source проект Johan Nilsson android-pulltorefresh на Github.





В ListView вставляется Header, который прячется при необходимости. Решение работающее, но очень криво. Часто подвисает в каком-либо состоянии. Если в списке мало элементов и они не занимают все пространство, отведенное для ListView, то Header не скрывается, а надпись изменяется на Tap to refresh. Логичное ограничение данной реализации. Еще одна особенность была обнаружена при добавлении этого проекта в рабочее приложение. Так как Pull to refresh — это Header, то часто выполняется метод setSelection(1), чтобы спрятать Header. А так как мы используем сохранение и восстановление позиции, то конечно постоянный вызов setSelection нам все портит.

Поначалу решили доработать эту реализацию, исправить самые неприятные моменты. Даже встроили в один проект. Но сделать так, как хочется, не получилось, сказались ограничения от использования Header'а.

Возникла идея, как сделать по-другому и появилось свободное время. Раз проблема в Header'е, значит, его нужно убрать и поместить сам Pull to refresh View и ListView в один LinearLayout и двигать его. В идеале хотелось получить такой же Pull to refresh как в официальном Twitter-клиенте. Но там используется другая связка. Pull to refresh там плавный и хорошо везде работающий. Вообще много времени провели, наблюдая за работой официального Twitter-клиента.

Вариантов что и как двигать много. Можно двигать и Pull to refresh View, и ListView, и LinearLayout. Можно двигать, изменяя Padding, Margin или ScrollTo. После большого количества экспериментов на эмуляторе и на реальных устройствах, была найдена наиболее оптимальная связка. Первоначальный сдвиг делается изменением Padding на высоту Pull to refresh View, таким образом, мы его прячем с экрана. А дальше двигаем весь Layout изменением ScrollTo.

Получилось красиво и максимально плавно. Но возникла проблема, на тот момент кажущаяся непреодолимой. Почему-то при сдвиге Layout все начинало прыгать. Приходили координаты то ниже, то выше, и никак не получалось исправить. Чуть даже не вернулись к первоначальному плану — доработать Pull to refresh от Johan Nilsson.

Но потом как по волшебству на глаза попал другой проект. Альтернативный Pull To Refresh от guillep на том же Github. Он еще ужаснее работает, чем у Johan Nilsson, но используется совсем другой механизм, похожий на нашу идею. И самое главное там обходится проблема с прыгающим Layout'ом. Берется три последних координаты и вычисляется средняя. Таким образом, обеспечивается плавность.



Мы взяли понемногу из каждого проекта, все объединили, и получился хорошо работающий Pull to refresh, но оставалось незаконченным состояние загрузки. После отпускания Release to refresh превращается в Loading и в таком состоянии оно должно скролиться вместе со списком, поэтому оно должно быть частью ListView. Отдельный Pull to refresh View должен исчезнуть, а в ListView должно добавиться Pull to refresh View в состоянии Loading.

Так как наше View должно постоянно то появляться, то исчезать в ListView, использовать его как Header нельзя. Но и изменять текущие адаптеры тоже не хочется. Необходимо, чтобы при добавлении Pull to refresh в рабочий проект, было как можно меньше изменений. Решили использовать специальный Adapter внутри Pull to refresh как Декоратор. И этот адаптер контролирует наличие специального View в состоянии Loading.



Было много проблем, особенно при добавлении в рабочий проект, но все они успешно решились. В конце концов, получился класс, которым можно заменять обычный ListView и в котором реализован хорошо работающий Pull to refresh. Он прекрасно работает на всех API, начиная с версии 1.5.

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

Проект на GitHub.
Share post

Similar posts

AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 10

    +4
    Раз уж вы использовали 2 проекта с GitHub, может и свой выложите на GitHub?
      +1
      Да, так и надо сделать. Как-то сразу об этом не подумал
      +2
      можете ещё посмотреть на Feed Framework code.google.com/p/libs-for-android/ там как раз используется подход с декоратором. плюс там реализована такая немаловажная часть, о которой все почему-то забывают, как отображение не только статуса загрузки но и ошибок загрузки.
        0
        а просто кнопка — «обновить» — разве не проще и понятнее?
          0
          Приложений с Pull to refresh появляется все больше и больше, значит пользователям нравится
            0
            Мне вот подход с pull to refresh нравится больше чем просто кнопка, ибо проще потянуть список, чем тянуться до кнопки. Просто потому, что список, как правило больше места занимает на экране.
              0
              tap vs tap+drag
                0
                tap+drag vs *ищем кнопку, целимся * tap.
            +1
            это даже просто интуитивнее. прочитали список, хотим обновить, прочитать новые, скроллим дальше, за пределы экрана, и оно автоматически предлагает обновить. даже когда человек ни разу не сталкивался в pull to refresh, при листании списка обязательно на него наткнется
              0
              В библиотеке Android support версии 19.1 появился новый компонент «потяни, чтобы обновить» под названием SwipeRefreshLayout.
              habrahabr.ru/post/218365/

              Only users with full accounts can post comments. Log in, please.