Android Tabs con Fragment y RecyclerView

Vamos a crear un ejemplo para este tutorial en el cual nuestro activity tendrá tabs haciendo uso de fragment y este fragment utilizará un RecyclerView.

Vayamos paso por paso:

Crearemos el Activity que definirá los tab pasandole la lista de nuestro modelo a mostrar en cada tab. Después el Fragment contendrá el RecyclerView; entre ambos trabajrán para mostrar cada ítem adecuadamente y devolverán al Activity la acción sobre un item, enviando el modelo sobre el que se hizo click.

Las dependencias que necesitas en tu archivo build

Estas son las dependencias que incluyes

    dependencies {
       compile 'com.android.support:appcompat-v7:25.1.1'
       compile 'com.android.support:design:25.1.1'
       compile 'com.android.support:support-v4:25.1.1'
       compile 'com.android.support:recyclerview-v7:25.1.1'
    }

El modelo POJO

Este el un modelo bastante tonto pero que nos servirá bien para que crees tu app de ejemplo con tabs usando fragment. Extiende de Parcelable a fin de enviar este modelo al fragment como parámetro.

public class DummyModel implements Parcelable {
    private final String id;
    private final String title;
    private final String detail;
....
}

El activity principal con el TabLayout

Tu Activity principal con dos elementos, el widget TabLayout que provee un layout horizontal para mostrar tabs y el ViewPager que te permitirá desplazarte entre los tabs deslizando con un gesto horizontalmente.

Tu xml debe lucir de este modo para definir el TabLayout y el ViewPager a fin de mostrar tabs.

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
       xmlns:tools="http://schemas.android.com/tools"
       android:id="@+id/activity_main"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:paddingBottom="@dimen/activity_vertical_margin"
       android:paddingLeft="@dimen/activity_horizontal_margin"
       android:paddingRight="@dimen/activity_horizontal_margin"
       android:paddingTop="@dimen/activity_vertical_margin"
       tools:context="com.gustavopeiretti.tabexample.TabExampleMainActivity">
    
       <android.support.design.widget.TabLayout
           android:id="@+id/tab_layout"
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
           android:background="?attr/colorPrimary"
           android:elevation="6dp"
           android:minHeight="?attr/actionBarSize"
           android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"/>
    
       <android.support.v4.view.ViewPager
           android:id="@+id/pager"
           android:layout_width="match_parent"
           android:layout_height="fill_parent"
           android:layout_below="@id/tab_layout"/>
    
    </RelativeLayout>

Para el TabLayout agregarás en este ejemplo tres tabs con sus respectivos títulos.

TabLayout tabLayout = (TabLayout) findViewById(R.id.tab_layout);
tabLayout.addTab(tabLayout.newTab().setText(R.string.title_tab0));
//... otros tabs

Luego para el ViewPager debes agregarle un adapter que se encargará entre otras cosas de devolver el Fragment que corresponda a la posición del tab elegido.

final ViewPager viewPager = (ViewPager) findViewById(R.id.pager);
final PagerAdapter adapter = new PagerAdapter(...)
viewPager.setAdapter(adapter);

El Activity también implementará un Listener ItemFragment.OnListFragmentInteractionListener. Verás mas adelante que recibirá la info desde el RecyclerView cuando se haga click sobre un elemento y enviará el Modelo que se ha clickeado al Activity TabExampleMainActivity.

public void onListFragmentInteraction(DummyModel model) {
        Toast.makeText(TabExampleMainActivity.this, DummyModel.class.getSimpleName() + ":" + model.getId() + " - "  +model.getTitle(), Toast.LENGTH_LONG).show();
    }

Puedes definir otro listener para cuando se pasa de un tab a otro

