Beray Bentesen in Xamarin Android

Multiple RecyclerView with ViewPager and TabLayout - Xamarin Android

 

This article will be covering :

  • Working with RecyclerView , ViewPager and TabLayout etc.
  • Loading images from url with Glide
  • Using Fragment and FragmentPagerAdapter
  • In order to use example codes you have to include the Xamarin.Android.Support.v4 , Xamarin.Android.Support.Design and Glide.Xamarin for downloading and caching images without any exception.

Working with Layout

  • Material theme is required for this project for several reason.
    • Modifying TabLayout properties (e.g. indicator color, text appearance)
    • To be able to use AppCompatActivity and SupportFragmentManager and so on
  • To be able to use material theme, create values-21 folder in Resources and create a new XML file named 'styles'.
  • Copy and paste following XML codes
<?xml version="1.0" encoding="UTF-8" ?>  
<resources>  
  <style name="MaterialTheme" parent="MaterialTheme.Base"/>

   <style name="MaterialTheme.Base" parent="Theme.AppCompat.Light.NoActionBar">
    <item name="colorAccent">#3a67e0</item>
     <item name="colorPrimary">#3a67e0</item>
    <item name="colorPrimaryDark">#ffffff</item>
    <item name="android:windowDrawsSystemBarBackgrounds">true</item>
    <item name="android:statusBarColor">#0d47a1</item>  
   </style>

    <style name="TabLayoutTheme" parent="Widget.Design.TabLayout">
     <item name="tabIndicatorColor">#000000</item>
     <item name="tabIndicatorHeight">2dp</item>
     <item name="tabTextAppearance">@style/TabLayoutTextAppearance</item>
    </style>

    <style name="TabLayoutTextAppearance" parent="TextAppearance.Design.Tab">
      <item name="textAllCaps">false</item>
      <item name="android:textSize">19sp</item>
      <item name="android:fontFamily">sans-serif-light</item>
    </style>

</resources>  
  • Create a new Layout (or use MainLayout) where ViewPager and TabLayout will be located
  • Copy and paste following layout code into yours
<?xml version="1.0" encoding="utf-8"?>  
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="?attr/colorPrimary"
        android:minHeight="?attr/actionBarSize" />
    <android.support.design.widget.TabLayout
        app:tabMode="scrollable"
        app:tabGravity="fill"
        style="@style/TabLayoutTheme"
        android:id="@+id/tabLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <android.support.v4.view.ViewPager
        android:id="@+id/viewPager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</LinearLayout>  
  • Each page will need RecyclerViewLayout so you must create a new layout for RecyclerView
  • Copy and paste following layout into RecyclerViewLayout
<?xml version="1.0" encoding="utf-8"?>  
<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"  
    xmlns:app="http://schemas.android.com/apk/res-auto"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    android:clipToPadding="false"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/recyclerView" />
  • RecyclerView needs item layout for each row so you must create a new layout for item layout.
  • This example has ImageView with TextView
<?xml version="1.0" encoding="utf-8"?>  
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <ImageView
        android:layout_margin="10dp"
        android:layout_alignParentLeft="true"
        android:layout_width="120dp"
        android:layout_height="120dp"
        android:id="@+id/imageView"
        android:adjustViewBounds="true" />
    <TextView
        android:layout_toEndOf="@id/imageView"
        android:layout_marginStart="30dp"
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:fontFamily="sans-serif"
        android:textSize="21sp"
        android:layout_centerVertical="true" />
</RelativeLayout>  

Working with Code

  • The first step is creating a Fragment class.
  • Because ViewPager will always need a single view for each data, application will follow a certain pattern while returning fragments.
  • Func< T > is a predefined delegate type for a method that returns some value of the type and we can use this type to reference a method that returns some value of T which will be a View.
public class RecyclerViewFragment : Android.Support.V4.App.Fragment  
    {
        readonly Func<LayoutInflater, ViewGroup, Bundle, View> view;

        public RecyclerViewFragment(Func<LayoutInflater, ViewGroup, Bundle, View> view)
        {
            this.view = view;
        }

        public override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);
        }

        public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
        {
            base.OnCreateView(inflater, container, savedInstanceState);

            return view(inflater, container, savedInstanceState);
        }
    }
  • FragmentPagerAdapter is implementation of PagerAdapter that represents each page as a Fragment that is persistently kept in the fragment manager as long as the user can return to the page.
  • In this example, FragmentPagerAdapter will be holding RecyclerViews
  • addFragmentView method will be adding Fragments into fragmentList in Activity where views will be inflating.
public class RecyclerViewFragmentPagerAdapter : FragmentPagerAdapter  
    {
        readonly List<Fragment> fragmentList = new List<Fragment>();
        readonly ICharSequence[] titles;

        public RecyclerViewFragmentPagerAdapter(FragmentManager fragmentManager, ICharSequence[] titles) : base(fragmentManager)
        {
            this.titles = titles;
        }

        public override int Count
        {
            get
            {
                return fragmentList.Count;
            }
        }

        public override Fragment GetItem(int position)
        {
            return fragmentList[position];
        }

        public override ICharSequence GetPageTitleFormatted(int position)
        {
            return titles[position];
        }

        public void addFragmentView(Func<LayoutInflater, ViewGroup, Bundle, View> fragmentView)
        {
            fragmentList.Add(new RecyclerViewFragment(fragmentView));
        }
    }
  • RecyclerView needs data model for each item
  • This example has two string for TextView and ImageView
