Last active
November 7, 2018 10:08
-
-
Save georgejecook/f42f5ccc307d0aa82a25 to your computer and use it in GitHub Desktop.
ADVANCED XAMARIN FORMS TECHNIQUES FOR FLEXIBLE AND PERFORMANT CROSS PLATFORM APPS - PART 5, PAGE IN PAGE EMBEDDING.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="UTF-8"?> | |
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="TwinEvents.Core.Main.View.MainPage" | |
xmlns:controls="clr-namespace:TwinEvents.Core.Controls;assembly=TwinEvents" xmlns:gestures="clr-namespace:TwinTechs.Gestures;assembly=TwinTechsLib"> | |
<controls:ExtendedAbsoluteLayout x:Name="MainLayout" | |
BackgroundColor="Black"> | |
<controls:ExtendedAbsoluteLayout x:Name="MainContentLayout"> | |
<controls:CustomTabPageHeader x:Name="HeaderView" /> | |
<controls:SwipablePageContainer x:Name="PageViews" | |
VerticalOptions="FillAndExpand" | |
BackgroundColor="Black"> | |
</controls:SwipablePageContainer> | |
</controls:ExtendedAbsoluteLayout> | |
<AbsoluteLayout x:Name="LoginOverlay" | |
IsVisible="false"> | |
<AbsoluteLayout BackgroundColor="Black" | |
AbsoluteLayout.LayoutFlags="PositionProportional" | |
AbsoluteLayout.LayoutBounds="0.5,0.5,250,100"> | |
<Button Text="Login with your google+ account" | |
x:Name="LoginButton" | |
Clicked="OnClickedLogin" | |
AbsoluteLayout.LayoutFlags="PositionProportional" | |
AbsoluteLayout.LayoutBounds="0.5,0.5,250,100" | |
BackgroundColor="Black" | |
TextColor="White" /> | |
<Label Text="Uploading" | |
IsVisible="false" | |
x:Name="OverlayLabel" | |
TextColor="White" | |
XAlign="Center" | |
AbsoluteLayout.LayoutFlags="XProportional" | |
AbsoluteLayout.LayoutBounds="0.5,20,250,40" | |
BackgroundColor="Transparent" /> | |
<ActivityIndicator IsRunning="false" | |
IsVisible="false" | |
x:Name="OverlaySpinner" | |
Color="White" | |
AbsoluteLayout.LayoutFlags="XProportional" | |
AbsoluteLayout.LayoutBounds="0.5,40,250,40" /> | |
</AbsoluteLayout> | |
</AbsoluteLayout> | |
</controls:ExtendedAbsoluteLayout> | |
</ContentPage> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using Xamarin.Forms; | |
using TwinEvents.Core.Events.View; | |
using TwinEvents.Core.App.View; | |
using TwinTechs.Controls; | |
using TwinEvents.Core.Info.View; | |
using TwinEvents.Core.Media.View; | |
using TwinEvents.Core.Sessions.View; | |
using TwinTechs.Gestures; | |
using TwinEvents.Core.App.Logging; | |
using XLabs.Ioc; | |
using System.Windows.Input; | |
using TwinEvents.Core.Controls; | |
using System.Threading.Tasks; | |
using TwinEvents.Core.App; | |
namespace TwinEvents.Core.Main.View | |
{ | |
public partial class MainPage : ContentPage, INavigationService | |
{ | |
bool _isChildrenCreated; | |
ILog _log; | |
public MainPage () | |
{ | |
InitializeComponent (); | |
} | |
protected override void OnBindingContextChanged () | |
{ | |
if (ViewModel != null) { | |
//Main page is an exception becaut it IS the navigation service, so it was created before we set it | |
ViewModel.NavigationService = this; | |
} | |
base.OnBindingContextChanged (); | |
if (ViewModel != null && !_isChildrenCreated) { | |
AppHelper.NavigationService = this; | |
ViewFactory.NavigationService = this; | |
LoginOverlay.BackgroundColor = Color.FromRgba (0, 0, 0, 0.5); | |
var eventPage = (EventPage)ViewFactory.CreatePageWithExistingViewModel (ViewModel.EventPageVM); | |
var infoPage = (InfoPage)ViewFactory.CreatePageWithExistingViewModel (ViewModel.InfoPageVM); | |
var sessionsPage = (SessionsPage)ViewFactory.CreatePageWithExistingViewModel (ViewModel.SessionPageVM); | |
var mediaPage = (MediaPage)ViewFactory.CreatePageWithExistingViewModel (ViewModel.MediaPageVM); | |
_isChildrenCreated = true; | |
MainLayout.IsExclusivelyHandlingLayout = true; | |
MainLayout.OnLayoutChildren += MainLayout_OnLayoutChildren; | |
MainContentLayout.IsExclusivelyHandlingLayout = true; | |
MainContentLayout.OnLayoutChildren += MainContentLayout_OnLayoutChildren; | |
var pages = new Page[]{ eventPage, infoPage, sessionsPage, mediaPage }; | |
PageViews.ButtonsView = HeaderView; | |
PageViews.ChildPages = new List<Page> (pages); | |
} | |
} | |
void MainLayout_OnLayoutChildren (bool isChanged, double x, double y, double width, double height) | |
{ | |
if (isChanged) { | |
MainContentLayout.Layout (new Rectangle (0, 0, width, height)); | |
if (_presentedContainer != null) { | |
_presentedContainer.Layout (new Rectangle (0, 0, width, height)); | |
} | |
if (LoginOverlay.IsVisible) { | |
LoginOverlay.Layout (new Rectangle (0, 0, width, height)); | |
} | |
} | |
} | |
void MainContentLayout_OnLayoutChildren (bool isChanged, double x, double y, double width, double height) | |
{ | |
if (isChanged) { | |
double headerHeight = width > height ? 50 : 80; | |
HeaderView.Layout (new Rectangle (0, 0, width, headerHeight)); | |
PageViews.Layout (new Rectangle (0, headerHeight, width, height - headerHeight)); | |
} | |
} | |
protected override void OnAppearing () | |
{ | |
base.OnAppearing (); | |
ViewModel.OnViewAppearing (); | |
NavigationPage.SetHasNavigationBar (this, true); | |
} | |
MainPageVM ViewModel { | |
get { | |
return BindingContext as MainPageVM; | |
} | |
} | |
#region INavigationService implementation | |
Page _presentedPage; | |
PageViewContainer _presentedContainer; | |
public async Task PresentPage (Page page, bool animated = true, bool isOverlayAnimation = false) | |
{ | |
if (_presentedPage != null) { | |
_log.Warn ("trying to present page when one is already presented - call dismiss first! Ignoring"); | |
return; | |
} | |
if (page != null) { | |
_presentedPage = page; | |
if (page.BindingContext is BaseViewModel) { | |
((BaseViewModel)page.BindingContext).OnViewAppearing (); | |
} | |
var presentedContainer = new PageViewContainer (); | |
presentedContainer.Layout (new Rectangle (isOverlayAnimation ? 0 : Width, 0, Width, Height)); | |
presentedContainer.Content = _presentedPage; | |
MainLayout.Children.Add (presentedContainer); | |
if (isOverlayAnimation) { | |
presentedContainer.Opacity = 0; | |
presentedContainer.FadeTo (1, 100); | |
} else { | |
var scale = MainContentLayout.ScaleTo (0.95, 400); | |
var fade = MainContentLayout.FadeTo (0.8, 400); | |
var move = presentedContainer.LayoutTo (new Rectangle (0, 0, Width, Height), 500, Easing.SpringIn); | |
await Task.WhenAll (scale, fade, move); | |
if (Bounds != presentedContainer.Bounds) { | |
presentedContainer.Layout (new Rectangle (0, 0, Width, Height)); | |
} | |
} | |
_presentedContainer = presentedContainer; | |
} | |
} | |
bool _isDismissingPresentedPage; | |
public async Task DismissPresentedPage (bool animated = true, bool isOverlayAnimation = false) | |
{ | |
if (_presentedPage != null && !_isDismissingPresentedPage) { | |
_isDismissingPresentedPage = true; | |
if (_presentedPage.BindingContext is BaseViewModel) { | |
((BaseViewModel)_presentedPage.BindingContext).OnViewDisappearing (); | |
} | |
if (isOverlayAnimation) { | |
await _presentedContainer.FadeTo (0, 100); | |
} else { | |
MainContentLayout.ScaleTo (1, 300); | |
MainContentLayout.FadeTo (1, 300); | |
await _presentedContainer.LayoutTo (new Rectangle (Width, 0, Width, Height), 400, Easing.SpringOut); | |
} | |
_presentedPage.BindingContext = null; | |
_presentedPage = null; | |
MainLayout.Children.Remove (_presentedContainer); | |
_presentedContainer.Content = null; | |
_isDismissingPresentedPage = false; | |
} | |
} | |
public async Task PresentPage<TViewModel> (bool animated = true) where TViewModel : class, IViewModel | |
{ | |
var page = (Page)ViewFactory.CreatePage<TViewModel> (); | |
if (page != null) { | |
await PresentPage (page, animated); | |
} else { | |
_log.Error ("tried to show modal for non-registered page type"); | |
} | |
} | |
public Page PresentedPage () | |
{ | |
return _presentedPage; | |
} | |
public void ToggleLoginOverlay (bool _isShowing) | |
{ | |
LoginButton.IsVisible = _isShowing; | |
OverlaySpinner.IsVisible = false; | |
OverlaySpinner.IsRunning = false; | |
OverlayLabel.IsVisible = false; | |
LoginOverlay.IsVisible = _isShowing; | |
LoginOverlay.Layout (new Rectangle (0, 0, Width, Height)); | |
MainLayout.RaiseChild (LoginOverlay); | |
} | |
public void ToggleBusyOverlay (bool _isShowing, string title = "") | |
{ | |
OverlaySpinner.IsVisible = _isShowing; | |
OverlaySpinner.IsRunning = _isShowing; | |
OverlayLabel.IsVisible = _isShowing; | |
OverlayLabel.Text = title; | |
LoginButton.IsVisible = false; | |
LoginOverlay.IsVisible = _isShowing; | |
LoginOverlay.Layout (new Rectangle (0, 0, Width, Height)); | |
MainLayout.RaiseChild (LoginOverlay); | |
} | |
#endregion | |
void OnClickedLogin (object sender, EventArgs ev) | |
{ | |
ViewModel.DoLogin (); | |
} | |
} | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//specialized class for showing a page within a page | |
public class PageViewContainer : View | |
{ | |
public PageViewContainer () | |
{ | |
} | |
public static readonly BindableProperty ContentProperty = BindableProperty.Create<PageViewContainer,Page> (s => s.Content, null); | |
public Page Content { | |
get{ return (Page)GetValue (ContentProperty); } | |
set{ SetValue (ContentProperty, value); } | |
} | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class PageViewContainerRenderer : ViewRenderer<PageViewContainer,UIView> | |
{ | |
public PageViewContainerRenderer () | |
{ | |
} | |
ViewControllerContainer _viewControllerContainer; | |
protected override void OnElementChanged (ElementChangedEventArgs<PageViewContainer> e) | |
{ | |
base.OnElementChanged (e); | |
var pageViewContainer = e.NewElement as PageViewContainer; | |
if (_viewControllerContainer != null) { | |
_viewControllerContainer.ViewController = null; | |
_viewControllerContainer = null; | |
} | |
if (e.NewElement != null) { | |
_viewControllerContainer = new ViewControllerContainer (Bounds); | |
SetNativeControl (_viewControllerContainer); | |
} | |
} | |
Page _currentPage; | |
void ChangePage (Page page) | |
{ | |
if (_currentPage == page) { | |
return; | |
} | |
//TODO call page dissapaering/appearing methods | |
if (page != null) { | |
var pageRenderer = page.GetRenderer (); | |
UIViewController viewController = null; | |
if (pageRenderer?.ViewController != null) { | |
viewController = pageRenderer.ViewController; | |
} else { | |
viewController = page.CreateViewController (); | |
} | |
var parentPage = Element.GetParentPage (); | |
var renderer = parentPage.GetRenderer (); | |
if (_viewControllerContainer == null) { | |
_viewControllerContainer = new ViewControllerContainer (Bounds); | |
SetNativeControl (_viewControllerContainer); | |
} | |
_viewControllerContainer.ParentViewController = renderer.ViewController; | |
_viewControllerContainer.ViewController = viewController; | |
_currentPage = page; | |
FixPageLayouts (); | |
SetNeedsLayout (); | |
} else { | |
_viewControllerContainer = null; | |
} | |
} | |
public override void LayoutSubviews () | |
{ | |
base.LayoutSubviews (); | |
var page = Element?.Content; | |
if (page != null) { | |
page.Layout (new Rectangle (0, 0, Bounds.Width, Bounds.Height)); | |
} | |
_viewControllerContainer.Frame = Bounds; | |
} | |
protected override void OnElementPropertyChanged (object sender, System.ComponentModel.PropertyChangedEventArgs e) | |
{ | |
base.OnElementPropertyChanged (sender, e); | |
if (e.PropertyName == "Content" || e.PropertyName == "Renderer") { | |
Device.BeginInvokeOnMainThread (() => ChangePage (Element?.Content)); | |
} | |
} | |
void FixPageLayouts () | |
{ | |
var contentPage = _currentPage as ContentPage; | |
if (contentPage != null) { | |
contentPage.Layout (new Rectangle (0, 0, Bounds.Width, Bounds.Height)); | |
contentPage.ForceLayout (); | |
var layout = contentPage.Content as Layout<View>; | |
if (layout != null) { | |
layout.Layout (new Rectangle (0, 0, Bounds.Width, Bounds.Height)); | |
layout.ForceLayout (); | |
} | |
} | |
} | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="UTF-8"?> | |
<ContentPage | |
xmlns="http://xamarin.com/schemas/2014/forms" | |
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" | |
x:Class="TwinTechs.Example.Gestures.GestureYoutubeLikeExample" | |
xmlns:gestures="clr-namespace:TwinTechs.Gestures;assembly=TwinTechsForms" | |
xmlns:cells="clr-namespace:TwinTechs.Example.Gestures.Cells;assembly=TwinTechsFormsExample" | |
xmlns:local="clr-namespace:TwinTechs.Example.Gestures;assembly=TwinTechsFormsExample" | |
xmlns:controls="clr-namespace:TwinTechs.Controls;assembly=TwinTechsForms" | |
BackgroundColor="Silver" | |
Title="SwipeExample"> | |
<local:SimpleLayout | |
BackgroundColor="Silver" | |
x:Name="MainLayout" | |
IsHandlingLayoutManually="true"> | |
<ListView | |
BackgroundColor="Silver" | |
x:Name="MediaItemsListView" | |
SeparatorVisibility="None" | |
ItemSelected="OnItemSelected" | |
RowHeight="100"> | |
<ListView.ItemTemplate> | |
<DataTemplate> | |
<cells:VideoCell /> | |
</DataTemplate> | |
</ListView.ItemTemplate> | |
</ListView> | |
<controls:PageViewContainer | |
BackgroundColor="Transparent" | |
x:Name="PageContainer" /> | |
</local:SimpleLayout> | |
</ContentPage> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using Xamarin.Forms; | |
using TwinTechs.Gestures; | |
using System.Diagnostics; | |
namespace TwinTechs.Example.Gestures | |
{ | |
public partial class GestureYoutubeLikeExample : ContentPage | |
{ | |
Rectangle _contentBounds = new Rectangle (100, 200, 150, 150); | |
YoutubeStyleContentPage _contentPage; | |
PanGestureRecognizer _panGesture; | |
bool _didLayoutContainer; | |
public GestureYoutubeLikeExample () | |
{ | |
InitializeComponent (); | |
MainLayout.OnLayoutChildren += MainLayout_OnLayoutChildren; | |
MediaItemsListView.ItemsSource = DataProvider.GetMediaItems (); | |
} | |
protected override void LayoutChildren (double x, double y, double width, double height) | |
{ | |
base.LayoutChildren (x, y, width, height); | |
if (_contentPage != null) { | |
_contentPage.ParentHeight = height; | |
} | |
} | |
void MainLayout_OnLayoutChildren (double x, double y, double width, double height) | |
{ | |
MediaItemsListView.Layout (new Rectangle (0, 0, Width, Height)); | |
if (!_didLayoutContainer) { | |
_contentBounds.Y = height - 100; | |
_contentBounds.X = width - 160; | |
_contentBounds.Width = 160; | |
_contentBounds.Height = 100; | |
_didLayoutContainer = true; | |
} | |
PageContainer.Layout (_contentBounds); | |
} | |
Rectangle _startBounds; | |
void Gesture_OnAction (BaseGestureRecognizer recgonizer, GestureRecognizerState state) | |
{ | |
if (recgonizer.View != _contentPage.VideoPlayerView) { | |
return; | |
} | |
var panGesture = recgonizer as PanGestureRecognizer; | |
Point translation = panGesture.GetTranslationInView (MainLayout); | |
Point velocity = panGesture.GetVelocityInView (MainLayout); | |
panGesture.SetTranslationInView (new Point (0, 0), MainLayout); | |
switch (panGesture.State) { | |
case GestureRecognizerState.Began: | |
break; | |
case GestureRecognizerState.Changed: | |
var newY = _contentBounds.Y + translation.Y; | |
if (newY > 0 && newY < Height - _contentPage.MinimumHeightRequest) { | |
var minHeight = _contentPage.MinimumHeightRequest; | |
var minWidth = _contentPage.MinimumWidthRequest; | |
_contentBounds.Y = newY; | |
var complete = Math.Min (1, (Height - (_contentBounds.Y + minHeight)) / Height); | |
// Debug.WriteLine ("complete {0} newY {1} h{2}", complete, newY, Height); | |
var inverseCompletion = 1 - complete; | |
_contentBounds.X = (Width - minWidth) * inverseCompletion; | |
_contentBounds.Width = (minWidth) + ((Width - minWidth) * complete); | |
_contentBounds.Height = Math.Max (minHeight, (Height + minHeight) * complete); | |
PageContainer.Layout (_contentBounds); | |
} | |
break; | |
case GestureRecognizerState.Cancelled: | |
case GestureRecognizerState.Ended: | |
case GestureRecognizerState.Failed: | |
var isShowing = _contentBounds.Y < 200; | |
ToggleShowing (isShowing, true); | |
break; | |
default: | |
break; | |
} | |
} | |
protected override void OnDisappearing () | |
{ | |
base.OnDisappearing (); | |
if (_contentPage != null) { | |
_contentPage.VideoPlayerView.RemoveAllGestureRecognizers (); | |
} | |
} | |
void OnItemSelected (object sender, SelectedItemChangedEventArgs e) | |
{ | |
ToggleShowing (true, true); | |
} | |
void ToggleShowing (bool isShowing, bool animated) | |
{ | |
if (_contentPage == null) { | |
_contentPage = new YoutubeStyleContentPage (); | |
_contentPage.ParentHeight = Height; | |
PageContainer.Content = _contentPage; | |
_panGesture = new PanGestureRecognizer (); | |
_panGesture.OnAction += Gesture_OnAction; | |
_panGesture.IsConsumingTouchesInParallel = true; | |
_contentPage.VideoPlayerView.AddGestureRecognizer (_panGesture); | |
} | |
var minHeight = _contentPage.MinimumHeightRequest; | |
var minWidth = _contentPage.MinimumWidthRequest; | |
_contentBounds.Y = isShowing ? 0 : Height - minHeight; | |
_contentBounds.X = isShowing ? 0 : Width - minWidth; | |
_contentBounds.Width = isShowing ? Width : minWidth; | |
_contentBounds.Height = isShowing ? Height : minHeight; | |
if (MediaItemsListView.SelectedItem != null) { | |
_contentPage.Item = MediaItemsListView.SelectedItem as MediaItem; | |
} | |
if (animated) { | |
PageContainer.LayoutTo (_contentBounds); | |
} else { | |
PageContainer.Layout (_contentBounds); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment