Share via


보기 구현

‘ 사용자 지정 사용자 인터페이스 컨트롤은 화면에 레이아웃과 컨트롤을 배치하는 데 사용되는 View 클래스에서 파생되어야 합니다.Xamarin.Forms 이 문서에서는 디바이스 카메라에서 미리 보기 동영상 스트림을 표시하는 데 사용되는 Xamarin.Forms 사용자 지정 컨트롤의 사용자 지정 렌더러를 만드는 방법을 설명합니다.

모든 Xamarin.Forms 보기의 모양과 동작을 효과적으로 사용자 지정할 수 있습니다. iOS의 Xamarin.Forms 애플리케이션에서 View를 렌더링하는 경우 ViewRenderer 클래스가 인스턴스화되며, 차례로 네이티브 UIView 컨트롤이 인스턴스화됩니다. Android 플랫폼에서 ViewRenderer 클래스는 네이티브 View 컨트롤을 인스턴스화합니다. UWP(유니버설 Windows 플랫폼)에서 ViewRenderer 클래스는 네이티브 FrameworkElement 컨트롤을 인스턴스화합니다. Xamarin.Forms 컨트롤에 매핑되는 렌더러 및 네이티브 컨트롤 클래스에 대한 자세한 내용은 렌더러 기본 클래스 및 네이티브 컨트롤을 참조하세요.

참고 항목

Android의 일부 컨트롤은 ViewRenderer 클래스를 사용하지 않는 빠른 렌더러를 사용합니다. 빠른 렌더러에 관한 자세한 내용은 Xamarin.Forms 빠른 렌더러를 참조하세요.

다음 다이어그램은 View 및 이를 구현하는 해당 네이티브 컨트롤 간의 관계를 보여줍니다.

보기 클래스와 네이티브 클래스 구현 간의 관계

렌더링 프로세스는 각 플랫폼에서 View에 대한 사용자 지정 렌더러를 만들어 플랫폼별 사용자 지정을 구현하는 데 사용할 수 있습니다. 이 작업을 수행하는 프로세스는 다음과 같습니다.

  1. Xamarin.Forms 사용자 지정 컨트롤을 만듭니다.
  2. Xamarin.Forms에서 사용자 지정 컨트롤을 사용합니다.
  3. 각 플랫폼의 컨트롤에 대한 사용자 지정 렌더러를 만듭니다.

디바이스의 카메라에서 미리 보기 비디오 스트림을 표시하는 CameraPreview 렌더러를 구현하기 위해 각 항목을 차례로 살펴보겠습니다. 비디오 스트림을 탭하면 중지 및 시작됩니다.

사용자 지정 컨트롤 만들기

다음 코드 예제와 같이 View 클래스를 서브클래스하여 사용자 지정 컨트롤을 만들 수 있습니다.

public class CameraPreview : View
{
  public static readonly BindableProperty CameraProperty = BindableProperty.Create (
    propertyName: "Camera",
    returnType: typeof(CameraOptions),
    declaringType: typeof(CameraPreview),
    defaultValue: CameraOptions.Rear);

  public CameraOptions Camera
  {
    get { return (CameraOptions)GetValue (CameraProperty); }
    set { SetValue (CameraProperty, value); }
  }
}

CameraPreview 사용자 지정 컨트롤은 .NET Standard 라이브러리 프로젝트에서 생성되고 컨트롤에 대한 API를 정의합니다. 사용자 지정 컨트롤은 디바이스의 전면 또는 후면 카메라의 비디오 스트림을 표시해야 하는지 여부를 제어하는 데 사용되는 Camera 속성을 노출합니다. 컨트롤이 생성될 때 Camera 속성에 대해 값이 지정되지 않은 경우에는 기본적으로 후면 카메라가 지정됩니다.

사용자 지정 컨트롤 사용

CameraPreview 사용자 지정 컨트롤은 해당 위치의 네임스페이스를 선언하고 사용자 지정 컨트롤 요소의 네임스페이스 접두사를 사용하여 .NET 표준 라이브러리 프로젝트의 XAML에서 참조할 수 있습니다. 다음 코드 예제에서는 CameraPreview 사용자 지정 컨트롤을 XAML 페이지에서 사용하는 방법을 보여줍니다.

