참고 : https://www.codeproject.com/Articles/1174042/Fullscreen-Video-Background-Control-for-Xamarin-Fo

요거한번해봐야겠다.

Fullscreen Video Background Control for Xamarin.Forms

How to play video as page background, like Spotify and Uber, using Xamarin.Forms with Android and iOS Custom Renderer.

One of the cool trend on mobile UI I've seen a lot is using video as View background. You can see it on some big mobile app products like Tumblr, Spotify, and Vine. As you can see, they have this cool Home View with sign in and sing up button with video playing in background. This feature is so cool and can make your app look more professional. This time, I'll show you how to implement it in Xamarin.Forms app. All we need is to implement two custom renderers for Android and iOS each.

Note: Please be aware on implementing this feature. It can make your app freeze, draining phone battery, or even make the phone lag. It shouldn't be a problem for the latest devices as long as the video file is in reasonable resolution and file size. Also see some guides on encoding the video into h.264, also mentioned in this tutorial.

Creating Video View Control for Xamarin.Forms

Let's create a new Xamarin.Forms PCL project first and name it BackgroundVideo. Now let's head to the PCL library and create a new class called Video inherited from Xamarin.Forms.View.

using System;
using Xamarin.Forms;

namespace BackgroundVideo.Controls
{
  public class Video : View
  {
  }
}
Video View Class.

For the sake of the tutorial, we're going to make this control with simple requirements.

We need a bindable property to point which video to be displayed. I'm going to call it Source property. It's a string to locate which video file to be played. On iOS, Source property is relative to Resources directory as for Android, it is relative to Assets directory.

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); }
}
Video Source Property snippet.

Next thing we need is a boolean to define if we want the video in loop or not. Let's call this property Loop. By default, I set this value as true so when you set a video Source property, it would be looped by default.

Finally, we're going to need a callback fired when video is finished. For simplicity, I use Action class called OnFinishedPlaying. You can modify it to event or anything you comfortable with.

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; }
Video Loop Property and OnFinishedPlaying callback snippet.

After we created this class, next thing to do is to implement custom renderers for both iOS and Android.

Video View Control iOS Custom Renderer

First thing to do is to create a custom renderer class called VideoRenderer inherited from ViewRenderer<Video, UIView>. The idea is to use iOS native video player with the help of MPMoviePlayerController class and set its native control to our Video custom view. Also we're going to need an NSObject to listen the event from video player wether it is ended or not.

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;
  }
}
VideoRenderer for iOS.

To start iOS video player, we need to check wether the video from Source property exists in Resources budnle or not. If it doesn't exist, we'll display an empty view.

If the video file exists, we need to create MPMoviePlayerController and parse the location of the video file as NSUrl. To make our custom control clear, without border or anything, we need to set ControlStyle to MPMovieControlStyle.None and background color to UIColor.Clear.

Also, we probably will have one video file for any resolution. You don't want it to look stretched on some device, right? To make the video resolution looks consistent, we need to set video player ScalingMode to MPMovieScalingMode.AspectFill.

We also have this Loop property to define wether the video playing will be looped or not. To set it to loop, we need to change video player RepeatMode to MPMovieRepeatMode.One. Otherwise, set it to MPMovieRepeatMode.None.

Finally, to make video player play the file, we call PrepareToPlay() function. To display the video to our custom control, we need to use SetNativeControl() function.

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);
}
Initialization Video Player for iOS.

The rest of the code is to override OnElementChanged and OnElementPropertyChanged function so it can be functionally working from Xamarin.Forms project. Under OnElementChanged, we need to listen to video player playback finish event and invoke OnFinishedPlaying action. The following snippet is the simplest code necessary to make it work.

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;
    }
}
VideoRenderer for iOS.

Now that iOS implementation is completed, let's head to our Android project.

Video View Custom Renderer for Android

Create a new custom renderer on Android project and let's name it VideoRenderer, too. We'll inherit this renderer with ViewRenderer<Video, FrameLayout>, meaning it will be displayed as FrameLayout in native Android control.

One thing that made Android implementation a bit complicated is that we need two kind of views if you want to cover old Android versions. If you just want to cover modern Android OS from Ice Cream Sandwich or more, you can just focus on TextureView implementation, if not you'll also need to implement it using VideoView.

Please note that VideoView implementation here is not optimal. Maybe you'll notice some flickering. That's why I add view called _placeholder. This is just an empty view. It'll be displayed when no video playing or when in video source changed transition. If the video file ready to play and display, _placeholder will be hidden.

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;

  }
}
VideoRenderer class for Android.

Now before we thing about what video container to use, we need to implement the video player itself. Android already provide us with their MediaPlayer class. We'll need to use this object and make sure it only created once. We can reuse the same object if we change the video source.

We need to set Completion event to implement our OnFinishedPlaying callback. We also need to set Looping property to our custom Loop property.

