แชร์ผ่าน


Customizing a ContentPage

A ContentPage is a visual element that displays a single view and occupies most of the screen. This article demonstrates how to create a custom renderer for the ContentPage page, enabling developers to override the default native rendering with their own platform-specific customization.

Every Xamarin.Forms control has an accompanying renderer for each platform that creates an instance of a native control. When a ContentPage is rendered by a Xamarin.Forms application, in iOS the PageRenderer class is instantiated, which in turn instantiates a native UIViewController control. On the Android platform, the PageRenderer class instantiates a ViewGroup control. On the Universal Windows Platform (UWP), the PageRenderer class instantiates a FrameworkElement control. For more information about the renderer and native control classes that Xamarin.Forms controls map to, see Renderer Base Classes and Native Controls.

The following diagram illustrates the relationship between the ContentPage and the corresponding native controls that implement it:

Relationship Between ContentPage Class and Implementing Native Controls

The rendering process can be taken advantage of to implement platform-specific customizations by creating a custom renderer for a ContentPage on each platform. The process for doing this is as follows:

  1. Create a Xamarin.Forms page.
  2. Consume the page from Xamarin.Forms.
  3. Create the custom renderer for the page on each platform.

Each item will now be discussed in turn, to implement a CameraPage that provides a live camera feed and the ability to capture a photo.

Creating the Xamarin.Forms Page

An unaltered ContentPage can be added to the shared Xamarin.Forms project, as shown in the following XAML code example:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="CustomRenderer.CameraPage">
    <ContentPage.Content>
    </ContentPage.Content>
</ContentPage>

Similarly, the code-behind file for the ContentPage should also remain unaltered, as shown in the following code example:

public partial class CameraPage : ContentPage
{
    public CameraPage ()
    {
        // A custom renderer is used to display the camera UI
        InitializeComponent ();
    }
}

The following code example shows how the page can be created in C#:

public class CameraPageCS : ContentPage
{
    public CameraPageCS ()
    {
    }
}

An instance of the CameraPage will be used to display the live camera feed on each platform. Customization of the control will be carried out in the custom renderer, so no additional implementation is required in the CameraPage class.

Consuming the Xamarin.Forms Page

The empty CameraPage must be displayed by the Xamarin.Forms application. This occurs when a button on the MainPage instance is tapped, which in turn executes the OnTakePhotoButtonClicked method, as shown in the following code example:

async void OnTakePhotoButtonClicked (object sender, EventArgs e)
{
    await Navigation.PushAsync (new CameraPage ());
}

This code simply navigates to the CameraPage, on which custom renderers will customize the page's appearance on each platform.

Creating the Page Renderer on each Platform

The process for creating the custom renderer class is as follows:

  1. Create a subclass of the PageRenderer class.
  2. Override the OnElementChanged method that renders the native page and write logic to customize the page. The OnElementChanged method is called when the corresponding Xamarin.Forms control is created.
  3. Add an ExportRenderer attribute to the page renderer class to specify that it will be used to render the Xamarin.Forms page. This attribute is used to register the custom renderer with Xamarin.Forms.

Note

It is optional to provide a page renderer in each platform project. If a page renderer isn't registered, then the default renderer for the page will be used.

The following diagram illustrates the responsibilities of each project in the sample application, along with the relationship between them:

CameraPage Custom Renderer Project Responsibilities

The CameraPage instance is rendered by platform-specific CameraPageRenderer classes, which all derive from the PageRenderer class for that platform. This results in each CameraPage instance being rendered with a live camera feed, as shown in the following screenshots:

CameraPage on each Platform

The PageRenderer class exposes the OnElementChanged method, which is called when the Xamarin.Forms page is created to render the corresponding native control. This method takes an ElementChangedEventArgs parameter that contains OldElement and NewElement properties. These properties represent the Xamarin.Forms element that the renderer was attached to, and the Xamarin.Forms element that the renderer is attached to, respectively. In the sample application the OldElement property will be null and the NewElement property will contain a reference to the CameraPage instance.

An overridden version of the OnElementChanged method in the CameraPageRenderer class is the place to perform the native page customization. A reference to the Xamarin.Forms page instance that's being rendered can be obtained through the Element property.

Each custom renderer class is decorated with an ExportRenderer attribute that registers the renderer with Xamarin.Forms. The attribute takes two parameters – the type name of the Xamarin.Forms page being rendered, and the type name of the custom renderer. The assembly prefix to the attribute specifies that the attribute applies to the entire assembly.

