共用方式為


實作檢視

Xamarin.Forms 自定義使用者介面控件應該衍生自 View 類別,用來在畫面上放置版面配置和控件。 本文示範如何為 Xamarin.Forms 自定義控件建立自定義轉譯器,以用來顯示裝置相機的預覽視訊串流。

每個 Xamarin.Forms 檢視都有每個平臺的隨附轉譯器,可建立原生控件的實例。 View當 應用程式Xamarin.Forms在 iOS 中轉譯 時,類別ViewRenderer會具現化,進而具現化原生UIView控件。 在 Android 平台上,ViewRenderer 類別會具現化原生 View 控制項。 在通用 Windows 平台 (UWP) 上,ViewRenderer 類別會具現化原生 FrameworkElement 控制項。 如需控件對應之轉譯器和原生控件類別 Xamarin.Forms 的詳細資訊,請參閱 轉譯器基類和原生控件

注意

Android 上的某些控制項會使用快速轉譯器,不會取用 ViewRenderer 類別。 如需快速轉譯器的詳細資訊,請參閱 Xamarin.Forms 快速轉譯器

下圖說明 View 和實作它之對應原生控制項間的關聯性:

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 屬性的值,則預設會指定為後方相機。

使用自訂控制項

您可以宣告自訂控制項的位置命名空間,並使用自訂控制項元素上的命名空間前置詞,來在 .NET Standard 程式庫專案的 XAML 中參考 CameraPreview 自訂控制項。 下列程式碼範例示範 XAML 頁面如何使用 CameraPreview 自訂控制項:

<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控件子類別。 此外,請指定子類別將實 IVisualElementRenderer 作和 IViewRenderer 介面。
  2. IVisualElementRenderer在快速轉譯器類別中實作和 IViewRenderer 介面。
  3. ExportRenderer將屬性新增至自定義轉譯器類別,以指定將用來轉Xamarin.Forms譯自定義控件。 這個屬性是用來向 Xamarin.Forms註冊自定義轉譯器。

注意

對於大多數 Xamarin.Forms 元素,您可以選擇在每個平台專案中提供自定義轉譯器。 如果自訂轉譯器未註冊,則會使用控制項基底類別的預設轉譯器。 不過,轉譯 View 項目時,每個平台專案都必須要有自訂轉譯器。

下圖說明應用程式範例中每個專案的責任,以及這些專案之間的關聯性:

CameraPreview 自訂轉譯器專案責任

CameraPreview自定義控件是由平臺特定的轉譯器類別轉譯,這些類別衍生自 ViewRenderer iOS 和 UWP 上的 類別,以及 Android FrameLayout 上的 類別。 這會導致每個 CameraPreview 自訂控制項都使用平台特定控制項轉譯,如下列螢幕擷取畫面所示:

每個平台上的 CameraPreview

類別 ViewRendererOnElementChanged 公開 方法,這個方法會在建立自定義控件以轉譯對應的原生控件時 Xamarin.Forms 呼叫。 這個方法採用 ElementChangedEventArgs 參數,其中包含 OldElementNewElement 屬性。 這些屬性代表Xamarin.Forms轉譯器附加的專案,以及Xamarin.Forms轉譯器分別附加至的專案。 在範例應用程式中,OldElement 屬性會是 null,而 NewElement 屬性會包含 CameraPreview 執行個體的參考。

在每個平台特定的轉譯器類別中,OnElementChanged 方法之覆寫版本是執行原生控制項具現化和自訂的位置。 您應該使用 SetNativeControl 方法來具現化原生控制項,而且此方法也會將控制項參考指派給 Control 屬性。 此外,可以透過 屬性取得所轉譯之控件的Element參考Xamarin.Forms。

在某些情況下,可能會一再呼叫 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 元素時,才應該建立、設定和事件處理程式訂閱。 同樣地,應該只在轉譯器附加到的項目變更時,才取消訂閱任何已訂閱的事件處理常式。 採用這個方法將有助於建立高效能的自訂轉譯器,避免發生記憶體流失。

重要

只有當 e.NewElement 不是 null 時,才應呼叫 SetNativeControl方法。

每個自定義轉譯器類別都會以 ExportRenderer 向 註冊轉譯器 Xamarin.Forms的屬性裝飾。 屬性會採用兩個參數 :要轉譯之 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 事件,只要加以點選,就會停止和啟動視訊預覽。 當 Tapped 自定義轉譯器附加至新 Xamarin.Forms 元素時,事件會訂閱,而且只有在轉譯器附加至變更的專案時,才會取消訂閱。

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

在此範例中 OnElementChanged ,方法會 CameraFragment 建立 物件,前提是自定義轉譯器已附加至新 Xamarin.Forms 元素。 此 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 並呼叫 SetupCamera 方法,以使用 MediaCapture API 從相機提供預覽資料流。 然後會呼叫 SetNativeControl 方法,將 CaptureElement 執行個體的參考指派給 Control 屬性。 CaptureElement 控制項會公開由 OnCameraPreviewTapped 方法處理的 Tapped 事件,只要加以點選,就會停止和啟動視訊預覽。 當 Tapped 自定義轉譯器附加至新 Xamarin.Forms 元素時,事件會訂閱,而且只有在轉譯器附加至變更的專案時,才會取消訂閱。

注意

請務必停止並處置提供 UWP 應用程式中相機存取權的物件。 若未這樣做,則可能會干擾嘗試存取裝置相機的其他應用程式。 如需詳細資訊,請參閱顯示相機預覽

摘要

本文示範如何為 Xamarin.Forms 自定義控件建立自定義轉譯器,以用來顯示來自裝置相機的預覽視訊串流。 Xamarin.Forms 自定義使用者介面控件應該衍生自 View 類別,用來在畫面上放置版面配置和控件。