Implementieren einer Ansicht

Download Sample Das Beispiel herunterladen

Benutzerdefinierte Xamarin.Forms-Steuerelemente für die Benutzeroberfläche sollten von der View-Klasse abgeleitet werden. Diese Klasse wird verwendet, um einem Bildschirm Layout und Steuerelemente hinzuzufügen. In diesem Artikel wird veranschaulicht, wie Sie einen benutzerdefinierten Renderer für ein benutzerdefiniertes Xamarin.Forms-Steuerelement erstellen, mit dem Sie eine Vorschau eines Videostreams von der Kamera des Geräts anzeigen können.

Jede Xamarin.Forms-Ansicht verfügt über einen entsprechenden Renderer für jede Plattform, der eine Instanz eines nativen Steuerelements erstellt. Beim Rendern eines View-Objekts durch eine Xamarin.Forms-App in iOS wird die ViewRenderer-Klasse instanziiert, wodurch wiederum ein natives UIView-Steuerelement instanziiert wird. Auf der Android-Plattform instanziiert die ViewRenderer-Klasse ein natives View-Steuerelement. Auf der Universellen Windows-Plattform (UWP) instanziiert die ViewRenderer-Klasse ein natives FrameworkElement-Steuerelement. Weitere Informationen zu den Renderern und Klassen nativer Steuerelemente, auf die Xamarin.Forms-Steuerelemente verweisen, finden Sie unter Rendererbasisklassen und native Steuerelemente.

Hinweis

Einige Steuerelemente unter Android verwenden schnelle Renderer, die nicht die Klasse ViewRenderer verarbeiten. Weitere Informationen zu schnellen Renderern finden Sie unter Xamarin.FormsSchnelle Renderer.

Das folgende Diagramm veranschaulicht die Beziehungen zwischen dem View-Objekt und den entsprechenden nativen Steuerelementen, die dieses implementieren:

Relationship Between the View Class and its Implementing Native Classes

Der Renderingprozess kann genutzt werden, um plattformspezifische Anpassungen zu implementieren, indem für eine View-Klasse auf jeder Plattform ein benutzerdefinierter Renderer erstellt wird. Gehen Sie hierfür folgendermaßen vor:

  1. Erstellen Sie ein benutzerdefiniertes Xamarin.Forms-Steuerelement.
  2. Nutzen Sie das benutzerdefinierte Steuerelement über Xamarin.Forms.
  3. Erstellen Sie den benutzerdefinierten Renderer für das Steuerelement auf jeder Plattform.

Im Folgenden wird jedes Element zum Implementieren eines CameraPreview-Renderers einzeln erläutert, der einen Vorschauvideostream der Kamera des Geräts zeigt. Durch Tippen auf den Videostream wird er gestartet und gestoppt.

Erstellen des benutzerdefinierten Steuerelements

Sie können ein benutzerdefiniertes Steuerelement erstellen, indem Sie die View-Klasse wie in folgendem Codebeispiel als Unterklasse verwenden:

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

Das benutzerdefinierte Steuerelement CameraPreview wird im .NET Standard-Bibliotheksprojekt erstellt und definiert die API für das Steuerelement. Das benutzerdefinierte Steuerelement stellt eine Camera-Eigenschaft zur Verfügung, die verwendet wird, um zu steuern, ob der Videostream der vorderen oder hinteren Kamera des Geräts angezeigt werden soll. Wenn bei der Erstellung des Steuerelements kein Wert für die Camera-Eigenschaft angegeben ist, wird standardmäßig die hintere Kamera festgelegt.

Nutzen des benutzerdefinierten Steuerelements

Sie können auf das benutzerdefinierte Steuerelement CameraPreview in XAML im .NET Standard-Bibliotheksprojekt verweisen, indem Sie einen Namespace für seine Position deklarieren und das Namespacepräfix für das benutzerdefinierte Steuerelement verwenden. Das folgende Codebeispiel veranschaulicht, wie das benutzerdefinierte Steuerelement CameraPreview von der XAML-Seite genutzt werden kann:

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

Das local-Namespacepräfix kann beliebig benannt werden. Die Werte clr-namespace und assembly müssen jedoch mit den Angaben des benutzerdefinierten Steuerelements übereinstimmen. Wenn der Namespace deklariert wurde, wird das Präfix verwendet, um auf das benutzerdefinierte Steuerelement zu verweisen.

Im folgenden Codebeispiel wird veranschaulicht, wie das benutzerdefinierte Steuerelement CameraPreview von einer C#-Seite genutzt werden kann:

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

Eine Instanz des benutzerdefinierten Steuerelements CameraPreview wird zum Anzeigen des Vorschauvideostreams der Gerätekamera verwendet. Abgesehen von der optionalen Angabe eines Werts für die Camera-Eigenschaft, findet die Anpassung des Steuerelements im benutzerdefinierten Renderer statt.

Ein benutzerdefinierter Renderer kann nun zu jedem Anwendungsprojekt hinzugefügt werden, um plattformspezifische Steuerelemente für die Kameravorschau zu erstellen.

Erstellen des benutzerdefinierten Renderers auf jeder Plattform