<ContentPage ...
             xmlns:local="clr-namespace:CustomRenderer;assembly=CustomRenderer"
             ...>
    <StackLayout>
        <Label Text="Camera Preview:" />
        <local:CameraPreview Camera="Rear"
                             HorizontalOptions="FillAndExpand"
                             VerticalOptions="FillAndExpand" />
    </StackLayout>
</ContentPage>

local 네임스페이스 접두사는 원하는 이름으로 지정할 수 있습니다. 그러나 clr-namespaceassembly 값은 사용자 지정 컨트롤의 세부 정보와 일치해야 합니다. 네임스페이스가 선언되면 사용자 지정 컨트롤을 참조하는 데 사용됩니다.

다음 코드 예제에서는 C# 페이지에서 CameraPreview 사용자 지정 컨트롤을 사용하는 방법을 보여줍니다.

public class MainPageCS : ContentPage
{
  public MainPageCS ()
  {
    ...
    Content = new StackLayout
    {
      Children =
      {
        new Label { Text = "Camera Preview:" },
        new CameraPreview
        {
          Camera = CameraOptions.Rear,
          HorizontalOptions = LayoutOptions.FillAndExpand,
          VerticalOptions = LayoutOptions.FillAndExpand
        }
      }
    };
  }
}

CameraPreview 사용자 지정 컨트롤의 인스턴스는 디바이스의 카메라에서 미리 보기 비디오 스트림을 표시하는 데 사용됩니다. 필요에 따라 Camera 속성에 대한 값을 지정하는 것 외에 컨트롤의 사용자 지정은 사용자 지정 렌더러에서 수행됩니다.

이제 각 애플리케이션 프로젝트에 사용자 지정 렌더러를 추가하여 플랫폼별 카메라 미리 보기 컨트롤을 만들 수 있습니다.

각 플랫폼에서 사용자 지정 렌더러 만들기

iOS 및 UWP에서 사용자 지정 렌더러 클래스를 만드는 프로세스는 다음과 같습니다.

  1. 사용자 지정 컨트롤을 렌더링하는 ViewRenderer<T1,T2> 클래스의 서브클래스를 만듭니다. 첫 번째 형식 인수는 렌더러가 사용할(이 경우 CameraPreview) 사용자 지정 컨트롤이어야 합니다. 두 번째 형식 인수는 사용자 지정 컨트롤을 구현할 네이티브 컨트롤이어야 합니다.
  2. 사용자 지정 컨트롤을 렌더링하고 이를 사용자 지정하기 위한 논리를 작성하는 OnElementChanged 메서드를 재정의합니다. 이 메서드는 해당 Xamarin.Forms 컨트롤이 생성될 때 호출됩니다.
  3. 사용자 지정 렌더러 클래스에 ExportRenderer 특성을 추가하여 Xamarin.Forms 사용자 지정 컨트롤을 렌더링하는 데 사용하도록 지정합니다. 이 특성은 사용자 지정 렌더러를 Xamarin.Forms에 등록하는 데 사용됩니다.

Android에서 사용자 지정 렌더러 클래스를 빠른 렌더러로 만드는 프로세스는 다음과 같습니다.

  1. 사용자 지정 컨트롤을 렌더링하는 Android 컨트롤의 서브클래스를 만듭니다. 또한 서브클래스가 IVisualElementRendererIViewRenderer 인터페이스를 구현하도록 지정합니다.
  2. 빠른 렌더러 클래스에서 IVisualElementRendererIViewRenderer 인터페이스를 구현합니다.
  3. 사용자 지정 렌더러 클래스에 ExportRenderer 특성을 추가하여 Xamarin.Forms 사용자 지정 컨트롤을 렌더링하는 데 사용하도록 지정합니다. 이 특성은 사용자 지정 렌더러를 Xamarin.Forms에 등록하는 데 사용됩니다.

참고 항목