private TabLayout.OnTabSelectedListener getOnTabSelectedListener(final ViewPager viewPager) {
        return new TabLayout.OnTabSelectedListener() {
            @Override
            public void onTabSelected(TabLayout.Tab tab) {
                viewPager.setCurrentItem(tab.getPosition());
                Toast.makeText(TabExampleMainActivity.this, "Tab selected " +  tab.getPosition(), Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onTabUnselected(TabLayout.Tab tab) {
                // nothing now
            }

            @Override
            public void onTabReselected(TabLayout.Tab tab) {
                // nothing now
            }
        };
    }

El código completo para mostar tab en un Activity te queda así.

public class TabExampleMainActivity extends AppCompatActivity implements ItemFragment.OnListFragmentInteractionListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.tab_example_activity_main);

        // define TabLayout
        TabLayout tabLayout = (TabLayout) findViewById(R.id.tab_layout);
        tabLayout.addTab(tabLayout.newTab().setText(R.string.title_tab0));
        tabLayout.addTab(tabLayout.newTab().setText(R.string.title_tab1));
        tabLayout.addTab(tabLayout.newTab().setText(R.string.title_tab2));
        tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);

        // define ViewPager
        final ViewPager viewPager = (ViewPager) findViewById(R.id.pager);

        // fake list model... for test
        DummyModel[] listModel0 = createDummyListModel("tab 0");
        DummyModel[] listModel1 = createDummyListModel("tab 1");
        DummyModel[] listModel2 = createDummyListModel("tab 2");

        //  ViewPager need a PagerAdapter
        final PagerAdapter adapter = new PagerAdapter
                (getSupportFragmentManager(), tabLayout.getTabCount(), listModel0, listModel1, listModel2 );

        viewPager.setAdapter(adapter);

        // Listeners
        viewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(tabLayout));
        tabLayout.addOnTabSelectedListener(getOnTabSelectedListener(viewPager));

    }

    /**
     * Listener for tab selected
     * @param viewPager
     * @return
     */
    @NonNull
    private TabLayout.OnTabSelectedListener getOnTabSelectedListener(final ViewPager viewPager) {
        return new TabLayout.OnTabSelectedListener() {
            @Override
            public void onTabSelected(TabLayout.Tab tab) {
                viewPager.setCurrentItem(tab.getPosition());
                Toast.makeText(TabExampleMainActivity.this, "Tab selected " +  tab.getPosition(), Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onTabUnselected(TabLayout.Tab tab) {
                // nothing now
            }

            @Override
            public void onTabReselected(TabLayout.Tab tab) {
                // nothing now
            }
        };
    }

    /**
     * Listener that comunicate fragment / recycler with this activity
     * @param model
     */
    @Override
    public void onListFragmentInteraction(DummyModel model) {
        // the user clicked on this item over the list
        Toast.makeText(TabExampleMainActivity.this, DummyModel.class.getSimpleName() + ":" + model.getId() + " - "  +model.getTitle(), Toast.LENGTH_LONG).show();
    }

    // model for test purpose
    private DummyModel[] createDummyListModel(String msj) {
        List<DummyModel> l = new ArrayList<>();
        for(int i = 0; i < 20; i++  ) {
            l.add(new DummyModel(String.valueOf(i), "Title " + i , "Descrip. " + i + " -" + msj));
        }
        return l.toArray(new DummyModel[l.size()]);
    }

}

Creas el PagerAdapter

El PagerAdapter debe extender de FragmentStatePagerAdapter. Observa como recibe la info de nuestro modelo para cada tab y como devuelve un fragment para cada posición del tab según su selección.

public class PagerAdapter extends FragmentStatePagerAdapter {

   private int numTabs;
   private DummyModel[] dummyModels0;
   private DummyModel[] dummyModels1;
   private DummyModel[] dummyModels2;

   public PagerAdapter(FragmentManager fm, int numTabs, DummyModel[] dummyModels0,
                       DummyModel[] dummyModels1, DummyModel[] dummyModels2) {
       super(fm);
       this.numTabs = numTabs;
       this.dummyModels0 = dummyModels0;
       this.dummyModels1 = dummyModels1;
       this.dummyModels2 = dummyModels2;
   }

   @Override
   public Fragment getItem(int position) {
       switch (position) {
           case 0:
               ItemFragment tab1 = ItemFragment.newInstance(dummyModels0);
               return tab1;
           case 1:
               ItemFragment tab2 = ItemFragment.newInstance(dummyModels1);
               return tab2;
           case 2:
               ItemFragment tab3 = ItemFragment.newInstance(dummyModels2);
               return tab3;
           default:
               throw new RuntimeException("Tab position invalid " + position);
       }
   }

   @Override
   public int getCount() {
       return numTabs;
   }

}

El fragment que mostrará la información de nuestro modelo

El fragment lo creas con los datos del model que deseas mostrar y harás uso de un RecyclerView a fin de displayarlos luego.

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:orientation="horizontal">
    
       <TextView
           android:id="@+id/id"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:layout_margin="@dimen/text_margin"
           android:textAppearance="?attr/textAppearanceListItem" />
    
       <TextView
           android:id="@+id/title"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:layout_margin="@dimen/text_margin"
           android:textAppearance="?attr/textAppearanceListItem" />
    
       <TextView
           android:id="@+id/detail"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:layout_margin="@dimen/text_margin"
           android:textAppearance="?attr/textAppearanceListItem" />
    
    </LinearLayout>

Como crear RecyclerView

El fragment utilizará un RecyclerView

    <android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
       xmlns:app="http://schemas.android.com/apk/res-auto"
       xmlns:tools="http://schemas.android.com/tools"
       android:id="@+id/dummyModels"
       android:name="com.gustavopeiretti.tabexample.ItemFragment"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:layout_marginLeft="16dp"
       android:layout_marginRight="16dp"
       app:layoutManager="LinearLayoutManager"
       tools:context="com.gustavopeiretti.tabexample.ItemFragment"
       tools:listitem="@layout/fragment_item" />

Recuerda que antes creaste el PagerAdapter que recibe este array y luego crea las instancias de ItemFragment para cada tab pasandole el respectivo array del modelo a mostrar.

Tu clase ItemFragment recibirá como argumento el array del modelo que deseas mostrar en este fragment y lo dejará como parámetro para cuando se cree el Fragment efectivamente en onCreate().

public static ItemFragment newInstance(DummyModel[] dummyModels) {
   ItemFragment fragment = new ItemFragment();
   Bundle args = new Bundle();
   args.putParcelableArray(KEY_MODEL, dummyModels);
   fragment.setArguments(args);
   return fragment;
}

onCreate() se ejecuta cuando efectivamente se crea el fragment y allí obtendremos la lista que dejamos previamente en el Bundle.

public void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   if (getArguments() == null) {
       throw new RuntimeException("You must to send a dummyModels ");
   }
   dummyModels = (DummyModel[]) getArguments().getParcelableArray(KEY_MODEL);

}