Gehen Sie folgendermaßen vor, um eine Klasse für einen benutzerdefinierten Renderer unter iOS und UWP zu erstellen:

  1. Erstellen Sie eine Unterklasse der ViewRenderer<T1,T2>-Klasse, die das benutzerdefinierte Steuerelement rendert. Das erste Typargument sollte das benutzerdefinierte Steuerelement sein, für das der Renderer erstellt werden soll. In diesem Fall ist das CameraPreview. Das zweite Typargument sollte das native Steuerelement sein, das das benutzerdefinierte Steuerelement implementiert.
  2. Überschreiben Sie die OnElementChanged-Methode, die das benutzerdefinierte Steuerelement rendert, und schreiben Sie Logik, um dieses anzupassen. Diese Methode wird aufgerufen, wenn das entsprechende Xamarin.Forms-Steuerelement erstellt wird.
  3. Fügen Sie der Klasse des benutzerdefinierten Renderers ein ExportRenderer-Attribut hinzu, um anzugeben, dass sie zum Rendern des benutzerdefinierten Xamarin.Forms-Steuerelements verwendet werden soll. Dieses Attribut wird verwendet, um den benutzerdefinierten Renderer bei Xamarin.Forms zu registrieren.

Gehen Sie folgendermaßen vor, um eine benutzerdefinierte Rendererklasse unter Android als schneller Renderer zu erstellen:

  1. Erstellen Sie eine Unterklasse des Android-Steuerelements, die das benutzerdefinierte Steuerelement rendert. Geben Sie außerdem an, dass die Unterklasse die Schnittstellen IVisualElementRenderer und IViewRenderer implementiert.
  2. Implementieren Sie die Schnittstellen IVisualElementRenderer und IViewRenderer in der schnellen Rendererklasse.
  3. Fügen Sie der Klasse des benutzerdefinierten Renderers ein ExportRenderer-Attribut hinzu, um anzugeben, dass sie zum Rendern des benutzerdefinierten Xamarin.Forms-Steuerelements verwendet werden soll. Dieses Attribut wird verwendet, um den benutzerdefinierten Renderer bei Xamarin.Forms zu registrieren.

Hinweis

Bei den meisten Xamarin.Forms-Elementen ist das Angeben eines benutzerdefinierten Renderers in jedem Plattformprojekt optional. Wenn kein benutzerdefinierter Renderer registriert wurde, wird der Standardrenderer für die Basisklasse des Steuerelements verwendet. Benutzerdefinierte Renderer sind jedoch in jedem Plattformprojekt erforderlich, wenn ein View-Element gerendert wird.

Das folgende Diagramm veranschaulicht die Zuständigkeiten jedes Projekts in der Beispielanwendung sowie deren Beziehungen zueinander:

CameraPreview Custom Renderer Project Responsibilities

Das benutzerdefinierte Steuerelement CameraPreview wird von plattformspezifischen Rendererklassen gerendert, die unter iOS und UWP von der Klasse ViewRenderer und unter Android von der Klasse FrameLayout abgeleitet werden. Das führt dazu, dass jedes benutzerdefinierte CameraPreview-Steuerelement mit plattformspezifischen Steuerelementen gerendert wird. Dies wird in folgenden Screenshots veranschaulicht:

CameraPreview on each Platform

Die ViewRenderer-Klasse stellt die OnElementChanged-Methode zur Verfügung, die bei der Erstellung des benutzerdefinierten Xamarin.Forms-Steuerelements aufgerufen wird, um das entsprechende native Steuerelement zu rendern. Diese Methode akzeptiert einen ElementChangedEventArgs-Parameter, der die Eigenschaften OldElement und NewElement enthält. Diese Eigenschaften stellen jeweils das Xamarin.Forms-Element, an das der Renderer angefügt war, und das Xamarin.Forms-Element dar, an das der Renderer angefügt ist. In der Beispielanwendung weist die OldElement-Eigenschaft den Wert null auf, und die NewElement-Eigenschaft enthält einen Verweis auf die CameraPreview-Instanz.

Das native Steuerelement sollte bei der überschriebenen Version der OnElementChanged-Methode in jeder plattformspezifischen Rendererklasse instanziiert und angepasst werden. Mit der SetNativeControl-Methode sollte das native Steuerelement instanziiert und die Steuerelementreferenz der Eigenschaft Control zugewiesen werden. Zusätzlich kann ein Verweis auf das Xamarin.Forms-Steuerelement, das gerendert wird, über die Element-Eigenschaft abgerufen werden.

In einigen Fällen kann die OnElementChanged-Methode mehrmals aufgerufen werden. Daher müssen Sie bei der Instanziierung eines nativen Steuerelements sorgfältig vorgehen, um Arbeitsspeicherverluste zu vermeiden. Im folgenden Codebeispiel ist der Ansatz zu sehen, der beim Instanziieren eines neuen nativen Steuerelements in einem benutzerdefinierten Renderer verwendet werden soll:

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