There is one thing that different from our iOS implementation, there is no easy property set to display video resolution as aspect fill! That means we need to implement our own method into custom function called AdjustTextureViewAspect(). This function will be called on VideoSizeChanged callback. We'll talk about this implementation later.

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();
}
Video player implementation for Android.

Now that we have our video player object, next thing is to create function that play video from Source property. Please remember that video file on Android need to be stored under Assets directory. We can open this file by using Assets.OpenFd(fullPath) function.

If the file doesn't exist, it'll throw Java.IO.IOException. That means we don't need to display anything on our video container.

If the file exists, we just need to reset our video player, then set data source based on previous step. We can't just play the video directly, so we need to prepare it first. When preparation complete, it'll trigger Prepared event and display the video to one of our implemented video view from previous step.

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();
  }
}
Play video implementation for Android.

As previously mentioned, Android doesn't provide us easy property to scale our video to aspect fill. You know it yourself that Android devices have so many screen resolution so keep the video like it is is not an option. We need to scale it properly so it won't look stretched.

Good news is, we can do that if we use TextureView. Bad news is for now I don't know how to implement it with VideoView. But it's better than nothing right?

The idea to make video scale properly is to use matrix to scale the content of TextureView. It is scaled up or down based on video size and view size. Then, after it's scaled, it is positioned at the center of the view.

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);
}
Adjust resolution to Aspect Fill video for Android.

As mentioned earlier, if we want to support a wide range of Android OS, we need to implement it into TextureView and VideoView. This will be implemented under OnElementChanged function. Both implementation have some same properties. We will make their Background color to transparent and layout parameters to match parent. This way it won't have any color to display when there is no video, and it'll fill entire container.

Following snippet is how to implement it on our Video custom renderer. You see it's similar with our iOS implementation, except for container creation and video playing.

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;
  }
}
VideoRenderer for Android.

Since we're using TextureView and VideoView, there is some function from interfaces need to be implemented. One of them is to remove video when texture or surface is destroyed. To do that, we're going to need to set >_placeholder visibility to visible.

private void RemoveVideo()
{
  _placeholder.Visibility = ViewStates.Visible;
}
Display placeholder to hide video.

When using TextureView, we need to implement TextureView.ISurfaceTextureListener interface. We set video player's surface when texture available and hide it when texture destroyed. Following snippet shows you how to implement it.

#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
Texture listener implementation.

When using VideoView, we need to implement ISurfaceHolderCallback interface. Similar with TextureView, we set video player's display when surface created and hide it when surface destroyed. The complete implementation of this interface can be see on following snippet.

#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
Texture and Surface listener implementation.

That's all we need for Android. Now that we all have everything needed, we can test this control to Xamarin.Forms Page.

Testing to Xamarin.Forms Page

Before we create a test page, I recommend you to prepare your own video file. It is recommended as vertical video so a lot of space won't be wasted.

But, if you don't have any video to test, don't worry. You can download free videos to use from Coverr. They don't have any vertical videos, we can still use it. You can either crop it into vertical video or you can just use it as it is since we already handle scaling to aspect fill on our code.

So use any video you like. I recommend any file as long as it's mp4 video with h264 encoding. In this tutorial, I use video from Coverr called Orchestra. You can download it from here.

Note: For some Android and iOS devices, especially the old products, they probably can't play some mp4 files. This is mostly caused by not-supported baseline profile. To fix that, you can re-encode the video using a tool like ffmpeg and change its baseline profile based on your preferences. See following table to check baseline profile compatibility with iOS. See Supported Media Formats from official Android guide, too.

Profile Level Devices Options
Baseline 3.0 All devices -profile:v baseline -level 3.0
Baseline 3.1 iPhone 3G and later, iPod touch 2nd generation and later -profile:v baseline -level 3.1
Main 3.1 iPad (all versions), Apple TV 2 and later, iPhone 4 and later -profile:v main -level 3.1
Main 4.0 Apple TV 3 and later, iPad 2 and later, iPhone 4s and later -profile:v main -level 4.0
High 4.0 Apple TV 3 and later, iPad 2 and later, iPhone 4s and later -profile:v high -level 4.0
High 4.1 iPad 2 and later, iPhone 4s and later, iPhone 5c and later -profile:v high -level 4.1
High 4.2 iPad Air and later, iPhone 5s and later -profile:v high -level 4.2
h.264 baseline profiles for iOS. Source: ffmpeg

After you get your video file, place it to the folders for each OS. On Android, you should put it under Assets directory. On iOS, you should put it under Resources directory. For this tutorial I put the file under Assets/Videos on Android and Resources/Videos on iOS.

Once you put them all to correct folder, we need to create our Page on Xamarin.Forms PCL project.

This is a simple page with smallest components. We'll create a Home Page, with video background, two text boxes for username and password, and to buttons for sign in and sign up. There is no logic in this page, I just want to show you how to make a beautiful home page.

For better controls placement, I use Grid as container. See following snippet for the complete 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>
Sample of Home Page with Background Video.