public class RecyclerViewDataModel  
    {
        public string someString { get; set; }
        public string imageUrl { get; set; }
    }
  • RecyclerView.Adapter is also required to present data in each view
  • This example adapter also has item click feature which can be used for any action
public class RecyclerViewAdapter : RecyclerView.Adapter  
    {

        public List<RecyclerViewDataModel> dataModelList;
        public Context context;
        public event EventHandler<int> eventHandler;


        public RecyclerViewAdapter(List<RecyclerViewDataModel> dataModelList, Context context)
        {
            this.dataModelList = dataModelList;
            this.context = context;
        }

        public override int ItemCount
        {
            get
            {
                return dataModelList.Count;
            }
        }

        public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
        {
            var item = dataModelList[position];

            var viewHolder = holder as RecyclerViewHolder;

            if (holder == viewHolder && viewHolder != null)
            {
                viewHolder.textView.Text = item.someString;

                Glide.With(context).Load(item.imageUrl).Into(viewHolder.imageView);
            }
        }

        public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType)
        {
            var view = LayoutInflater.From(parent.Context).Inflate(Resource.Layout.RecyclerViewItemLayout, parent, false);

            var viewHolder = new RecyclerViewHolder(view, clickEvent);

            return viewHolder;
        }

        public void clickEvent(int position)
        {
            if (eventHandler != null)
                eventHandler(this, position);   
        }
    }

    public class RecyclerViewHolder : RecyclerView.ViewHolder
    {
        public View view { get; set; }
        public ImageView imageView { get; set; }
        public TextView textView { get; set; }

        public RecyclerViewHolder(View view, Action<int> eventHandler) : base(view)
        {
            this.view = view;
            imageView = view.FindViewById<ImageView>(Resource.Id.imageView);
            textView = view.FindViewById<TextView>(Resource.Id.textView);
            view.Click += (sender, e) => eventHandler(AdapterPosition);
        }

    }
  • You must add following variables at the top of page and set theme to prevent any exception
[Activity(Theme = "@style/MaterialTheme", MainLauncher = true, Icon = "@mipmap/icon")]
    public class MainActivity : AppCompatActivity
    {
        Android.Support.V7.Widget.Toolbar toolbar;
        ViewPager viewPager;
        TabLayout tabLayout;
        RecyclerView recyclerView;
        RecyclerViewAdapter recyclerViewAdapter;
        RecyclerViewFragmentPagerAdapter recyclerViewFragmentPagerAdapter;
        ICharSequence[] titles;
        LinearLayoutManager linearLayoutManager;
        List<RecyclerViewDataModel> dataModelList;
                ...
     }
  • Variables must be initialized in OnCreate method
toolbar = FindViewById<Android.Support.V7.Widget.Toolbar>(Resource.Id.toolbar);  
            SetSupportActionBar(toolbar);
            SupportActionBar.SetDisplayHomeAsUpEnabled(false);

            tabLayout = FindViewById<TabLayout>(Resource.Id.tabLayout);
            viewPager = FindViewById<ViewPager>(Resource.Id.viewPager);

            dataModelList = new List<RecyclerViewDataModel>();

            loadData();

            viewPager.Adapter = recyclerViewFragmentPagerAdapter;
            tabLayout.SetupWithViewPager(viewPager);
  • loadData method fill dataModelList and return fragment for each item in dataModelList
void loadData()  
        {
            for (int i = 0; i <= 7; i++)
            {
                dataModelList.Add(new RecyclerViewDataModel
                {
                    someString = "SomeString " + i,
                    imageUrl = "https://blog.xamarin.com/wp-content/uploads/2015/03/RDXWoY7W_400x400.png"
                });
            }

            var sArray = dataModelList.Select(x => x.someString).ToArray();

            titles = CharSequence.ArrayFromStringArray(sArray);

            recyclerViewFragmentPagerAdapter = new RecyclerViewFragmentPagerAdapter(SupportFragmentManager, titles);

            foreach (var item in dataModelList)
            {
                createFragment(dataModelList);
            }
        }
  • createFragment method will return RecyclerView after setting up adapter, layout manager and event handler.
void createFragment(List<RecyclerViewDataModel> list)  
        {
            recyclerViewFragmentPagerAdapter.addFragmentView((arg1, arg2, arg3) =>
            {

                var view = arg1.Inflate(Resource.Layout.RecyclerViewLayout, arg2, false);

                recyclerView = view.FindViewById<RecyclerView>(Resource.Id.recyclerView);

                linearLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.Vertical, false);

                recyclerViewAdapter = new RecyclerViewAdapter(list, this);

                recyclerView.SetLayoutManager(linearLayoutManager);

                recyclerView.SetAdapter(recyclerViewAdapter);

                recyclerViewAdapter.NotifyDataSetChanged();

                recyclerViewAdapter.eventHandler += (sender, e) =>
                {
                    // Accessing data from selected item
                    var item = list[e];
                    Console.WriteLine(item.someString);
                };

                return view;

            });
        }

Result :

Download project