Adding native orientation events

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

Tip

Unfortunately for Android, we can only gather whether the orientation is in landscape or portrait. IOS has the ability to determine whether we are in landscape left or landscape right.

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.

Tip

When the camera is rendered for iOS, we have to reduce the render bound width by a certain amount to make sure that the camera bounds don't render behind the button container. Every time the orientation changes, we will use this value to reduce the width of the render bounds.

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.

Tip

We must set up the IsVisible bindings after the camera has become available because a layout pass is not called on items that are invisible when the page is loaded. We need the height and width to be set on these items even if the items are invisible.

        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.

Note

Remember, we must set the IsVisible binding after the page has done the layout so that the LoadingView bounds are set correctly according to the Grid.

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.14.142.115