onCreateView() se ejecuta cuando se “pinta” la interface, allí debes crear el RecyclerView que usarás definiendo el Adapter y le pasaremos el listener que al final recibirá la acción click sobre el item y la comunicará al Activitity TabExampleMainActivity

Te recomiendo leer luego este otro post a fin de comprender mejor RecyclerView

public View onCreateView(LayoutInflater inflater, ViewGroup container,
                        Bundle savedInstanceState) {
   View view = inflater.inflate(R.layout.fragment_item_list, container, false);

   Context context = view.getContext();
   RecyclerView recyclerView = (RecyclerView) view;
   recyclerView.setLayoutManager(new LinearLayoutManager(context));
   recyclerView.setAdapter(new ItemRecyclerViewAdapter(dummyModels, interactionListener));
   return view;
}

Observa que las Activities (en este ejemplo ‘TabExampleMainActivity’) que usen este Fragment están obligadas a implementar OnListFragmentInteractionListener . Esto es a fin de poder enviarle luego a la Activity el ítem que fue elegido al hacer click. Por lo que TabExampleMainActivity debe implementar este Listener.

Así queda tu clase ItemFragment completa

public class ItemFragment extends Fragment {

   private static final String KEY_MODEL = "KEY_MODEL";

   private DummyModel[] dummyModels;
   private OnListFragmentInteractionListener interactionListener;

   public ItemFragment() {
   }

