Это рассказ о том, как получилась работающая реализация Pull to refresh под Android. Совсем не будет примеров кода. Немного картинок и в конце статьи ссылка на архив проекта.
Недавно встала необходимость встроить в текущее приложение Pull to refresh. Это сейчас модно, пользователи привыкли, и для твиттер-клиентов уже стало стандартом. Поиск готовых решений в сети вывел на единственный вариант — open-source проект Johan Nilsson android-pulltorefresh на Github.
![](https://habrastorage.org/r/w780q1/storage1/ccf36927/c868a5b0/c55179ac/a4846009.jpg)
В 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'ом. Берется три последних координаты и вычисляется средняя. Таким образом, обеспечивается плавность.
![](https://habrastorage.org/r/w780q1/storage1/75cbfac6/c457f38a/56e567a1/f22e8bbe.jpg)
Мы взяли понемногу из каждого проекта, все объединили, и получился хорошо работающий 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.
![](https://habrastorage.org/r/w780q1/storage1/370d312d/3954c850/580904a4/82be3770.jpg)
Было много проблем, особенно при добавлении в рабочий проект, но все они успешно решились. В конце концов, получился класс, которым можно заменять обычный ListView и в котором реализован хорошо работающий Pull to refresh. Он прекрасно работает на всех API, начиная с версии 1.5.
Только на некоторых устройствах Samsung с переделанным ListView наблюдаются иногда проблемы. Но уже есть идеи как и что можно переделать.
Проект на GitHub.
Недавно встала необходимость встроить в текущее приложение Pull to refresh. Это сейчас модно, пользователи привыкли, и для твиттер-клиентов уже стало стандартом. Поиск готовых решений в сети вывел на единственный вариант — open-source проект Johan Nilsson android-pulltorefresh на Github.
![](https://habrastorage.org/storage1/ccf36927/c868a5b0/c55179ac/a4846009.jpg)
В 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'ом. Берется три последних координаты и вычисляется средняя. Таким образом, обеспечивается плавность.
![](https://habrastorage.org/storage1/75cbfac6/c457f38a/56e567a1/f22e8bbe.jpg)
Мы взяли понемногу из каждого проекта, все объединили, и получился хорошо работающий 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.
![](https://habrastorage.org/storage1/370d312d/3954c850/580904a4/82be3770.jpg)
Было много проблем, особенно при добавлении в рабочий проект, но все они успешно решились. В конце концов, получился класс, которым можно заменять обычный ListView и в котором реализован хорошо работающий Pull to refresh. Он прекрасно работает на всех API, начиная с версии 1.5.
Только на некоторых устройствах Samsung с переделанным ListView наблюдаются иногда проблемы. Но уже есть идеи как и что можно переделать.
Проект на GitHub.