O ViewPager é um dos componentes mais famosos e amplamente usados da Biblioteca de suporte do Android. Todos os carrosséis, onboardings e sliders mais simples são feitos nele. Em fevereiro de 2019, a equipe de desenvolvimento do AndroidX lançou o ViewPager2. Vejamos quais eram esses pré-requisitos e quais as vantagens da versão atualizada do componente.

ViewPager 2
No momento da redação da publicação (julho de 2019), uma versão beta do
ViewPager2 estava disponível , o que significa que os problemas mencionados abaixo podem ser corrigidos e a funcionalidade aprimorada e expandida. Os desenvolvedores prometem no futuro adicionar suporte ao TabLayout (embora ele possa funcionar apenas com a primeira versão), otimizar o desempenho do adaptador, fazer muitas pequenas correções e finalizar a documentação.
Integração
O componente não é fornecido com pacotes padrão, mas é conectado separadamente. Para fazer isso, adicione a seguinte linha ao bloco de dependências no script gradle do seu módulo:
implementation "androidx.viewpager2:viewpager2:1.0.0-beta02"
Implementação
Vamos começar com as boas notícias: a transição da primeira para a segunda versão é a mais simples possível e se resume a uma mudança nas importações. A boa e antiga sintaxe não foi tocada: o
método getCurrentItem () retorna a página atual,
ViewPager2.onPageChangeCallback permite que
você assine o
estado do pager, o adaptador ainda está instalado via
setAdapter ().
Vale a pena ir mais fundo, pois fica claro que o primeiro e o segundo pagers não têm nada em comum, exceto as interfaces. A familiaridade com a implementação do método setAdapter () não deixa margem para dúvidas:
public final void setAdapter(@Nullable Adapter adapter) { mRecyclerView.setAdapter(adapter); }
Sim, o ViewPager2 é apenas um invólucro do
RecyclerView . Por um lado, essa é uma grande vantagem, por outro - acrescenta dor de cabeça. Disfarçar o
RecyclerView como um folheto tornou-se possível com o advento do
PagerSnapHelper . Esta classe muda a física do scroll. Quando o usuário solta o dedo,
PagerSnapHelper calcula qual item da lista está mais próximo da linha central da lista e, com uma animação suave, o alinha exatamente no centro. Assim, se o furto foi nítido o suficiente, a lista rola para o próximo elemento, caso contrário - com a animação retorna ao seu estado original.
new PagerSnapHelper().attachToRecyclerView(mRecyclerView);

