В этой статье вы увидите:

  • Работу с RecyclerView, ViewPager и TabLayout;
  • Загрузку изображений по URL с использованием Glide;
  • Использование Fragment и FragmentPagerAdapter;

Для использования примеров кода, вам необходимо подключить следующие NuGet пакеты: Xamarin.Android.Support.v4, Xamarin.Android.Support.Design и Glide.Xamarin для загрузки и кеширования изображений без каких-либо исключений.

Разметка

Для этого проекта нам потребуется Material Theme, и вот почему:

  • Изменение свойств TabLayout (например, цвет, вид текста)
  • Чтобы иметь возможность использовать AppCompatActivity и SupportFragmentManager и др.

Чтобы использовать Material Theme, в директории ресурсов создайте поддиректорию values-21, а в ней новый XML-файл с именем “styles”.

Скопируйте в созданный файл, следующий XML-код:

<?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>

Создайте новый макет (или используйте MainLayout), где будут располагаться ViewPager и TabLayout.

Скопируйте и вставьте следующий код:

<?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>

Для каждой страницы потребуется RecyclerViewLayout, поэтому вы должны создать новый макет для RecyclerView.

Скопируйте и вставьте следующий макет в 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 нуждается в разметке для каждого элемента, поэтому вы должны ее создать.

В этом примере есть ImageView и 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>

Код

На первым шаге, создадим класс фрагмента. Поскольку ViewPager всегда будет иметь единое представление для всех данных, приложение будет следовать определенной схеме, возвращая фрагменты.

Func<T> предопределенный тип делегата для метода, который возвращает некоторое значение типа T, и мы можем использовать этот тип для ссылки на метод, возвращающий некоторое значение, которое будет представлять собой 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 реализует PagerAdapter, представляет каждую страницу как фрагмент, который постоянно хранится в менеджере фрагментов, пока пользователь может вернуться на страницу. В этом примере FragmentPagerAdapter будет содержать объекты RecyclerView.

Метод addFragmentView будет добавлять фрагменты в поле fragmentList нашей Activity.

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 => 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 нуждается в некоторой модели данных для каждого элемента. В этом примере есть два свойства для TextView и ImageView.

public class RecyclerViewDataModel
{
	public string someString { get; set; }
	public string imageUrl { get; set; }
}

Как и в случае с ListView, для вывода сложных объектов в RecyclerView необходимо определить свой адаптер. Поэтому определим собственный класс RecyclerViewAdapter, который должен наследоваться от абстрактного класса RecyclerView.Adapter.

Этот пример адаптера имеет возможность обработки клика по элементу.

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

}

Так же, не забудем добавить определение следующих переменных:

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

	// . . .
}

Переменные должны быть инициализированы в методе OnCreate

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 заполняет dataModelList и возвращает фрагмент для каждого элемента из 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 вернет RecyclerView, после настройки адаптера, менеджера макетов и обработчика событий.

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) =>
		{
			// Доступ к данным выбранного элемента
			var item = list[e];
			Console.WriteLine(item.someString);
		};

		return view;

	});
}

Результат

Исходный код проекта на GitHub

Теги: xamarin, android, xamarin.android, c#, перевод

Редактировать