Integração de ViewPager e ListView em Fragments Android

A implementação de navegação aninhada em aplicações Android frequentemente exige a combinação de múltiplos componentes de UI. Um cenário comum envolve uma barra de navegação inferior que alterna Fragments principais, onde um desses Fragments contém um ViewPager com navegação superior, e finalmente, uma das páginas do ViewPager exibe uma ListView. Este artigo detalha a arquitetura necessária para integrar esses componentes, resolvendo conflitos de toque e gerenciando o ciclo de vida dos Fragments.

Estrutura da Atividade Principal

A atividade principal (MainActivity) gerencia a navegação de nível superior. Utilizamos um FrameLayout como container para trocar os Fragments inferiores (Home, Explorar, Estatísticas, Configurações) e uma barra layoutada manualmente para os botões de navegação.

package com.exemplo.app.navegacao;

import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import android.os.Bundle;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    private List<Fragment> fragmentStack;
    private FrameLayout containerLayout;
    private LinearLayout[] navButtons;
    private ImageButton[] navIcons;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.layout_main_activity);
        configurarComponentes();
        inicializarFragments();
        definirOuvintes();
        carregarFragmentoInicial(0);
    }

    private void configurarComponentes() {
        containerLayout = findViewById(R.id.container_fragmento);
        navButtons = new LinearLayout[4];
        navIcons = new ImageButton[4];
        
        int[] idsBotoes = {R.id.btn_home, R.id.btn_explorar, R.id.btn_stats, R.id.btn_config};
        int[] idsLayouts = {R.id lay_home, R.id.lay_explorar, R.id.lay_stats, R.id.lay_config};

        for (int i = 0; i < 4; i++) {
            navButtons[i] = findViewById(idsBotoes[i]);
            navButtons[i] = findViewById(idsBotoes[i]);
            navButtons[i].setOnClickListener(navegacaoListener);
            navButtons[i] = findViewById(idsBotoes[i]);
        }
    }

    private void inicializarFragments() {
        fragmentStack = new ArrayList<>();
        fragmentStack.add(new HomeTabFragment());
        fragmentStack.add(new ExplorarFragment());
        fragmentStack.add(new EstatisticasFragment());
        fragmentStack.add(new ConfigFragment());
    }

    private void definirOuvintes() {
        // A lógica de clique é unificada no listener abaixo
    }

    View.OnClickListener navegacaoListener = v -> {
        resetarIcones();
        int index = -1;
        if (v.getId() == R.id.lay_home) index = 0;
        else if (v.getId() == R.id.lay_explorar) index = 1;
        else if (v.getId() == R.id.lay_stats) index = 2;
        else if (v.getId() == R.id.lay_config) index = 3;
        
        if (index != -1) {
            atualizarSelecao(index);
        }
    };

    private void resetarIcones() {
        // Restaurar ícones para o estado não selecionado
        navIcons[0].setImageResource(R.drawable.ic_home_outline);
        navIcons[1].setImageResource(R.drawable.ic_explore_outline);
        navIcons[2].setImageResource(R.drawable.ic_stats_outline);
        navIcons[3].setImageResource(R.drawable.ic_settings_outline);
    }

    private void atualizarSelecao(int indice) {
        switch (indice) {
            case 0: navIcons[0].setImageResource(R.drawable.ic_home_filled); break;
            case 1: navIcons[1].setImageResource(R.drawable.ic_explore_filled); break;
            case 2: navIcons[2].setImageResource(R.drawable.ic_stats_filled); break;
            case 3: navIcons[3].setImageResource(R.drawable.ic_settings_filled); break;
        }
        trocarFragmento(indice);
    }

    private void trocarFragmento(int indice) {
        FragmentManager fm = getSupportFragmentManager();
        FragmentTransaction ft = fm.beginTransaction();
        ft.replace(R.id.container_fragmento, fragmentStack.get(indice));
        ft.commit();
    }

    private void carregarFragmentoInicial(int indice) {
        resetarIcones();
        atualizarSelecao(indice);
    }
}