That's it. If you don't want the video to be looped, just change its Loop property. If you want to do something when video ended, just set OnFinishedPlaying from C# code. Now let's see how it runs.

See it in Action

If you set everything correctly, The following figure is how it run on iOS device or emulator. As you can see, there are two text boxes and two buttons. The video is playing as the page background smoothly.

Xamarin iOS Background Video Demo

Similar with iOS version, the following animated gif image shows how it looks on Android device or emulator. See that text box style difference from iOS version. But let's care about it later, the point is video background consistently work just like iOS.

 

Xamarin Android Background Video Demo

All you need to do the rest is to make styling more consistent through any platforms.

Summary

Once again, all I can say is you can make any cross platform control you want by using Custom Renderer. As long as you understand how to code in native language (well, you can Google it though), you can create anything.

As for performance, I believe I said it earlier, you probably see some flickering on old Android devices. For now I don't have any idea to optimize it.

If you have any idea and suggestion, feel free to leave a comment below.

You can download completed project on GitHub.

Xamarin Forms 으로 모바일 개발을 하다보면

xaml 에 대한 화면을 미리 보고 싶을때가 있다. (아니 미리보는게 편하다.)

다행히 Xamarin.Forms Previewer 가 있어 사용해 봤는데 너무 느려서

사용을 안해왔다;; 코딩을 바꾸는데도 버벅인다;;

차라리 디바이스에 계속 배포해서 확인하는게 더 빠르다.

그런데 가끔 유투브 동영상을 보면 고릴라 플레이어로 디자인을 미리 보기 하면서 코딩하는걸 봐왔다..

그런데 고릴라 플레이어 셋팅하는게 그리 간단하지 않아 보여

계속 미루다가 드디어 오늘 셋팅을 완료했다.

그래서 과정을 정리해본다.

 

일단 아래 블로그는 mac 환경에서 환경 셋팅을 한 내용이다.

http://picory.com/entry/Forns-XAML-Previewer-Gorilla-Player

난 Window10 으로 시도했다.

 

1. 고릴라 플레이어 사이트에서 계정을 만든다.

아래링크로 들어가 REGISTER NOW 를 클릭한다.

https://grialkit.com/gorilla-player/

계정을 만든다. 암호는 대문자가 포함되어야한다. (특수문자는 없어도 됨)

 

계정을 만들었으면 계정 메일로 아래와 같이 메일이 오고 링크를 클릭하여 메일 인증을 한다.

메일 인증 완료!

 

2. 고릴라 플레이어(Gorilla Player)를 다운로드 한다.

아래 링크로 들어가서

https://grialkit.com/gorilla-download/

Windows 버전을 다운로드 하고 설치를 한다.

가끔사이트가 먹통이 될때가 있어 설치파일을 첨부한다.

설치 과정은 아래와 같다. 특별한 건 없다.

 

3. 고릴라 플레이어를 실행한다.

고릴라 플레이어 실행 과정이다.

아래 그림에서는 당연히 동의를....

설치할걸 체크해야하는데 기본적으로 알아서 잘 체크가 되어있다.

체크 추가 없이 다음.. (Install addin for Visual Studio, Install Player + Sample)

설치 진행중...

설치 완료!!

아래 처럼 고릴라 아이콘이 시계표시줄에 표시되어있으면 일단 정상적으로 설치 및 실행이 된것이다.

 

4. 시뮬레이터 셋팅

실행(Continue)을 하면 아래와 같은 화면이 나온다.

친절하게 설명이 잘되어있다.

아래 부분이 조금 헷깔리는데 구글 플레이에서 고릴라플레이어앱을 다운받거나 직접 소스를 열어서 설치를 할수 있다.

시뮬레이터에 설치할것이므로 소스를 열어서 직접 설치를해야한다.

아래 화면에서 빨간색 버튼을 클릭한다. 그러면  Player.sln 솔루션 이 열린다.

아래와 같이 솔루션이 열리면 구동하고자 하는 시뮬레이터를 선택한 후 Run 한다.

그럼 아래 처럼 시뮬레이터에 고릴라 플레이어앱이 설치가 된다.

설치된 앱을 구동해 보자.

앱을 구동하면 Connecting 을 기다리고 있다.

 

5. xaml 파일 보기

이제 xaml 파일을 열어 디자인이 보여지는지 확인해보자.

Visual Studio 를 열고 도구>Gorilla Player 에서 아래 그림과 같이 Disconnect From Gorilla  으로 되어있는지 확인한다.

만약 안되어있다면 Connect From Gorilla 를 실행해야된다.

이제 xaml 파일을 하나 선택하고 마우스 우측 버튼의 메뉴에서

Stick Gorilla to this XAML 를 클릭한다.

그럼 아래 처럼 시뮬레이터에서 먼가 연결이 진행된다.

엇 에러가 발생되었다.

xaml 에 에러가 있거나 하면 아래 처럼 에러가 표시된다.