대부분의 Xamarin.Forms 요소의 경우 각 플랫폼 프로젝트에서 사용자 지정 렌더러를 제공하는 것은 선택 사항입니다. 사용자 지정 렌더러가 등록되지 않은 경우 컨트롤의 기본 클래스에 대한 기본 렌더러가 사용됩니다. 그러나 보기 요소를 렌더링하는 경우에는 각 플랫폼 프로젝트에 사용자 지정 렌더러가 필요합니다.

다음 다이어그램은 샘플 애플리케이션에서 각 프로젝트의 책임과 이들 간의 관계를 보여줍니다.

CameraPreview 사용자 지정 렌더러 프로젝트 책임

CameraPreview 사용자 지정 컨트롤은 iOS 및 UWP의 ViewRenderer 클래스와 Android의 FrameLayout 클래스에서 파생되는 플랫폼별 렌더러 클래스에서 렌더링됩니다. 그러면 다음 스크린샷과 같이 각 CameraPreview 사용자 지정 컨트롤이 플랫폼별 컨트롤로 렌더링됩니다.

각 플랫폼의 CameraPreview

ViewRenderer 클래스는 해당 네이티브 컨트롤을 렌더링하기 위해 Xamarin.Forms 사용자 지정 컨트롤이 생성될 때 호출되는 OnElementChanged 메서드를 노출합니다. 이 메서드는 OldElementNewElement 속성이 포함된 ElementChangedEventArgs 매개 변수를 가져옵니다. 이러한 속성은 렌더러가 ‘연결된’ Xamarin.Forms 요소와 렌더러가 ‘연결되는’ Xamarin.Forms 요소를 각각 나타냅니다. 샘플 애플리케이션에서 OldElement 속성은 null이고, NewElement 속성은 CameraPreview 인스턴스에 대한 참조를 포함합니다.

각 플랫폼별 렌더러 클래스에서 OnElementChanged 메서드의 재정의된 버전은 네이티브 컨트롤 인스턴스화 및 사용자 지정을 수행하는 위치입니다. SetNativeControl 메서드는 네이티브 컨트롤을 인스턴스화하는 데 사용되어야 하며, 이 메서드는 또한 Control 속성에 컨트롤 참조를 할당합니다. 또한 렌더링되는 Xamarin.Forms 컨트롤에 대한 참조는 Element 속성을 통해 얻을 수 있습니다.

그러나 일부 경우에는 OnElementChanged 메서드가 여러 번 호출될 수 있습니다. 따라서 메모리 누수를 방지하려면 새로운 네이티브 컨트롤을 인스턴스화할 때 주의를 기울여야 합니다. 사용자 지정 렌더러에서 새 네이티브 컨트롤을 인스턴스화할 때 사용하는 방법은 다음 코드 예제에 표시되어 있습니다.

protected override void OnElementChanged (ElementChangedEventArgs<NativeListView> e)
{
  base.OnElementChanged (e);

  if (e.OldElement != null)
  {
    // Unsubscribe from event handlers and cleanup any resources
  }

  if (e.NewElement != null)
  {    
    if (Control == null)
    {
      // Instantiate the native control and assign it to the Control property with
      // the SetNativeControl method
    }
    // Configure the control and subscribe to event handlers
  }
}

Control 속성이 null일 경우 새 네이티브 컨트롤은 한 번만 인스턴스화되어야 합니다. 또한 사용자 지정 렌더러가 새 Xamarin.Forms 요소에 연결된 경우에만 컨트롤을 만들어서 구성하고 이벤트 처리기를 구독해야 합니다. 마찬가지로, 렌더러가 변경된 항목에 연결된 경우 구독 대상 이벤트 처리기의 구독이 취소되어야 합니다. 이러한 방식을 채택하면 메모리 누수가 없는 성능이 뛰어난 사용자 지정 렌더러를 만드는 데 도움이 됩니다.

Important

e.NewElementnull이 아닌 경우에만 SetNativeControl 메서드를 호출해야 합니다.

각 사용자 지정 렌더러 클래스는 렌더러를 Xamarin.Forms에 등록하는 ExportRenderer 특성으로 데코레이트됩니다. 이 특성은 렌더링되는 Xamarin.Forms 사용자 지정 컨트롤의 형식 이름과 사용자 지정 렌더러의 형식 이름이라는 두 가지 매개 변수를 사용합니다. 특성의 assembly 접두사는 특성이 전체 어셈블리에 적용되도록 지정합니다.