    /**
     * Receive the model list
     */
   public static ItemFragment newInstance(DummyModel[] dummyModels) {
       ItemFragment fragment = new ItemFragment();
       Bundle args = new Bundle();
       args.putParcelableArray(KEY_MODEL, dummyModels);
       fragment.setArguments(args);
       return fragment;
   }

   @Override
   public void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       if (getArguments() == null) {
           throw new RuntimeException("You must to send a dummyModels ");
       }
       dummyModels = (DummyModel[]) getArguments().getParcelableArray(KEY_MODEL);

   }

   @Override
   public View onCreateView(LayoutInflater inflater, ViewGroup container,
                            Bundle savedInstanceState) {
       View view = inflater.inflate(R.layout.fragment_item_list, container, false);

       Context context = view.getContext();
       RecyclerView recyclerView = (RecyclerView) view;
       recyclerView.setLayoutManager(new LinearLayoutManager(context));
       recyclerView.setAdapter(new ItemRecyclerViewAdapter(dummyModels, interactionListener));
       return view;
   }

   @Override
   public void onAttach(Context context) {
       super.onAttach(context);
       // activity must implement OnListFragmentInteractionListener
       if (context instanceof OnListFragmentInteractionListener) {
           interactionListener = (OnListFragmentInteractionListener) context;
       } else {
           throw new RuntimeException(context.toString()
                   + " must implement OnListFragmentInteractionListener");
       }
   }

   @Override
   public void onDetach() {
       super.onDetach();
       interactionListener = null;
   }

   /**
    * This interface must be implemented by activities that contain this
    * fragment to allow an interaction in this fragment to be communicated
    * <p/>
    */
   public interface OnListFragmentInteractionListener {
       void onListFragmentInteraction(DummyModel item);
   }
}

El RecyclerView.Adapter

Crea esta clase ItemRecyclerViewAdapter que extienda de RecyclerView.Adapter la que será al final la encargada de setear los valores que recibe y de informar haciendo uso del listener ItemFragment.OnListFragmentInteractionListener cuando se haga click sobre un item.

public class ItemRecyclerViewAdapter extends RecyclerView.Adapter<ItemRecyclerViewAdapter.ViewHolder> {

   private final DummyModel[] dummyModels;
   private final ItemFragment.OnListFragmentInteractionListener interactionListener;

   public ItemRecyclerViewAdapter(DummyModel[] items, ItemFragment.OnListFragmentInteractionListener listener) {
       dummyModels = items;
       interactionListener = listener;
   }

   @Override
   public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
       View view = LayoutInflater.from(parent.getContext())
               .inflate(R.layout.fragment_item, parent, false);
       return new ViewHolder(view);
   }

   @Override
   public void onBindViewHolder(final ViewHolder holder, int position) {
       DummyModel dm = dummyModels[position];
       holder.dummyModelItem = dm;
       holder.idView.setText(dm.getId());
       holder.titleView.setText(dm.getTitle());
       holder.detailView.setText(dm.getDetail());

       holder.mView.setOnClickListener(new View.OnClickListener() {
           @Override
           public void onClick(View v) {
               if (null != interactionListener) {
                   // Notify the active callbacks interface (the activity, if the
                   // fragment is attached to one) that an item has been selected.
                   interactionListener.onListFragmentInteraction(holder.dummyModelItem);
               }
           }
       });
   }

   @Override
   public int getItemCount() {
       return dummyModels.length;
   }

   public class ViewHolder extends RecyclerView.ViewHolder {
       public final View mView;
       public final TextView idView;
       public final TextView titleView;
       public final TextView detailView;
       public DummyModel dummyModelItem;

       public ViewHolder(View view) {
           super(view);
           mView = view;
           idView = (TextView) view.findViewById(R.id.id);
           titleView = (TextView) view.findViewById(R.id.title);
           detailView = (TextView) view.findViewById(R.id.detail);
       }
   }
}

Descarga este código completo para visualizar en Android Studio

Hi! If you find my posts helpful, please support me by inviting me for a coffee :)

Ver también