마스터 디테일 페이지(MasterDetailPage) 인 경우는 아직 표현하지 못하는것 같다.

아래처럼 xaml 페이지가 정상적으로 시뮬레이터에 표시가 된다.

xaml 의 내용이 시뮬레이터에 보여지고 있다면 아래처럼 우측 상단에 고릴라 아이콘이 나타난다.

xaml 파일을 변경하면 실시간으로 시뮬레이터에 반영된다. (아래 동영상참고)

* 참고

도움말 : https://github.com/UXDivers/Gorilla-Player-Support

* 추가

혹 비쥬얼 스튜디오에 addin 이 제대로 되지 않았다면 직접 addin 쪽만 아래 링크나 첨부파일로 설치를 진행하면된다.

https://marketplace.visualstudio.com/items?itemName=UXDivers.GorillaPlayerVisualStudioddin

UXDivers.Artina.Player.VSAddin.vsix

 

 

출처 : https://github.com/jsuarezruiz/xamarin-forms-goodlooking-UI

Xamarin.Forms goodlooking UI Samples

A curated list of awesome Xamarin.Forms samples to show how to create goodlooking UI with Xamarin.Forms.

Work in progress. Contributions are always welcome!

Would you like to see a list of published applications made with Xamarin.Forms?. Thank you David Ortinau for the list!

ConferenceVision

Sample Xamarin.Forms 3.0 Phone App showed in Microsoft Build 2018. Use your camera during a conference to capture your experience. Let Vision do the heavy lifting of identifying known products and scan your images to text for easier searching.

Platforms

Android, iOS & UWP.

Features:

  • Xamarin.Forms 3.0
  • FlexLayout
  • CSS
  • VisualStateManager
  • Custom Vision
  • Computer Vision API

More information:

SmartHotel

Travelers are always on the go, so SmartHotel360 offers a beautiful fully-native cross-device mobile app for guests and business travelers built with Xamarin.

Platforms

Android, iOS & UWP.

Features:

  • Custom animations from XAML.
  • Custom Map.
  • SkiaSharp Charts.
  • Xamarin.Forms Native Forms.
  • Live Player.
  • NFC.

More information:

BikeSharing

BikeSharing360 is a fictitious example of a smart bike sharing system with 10,000 bikes distributed in 650 stations located throughout New York City and Seattle. Their vision is to provide a modern and personalized experience to riders and to run their business with intelligence. Was a demo in the Connect(); 2016.

Platforms

Android, iOS & UWP.

Features:

  • Custom animations from XAML.
  • Custom Map.

More information:

Bikr

A bike activity App. Sample to show Microcharts usage.

Platforms

Android, iOS & UWP.

Features:

  • SkiaSharp charts.

More information:

Evolve

Xamarin Evolve 2016 Mobile App.

This app is around 15,000 lines of code. The iOS version contains 93% shared code, the Android version contains 90% shared code, the UWP has 99% shared code, and our Azure backend contains 23% shared code with the clients!

Platforms

Android, iOS & UWP.

Features:

  • Azure + Online/Offline Sync
  • Barcode Scanning
  • Calendar Integration
  • Maps & Navigation
  • Push Notifications
  • Phone Dialer
  • Wi-Fi configuration
  • URL Navigation (Universal Links + Google App Indexing)

More information:

GreatPlaces

ListView with transparent background and a purple gradient behind.

Platforms

Android, iOS & UWP.

Features:

  • Custom ViewCells.

More information:

InAnger

Samples to show that Xamarin.Forms is capable of creating something that looks really good.

Platforms

Android & iOS.

Samples

  • Find a Vet
  • Phoenix Peaks
  • Jobbberr
  • Woofer
  • HeatMap
  • Hot Sauce
  • Findr
  • MallDash

More information:

Instagram

Platforms

Android, iOS & UWP.

Features:

More information:

Social Media

This sample show how to recreate a beautifull Social Network profile type page in Xamarin.Forms.

Features:

  • Curved header Image.
  • Profile Image that overlaps the header.

More information:

KickassUI.FancyTutorial

A simple (but beautiful) Xamarin.Forms tutorial screen.

Platforms

Android & iOS.

Features

More information:

KickassUI.Runkeeper

A Xamarin.Forms version of the Runkeeper App.

Platforms

Android & iOS.

Features:

  • Custom Tabs.
  • Custom fonts in the page header.
  • Custom logo in page header.
  • Carousel.
  • Custom Map.

More information:

KickassUI.Spotify

A Xamarin.Forms version of the Spotify App.

Platforms

Android & iOS.

Features:

  • Translucent navigation bar.
  • Blurred fullscreen background.
  • Custom Slider.

More information:

KickassUI.Twitter

A Xamarin.Forms version of the Twitter App.

Platforms

Android & iOS.

Features:

  • Android floating action button.
  • MasterDetailPage custom icon.
  • Custom Tabs (colors).

More information:

Timeline

A timeline of activities. This is useful for things like transportation schedules or class times.