다음 섹션에서는 각 플랫폼별 사용자 지정 렌더러 클래스의 구현을 설명합니다.

iOS에서 사용자 지정 렌더러 만들기

다음 코드 예제에서는 iOS 플랫폼용 사용자 지정 렌더러를 보여줍니다.

[assembly: ExportRenderer (typeof(CameraPreview), typeof(CameraPreviewRenderer))]
namespace CustomRenderer.iOS
{
    public class CameraPreviewRenderer : ViewRenderer<CameraPreview, UICameraPreview>
    {
        UICameraPreview uiCameraPreview;

        protected override void OnElementChanged (ElementChangedEventArgs<CameraPreview> e)
        {
            base.OnElementChanged (e);

            if (e.OldElement != null) {
                // Unsubscribe
                uiCameraPreview.Tapped -= OnCameraPreviewTapped;
            }
            if (e.NewElement != null) {
                if (Control == null) {
                  uiCameraPreview = new UICameraPreview (e.NewElement.Camera);
                  SetNativeControl (uiCameraPreview);
                }
                // Subscribe
                uiCameraPreview.Tapped += OnCameraPreviewTapped;
            }
        }

        void OnCameraPreviewTapped (object sender, EventArgs e)
        {
            if (uiCameraPreview.IsPreviewing) {
                uiCameraPreview.CaptureSession.StopRunning ();
                uiCameraPreview.IsPreviewing = false;
            } else {
                uiCameraPreview.CaptureSession.StartRunning ();
                uiCameraPreview.IsPreviewing = true;
            }
        }
        ...
    }
}

Control 속성이 null인 경우 SetNativeControl 메서드가 호출되어 새 UICameraPreview 컨트롤을 인스턴스화하고 Control 속성에 대한 참조를 할당합니다. UICameraPreview 컨트롤은 카메라의 미리 보기 스트림을 제공하는 데 AVCapture API를 사용하는 플랫폼별 사용자 지정 컨트롤입니다. OnCameraPreviewTapped 메서드에 의해 처리되는 Tapped 이벤트를 노출하여 탭할 때 비디오 미리 보기를 중지 및 시작합니다. 사용자 지정 렌더러가 새 Xamarin.Forms 요소에 연결되어 있는 경우 Tapped 이벤트가 구독되며, 렌더러가 연결된 요소가 변경되는 경우에만 구독이 취소됩니다.

Android에서 사용자 지정 렌더러 만들기

다음 코드 예제에서는 Android 플랫폼용 빠른 렌더러를 보여줍니다.

[assembly: ExportRenderer(typeof(CustomRenderer.CameraPreview), typeof(CameraPreviewRenderer))]
namespace CustomRenderer.Droid
{
    public class CameraPreviewRenderer : FrameLayout, IVisualElementRenderer, IViewRenderer
    {
        // ...
        CameraPreview element;
        VisualElementTracker visualElementTracker;
        VisualElementRenderer visualElementRenderer;
        FragmentManager fragmentManager;
        CameraFragment cameraFragment;

        FragmentManager FragmentManager => fragmentManager ??= Context.GetFragmentManager();

        public event EventHandler<VisualElementChangedEventArgs> ElementChanged;
        public event EventHandler<PropertyChangedEventArgs> ElementPropertyChanged;

        CameraPreview Element
        {
            get => element;
            set
            {
                if (element == value)
                {
                    return;
                }

                var oldElement = element;
                element = value;
                OnElementChanged(new ElementChangedEventArgs<CameraPreview>(oldElement, element));
            }
        }

        public CameraPreviewRenderer(Context context) : base(context)
        {
            visualElementRenderer = new VisualElementRenderer(this);
        }

        void OnElementChanged(ElementChangedEventArgs<CameraPreview> e)
        {
            CameraFragment newFragment = null;

            if (e.OldElement != null)
            {
                e.OldElement.PropertyChanged -= OnElementPropertyChanged;
                cameraFragment.Dispose();
            }
            if (e.NewElement != null)
            {
                this.EnsureId();

                e.NewElement.PropertyChanged += OnElementPropertyChanged;

                ElevationHelper.SetElevation(this, e.NewElement);
                newFragment = new CameraFragment { Element = element };
            }

            FragmentManager.BeginTransaction()
                .Replace(Id, cameraFragment = newFragment, "camera")
                .Commit();
            ElementChanged?.Invoke(this, new VisualElementChangedEventArgs(e.OldElement, e.NewElement));
        }

