Our next step is to add the notifications that are going to fire every time an orientation occurs. These events will come from the native side, so in order to have these pass down to our Xamarin.Forms
project, we are going to use the static events on the OrientationPage
.
Let's start with the iOS project. Open the AppDelegate.cs
file and add the following function:
public override void DidChangeStatusBarOrientation(UIApplication application, UIInterfaceOrientation oldStatusBarOrientation) { // change listview opacity based upon orientation switch (UIApplication.SharedApplication.StatusBarOrientation) { case UIInterfaceOrientation.Portrait: case UIInterfaceOrientation.PortraitUpsideDown: OrientationPage.NotifyOrientationChange(Orientation.Portrait); break; case UIInterfaceOrientation.LandscapeLeft: OrientationPage.NotifyOrientationChange(Orientation.LandscapeLeft); break; case UIInterfaceOrientation.LandscapeRight: OrientationPage.NotifyOrientationChange(Orientation.LandscapeRight); break; } }
The DidChangeStatusBarOrientation
function is contained in all AppDelegate
objects. When we override this, we reference the exact orientation in the UIApplication.SharedApplication.StatusBarOrientation
property of the AppDelegate
. Every time an orientation occurs, this method will be called and we will then call the static method NotifyOrientationChange
on the OrientationPage
to fire the event back to the Xamarin.Forms
page.
Let's do the same for Android. Open the MainActivity.cs
file and add the following:
public override void OnConfigurationChanged(Android.Content.Res.Configuration newConfig) { base.OnConfigurationChanged(newConfig); switch (newConfig.Orientation) { case Android.Content.Res.Orientation.Portrait: OrientationPage.NotifyOrientationChange(Portable.Enums.Orientation.Portrait); break; case Android.Content.Res.Orientation.Landscape: OrientationPage.NotifyOrientationChange(Portable.Enums.Orientation.LandscapeLeft); break; } }
Like the DidChangeStatusBarOrientation
function, the Android OnConfigurationChanged
method will be called whenever the orientation changes. We use a switch statement again to call the static method on the OrientationPage
.
Let's now add the logic behind the CameraPage
. Here we will be responding to the native orientation events when they occur.
Open the CameraPage.xaml.cs
file and implement the private properties:
public partial class CameraPage : ExtendedContentPage, INavigableXamarinFormsPage { #region Private Properties private float CAMERA_BUTTON_CONTAINER_WIDTH = 70f; private CameraPageViewModel _model; #endregion }
We use the CAMERA_BUTTON_CONTAINER_WIDTH
property when we render the camera stream on iOS, to make sure the stream bounds do not render behind the button container.
We also have the CameraPageViewModel
to keep locally when we retrieve it from the IoC container. Let's add the constructor as follows:
#region Constructors public CameraPage(CameraPageViewModel model) : base(model) { BindingContext = model; _model = model; InitializeComponent(); Appearing += HandleAppearing; Disappearing += HandleDisappearing; CameraView.Photo += HandlePictureTaken; CameraView.AvailabilityChange += HandleCameraAvailability; CameraView.Loading += HandleLoading; CameraView.Busy += HandleBusy; FocusView.TouchFocus += HandleFocusChange; } #endregion
Here we retrieve the CameraPageViewModel
from the IoC container. We also register event functions when the page appears and disappears. We also register event functions on the CameraView
when we take a photo, when the camera initialization occurs, when the camera is loading, and when the camera is busy. Then, we register one event for the TouchFocus
event on the FocusView
. Every time a new point (x, y) is received, we pass this to the CameraView
to perform a focus.
Let's add the EventHandler
functions for page appearing and disappearing. Here we will register and deregister to the orientation OrientationHandler
event:
private void HandleDisappearing(object sender, EventArgs e) { OrientationHandler -= HandleOrientationChange; _model.OnDisappear(); } private void HandleAppearing(object sender, EventArgs e) { OrientationHandler += HandleOrientationChange; _model.OnAppear(); }
Let's add the HandleOrientationChange
method for updating the button container width by resizing the width of the ColumnDefinition
. After we resize the ColumnDefinition
, we then call Reset
on the FocusView
object; if the camera is ready to take a photo, we center the focus point of the camera to the middle of the screen. Then call the NotifyOrientationChange
on the CameraView
to update the renderer camera stream bounds and rotation:
public void HandleOrientationChange(object sender, Orientation arg) { FocusView.Orientation = CameraView.Orientation = OrientationPage.PageOrientation = _model.PageOrientation = arg; switch (PageOrientation) { case Orientation.LandscapeLeft: case Orientation.LandscapeRight: MainLayout.ColumnDefinitions[5].Width = new GridLength(CAMERA_BUTTON_CONTAINER_WIDTH, GridUnitType.Absolute); break; case Orientation.Portrait: MainLayout.ColumnDefinitions[4].Width = new GridLength(CAMERA_BUTTON_CONTAINER_WIDTH, GridUnitType.Absolute); break; } if (_model.CanCapture) { FocusView.Reset(); } CameraView.NotifyOrientationChange(arg); }
Next, we have the HandleBusy
and HandleLoading
functions, which simply set the CameraLoading
property on the view-model:
private void HandleBusy(object sender, bool e) { _model.CameraLoading = e; } private void HandleLoading(object sender, bool e) { _model.CameraLoading = e; }
Then add the HandleShutter
, which will call the NotifyShutter
method on the CameraView
:
public void HandleShutter(object sender, EventArgs args) { CameraView.NotifyShutter(); }
Then we have the HandleFlash
function responsible for updating the FlashOn
property of the view-model and calling the NotifyFlash
method on the CameraView
:
public void HandleFlash(object sender, EventArgs args) { _model.IsFlashOn = !_model.IsFlashOn; CameraView.NotifyFlash(_model.IsFlashOn); }
The HandlePictureTaken
function is called every time the camera button is touched and data is received from the native camera. We then pass the byte array back to the view-model using the AddPhoto
method:
public void HandlePictureTaken(object sender, byte[] data) { if (_model.CanCapture) { _model.AddPhoto(data); } }
Next we have the HandleCameraAvailability
method, which is called when the native camera availability status changes. If the camera is available, we the set the view-model properties, assign the starting orientation, and set up IsVisible
bindings on the camera button containers to the PageOrientation
property of the view-model.
public void HandleCameraAvailability(object sender, bool available) { _model.CanCapture = available; if (available) { _model.CameraLoading = false; // wait until camera is available before animating focus target, we have to invoke on UI thread as this is run asynchronously Device.BeginInvokeOnMainThread(() => { // set starting list opacity based on orientation var orientation = (Height > Width) ? Orientation.Portrait : Orientation.LandscapeLeft; // set starting orientation HandleOrientationChange(null, orientation); // these bindings are created after page intitalizes PhotoEditLayout.SetBinding(VisualElement.IsVisibleProperty, new Binding("PhotoEditOn")); // camera button layouts CameraButtonContainerLandscape.SetBinding(VisualElement.OpacityProperty, new Binding("PageOrientation", converter: new OrientationToDoubleConverter(), converterParameter: "1, 1")); CameraButtonContainerLandscape.SetBinding(VisualElement.IsVisibleProperty, new Binding("PageOrientation", converter: new OrientationToBoolConverter(), converterParameter: "true, false")); CameraButtonContainerPortrait.SetBinding(VisualElement.OpacityProperty, new Binding("PageOrientation", converter: new OrientationToDoubleConverter(), converterParameter: "0, 1")); CameraButtonContainerPortrait.SetBinding(VisualElement.IsVisibleProperty, new Binding("PageOrientation", converter: new OrientationToBoolConverter(), converterParameter: "false, true")); FocusView.Reset(); }); } }
Now we must add the OnNavigatedTo
method. Here we will set a new binding on the IsVisible
property of the LoadingView
.
We must also call the SetFocusPoints
on the FocusView
to set the starting focus points in both landscape and portrait. These starting points will be calculated from the height and width properties to get the center of the screen. Then we call NotifyOpenCamera
to begin the process on the native camera to initialize it and open the camera. On only for iOS do we call the NotifyWidths
method, so the widths of the button container are passed to the iOS native camera class:
public void OnNavigatedTo(IDictionary<string, object> navigationParameters) { _model.CameraLoading = false; LoadingView.SetBinding(VisualElement.IsVisibleProperty, new Binding("CameraLoading")); _model.CanCapture = CameraView.CameraAvailable; switch (PageOrientation) { case Orientation.Portrait: FocusView.SetFocusPoints(new Point(Width / 2, Height / 2), new Point(Height / 2, Width / 2)); break; case Orientation.LandscapeLeft: case Orientation.LandscapeRight: FocusView.SetFocusPoints(new Point(Height / 2, Width / 2), new Point(Width / 2, Height / 2)); break; } CameraView.NotifyOpenCamera(true); #if __IOS__ CameraView.NotifyWidths (CAMERA_BUTTON_CONTAINER_WIDTH); #endif this.Show(navigationParameters); }
Lastly, we have the HandleDelete
method for removing the photo edit view and clearing the image bytes to free memory:
public void HandleDelete(object sender, EventArgs args) { _model.ResetEditPhoto(); } #endregion }
Excellent! We now have implemented our entire CameraPage
and native camera implementation for iOS and Android.
3.14.142.115