Ao usar o PagerSnapHelper, verifique se a largura e a altura do próprio RecyclerView, bem como de todos os seus ViewHolders, estão definidas como MATCH_PARENT. Caso contrário, o comportamento do SnapHelper será imprevisível, podem ocorrer erros em locais completamente inesperados. Tudo isso torna a criação de um carrossel de elementos de pequena estatura bastante demorada, embora possível.
Dado todo o exposto, no layout o widget será semelhante a este:
<androidx.viewpager2.widget.ViewPager2 android:id="@+id/main_pager" android:layout_width="match_parent" android:layout_height="match_parent" />
No mesmo pacote que o
ViewPager2, também podemos encontrar a classe
ScrollEventAdapter , que ajuda a manter a continuidade da sintaxe.
ScrollEventAdapter implementa
RecyclerView.OnScrollListener e transforma eventos de rolagem
em eventos
OnPageChangeCallback .
@Override public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { if (mAdapterState != STATE_IN_PROGRESS_MANUAL_DRAG && newState == RecyclerView.SCROLL_STATE_DRAGGING) { ... dispatchStateChanged(SCROLL_STATE_DRAGGING); return; } ... }
Agora
OnPageChangeCallback é representado não por uma interface, mas por uma classe abstrata, que permite substituir apenas os métodos necessários (na maioria dos casos, você só precisa do
nPageSelected (Int) , que funciona quando uma página específica é selecionada):
main_pager.registerOnPageChangeCallback( object : ViewPager2.OnPageChangeCallback() { override fun onPageSelected(position: Int) {
Funcionalidades
Digno de nota é o método
setPageTransformer () , que usa o
ViewPager2.PageTransformer como parâmetro. Ele define um
retorno de
chamada para cada evento de seleção de página e serve para definir sua própria animação para esta página.
O retorno de chamada recebe a
visualização da página atual e seu número como entrada. O análogo mais próximo desse método é o
ItemAnimator do
RecyclerView .
Nas novas versões da biblioteca, duas implementações do transformador foram adicionadas:
CompositePageTransformer e
MarginPageTransformer . O primeiro é responsável pela combinação de transformadores para aplicar várias transformações em um pager de uma vez e o segundo pelo recuo entre páginas:

Além disso, o novo widget suporta alterações de orientação: basta chamar o
método setOrientation () , você pode transformar seu pager em uma lista vertical com furtos de cima para baixo:
main_pager.setOrientation(ViewPager2.ORIENTATION_VERTICAL)
Isso acontece novamente graças à transição para o
RecyclerView : sob o capô, uma mudança na orientação do
LayoutManager é chamada , responsável pela exibição dos itens da lista. Note-se que delegar um grande número de tarefas para outras classes beneficiou o novo componente: sua listagem se tornou muito mais compacta e legível.
Este não é o fim da diversão. Em uma atualização, o
ViewPager2 recebeu suporte para o
ItemDecoration : uma classe
auxiliar para decorar o
View filho. Este mecanismo pode ser usado para desenhar separadores entre elementos, bordas e destaque de células.
Já existem muitas implementações prontas de decoradores, porque por muitos anos elas foram usadas com sucesso ao trabalhar com o
RecyclerView usual. Todos os desenvolvimentos agora são aplicáveis aos pagers. Pronto para uso, está disponível uma implementação padrão de separadores de pager:
main_pager.addItemDecoration( DividerItemDecoration(this, RecyclerView.HORIZONTAL) )
Juntamente com a próxima atualização em maio de 2019, o
ViewPager2 adicionou outro método importante:
setOffscreenPageLimit (Int) . Ele é responsável por quantos elementos à direita e à esquerda da central serão inicializados no pager. Embora o
RecyclerView seja responsável por armazenar em cache e exibir a
Visualização por padrão, usando este método, você pode definir explicitamente o número desejado de itens a serem carregados.
Adaptador
O sucessor ideológico do primeiro adaptador de pager é o
FragmentStateAdapter : as interfaces de interação e a nomeação de classes são quase as mesmas. As alterações afetaram apenas a nomeação de alguns métodos. Se anteriormente era necessário implementar a função abstrata
getItem (position) para retornar a instância do
Fragment desejada para a posição especificada e essa nomeação poderia ser interpretada de duas maneiras, agora essa função foi renomeada para
createFragment (position) . O número total de fragmentos é fornecido como antes pela função
getCount () .
Das importantes mudanças estruturais na interface, também deve ser observado que o adaptador agora tem a capacidade de controlar o ciclo de vida de seus elementos; portanto, junto com o
FragmentManager no construtor, ele aceita um
objeto de Ciclo de Vida , uma
Atividade ou um
Fragmento . Por esse
motivo , por segurança, os
métodos saveState () e
restoreState () foram declarados finais e fechados para herança.
A classe
FragmentViewHolder é responsável por armazenar fragmentos dentro do
RecyclerView . O método
onCreateViewHolder () de
FragmentStateAdapter chama
FragmentViewHolder.create () .
static FragmentViewHolder create(ViewGroup parent) { FrameLayout container = new FrameLayout(parent.getContext()); container.setLayoutParams( new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ) ); container.setId(ViewCompat.generateViewId()); container.setSaveEnabled(false); return new FragmentViewHolder(container); }
Quando o método
onBindViewHolder () é
chamado , o identificador do elemento na posição atual e o identificador
ViewHolder são
associados , para anexar ainda mais o fragmento a ele:
final long itemId = holder.getItemId(); final int viewHolderId = holder.getContainer().getId(); final Long boundItemId = itemForViewHolder(viewHolderId); ... mItemIdToViewHolder.put(itemId, viewHolderId); ensureFragment(position);
E, finalmente, ao anexar um contêiner do
ViewHolder à hierarquia do
View , uma
FragmentTransaction é executada, adicionando um
Fragment ao contêiner:
void placeFragmentInViewHolder(@NonNull final FragmentViewHolder holder) { Fragment fragment = mFragments.get(holder.getItemId()); ... scheduleViewAttach(fragment, container); mFragmentManager.beginTransaction() .add(fragment, "f" + holder.getItemId()) .setMaxLifecycle(fragment, STARTED) .commitNow(); ... }
Assim,
surgem dois usos do
ViewPager2 : herdando a classe do adaptador, diretamente do
RecyclerView.Adapter ou do
FragmentStateAdapter .
Certamente você terá uma pergunta: por que usar um segundo pager com fragmentos e um adaptador para eles quando existe uma primeira versão normalmente funcionando?
O ViewPager está longe de ser uma “bala de prata” ao trabalhar com grandes listas de dados dinâmicos. É ótimo para criar carrosséis com um conjunto estático de fotos ou banners, mas feeds de notícias paginados com o carregamento de postagens de publicidade, a filtragem dá origem a monstros feios e com forte suporte. Mais cedo ou mais tarde, você certamente encontrará um desejo ardente de reescrever tudo no
RecyclerView . Agora você não precisa fazer isso, porque o pager se transformou nele, emprestando seus poderosos recursos para trabalhar com listas dinâmicas, enquanto os agrupa na sintaxe usual.
A única coisa que o
PagerAdapter pode
nos oferecer é o método
notifyDataSetChanged () , que força o
ViewPager a redesenhar todos os itens da lista renderizada. Você pode razoavelmente perceber que ninguém está nos impedindo de armazenar uma lista de posições para elementos existentes e retornando
POSITION_UNCHANGED do método
getItemPosition () para eles, é isso. No entanto, essa solução não pode ser chamada de bonita; é bastante complicada; além disso, é difícil expandir os casos em que os elementos da lista estão constantemente mudando e não apenas adicionados sequencialmente ao final.
O FragmentStateAdapter possui um arsenal completo de métodos
RecyclerView.Adapter , para que a lógica dos elementos redesenhados possa ser configurada com muito mais flexibilidade. Além disso, junto com o
FragmentStateAdapter, você pode usar o
DiffUtil , que permite automatizar quase completamente o trabalho de notificação de alterações.

Atenção! Para que os métodos notify ... funcionem corretamente (exceto para notifyDataSetChanged ), os métodos getItemId (Int) ec ontainsItem (Long) devem ser redefinidos. Isso é feito porque a implementação padrão olha apenas para o número da página e, por exemplo, se você adicionar um novo elemento após o atual, ele não será adicionado, pois o getItemId permanecerá inalterado. Um exemplo de substituição desses dois métodos com base em uma lista de elementos do tipo Int :
override fun getItemId(position: Int): Long { return items[position].toLong() } override fun containsItem(itemId: Long): Boolean { return items.contains(itemId.toInt()) }
A principal razão para a aparência do
ViewPager2 é a relutância em reinventar a roda. Por um lado, a
equipe de desenvolvimento do
AndroidX está claramente pronta para abandonar o obsoleto
ViewPager agora e certamente não vai investir na expansão de sua funcionalidade. Sim e porque? Afinal, o
RecyclerView já sabe tudo o que é necessário. Por outro lado, a remoção e o término do suporte a um componente tão amplamente usado obviamente não acrescentam lealdade à comunidade.
Para resumir: o
ViewPager2 é definitivamente digno de atenção, embora no momento não esteja isento de falhas sérias.
Contras
- Umidade e um grande número de bugs (desculpável pela versão beta);
- Proximidade. O RecyclerView é um campo privado do ViewPager2 , que nos priva de muitas oportunidades: é impossível implementar deslizar para dispensar ou arrastar e soltar (o ItemTouchHelper se conecta diretamente ao RecyclerView ), você não pode redefinir o ItemAnimator de nenhuma forma, não acesse o LayoutManager diretamente e use o RecycledViewPool . No entanto, com o lançamento de novas versões do componente, o número de métodos de interface herdados do RecyclerView está aumentando (por exemplo, ItemDecoration ), e podemos esperar adicionar os métodos ausentes no futuro.
Prós
- Suporte para todas as vantagens do RecyclerView.Adapter : combinando elementos de diferentes tipos em uma lista, adicionando e removendo elementos diretamente durante o furto, redesenhando animado o conteúdo da lista ao mudar;
- Suporte para todo o espectro de métodos de notificação ... e cálculo automático de alterações usando o DiffUtil ;
- Facilidade de transição devido à continuidade da sintaxe;
- Suporte para orientação vertical e horizontal "fora da caixa";
- Suporte a RTL ;
- ItemDecorator de suporte;
- Suporte para rolagem de software através de fakeScrollBy () ;
- Capacidade de definir manualmente o número de itens carregados;
- A capacidade de usar qualquer uma das soluções de código aberto prontas para reduzir o código padrão , o que é inevitável ao escrever o RecyclerView.Adapter personalizado. Por exemplo, EasyAdapter .
Como resumo, quero dizer que o
ViewPager2 realmente vale a pena olhar
mais de perto . Esta é uma solução promissora, extensível e funcional. E embora seja muito cedo para lançar um
novo widget em produção, é seguro dizer que, após um lançamento completo, ele pode e deve suplantar completamente seu ancestral.
Para aqueles ousados e decisivos, que o artigo inspirou para experimentar, o
PagerSnapHelper apareceu na 28ª versão da
Biblioteca de suporte , o que significa que você pode usá-lo junto com o
RecyclerView criando o
ViewPager2 .
A operação de
exemplo do
ViewPager2 e
FragmentStateAdapter .
Notas de versão oficiais
do ViewPager2