        async void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            ElementPropertyChanged?.Invoke(this, e);

            switch (e.PropertyName)
            {
                case "Width":
                    await cameraFragment.RetrieveCameraDevice();
                    break;
            }
        }       
        // ...
    }
}

이 예제에서 사용자 지정 렌더러가 새 Xamarin.Forms 요소에 연결되어 있는 경우 OnElementChanged 메서드가 CameraFragment 개체를 만듭니다. CameraFragment 형식은 카메라의 미리 보기 스트림을 제공하는 데 Camera2 API를 사용하는 사용자 지정 클래스입니다. 렌더러가 연결된 Xamarin.Forms 요소가 변경되면 CameraFragment 개체가 삭제됩니다.

UWP에서 사용자 지정 렌더러 만들기

다음 코드 예제는 UWP용 사용자 지정 렌더러를 보여줍니다.

[assembly: ExportRenderer(typeof(CameraPreview), typeof(CameraPreviewRenderer))]
namespace CustomRenderer.UWP
{
    public class CameraPreviewRenderer : ViewRenderer<CameraPreview, Windows.UI.Xaml.Controls.CaptureElement>
    {
        ...
        CaptureElement _captureElement;
        bool _isPreviewing;

        protected override void OnElementChanged(ElementChangedEventArgs<CameraPreview> e)
        {
            base.OnElementChanged(e);

            if (e.OldElement != null)
            {
                // Unsubscribe
                Tapped -= OnCameraPreviewTapped;
                ...
            }
            if (e.NewElement != null)
            {
                if (Control == null)
                {
                  ...
                  _captureElement = new CaptureElement();
                  _captureElement.Stretch = Stretch.UniformToFill;

                  SetupCamera();
                  SetNativeControl(_captureElement);
                }
                // Subscribe
                Tapped += OnCameraPreviewTapped;
            }
        }

        async void OnCameraPreviewTapped(object sender, TappedRoutedEventArgs e)
        {
            if (_isPreviewing)
            {
                await StopPreviewAsync();
            }
            else
            {
                await StartPreviewAsync();
            }
        }
        ...
    }
}

Control 속성이 null인 경우 새 CaptureElement가 인스턴스화되고 카메라에서 미리 보기 스트림을 제공하는 데 MediaCapture API를 사용하는 SetupCamera 메서드가 호출됩니다. 그런 다음, SetNativeControl 메서드가 호출되어 CaptureElement 인스턴스에 대한 참조를 Control 속성에 할당합니다. CaptureElement 컨트롤은 OnCameraPreviewTapped 메서드에 의해 처리되는 Tapped 이벤트를 노출하여 탭할 때 비디오 미리 보기를 중지 및 시작합니다. 사용자 지정 렌더러가 새 Xamarin.Forms 요소에 연결되어 있는 경우 Tapped 이벤트가 구독되며, 렌더러가 연결된 요소가 변경되는 경우에만 구독이 취소됩니다.

참고 항목

UWP 애플리케이션에서 카메라에 대한 액세스를 제공하는 개체를 중지 및 삭제하는 것이 중요합니다. 이렇게 하지 않으면 디바이스의 카메라에 액세스하려고 하는 다른 애플리케이션을 방해할 수 있습니다. 자세한 내용은 카메라 미리 보기 표시를 참조하세요.

요약

이 문서에서는 디바이스 카메라에서 미리 보기 비디오 스트림을 표시하는 데 사용되는 Xamarin.Forms 사용자 지정 컨트롤에 대한 사용자 지정 렌더러를 만드는 방법을 설명했습니다. Xamarin.Forms 사용자 지정 사용자 인터페이스 컨트롤은 화면에 레이아웃과 컨트롤을 배치하는 데 사용되는 View 클래스에서 파생되어야 합니다.