The following sections discuss the implementation of the CameraPageRenderer custom renderer for each platform.

Creating the Page Renderer on iOS

The following code example shows the page renderer for the iOS platform:

[assembly:ExportRenderer (typeof(CameraPage), typeof(CameraPageRenderer))]
namespace CustomRenderer.iOS
{
    public class CameraPageRenderer : PageRenderer
    {
        ...

        protected override void OnElementChanged (VisualElementChangedEventArgs e)
        {
            base.OnElementChanged (e);

            if (e.OldElement != null || Element == null) {
                return;
            }

            try {
                SetupUserInterface ();
                SetupEventHandlers ();
                SetupLiveCameraStream ();
                AuthorizeCameraUse ();
            } catch (Exception ex) {
                System.Diagnostics.Debug.WriteLine (@"            ERROR: ", ex.Message);
            }
        }
        ...
    }
}

The call to the base class's OnElementChanged method instantiates an iOS UIViewController control. The live camera stream is only rendered provided that the renderer isn't already attached to an existing Xamarin.Forms element, and provided that a page instance exists that is being rendered by the custom renderer.

The page is then customized by a series of methods that use the AVCapture APIs to provide the live stream from the camera and the ability to capture a photo.

Creating the Page Renderer on Android

The following code example shows the page renderer for the Android platform:

[assembly: ExportRenderer(typeof(CameraPage), typeof(CameraPageRenderer))]
namespace CustomRenderer.Droid
{
    public class CameraPageRenderer : PageRenderer, TextureView.ISurfaceTextureListener
    {
        ...
        public CameraPageRenderer(Context context) : base(context)
        {
        }

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

            if (e.OldElement != null || Element == null)
            {
                return;
            }

            try
            {
                SetupUserInterface();
                SetupEventHandlers();
                AddView(view);
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(@"            ERROR: ", ex.Message);
            }
        }
        ...
    }
}

The call to the base class's OnElementChanged method instantiates an Android ViewGroup control, which is a group of views. The live camera stream is only rendered provided that the renderer isn't already attached to an existing Xamarin.Forms element, and provided that a page instance exists that is being rendered by the custom renderer.

The page is then customized by invoking a series of methods that use the Camera API to provide the live stream from the camera and the ability to capture a photo, before the AddView method is invoked to add the live camera stream UI to the ViewGroup. Note that on Android it's also necessary to override the OnLayout method to perform measure and layout operations on the view.

Creating the Page Renderer on UWP

The following code example shows the page renderer for UWP:

[assembly: ExportRenderer(typeof(CameraPage), typeof(CameraPageRenderer))]
namespace CustomRenderer.UWP
{
    public class CameraPageRenderer : PageRenderer
    {
        ...
        protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Page> e)
        {
            base.OnElementChanged(e);

            if (e.OldElement != null || Element == null)
            {
                return;
            }

            try
            {
                ...
                SetupUserInterface();
                SetupBasedOnStateAsync();

                this.Children.Add(page);
            }
            ...
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            page.Arrange(new Windows.Foundation.Rect(0, 0, finalSize.Width, finalSize.Height));
            return finalSize;
        }
        ...
    }
}

The call to the base class's OnElementChanged method instantiates a FrameworkElement control, on which the page is rendered. The live camera stream is only rendered provided that the renderer isn't already attached to an existing Xamarin.Forms element, and provided that a page instance exists that is being rendered by the custom renderer. The page is then customized by invoking a series of methods that use the MediaCapture API to provide the live stream from the camera and the ability to capture a photo before the customized page is added to the Children collection for display.

When implementing a custom renderer that derives from PageRenderer on UWP, the ArrangeOverride method should also be implemented to arrange the page controls, because the base renderer doesn't know what to do with them. Otherwise, a blank page results. Therefore, in this example the ArrangeOverride method calls the Arrange method on the Page instance.

Note

It's important to stop and dispose of the objects that provide access to the camera in a UWP application. Failure to do so can interfere with other applications that attempt to access the device's camera. For more information, see Display the camera preview.

Summary

This article has demonstrated how to create a custom renderer for the ContentPage page, enabling developers to override the default native rendering with their own platform-specific customization. A ContentPage is a visual element that displays a single view and occupies most of the screen.