Fragmento Home com ViewPager

O Fragmento "Home" é mais complexo, pois contém uma navegação secundária horizontal. Utilizamos um RadioGroup dentro de um HorizontalScrollView para as abas superiores e um ViewPager para o conteúdo. É crucial sincronizar o deslizamento do ViewPager com a seleção das abas.

package com.exemplo.app.navegacao;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.HorizontalScrollView;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.viewpager.widget.ViewPager;

public class HomeTabFragment extends Fragment implements ViewPager.OnPageChangeListener {

    private View rootView;
    private RadioGroup grupoAbas;
    private ViewPager pagerConteudo;
    private HorizontalScrollView scrollAbas;
    private String[] categorias;
    private CategoriaPagerAdapter adaptador;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        if (rootView == null) {
            rootView = inflater.inflate(R.layout.layout_home_tab, container, false);
            categorias = getResources().getStringArray(R.array.categorias_news);
            
            pagerConteudo = rootView.findViewById(R.id.viewpager_news);
            grupoAbas = rootView.findViewById(R.id.radio_grupo_abas);
            scrollAbas = rootView.findViewById(R.id.scroll_abas);

            inicializarViewPager();
            criarAbasSuperiores(inflater);
            
            grupoAbas.setOnCheckedChangeListener((group, checkedId) -> {
                pagerConteudo.setCurrentItem(checkedId);
            });
        }
        return rootView;
    }

    private void inicializarViewPager() {
        FragmentManager fm = getActivity().getSupportFragmentManager();
        adaptador = new CategoriaPagerAdapter(fm, categorias);
        pagerConteudo.setAdapter(adaptador);
        pagerConteudo.setOffscreenPageLimit(2);
        pagerConteudo.addOnPageChangeListener(this);
    }

    private void criarAbasSuperiores(LayoutInflater inflater) {
        for (int i = 0; i < categorias.length; i++) {
            RadioButton rb = (RadioButton) inflater.inflate(R.layout.item_tab_radio, null);
            rb.setId(i);
            rb.setText(categorias[i]);
            grupoAbas.addView(rb);
        }
        grupoAbas.check(0);
    }

    @Override
    public void onPageSelected(int position) {
        sincronizarAba(position);
    }

    private void sincronizarAba(int indice) {
        RadioButton rb = (RadioButton) grupoAbas.getChildAt(indice);
        if (rb != null) {
            rb.setChecked(true);
            // Lógica para scroll horizontal automático da aba selecionada
            int scrollX = rb.getLeft() - (scrollAbas.getWidth() / 2) + (rb.getWidth() / 2);
            scrollAbas.smoothScrollTo(scrollX, 0);
        }
    }

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {}
    @Override
    public void onPageScrollStateChanged(int state) {}
}

Implementação do Fragmento com Lista

Para uma das categorias do ViewPager (ex: "Headlines"), utilizamos uma ListView. Este Fragmento recebe dados via Bundle e configura um adaptador simples. Note o uso de um ID específico para a ListView para evitar conflitos com o sistema.

package com.exemplo.app.navegacao;

import android.content.DialogInterface;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
import java.util.ArrayList;

public class NewsListFragment extends Fragment {

    private static final String ARG_DADOS = "arg_dados_noticias";
    private String[] manchetes;
    private ArrayList<String> itensLista = new ArrayList<>();
    private String[] detalhesInfo = {"Detalhe A", "Detalhe B", "Detalhe C", "Detalhe D"};

    public static NewsListFragment criarInstancia(String[] dados) {
        NewsListFragment fragment = new NewsListFragment();
        Bundle args = new Bundle();
        args.putStringArray(ARG_DADOS, dados);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            manchetes = getArguments().getStringArray(ARG_DADOS);
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.layout_lista_noticias, container, false);
        ListView lista = view.findViewById(R.id.lista_conteudo);