Platforms

Android, iOS & UWP.

Features:

  • ListView Header and Footer.
  • Custom ViewCell.

More information:

Xamarin.Netflix

A Xamarin.Forms version of the Netflix App.

Choose Profile Home Main Menu Movie Detail

Platforms

Android & iOS.

Features:

  • Horizontal List.
  • Transparent NavigationBar on Android and iOS.
  • Parallax effect.
  • Multi-column List.

More information:

Facebook

A Xamarin.Forms version of the Facebook App.

Platforms

Android, iOS & UWP.

Features:

  • Right Slide Bar.
  • Disappearing NavigationBar.
  • Like Animations.

More information:

Movies

Movies is a Xamarin.Forms GTK backend application that makes use of The Movie Database (TMDb) API to demonstrate the possibilities of the new backend making use of a great variety of functionality.

Platforms

Android, iOS, Linux (GTK), macOS, UWP and Windows (WPF).

Features:

  • MasterDetailPage.
  • Maps.
  • Gtk Themes.
  • Desktop Apps for Linux, macOS and Windows.

More information:

WalkthroughApp

WalkthroughApp is a Xamarin.Forms application that makes use CarouselView and Lottie, to demonstrate the possibilities of creating a fluid and powerful Walkthrough without requiere Custom Renderers.

Platforms

Android, iOS.

Features:

  • CarouselView
  • Lottie
  • Animations

More information:

Syncfusion 의 SfRotator 는 아래 그림과 같이 이미지 등을 슬라이드로 보여주는 컨트롤입니다.

우선 보여줄 이미지를 준비합니다.

전 Images 폴더를 만들어 이미지를 넣었습니다.

이미지를 넣고 추가로 해야할건 각 이미지들의 속성에서 '빌드작업'을 '포함 리소스'로 변경해야합니다.

포함리소스 관련해서는 아래 포스팅 참고해주세요