Ein neues natives Steuerelement sollte nur einmal instanziiert werden, wenn der Wert der Eigenschaft Controlnull lautet. Ein Steuerelement sollte außerdem nur dann erstellt, konfiguriert und vom Ereignishandler abonniert werden, wenn der angepasste Renderer an ein neues Xamarin.Forms-Element angefügt wird. Gleichermaßen sollte das Abonnement für Ereignishandler nur dann gekündigt werden, wenn sich das Element ändert, an das der Renderer angefügt wurde. Mit diesem Ansatz kann ein leistungsstarker benutzerdefinierter Renderer erstellt werden, der nicht durch Speicherverluste beeinträchtigt wird.

Wichtig

Die SetNativeControl-Methode sollte nur aufgerufen werden, wenn e.NewElement nicht null ist.

Jede benutzerdefinierte Rendererklasse ist mit einem ExportRenderer-Attribut versehen, das den Renderer bei Xamarin.Forms registriert. Das Attribut benötigt zwei Parameter: den Typnamen des zu rendernden benutzerdefinierten Xamarin.Forms-Steuerelements und den Typnamen des angepassten Renderers. Das Präfix assembly für das Attribut gibt an, dass das Attribut für die gesamte Assembly gilt.

In den folgenden Abschnitten wird die Implementierung jeder plattformspezifischen, benutzerdefinierten Rendererklasse erläutert.

Erstellen des benutzerdefinierten Renderers unter iOS

Im folgenden Codebeispiel wird der benutzerdefinierte Renderer für die iOS-Plattform veranschaulicht:

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

Sofern die Control-Eigenschaft den Wert null aufweist, wird die SetNativeControl-Methode aufgerufen, um ein neues UICameraPreview-Steuerelement zu instanziieren und der Control-Eigenschaft einen Verweis auf dieses Steuerelement zuzuweisen. Das UICameraPreview-Steuerelement ist ein plattformspezifisches, benutzerdefiniertes Steuerelement, das die AVCapture-APIs nutzt, um den Vorschauvideostream der Kamera bereitzustellen. Es stellt ein Tapped-Ereignis zur Verfügung, das von der OnCameraPreviewTapped-Methode verarbeitet wird, um die Videovorschau zu stoppen und zu starten, wenn sie angetippt wird. Wenn der angepasste Renderer an ein neues Xamarin.Forms-Element angefügt wird, wird das Ereignis Tapped abonniert. Dieses Abonnement wird erst bei Änderung des betreffenden Elements gekündigt.

Erstellen des benutzerdefinierten Renderers unter Android

Im folgenden Codebeispiel wird der schnelle Renderer für die Android-Plattform veranschaulicht:

[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;
            }
        }       
        // ...
    }
}

In diesem Beispiel erstellt die Methode OnElementChanged ein CameraFragment-Objekt, wenn der benutzerdefinierte Renderer an ein neues Xamarin.Forms-Element angefügt wird: Der Typ CameraFragment ist eine plattformspezifische Klasse, die mithilfe der API Camera2 den Vorschauvideostream der Kamera bereitstellt. Das CameraFragment-Objekt wird beseitigt, wenn das Xamarin.Forms-Element geändert wird, an das der Renderer angefügt ist.

Erstellen des benutzerdefinierten Renderers auf der UWP

Im folgenden Codebeispiel wird der benutzerdefinierte Renderer für die UWP veranschaulicht:

[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();
            }
        }
        ...
    }
}

Sofern die Control-Eigenschaft den Wert null aufweist, wird eine neue CaptureElement-Klasse instanziiert und die SetupCamera-Methode wird aufgerufen, die die MediaCapture-API verwendet, um den Vorschauvideostream der Kamera bereitzustellen. Anschließend wird die SetNativeControl-Methode aufgerufen, um der Control-Eigenschaft einen Verweis auf die CaptureElement-Instanz zuzuweisen. Das CaptureElement-Steuerelement stellt ein Tapped-Ereignis zur Verfügung, das von der OnCameraPreviewTapped-Methode verarbeitet wird, um die Videovorschau zu stoppen und zu starten, wenn sie angetippt wird. Wenn der angepasste Renderer an ein neues Xamarin.Forms-Element angefügt wird, wird das Ereignis Tapped abonniert. Dieses Abonnement wird erst bei Änderung des betreffenden Elements gekündigt.

Hinweis

Es ist wichtig, die Objekte, die den Zugriff auf die Kamera ermöglichen, in einer UWP-Anwendung zu stoppen und zu löschen. Wenn dies nicht geschieht, kann dies andere Anwendungen beeinträchtigen, die versuchen, auf die Kamera des Geräts zuzugreifen. Weitere Informationen finden Sie unter Display the camera preview (Anzeigen der Kameravorschau).

Zusammenfassung

In diesem Artikel wird die Erstellung eines angepassten Renderers für ein benutzerdefiniertes Xamarin.Forms-Steuerelement erläutert, mit dessen Hilfe Sie eine Vorschau des Videostreams von der Gerätekamera anzeigen können. Benutzerdefinierte Xamarin.Forms-Steuerelemente für die Benutzeroberfläche sollten von der Klasse View abgeleitet werden. Diese Klasse wird verwendet, um einem Bildschirm Layout und Steuerelemente hinzuzufügen.