        // Preencher dados simulados
        for (int i = 0; i < 50; i++) {
            itensLista.add(manchetes[i % manchetes.length]);
        }

        ArrayAdapter<String> adaptador = new ArrayAdapter<>(
            getContext(), 
            android.R.layout.simple_list_item_1, 
            itensLista
        );
        lista.setAdapter(adaptador);

        lista.setOnItemClickListener((parent, view1, position, id) -> {
            new AlertDialog.Builder(getContext())
                .setTitle("Detalhes")
                .setMessage(detalhesInfo[position % detalhesInfo.length])
                .setPositiveButton("Fechar", (dialog, which) -> dialog.dismiss())
                .show();
        });

        return view;
    }
}

Adaptador do ViewPager

O adaptador gerencia a criação dos Fragments para cada página do ViewPager. Ele decide dinamicamente se deve retornar o Fragment de lista ou um Fragment de texto simples baseado no índice.

package com.exemplo.app.navegacao;

import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapter;

public class CategoriaPagerAdapter extends FragmentStatePagerAdapter {

    private String[] categorias;
    private String[] dadosNoticias = {
        "Notícia Urgente 1", 
        "Atualização Política", 
        "Mercado Financeiro", 
        "Tecnologia Hoje"
    };

    public CategoriaPagerAdapter(FragmentManager fm, String[] categorias) {
        super(fm);
        this.categorias = categorias;
    }

    @Override
    public Fragment getItem(int position) {
        // Exemplo: Apenas a segunda aba usa ListView, outras usam texto
        if (position == 1) {
            return NewsListFragment.criarInstancia(dadosNoticias);
        }
        return TextoSimplesFragment.novo(categorias[position]);
    }

    @Override
    public int getCount() {
        return categorias.length;
    }
}

Resolução de Conflitos de Toque

Um problema comum nessa arquitetura é o conflito de gestos entre o ViewPager (deslize horizontal) e a ListView ou ScrollView (deslize vertical). Para garantir que o ViewPager não intercepte o scroll vertical da lista, podemos sobrescrever a interceptação de toque no Fragmento pai.

    private float distanciaX, distanciaY, ultimoX, ultimoY;

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                distanciaX = distanciaY = 0f;
                ultimoX = ev.getX();
                ultimoY = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                float atualX = ev.getX();
                float atualY = ev.getY();

                distanciaX += Math.abs(atualX - ultimoX);
                distanciaY += Math.abs(atualY - ultimoY);
                ultimoX = atualX;
                ultimoY = atualY;

                // Se o movimento horizontal for predominante, intercepta para o ViewPager
                if (distanciaX > distanciaY && distanciaX > 10) {
                    return true;
                }
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }

Definições de Layout e Recursos

Os layouts XML devem seguir a hierarquia definida. O layout da lista requer um ID específico. O layout principal utiliza um peso (layout_weight) para garantir que a área de conteúdo ocupe o espaço restante acima da barra de navegação.

layout_lista_noticias.xml

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ListView
        android:id="@+id/lista_conteudo"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</FrameLayout>

layout_home_tab.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <HorizontalScrollView
        android:id="@+id/scroll_abas"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:scrollbars="none">

        <RadioGroup
            android:id="@+id/radio_grupo_abas"
            android:orientation="horizontal"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
    </HorizontalScrollView>

    <androidx.viewpager.widget.ViewPager
        android:id="@+id/viewpager_news"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"/>

</LinearLayout>

strings.xml

<resources>
    <string-array name="categorias_news">
        <item>Seguir</item>
        <item>Manchetes</item>
        <item>Vídeos</item>
        <item>Local</item>
        <item>Política</item>
        <item>Entretenimento</item>
    </string-array>
</resources>

Tags: android ViewPager listview Fragment UI Design

Publicado em 8-19 19:09