2017/07/06 - [C#.NET/Xamarin] - (Xamarin Forms) 4.Image

 

이제 main1~5.jpg 그림파일을 5개를 이용해서 슬라이드 되도록 할것입니다.

우선  nuget 에서 SfRotator 를 검색해서 설치합니다.

 

아래처럼 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:syncfusion="clr-namespace:Syncfusion.SfRotator.XForms;assembly=Syncfusion.SfRotator.XForms"
             x:Class="MiGong.HomePage">
    <ContentPage.Content>
        <StackLayout>
            <syncfusion:SfRotator x:Name="rotator" NavigationStripMode="Dots"  NavigationStripPosition="Bottom"
                                   EnableAutoPlay="true" NavigationDelay="3000"/>

            <Label Text="Welcome to Xamarin.Forms!"
                VerticalOptions="CenterAndExpand"
                HorizontalOptions="CenterAndExpand" />
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

NavigationStripMode 는 네비게이션을 점으로 보일지 아니면 그냥 썸네일 방식으로 보여줄지 여부입니다.

NavigationStripPosition 은 네비게이션의 위치입니다.

EnableAutoPlay 는 자동으로 슬라이드가 될지 여부입니다.

NavigationDelay 는 자동으로 슬라이드되는 경우 정지될 시간입니다. (밀리초단위)

추가로 여러가지 속성이 있습니다.

아래 링크 참고해주세요

https://help.syncfusion.com/xamarin/sfrotator/overview

이제 비하인드 코드 xaml.cs 파일을 아래 처럼 작성합니다.

            List<SfRotatorItem> rotatorItem1 = new List<SfRotatorItem>();

            foreach (string imageName in new string[]{ "main1.jpg", "main2.jpg", "main3.jpg", "main4.jpg", "main5.jpg" })
            {
                SfRotatorItem item = new SfRotatorItem();
               
                Image image
                    = new Image()
                    {
                        Source = ImageSource.FromResource("MiGong.Images." + imageName),
                        Aspect = Aspect.AspectFill,
                        VerticalOptions = LayoutOptions.Center,
                        HeightRequest = 250
                        //WidthRequest = 400
                    };
               
                item.ItemContent = image;
                rotatorItem1.Add(item);
            }
            this.rotator.ItemsSource = rotatorItem1;

이제 실행해 보면 아래처럼 나타납니다.

 

 

 

이미지를 표현하는데 아래 처럼 에러가 발생되었다.

Unhandled Exception:

Java.Lang.OutOfMemoryError: Failed to allocate a 100712460 byte allocation with 16768736 free bytes and 55MB until OOM 발생

 

용량이 커서 그런가 했는데 1메가도 안된다.

무슨 문제인지 찾아보니 크기 문제였다.

크기를 조정하니 해결이 되었다.

다른 방법도 있는것 같다 아래 링크 참고..

https://stackoverflow.com/questions/32244851/androidjava-lang-outofmemoryerror-failed-to-allocate-a-23970828-byte-allocatio/32245018

 

 

 

 

 

우선 서버 만들기는 아래 포스팅을 참고한다.

2017/11/27 - [C#.NET/C#] - 카카오 쳇봇 만들기 - 2 (C# 서버 만들기)

2017/11/27 - [C#.NET/C#] - 카카오 쳇봇 만들기 - 3 (C# 서버 만들기)

 

서버쪽 코드

        #region PostUpLoadImage
        [Route("api/Files/Upload")]
        public async Task<string> PostUpLoadImage()
        {
            try
            {
                var httpRequest = HttpContext.Current.Request;

                if (httpRequest.Files.Count > 0)
                {
                    foreach (string file in httpRequest.Files)
                    {
                        var postedFile = httpRequest.Files[file];

                        var fileName = postedFile.FileName.Split('\\').LastOrDefault().Split('/').LastOrDefault();

                        var filePath = HttpContext.Current.Server.MapPath("~/Uploads/" + fileName);

                        postedFile.SaveAs(filePath);

                        return "/Uploads/" + fileName;
                    }
                }
            }
            catch (Exception exception)
            {
                return exception.Message;
            }

            return "no files";
        }
        #endregion

 

Xamarin Forms

Nuget 에서 Xam.Plugin.Media 설치

 

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"
             x:Class="MiGong.TestPage">
    <ContentPage.Content>
        <StackLayout>
            <Label Text="File Upload"
           HorizontalOptions="Center" TextColor="Black" FontSize="20"/>

            <Button Text="Pick Photo"
            BackgroundColor="Teal" TextColor="White" FontSize="20" Clicked="PickPhoto_Clicked"/>

            <Button Text="Take Photo"
            BackgroundColor="Navy" TextColor="White" FontSize="20" Clicked="TakePhoto_Clicked"/>

            <Image x:Name="FileImage" WidthRequest="400" HeightRequest="220"/>

            <Label x:Name="LocalPathLabel" TextColor="Black" FontSize="18"/>

            <Button Text="Upload Photo"
            BackgroundColor="Purple" TextColor="White" FontSize="20" Clicked="UploadFile_Clicked"/>

            <Label x:Name="RemotePathLabel" FontSize="20" TextColor="Black"/>

        </StackLayout>
    </ContentPage.Content>
</ContentPage>

xaml.cs 코드 - 동작은 간단하다 파일을 선택하거나 카메라를 찍어서 서버에 해당 파일을 업로드한다.

[XamlCompilation(XamlCompilationOptions.Compile)]
 public partial class TestPage : ContentPage
 {
        private MediaFile _mediaFile;

        public TestPage ()
  {
   InitializeComponent ();
  }

        private async void PickPhoto_Clicked(object sender, EventArgs e)
        {
            await CrossMedia.Current.Initialize();

            if (!CrossMedia.Current.IsPickPhotoSupported)
            {
                await DisplayAlert("No PickPhoto", ":( No PickPhoto available.", "OK");
                return;
            }

            _mediaFile = await CrossMedia.Current.PickPhotoAsync();

            if (_mediaFile == null)
                return;

            LocalPathLabel.Text = _mediaFile.Path;

            FileImage.Source = ImageSource.FromStream(() =>
            {
                return _mediaFile.GetStream();
            });
        }

        private async void TakePhoto_Clicked(object sender, EventArgs e)
        {
            await CrossMedia.Current.Initialize();

            if (!CrossMedia.Current.IsCameraAvailable || !CrossMedia.Current.IsTakePhotoSupported)
            {
                await DisplayAlert("No Camera", ":( No camera available.", "OK");
                return;
            }

            _mediaFile = await CrossMedia.Current.TakePhotoAsync(new StoreCameraMediaOptions
            {
                Directory = "Sample",
                Name = "myImage.jpg"
            });

            if (_mediaFile == null)
                return;

            LocalPathLabel.Text = _mediaFile.Path;

            FileImage.Source = ImageSource.FromStream(() =>
            {
                return _mediaFile.GetStream();
            });
        }

        private async void UploadFile_Clicked(object sender, EventArgs e)
        {
            var content = new MultipartFormDataContent();

            content.Add(new StreamContent(_mediaFile.GetStream()),
                "\"file\"",
                $"\"{_mediaFile.Path}\"");

            var httpClient = new HttpClient();

            var uploadServiceBaseAddress = "http://localhost:12214/api/Files/Upload";

            var httpResponseMessage = await httpClient.PostAsync(uploadServiceBaseAddress, content);

            RemotePathLabel.Text = await httpResponseMessage.Content.ReadAsStringAsync();
        }
    }

안드로이드쪽 MainActivity.cs 의 아래 굵은 부분 코딩 추가

public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
    {
        protected override void OnCreate(Bundle bundle)
        {
            TabLayoutResource = Resource.Layout.Tabbar;
            ToolbarResource = Resource.Layout.Toolbar;

            base.OnCreate(bundle);

            CrossCurrentActivity.Current.Init(this, bundle);

            global::Xamarin.Forms.Forms.Init(this, bundle);
            LoadApplication(new App());
        }

        public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Android.Content.PM.Permission[] grantResults)
        {
            Plugin.Permissions.PermissionsImplementation.Current.OnRequestPermissionsResult(requestCode, permissions, grantResults);
        }

    }

AssemblyInfo.cs 파일에 아래 코드 추가

[assembly: UsesFeature("android.hardware.camera", Required = false)]
[assembly: UsesFeature("android.hardware.camera.autofocus", Required = false)]

매니패스트 권한을 부여한다.

 <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
 <uses-permission android:name="android.permission.CAMERA" />

서버쪽에 사이트 루트에 Uploads 폴더를 만든다.

위 까지 하면 파일 선택하여 업로드가 가능하다.

카메라를 사용하려면 좀더 설정이 필요하다.

AndroidManifest.xml 에 아래 내용추가

 <application ...">
    <provider android:name="android.support.v4.content.FileProvider"
          android:authorities="${applicationId}.fileprovider"
          android:exported="false"
          android:grantUriPermissions="true">

      <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths"></meta-data>
    </provider>
  </application>

안드로이드 쪽 프로젝트에 아래 처럼 Resources 폴더 밑에 xml 폴더를 만들고 file_paths.xml  을 만들고

 

내용은 아래처럼 작성

<?xml version="1.0" encoding="utf-8" ?>
  <paths xmlns:android="http://schemas.android.com/apk/res/android">
<!--
    <external-path name="my_images" path="Android/data/com.example.package.name/files/Pictures" />
    <external-path name="my_movies" path="Android/data/com.example.package.name/files/Movies" />
-->
   
    <external-files-path name="my_images" path="Pictures" />
    <external-files-path name="my_movies" path="Movies" />
  </paths>

 

추가 자세한 사항은 아래 링크를 참고

https://github.com/jamesmontemagno/MediaPlugin

 

https://www.c-sharpcorner.com/article/tabbed-page-in-xamarin-forms/

Async programming is all the rage in mobile app development for good reasons. Using async methods for long running tasks, like downloading data, helps keep your user interface responsive, while not using async methods, or the improper use of async/await, can cause your app’s UI to stop responding to user input until the long running task completes. This can result in a poor user experience, which can then lead to poor reviews on the app stores, which is never good for business.

Today we’ll take a look at the use of async and how to utilize it to prevent jerky and unexpected behaviors in a ListView.

What is async/await?

The async and await keywords were introduced in .NET 4.5 to make calling async methods easier and to make your async code more easily readable. The async/await API is syntactic sugar that uses the TPL (Task Parallel Library) behind the scenes. If you wanted to start a new task and have code run on the UI thread after the task completes prior .NET 4.5, your code would have looked something like this:

That’s not very pretty. Using async/await, the above becomes:

The above code gets compiled behind the scenes to the same TPL code as it does in the first example, so as noted, this is just syntactic sugar, and how sweet it is!

Using Async: Pitfalls

In reading about using async/await, you may have seen the phrase “async all the way” thrown around, but what does that really mean? Simply put, it means that any method that calls an async method (i.e. a method that has the async keyword in its signature) should use the await keyword when calling the async method. Not using the await keyword when calling an async method can result in exceptions that are thrown being swallowed by the runtime, which can cause issues that are difficult to track down. Using the await keyword requires that the calling method also use the async keyword in its signature. For example:

This poses a problem if you want to call an async method using the await keyword when you can’t use the async modifier on the calling method, for instance if the calling method is a method whose signature can’t use the async keyword or is a constructor or a method that the OS calls, such as GetView in an Android ArrayAdapter or GetCell in an iOS UITableViewDataSource. For example:

 

As you may know, an async method has to return either void, Task, or Task<T>, and returning void should only be used when making an event handler async. In the case of the GetView method noted above, you need to return an Android View, which can’t be changed to return Task<View> as the OS method that calls it obviously does not use the await keyword and so can’t handle a Task<T> being returned. Thus you can’t add the async keyword to the above method and therefore can’t use the await keyword when calling an async method from the above method.

To get around this, one might be tempted, as I have been in the past, to just call a method from GetView (or similar method where the signature can’t be changed regardless of the platform) as an intermediate method, and then call the async method from the intermediate method:

 

The problem here is that IntermediateMethod is now an async method and thus should be awaited just like the MyMethodAsync method needed to be. So, you have gained nothing here, as IntermediateMethod is now async and should be awaited. In addition, the GetView method will continue running all of the code after calling IntermediateMethod(), which may or may not be desirable. If the code following the call to IntermediateMethod() depends on the results of the IntermediateMethod(), then it isn’t desirable. In such a scenario, you may be tempted to use the Wait() method call (or Result property) on the async task, e.g.:

 

Calling Wait() on the async method causes the calling thread to pause until the async method completes. If this is the UI thread, as would be the case here, then your UI will hang while the async task runs. This isn’t good, especially in an ArrayAdapter that is supplying the data for the rows of a ListView. The user will not be able to interact with the list view until the data for all of the rows has been downloaded, and scrolling will likely be jerky and/or completely non-responsive, which isn’t a good user experience. There’s also a Result property you can call on the async task. This would be used if your async task was returning data by using Task<T> as the return type of the async method. This would also cause the calling thread to wait for the result of the async task:

 

In fact doing the above may cause your UI to hang completely and for the ListView never to be populated, which is a non-starter. It may also just be jerky:

JerkyListView

In general, you should avoid using Wait() and Result, especially on the UI thread. In the iOS and Android sample projects linked at the end of this blog, you can look in ViewControllerJerky and MainActivityJerky respectively to see this behavior. Those files are not set to compile in the sample projects.

Using Async All the Way

So how do I get “async all the way” in this scenario?

One way around the above problems is to revert to the old TPL upon which async/await is based. You’re going to use TPL directly, but only once to start the chain of async method calls (and to start a new thread right away). Somewhere down the line the TPL will be used directly again, as you need to use TPL to start a new thread. You can’t start a new thread using only the async/await keywords, so some method down the chain will have to launch the new thread with TPL (or another mechanism). The async method that launches a new thread will be a framework method, like a .NET HttpClient async method in many, if not most, cases. If not using async framework methods, then some method of yours down the chain will have to launch a new thread and return Task or Task<T>.

Let’s start with an example using GetView in an Android project (though the same concept will work for any platform, i.e. Xamarin.iOS, Xamarin.Forms, etc.) Let’s say I have a ListView that I want to populate with text downloaded from the web dynamically (more likely one would download the whole list of strings first and then populate the list rows with the already downloaded content, but I’m downloading the strings row by row here for demonstration purposes, plus there are occasions where one may want to do it this way anyway). I certainly don’t want to block the UI thread waiting for the multiple downloads; rather, I would like the user to be able to start working with the ListView, scroll around, and have the text appear in each ListView cell as the text gets downloaded. I also want to make sure that if a cell scrolls out of view, that when it is reused it will cancel loading the text that is in the process of being downloaded and start loading new text for that row instead. We do this with TPL and cancellation tokens. Comments in the code should explain what’s being done.

 

In a nutshell, the above method checks to see if this is a reused cell and, if so, we cancel the existing async text download if still incomplete. It then loads placeholder text into the cell, launches the async task to download the correct text for the row, and returns the view with placeholder text right away, thereby populating the ListView. This keeps the UI responsive and shows something in the cell while the launched task does its work of getting the correct text from the web. As the text gets downloaded, you’ll see the placeholders change to the downloaded text one-by-one (not necessarily in order due to differing download times). I added a random delay to the async task to simulate this behavior since I’m making such a simple, quick request.

Here’s the implementation of GetTextAsync(...):

 

Note that I can decorate the lambda passed into Task.Run() with the async keyword, thus allowing me to await the call to my async method, and thereby achieving “async all the way.” No more Jerky ListView!

SmoothListView

See it in action

If you want to see the above in action for Xamarin.iOS, Xamarin.Android, and Xamarin.Forms, check it out on my GitHub repo. The iOS version is very similar to the above, the only difference being in how I attach the CancellationTokenSource to the cell since there is no Tag property as there is in an Android View. Xamarin.Forms, however, does not have a direct equivalent to GetView or GetCell that I’m aware of, so I simulate the same behavior by launching an async task from the main App class constructor to get the text for each row.

Happy async coding!

 

원본 : https://blog.xamarin.com/getting-started-with-async-await/

심각도 코드 설명 프로젝트 파일 줄 비표시 오류(Suppression) 상태
오류  Your project is not referencing the "MonoAndroid,Version=v8.1" framework. Add a reference to "MonoAndroid,Version=v8.1" in the "frameworks" section of your project.json, and then re-run NuGet restore. DustInfo.Android   

빌드시 위와 같은 에러가 발생되었다.

구글 플레이 스토어에 앱을 올릴때도 오레오 버전에 타겟팅하라는 메세지가 나오던데

아마도 이와 관련이 있는듯 하다.

 

아래 처럼 프로젝트 속성에서

응용프로그램 탭에서 Android 버전을 사용하여 컴파일 부분을 Android 8.1 을 선택하고

Android 매니페스트 탭에서 대상 Android 버전Android 8.1 을 선택하면 해결된다.

참고

https://forums.xamarin.com/discussion/123705/error-mesage-your-project-is-not-referencing-the-monoandroid-version-v8-1

이미지 표현시 아래 처럼 에러가 발생되었다.

Failed to allocate a 88542732 byte allocation with 16768576 free bytes and 70MB until OOM

이미지 크기가 width 1440 , height 2560 을 넘으면 이런 에러가 발생된다고 한다.

이미지 크기를 조절하니 에러가 바로 사라졌다.

+ Recent posts

티스토리 툴바