ViewPager 2 - nova funcionalidade no antigo invólucro

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); 

imagem
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) { //do your stuff } } ) 

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:

imagem

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

Source: https://habr.com/ru/post/pt461873/


All Articles