Одной из самых крутых тенденций в дизайне мобильных пользовательских интерфейсов, смело можно назвать использование видео в качестве фона для предоставления. Как пример, приложения Tumblr, Spotify и Vine. В этой статье мы разберём то, как реализовать аналогичное решение в приложении Xamarin.Forms, а в конце расскажем о меророиятии, которое скоро пройдёт в СПб. Всё, что нам нужно, это реализовать два пользовательских рендерера для Android и для iOS по отдельности.
Давайте сначала создадим новый проект Xamarin.Forms PCL и назовём его
Чтобы не усложнять описание мы попробуем создать этот элемент управления с простыми требованиями.
Для того чтобы указать, какое видео будет отображаться, нам нужно свойство привязки. Я буду называть его свойством
Следующее, что нам нужно, это логическое значение, чтобы определить, требуется ли видео в цикле или нет. Давайте назовем это свойство
Наконец, нам понадобится обратный вызов, который будет срабатывать при завершении видео. Для простоты я использую класс
После создания этого класса необходимо реализовать пользовательские рендереры для iOS и Android.
Прежде всего необходимо создать класс пользовательского рендерера с именем
Чтобы запустить видеопроигрыватель iOS, необходимо проверить, есть ли видео из
Если видеофайл есть, тогда необходимо создать
Кроме того, у нас, вероятно, будет один видеофайл для любого разрешения. Нам ведь не нужно, чтобы видео выглядело растянутым на каком-то устройство, верно? Чтобы разрешение видео всегда было правильным, нам нужно установить
У нас также есть свойство
Наконец, чтобы видеопроигрыватель начал воспроизводить файл, мы вызовем функцию
Остальная часть работы с кодом состоит в том, чтобы переопределить функции
Теперь, когда реализация iOS завершена, давайте рассмотрим проект Android.
Создайте новый пользовательский рендерер в проекте Android и также назовите его
Одна из трудностей при реализации рендерера для Android состоит в том, что нам нужны два вида представлений в том случае, если мы хотим также охватить и старые версии Android. Если вы всего лишь хотите охватить современные ОС от Android Ice Cream Sandwich и выше, вы можете просто сосредоточиться на реализации
Пожалуйста, обратите внимание на то, что реализация
А сейчас прежде чем решить, какой видеоконтейнер лучше использовать, нужно сперва реализовать сам видеопроигрыватель. У Android уже есть нужный нам класс —
Нам нужно установить событие
Кое-что здесь отличается от реализации рендерера в случае с iOS — тут нет такого простого набора свойств для отображения видео в разном разрешении, как заливки пропорций! Получается, нам необходимо реализовать собственный метод в настраиваемой функции, которая называется
Теперь, когда у нас есть объект видеопроигрывателя, следующая наша задача — создать функцию, которая воспроизводит видео из свойства
Если файл не существует, тогда функция выдаст
Если файл существует, нам просто нужно сбросить видеопроигрыватель, а затем задать источник данных, основываясь на предыдущем шаге. Мы не можем просто воспроизвести видео напрямую, так что нам нужно сначала его подготовить. После завершения подготовки инициируется событие
Как уже упоминалось выше, Android не предоставляет нам легкого свойства для масштабирования видео по пропорциям. Вы наверняка знаете, что устройства Android имеют очень много вариантов разрешения экрана, поэтому сохранить видео в его изначальном виде — совсем не вариант. Нам нужно правильно его масштабировать, чтобы оно не выглядело растянутым.
Хорошие новости заключаются в том, что мы, в общем, можем этого добиться, если воспользуемся
Идея достижения того, чтобы видео масштабировалось правильно, заключается в использовании матрицы для масштабирования содержимого
Как упоминалось ранее, если мы хотим поддержать широкий диапазон ОС Android, мы должны реализовать это в
В следующем фрагменте кода показано, как реализовать его в настраиваемом рендерере. Как видите, код довольно похож на тот, что мы использовали при реализации рендерера для iOS, за исключением создания контейнеров и воспроизведения видео.
Поскольку мы используем
При использовании TextureView необходимо реализовать интерфейс
При использовании
По части Android это было всё, что нам требовалось. Теперь, когда у нас есть все необходимое, мы можем протестировать этот элемент управления на странице Xamarin.Forms.
Перед созданием тестовой страницы рекомендуется подготовить собственный видеофайл. Лучше использовать вертикальное видео, чтобы эффективно использовать место.
Если видео для тестирования у вас нет, его можно бесплатно загрузить из Coverr. Там нет вертикальных видео, но мы всё же можем получить то, что нам нужно. Например, можно либо обрезать видео вертикально, либо использовать его таким, какое оно есть, так как мы уже обрабатываем в коде масштабирование аспекта при заполнении.
Так что можете использовать любое видео, которое есть под рукой. Я рекомендую воспользоваться любым видеофайлом в формате mp4 с кодировкой h264. В этом примере я использую видео из Coverr, которое называется Orchestra.
Справка: По поводу некоторых устройств Android и iOS, особенно старых моделей, надо оговориться, что они, вероятно, могут и не уметь воспроизводить файлы MP4. В основном это вызвано отсутствием поддержки базового профиля. Чтобы обойти этот момент, можно перекодировать видео с помощью такого инструмента, как
Если видеофайл у вас уже есть, поместите его в соответствующие папки для каждой ОС. В Android его следует поместить в каталог
После того как файлы будут помещены в правильные папки, необходимо создать страницу в проекте Xamarin.Forms PCL.
Это простая страница с минимумом компонентов. Мы создадим домашнюю страницу с фоновым видео, два текстовых поля для имени пользователя и пароля, а также кнопки для входа и регистрации. Здесь нет логики, я просто хочу показать, как делается красивая домашняя страница.
Для лучшего размещения элементов управления я использую сетку как контейнер. В следующем фрагменте представлен соответствующий код XAML полностью.
Ну вот и всё. Если вы не хотите, чтобы видео было зациклено, просто измените свойство
Если все задано правильно, то должно получиться, как на картинке, где показывается приложение, запущенное на устройстве или эмуляторе iOS. Как видите, там есть два текстовых поля и две кнопки. Видео плавно воспроизводится на фоне страницы.
Аналогично версии iOS, рисунок показывает, как это видео выглядит на устройстве или эмуляторе Android. Обратите внимание на отличие стиля текстового поля от версии для iOS. Но давайте позаботимся об этом позже, ведь суть-то в том, что фоновое видео слаженно работает — точно так же, как и на iOS.
Всё, что остается, это сделать стиль более согласованным с каждой платформой.
Поясним, что главная мысль здесь простая, — любой другой требующийся элемент управления можно сделать с помощью настраиваемого средства визуализации. Если вы понимаете, как кодить на нативном языке (ну, это можно погуглить, если что), тогда вы можете создать что угодно.
Что касается производительности, то, как я уже говорил, на старых устройствах Android вы, вероятно, будете замечать мерцание. На данный момент у меня нет идей по поводу того, как это можно было бы оптимизировать.
Если у вас есть идеи и предложения, поделитесь ими в комментариях ниже.
Уже готовый проект можно загрузить на GitHub.
Александр Алексеев — Xamarin-разработчик, фрилансер. Работает с .NET-платформой с 2012 года. Участвовал в разработке системы автоматизации закупок в компании Digamma. C 2015 года ушел во фриланс и перешел на мобильную разработку с использованием Xamarin. В текущее время работает в компании StecPoint над iOS приложением.
Ведет ресурс XamDev.ru и сообщества «Xamarin Developers» в социальных сетях: VK, Facebook, Telegram.
20 мая в Санкт-Петербурге пройдёт первый Xamarin Dev Days в России. Мы соберём тёплую дружественную компанию разработчиков на Xamarin, в том числе экспертов нашего сообщества.
Расписание:
09:00 – 09:30 Регистрация
09:30 – 10:10 Введение в Xamarin
10:20 – 11:00 Кроссплатформенный UI с Xamarin.Forms
11:10 – 11:50 Подключаем приложение к Azure
12:00 – 13:00 Обед
13:00 – 16:00 Воркшоп
Все подробности о мероприятии и регистрация по ссылке.
Создание элемента управления фоновым видео для Xamarin.Forms
Давайте сначала создадим новый проект Xamarin.Forms PCL и назовём его
BackgroundVideo
. Теперь давайте перейдём к библиотеке PCL и создадим новый класс с именем Video
, унаследованный от Xamarin.Forms.View
.using System;
using Xamarin.Forms;
namespace BackgroundVideo.Controls
{
public class Video : View
{
}
}
Чтобы не усложнять описание мы попробуем создать этот элемент управления с простыми требованиями.
Для того чтобы указать, какое видео будет отображаться, нам нужно свойство привязки. Я буду называть его свойством
Source
. Это строка для определения того, какой видеофайл следует воспроизвести. В iOS свойство Source
имеет отношение к каталогу Resources
, тогда как в Android оно относится к каталогу Assets
.public static readonly BindableProperty SourceProperty =
BindableProperty.Create(
nameof(Source),
typeof(string),
typeof(Video),
string.Empty,
BindingMode.TwoWay);
public string Source
{
get { return (string)GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
Следующее, что нам нужно, это логическое значение, чтобы определить, требуется ли видео в цикле или нет. Давайте назовем это свойство
Loop
. Изначально это значение задано как true
, поэтому при задании свойства источника видео — Source
это свойство будет зацикливаться по умолчанию.Наконец, нам понадобится обратный вызов, который будет срабатывать при завершении видео. Для простоты я использую класс
Action
под названием OnFinishedPlaying
. Можно изменить его на событие или на то, что будет удобно.public static readonly BindableProperty LoopProperty =
BindableProperty.Create(
nameof(Loop),
typeof(bool),
typeof(Video),
true,
BindingMode.TwoWay);
public bool Loop
{
get { return (bool)GetValue(LoopProperty); }
set { SetValue(LoopProperty, value); }
}
public Action OnFinishedPlaying { get; set; }
После создания этого класса необходимо реализовать пользовательские рендереры для iOS и Android.
Настраиваемый рендерер iOS для управления фоновым видео
Прежде всего необходимо создать класс пользовательского рендерера с именем
VideoRenderer
, который будет наследовать от ViewRenderer<Video, UIView>
. Идея состоит в том, чтобы использовать нативный видеопроигрыватель iOS с помощью класса MPMoviePlayerController
и установить его нативный элемент управления в наше настраиваемое Video
представление. Нам также понадобится NSObject
, чтобы анализировать событие от видеопроигрывателя, определяя закончилось оно или нет.using System;
using System.IO;
using BackgroundVideo.Controls;
using BackgroundVideo.iOS.Renderers;
using Foundation;
using MediaPlayer;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(Video), typeof(VideoRenderer))]
namespace BackgroundVideo.iOS.Renderers
{
public class VideoRenderer : ViewRenderer<Video, UIView>
{
MPMoviePlayerController videoPlayer;
NSObject notification = null;
}
}
Чтобы запустить видеопроигрыватель iOS, необходимо проверить, есть ли видео из
Source
в узле Resources или нет. Если его там нет, тогда отобразится пустое представление.Если видеофайл есть, тогда необходимо создать
MPMoviePlayerController
и интерпретировать расположение файла видео как NSUrl
. Чтобы сделать пользовательский элемент управления ясным, без границы или чего-либо ещё, нужно установить ControlStyle
на MPMovieControlStyle.None
, а цвет фона на UIColor.Clear
.Кроме того, у нас, вероятно, будет один видеофайл для любого разрешения. Нам ведь не нужно, чтобы видео выглядело растянутым на каком-то устройство, верно? Чтобы разрешение видео всегда было правильным, нам нужно установить
ScalingMode
у видеопроигрывателя на MPMovieScalingMode.AspectFill
.У нас также есть свойство
Loop
, определяющее, будет ли воспроизведение видео циклическим или нет. Чтобы установить цикл, нужно изменить RepeatMode
у видеопроигрывателя на MPMovieRepeatMode.One
. В противном случае установите его на MPMovieRepeatMode.None
.Наконец, чтобы видеопроигрыватель начал воспроизводить файл, мы вызовем функцию
PrepareToPlay()
. Чтобы отобразить видео в пользовательском элементе управления, необходимо использовать функцию SetNativeControl()
.void InitVideoPlayer()
{
var path = Path.Combine(NSBundle.MainBundle.BundlePath, Element.Source);
if (!NSFileManager.DefaultManager.FileExists(path))
{
Console.WriteLine("Video not exist");
videoPlayer = new MPMoviePlayerController();
videoPlayer.ControlStyle = MPMovieControlStyle.None;
videoPlayer.ScalingMode = MPMovieScalingMode.AspectFill;
videoPlayer.RepeatMode = MPMovieRepeatMode.One;
videoPlayer.View.BackgroundColor = UIColor.Clear;
SetNativeControl(videoPlayer.View);
return;
}
// Load the video from the app bundle.
NSUrl videoURL = new NSUrl(path, false);
// Create and configure the movie player.
videoPlayer = new MPMoviePlayerController(videoURL);
videoPlayer.ControlStyle = MPMovieControlStyle.None;
videoPlayer.ScalingMode = MPMovieScalingMode.AspectFill;
videoPlayer.RepeatMode = Element.Loop ? MPMovieRepeatMode.One : MPMovieRepeatMode.None;
videoPlayer.View.BackgroundColor = UIColor.Clear;
foreach (UIView subView in videoPlayer.View.Subviews)
{
subView.BackgroundColor = UIColor.Clear;
}
videoPlayer.PrepareToPlay();
SetNativeControl(videoPlayer.View);
}
Остальная часть работы с кодом состоит в том, чтобы переопределить функции
OnElementChanged
и OnElementPropertyChanged
так, чтобы с кодом можно было функционально работать из проекта Xamarin.Forms. В OnElementChanged
мы должны ожидать события окончания воспроизведения видеопроигрывателя и вызывать команду OnFinishedPlaying
. Следующий фрагмент является простейшим кодом, необходимым для того, чтобы это все работало.protected override void OnElementChanged(ElementChangedEventArgs<Video> e)
{
base.OnElementChanged(e);
if (Control == null)
{
InitVideoPlayer();
}
if (e.OldElement != null)
{
// Unsubscribe
notification?.Dispose();
}
if (e.NewElement != null)
{
// Subscribe
notification = MPMoviePlayerController.Notifications.ObservePlaybackDidFinish((sender, args) =>
{
/* Access strongly typed args */
Console.WriteLine("Notification: {0}", args.Notification);
Console.WriteLine("FinishReason: {0}", args.FinishReason);
Element?.OnFinishedPlaying?.Invoke();
});
}
}
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (Element == null || Control == null)
return;
if (e.PropertyName == Video.SourceProperty.PropertyName)
{
InitVideoPlayer();
}
else if (e.PropertyName == Video.LoopProperty.PropertyName)
{
var liveImage = Element as Video;
if (videoPlayer != null)
videoPlayer.RepeatMode = Element.Loop ? MPMovieRepeatMode.One : MPMovieRepeatMode.None;
}
}
Теперь, когда реализация iOS завершена, давайте рассмотрим проект Android.
Пользовательский рендерер видео для Android
Создайте новый пользовательский рендерер в проекте Android и также назовите его
VideoRenderer
. Мы наследуем этот рендерер с помощью ViewRenderer<Video, FrameLayout>
, и это означает, что он будет отображаться как FrameLayout
в нативном элементе управления Android.Одна из трудностей при реализации рендерера для Android состоит в том, что нам нужны два вида представлений в том случае, если мы хотим также охватить и старые версии Android. Если вы всего лишь хотите охватить современные ОС от Android Ice Cream Sandwich и выше, вы можете просто сосредоточиться на реализации
TextureView
, если же вам этого недостаточно, тогда нужно будет также осуществить реализацию с помощью VideoView
.Пожалуйста, обратите внимание на то, что реализация
VideoView
здесь не оптимальна. Возможно, Вы заметите некоторое мерцание. Вот поэтому я добавил пустое представление под названием _placeholder
. Оно будет отображаться в том случае, когда видео не воспроизводится или при изменении источника видео. Если видеофайл готов для воспроизведения и отображения, тогда _placeholder
будет скрыт.using System;
using Android.Graphics;
using Android.Graphics.Drawables;
using Android.Media;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using BackgroundVideo.Controls;
using BackgroundVideo.Droid.Renderers;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(Video), typeof(VideoRenderer))]
namespace BackgroundVideo.Droid.Renderers
{
public class VideoRenderer : ViewRenderer<Video, FrameLayout>,
TextureView.ISurfaceTextureListener,
ISurfaceHolderCallback
{
private bool _isCompletionSubscribed = false;
private FrameLayout _mainFrameLayout = null;
private Android.Views.View _mainVideoView = null;
private Android.Views.View _placeholder = null;
}
}
А сейчас прежде чем решить, какой видеоконтейнер лучше использовать, нужно сперва реализовать сам видеопроигрыватель. У Android уже есть нужный нам класс —
MediaPlayer
. Нам следует использовать этот объект и убедиться в том, что он создан только единожды. Мы можем повторно использовать один и тот же объект при изменении источника видео.Нам нужно установить событие
Completion
, чтобы реализовать обратный вызов OnFinishedPlaying
. Кроме того, необходимо задать значение Looping
для настраиваемого свойства Loop
.Кое-что здесь отличается от реализации рендерера в случае с iOS — тут нет такого простого набора свойств для отображения видео в разном разрешении, как заливки пропорций! Получается, нам необходимо реализовать собственный метод в настраиваемой функции, которая называется
AdjustTextureViewAspect()
. Эта функция будет вызываться в обратном вызове VideoSizeChanged
. Мы расскажем об этой реализации позже.private MediaPlayer _videoPlayer = null;
internal MediaPlayer VideoPlayer
{
get
{
if (_videoPlayer == null)
{
_videoPlayer = new MediaPlayer();
if (!_isCompletionSubscribed)
{
_isCompletionSubscribed = true;
_videoPlayer.Completion += Player_Completion;
}
_videoPlayer.VideoSizeChanged += (sender, args) =>
{
AdjustTextureViewAspect(args.Width, args.Height);
};
_videoPlayer.Info += (sender, args) =>
{
Console.WriteLine("onInfo what={0}, extra={1}", args.What, args.Extra);
if (args.What == MediaInfo.VideoRenderingStart)
{
Console.WriteLine("[MEDIA_INFO_VIDEO_RENDERING_START] placeholder GONE");
_placeholder.Visibility = ViewStates.Gone;
}
};
_videoPlayer.Prepared += (sender, args) =>
{
_mainVideoView.Visibility = ViewStates.Visible;
_videoPlayer.Start();
if (Element != null)
_videoPlayer.Looping = Element.Loop;
};
}
return _videoPlayer;
}
}
private void Player_Completion(object sender, EventArgs e)
{
Element?.OnFinishedPlaying?.Invoke();
}
Теперь, когда у нас есть объект видеопроигрывателя, следующая наша задача — создать функцию, которая воспроизводит видео из свойства
Source
. Пожалуйста, помните, что видеофайл на Android должен храниться в каталоге Assets
. Этот файл можно открыть с помощью функции Assets.OpenFd(fullPath)
.Если файл не существует, тогда функция выдаст
Java.IO.IOException
. Это значит, что в видеоконтейнере ничего отображать не нужно.Если файл существует, нам просто нужно сбросить видеопроигрыватель, а затем задать источник данных, основываясь на предыдущем шаге. Мы не можем просто воспроизвести видео напрямую, так что нам нужно сначала его подготовить. После завершения подготовки инициируется событие
Prepared
и отображается видео в одном из реализованных на предыдущем этапе видеопредставлений.private void PlayVideo(string fullPath)
{
Android.Content.Res.AssetFileDescriptor afd = null;
try
{
afd = Context.Assets.OpenFd(fullPath);
}
catch (Java.IO.IOException ex)
{
Console.WriteLine("Play video: " + Element.Source + " not found because " + ex);
_mainVideoView.Visibility = ViewStates.Gone;
}
catch (Exception ex)
{
Console.WriteLine("Error openfd: " + ex);
_mainVideoView.Visibility = ViewStates.Gone;
}
if (afd != null)
{
Console.WriteLine("Lenght " + afd.Length);
VideoPlayer.Reset();
VideoPlayer.SetDataSource(afd.FileDescriptor, afd.StartOffset, afd.Length);
VideoPlayer.PrepareAsync();
}
}
Как уже упоминалось выше, Android не предоставляет нам легкого свойства для масштабирования видео по пропорциям. Вы наверняка знаете, что устройства Android имеют очень много вариантов разрешения экрана, поэтому сохранить видео в его изначальном виде — совсем не вариант. Нам нужно правильно его масштабировать, чтобы оно не выглядело растянутым.
Хорошие новости заключаются в том, что мы, в общем, можем этого добиться, если воспользуемся
TextureView
. Плохая новость состоит в том, что на данный момент я не знаю, как это реализовать с VideoView
. Но это лучше, чем ничего, верно?Идея достижения того, чтобы видео масштабировалось правильно, заключается в использовании матрицы для масштабирования содержимого
TextureView
. Таким образом, масштабирование видео происходит по верхней или по нижней части в зависимости от размеров видео и предоставления. Затем, после масштабирования, видео располагается в центре представления.Как упоминалось ранее, если мы хотим поддержать широкий диапазон ОС Android, мы должны реализовать это в
TextureView
и VideoView
. Это будет осуществляться в рамках функции OnElementChanged
. В случае с обеими реализациями используются одни и те же свойства. Мы сделаем цвет фона прозрачным и приведем параметры макета в соответствие с родительским элементом. Таким образом, у фона не будет цвета, который мог бы отображаться при отсутствии видео, и этот фон заполнит весь контейнер. private void AdjustTextureViewAspect(int videoWidth, int videoHeight)
{
if (!(_mainVideoView is TextureView))
return;
if (Control == null)
return;
var control = Control;
var textureView = _mainVideoView as TextureView;
var controlWidth = control.Width;
var controlHeight = control.Height;
var aspectRatio = (double)videoHeight / videoWidth;
int newWidth, newHeight;
if (controlHeight <= (int)(controlWidth * aspectRatio))
{
// limited by narrow width; restrict height
newWidth = controlWidth;
newHeight = (int)(controlWidth * aspectRatio);
}
else
{
// limited by short height; restrict width
newWidth = (int)(controlHeight / aspectRatio);
newHeight = controlHeight;
}
int xoff = (controlWidth - newWidth) / 2;
int yoff = (controlHeight - newHeight) / 2;
Console.WriteLine("video=" + videoWidth + "x" + videoHeight +
" view=" + controlWidth + "x" + controlHeight +
" newView=" + newWidth + "x" + newHeight +
" off=" + xoff + "," + yoff);
var txform = new Matrix();
textureView.GetTransform(txform);
txform.SetScale((float)newWidth / controlWidth, (float)newHeight / controlHeight);
txform.PostTranslate(xoff, yoff);
textureView.SetTransform(txform);
}
В следующем фрагменте кода показано, как реализовать его в настраиваемом рендерере. Как видите, код довольно похож на тот, что мы использовали при реализации рендерера для iOS, за исключением создания контейнеров и воспроизведения видео.
protected override void OnElementChanged(ElementChangedEventArgs<Video> e)
{
base.OnElementChanged(e);
if (Control == null)
{
_mainFrameLayout = new FrameLayout(Context);
_placeholder = new Android.Views.View(Context)
{
Background = new ColorDrawable(Xamarin.Forms.Color.Transparent.ToAndroid()),
LayoutParameters = new LayoutParams(
ViewGroup.LayoutParams.MatchParent,
ViewGroup.LayoutParams.MatchParent),
};
if (Build.VERSION.SdkInt < BuildVersionCodes.IceCreamSandwich)
{
Console.WriteLine("Using VideoView");
var videoView = new VideoView(Context)
{
Background = new ColorDrawable(Xamarin.Forms.Color.Transparent.ToAndroid()),
Visibility = ViewStates.Gone,
LayoutParameters = new LayoutParams(
ViewGroup.LayoutParams.MatchParent,
ViewGroup.LayoutParams.MatchParent),
};
ISurfaceHolder holder = videoView.Holder;
if (Build.VERSION.SdkInt < BuildVersionCodes.Honeycomb)
{
holder.SetType(SurfaceType.PushBuffers);
}
holder.AddCallback(this);
_mainVideoView = videoView;
}
else
{
Console.WriteLine("Using TextureView");
var textureView = new TextureView(Context)
{
Background = new ColorDrawable(Xamarin.Forms.Color.Transparent.ToAndroid()),
Visibility = ViewStates.Gone,
LayoutParameters = new LayoutParams(
ViewGroup.LayoutParams.MatchParent,
ViewGroup.LayoutParams.MatchParent),
};
textureView.SurfaceTextureListener = this;
_mainVideoView = textureView;
}
_mainFrameLayout.AddView(_mainVideoView);
_mainFrameLayout.AddView(_placeholder);
SetNativeControl(_mainFrameLayout);
PlayVideo(Element.Source);
}
if (e.OldElement != null)
{
// Unsubscribe
if (_videoPlayer != null && _isCompletionSubscribed)
{
_isCompletionSubscribed = false;
_videoPlayer.Completion -= Player_Completion;
}
}
if (e.NewElement != null)
{
// Subscribe
if (_videoPlayer != null && !_isCompletionSubscribed)
{
_isCompletionSubscribed = true;
_videoPlayer.Completion += Player_Completion;
}
}
}
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (Element == null || Control == null)
return;
if (e.PropertyName == Video.SourceProperty.PropertyName)
{
Console.WriteLine("Play video: " + Element.Source);
PlayVideo(Element.Source);
}
else if (e.PropertyName == Video.LoopProperty.PropertyName)
{
Console.WriteLine("Is Looping? " + Element.Loop);
VideoPlayer.Looping = Element.Loop;
}
}
Поскольку мы используем
TextureView
и VideoView
, тут следует реализовать некоторые функции из интерфейсов. Одна из них предназначается для удаления видео при разрушении texture (текстуры) или surface (поверхности). Чтобы это сделать, нам нужно установить видимость >_placeholder
на visible.private void RemoveVideo()
{
_placeholder.Visibility = ViewStates.Visible;
}
При использовании TextureView необходимо реализовать интерфейс
TextureView.ISurfaceTextureListener
. Мы установили surface видеопроигрывателя на тот случай, когда texture доступна и указали сокрытие surface при уничтожении texture. В следующем фрагменте показано, как это реализовать.#region Surface Texture Listener
public void OnSurfaceTextureAvailable(SurfaceTexture surface, int width, int height)
{
Console.WriteLine("Surface.TextureAvailable");
VideoPlayer.SetSurface(new Surface(surface));
}
public bool OnSurfaceTextureDestroyed(SurfaceTexture surface)
{
Console.WriteLine("Surface.TextureDestroyed");
RemoveVideo();
return false;
}
public void OnSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height)
{
Console.WriteLine("Surface.TextureSizeChanged");
}
public void OnSurfaceTextureUpdated(SurfaceTexture surface)
{
Console.WriteLine("Surface.TextureUpdated");
}
#endregion
При использовании
VideoView
необходимо реализовать интерфейс ISurfaceHolderCallback
. Аналогично TextureView
, мы установили дисплей видеопроигрывателя на создание surface и указали его сокрытие при уничтожении surface. Полную реализация этого интерфейса можно рассмотреть на следующем фрагменте.#region Surface Holder Callback
public void SurfaceChanged(ISurfaceHolder holder, [GeneratedEnum] Format format, int width, int height)
{
Console.WriteLine("Surface.Changed");
}
public void SurfaceCreated(ISurfaceHolder holder)
{
Console.WriteLine("Surface.Created");
VideoPlayer.SetDisplay(holder);
}
public void SurfaceDestroyed(ISurfaceHolder holder)
{
Console.WriteLine("Surface.Destroyed");
RemoveVideo();
}
#endregion
По части Android это было всё, что нам требовалось. Теперь, когда у нас есть все необходимое, мы можем протестировать этот элемент управления на странице Xamarin.Forms.
Тестирование на странице Xamarin.Forms
Перед созданием тестовой страницы рекомендуется подготовить собственный видеофайл. Лучше использовать вертикальное видео, чтобы эффективно использовать место.
Если видео для тестирования у вас нет, его можно бесплатно загрузить из Coverr. Там нет вертикальных видео, но мы всё же можем получить то, что нам нужно. Например, можно либо обрезать видео вертикально, либо использовать его таким, какое оно есть, так как мы уже обрабатываем в коде масштабирование аспекта при заполнении.
Так что можете использовать любое видео, которое есть под рукой. Я рекомендую воспользоваться любым видеофайлом в формате mp4 с кодировкой h264. В этом примере я использую видео из Coverr, которое называется Orchestra.
Справка: По поводу некоторых устройств Android и iOS, особенно старых моделей, надо оговориться, что они, вероятно, могут и не уметь воспроизводить файлы MP4. В основном это вызвано отсутствием поддержки базового профиля. Чтобы обойти этот момент, можно перекодировать видео с помощью такого инструмента, как
ffmpeg
и изменить базовый профиль по своему вкусу. Обратитесь к следующей таблице, чтобы проверить совместимость базовых профилей с iOS. Ознакомьтесь также с материалом Поддерживаемые форматы носителей из официального руководства Android.Если видеофайл у вас уже есть, поместите его в соответствующие папки для каждой ОС. В Android его следует поместить в каталог
Assets
. В iOS его следует поместить в каталог Resources
. В этом примере я поместил файл в раздел Assets/Videos
у Android и в Resources/Videos
у iOS.После того как файлы будут помещены в правильные папки, необходимо создать страницу в проекте Xamarin.Forms PCL.
Это простая страница с минимумом компонентов. Мы создадим домашнюю страницу с фоновым видео, два текстовых поля для имени пользователя и пароля, а также кнопки для входа и регистрации. Здесь нет логики, я просто хочу показать, как делается красивая домашняя страница.
Для лучшего размещения элементов управления я использую сетку как контейнер. В следующем фрагменте представлен соответствующий код XAML полностью.
<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:BackgroundVideo"
xmlns:controls="clr-namespace:BackgroundVideo.Controls"
x:Class="BackgroundVideo.BackgroundVideoPage">
<Grid Padding="0" RowSpacing="0" ColumnSpacing="0">
<controls:Video x:Name="video" Source="Videos/Orchestra.mp4" Loop="true"
HorizontalOptions="Fill" VerticalOptions="Fill" />
<StackLayout VerticalOptions="Center" HorizontalOptions="FillAndExpand" Padding="20,10,10,20">
<Entry Placeholder="username" FontSize="Large"
FontFamily="Georgia" HeightRequest="50">
<Entry.PlaceholderColor>
<OnPlatform x:TypeArguments="Color" Android="Silver" />
</Entry.PlaceholderColor>
<Entry.TextColor>
<OnPlatform x:TypeArguments="Color" Android="White" />
</Entry.TextColor>
</Entry>
<Entry Placeholder="password" FontSize="Large"
FontFamily="Georgia" HeightRequest="50" IsPassword="true">
<Entry.PlaceholderColor>
<OnPlatform x:TypeArguments="Color" Android="Silver" />
</Entry.PlaceholderColor>
<Entry.TextColor>
<OnPlatform x:TypeArguments="Color" Android="White" />
</Entry.TextColor>
</Entry>
<BoxView Color="Transparent" HeightRequest="10" />
<Button Text="sign in" BackgroundColor="#3b5998" TextColor="#ffffff"
FontSize="Large" />
<Button Text="sign up" BackgroundColor="#fa3c4c" TextColor="#ffffff"
FontSize="Large" />
</StackLayout>
</Grid>
</ContentPage>
Ну вот и всё. Если вы не хотите, чтобы видео было зациклено, просто измените свойство
Loop
. Если требуется сделать что-то по завершении видео, просто установите OnFinishedPlaying
из кода C#. Теперь посмотрим, как это все работает.Просмотр в действии
Если все задано правильно, то должно получиться, как на картинке, где показывается приложение, запущенное на устройстве или эмуляторе iOS. Как видите, там есть два текстовых поля и две кнопки. Видео плавно воспроизводится на фоне страницы.
Аналогично версии iOS, рисунок показывает, как это видео выглядит на устройстве или эмуляторе Android. Обратите внимание на отличие стиля текстового поля от версии для iOS. Но давайте позаботимся об этом позже, ведь суть-то в том, что фоновое видео слаженно работает — точно так же, как и на iOS.
Всё, что остается, это сделать стиль более согласованным с каждой платформой.
Итог
Поясним, что главная мысль здесь простая, — любой другой требующийся элемент управления можно сделать с помощью настраиваемого средства визуализации. Если вы понимаете, как кодить на нативном языке (ну, это можно погуглить, если что), тогда вы можете создать что угодно.
Что касается производительности, то, как я уже говорил, на старых устройствах Android вы, вероятно, будете замечать мерцание. На данный момент у меня нет идей по поводу того, как это можно было бы оптимизировать.
Если у вас есть идеи и предложения, поделитесь ими в комментариях ниже.
Уже готовый проект можно загрузить на GitHub.
Благодарим за перевод
Александр Алексеев — Xamarin-разработчик, фрилансер. Работает с .NET-платформой с 2012 года. Участвовал в разработке системы автоматизации закупок в компании Digamma. C 2015 года ушел во фриланс и перешел на мобильную разработку с использованием Xamarin. В текущее время работает в компании StecPoint над iOS приложением.
Ведет ресурс XamDev.ru и сообщества «Xamarin Developers» в социальных сетях: VK, Facebook, Telegram.
Xamarin Dev Days в Санкт-Петербурге
20 мая в Санкт-Петербурге пройдёт первый Xamarin Dev Days в России. Мы соберём тёплую дружественную компанию разработчиков на Xamarin, в том числе экспертов нашего сообщества.
Расписание:
09:00 – 09:30 Регистрация
09:30 – 10:10 Введение в Xamarin
10:20 – 11:00 Кроссплатформенный UI с Xamarin.Forms
11:10 – 11:50 Подключаем приложение к Azure
12:00 – 13:00 Обед
13:00 – 16:00 Воркшоп
Все подробности о мероприятии и регистрация по ссылке.
Поделиться с друзьями
redmanmale
Немного забавно и грустно видеть пост в блоге MS про кроссплатформенное приложение для iOS и Android, но не Windows Phone.