TPV Additional Samples and event notification changes (#9487)

* TPV fixes and added CG demos

* - simplify two page sample

* - fixes

* - uwp screen size fixes

* - performance updates

* - android alive check

* - code cleanup and more samples

* - additional gallery updates

* - add one shot hinge angle to android head

* - add text

* - dispose of hinge changes

* - unit test compile fix

* - split hinge angle into separate file
This commit is contained in:
Shane Neuville 2020-02-09 14:14:33 -07:00 коммит произвёл GitHub
Родитель dd3dba3557
Коммит fda127d525
43 изменённых файлов: 1579 добавлений и 293 удалений

Просмотреть файл

@ -56,6 +56,7 @@ using Android.Support.V4.Content;
[assembly: ExportRenderer(typeof(ShellGestures.TouchTestView), typeof(ShellGesturesTouchTestViewRenderer))]
[assembly: ExportRenderer(typeof(Issue7249Switch), typeof(Issue7249SwitchRenderer))]
[assembly: ExportRenderer(typeof(Issue9360.Issue9360NavigationPage), typeof(Issue9360NavigationPageRenderer))]
[assembly: ExportRenderer(typeof(Xamarin.Forms.Controls.GalleryPages.TwoPaneViewGalleries.HingeAngleLabel), typeof(HingeAngleLabelRenderer))]
#if PRE_APPLICATION_CLASS
#elif FORMS_APPLICATION_ACTIVITY
@ -64,6 +65,56 @@ using Android.Support.V4.Content;
#endif
namespace Xamarin.Forms.ControlGallery.Android
{
public class HingeAngleLabelRenderer : Xamarin.Forms.Platform.Android.FastRenderers.LabelRenderer
{
System.Timers.Timer _hingeTimer;
public HingeAngleLabelRenderer(Context context) : base(context)
{
}
async void OnTimerElapsed(object sender, System.Timers.ElapsedEventArgs e)
{
if (_hingeTimer == null)
return;
_hingeTimer.Stop();
var hingeAngle = await DualScreen.DualScreenInfo.Current.GetHingeAngleAsync();
Device.BeginInvokeOnMainThread(() =>
{
if (_hingeTimer != null)
Element.Text = hingeAngle.ToString();
});
if(_hingeTimer != null)
_hingeTimer.Start();
}
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
if(_hingeTimer == null)
{
_hingeTimer = new System.Timers.Timer(100);
_hingeTimer.Elapsed += OnTimerElapsed;
_hingeTimer.Start();
}
}
protected override void Dispose(bool disposing)
{
if (_hingeTimer != null)
{
_hingeTimer.Elapsed -= OnTimerElapsed;
_hingeTimer.Stop();
_hingeTimer = null;
}
base.Dispose(disposing);
}
}
public class Issue9360NavigationPageRenderer : Xamarin.Forms.Platform.Android.AppCompat.NavigationPageRenderer
{
public Issue9360NavigationPageRenderer(Context context) : base(context)

Просмотреть файл

@ -276,4 +276,4 @@
<Target Name="AfterBuild">
</Target>
-->
</Project>
</Project>

Просмотреть файл

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
xmlns:local="clr-namespace:Xamarin.Forms.Controls.GalleryPages.TwoPaneViewGalleries" xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:dualscreen="clr-namespace:Xamarin.Forms.DualScreen;assembly=Xamarin.Forms.DualScreen"
mc:Ignorable="d"
x:Name="mainPage"
x:Class="Xamarin.Forms.Controls.GalleryPages.TwoPaneViewGalleries.CompanionPane"
BackgroundColor="LightGray"
Visual="Material">
<dualscreen:TwoPaneView x:Name="twoPaneView" MinWideModeWidth="4000" MinTallModeHeight="4000">
<dualscreen:TwoPaneView.Pane1>
<CarouselView x:Name="cv" BackgroundColor="LightGray" IsScrollAnimated="False" >
<CarouselView.ItemTemplate>
<DataTemplate>
<Frame BackgroundColor="LightGray" Padding="0" Margin="0"
WidthRequest="{Binding Source={x:Reference twoPaneView}, Path=Pane1.Width}"
HeightRequest="{Binding Source={x:Reference twoPaneView}, Path=Pane1.Height}">
<Frame Margin="20" BackgroundColor="White">
<Label FontSize="Large" Text="{Binding ., StringFormat='Slide Content {0}'}" VerticalTextAlignment="Center" HorizontalTextAlignment="Center" HorizontalOptions="Center" VerticalOptions="Center"></Label>
</Frame>
</Frame>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
</dualscreen:TwoPaneView.Pane1>
<dualscreen:TwoPaneView.Pane2>
<CollectionView x:Name="indicators"
SelectionMode="Single"
Margin="20, 20, 20, 20"
BackgroundColor="LightGray"
WidthRequest="{Binding Source={x:Reference twoPaneView}, Path=Pane2.Width}"
ItemsSource="{Binding Source={x:Reference cv}, Path=ItemsSource}">
<CollectionView.Resources>
<ResourceDictionary>
<Style TargetType="Frame">
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal">
<VisualState.Setters>
<Setter Property="Padding" Value="0"></Setter>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Selected">
<VisualState.Setters>
<Setter Property="BorderColor" Value="Green" />
<Setter Property="Padding" Value="1"></Setter>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
</ResourceDictionary>
</CollectionView.Resources>
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Vertical" ItemSpacing="10"></LinearItemsLayout>
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<Frame WidthRequest="{Binding Source={x:Reference twoPaneView}, Path=Pane2.Width}" CornerRadius="10" HeightRequest="60" BackgroundColor="White" Margin="0">
<StackLayout HorizontalOptions="Fill" VerticalOptions="Fill" Orientation="Horizontal">
<Label FontSize="Micro" Padding="20,0,20,0" VerticalTextAlignment="Center" WidthRequest="140" Text="{Binding ., StringFormat='Slide Content {0}'}"></Label>
<Label FontSize="Small" Padding="20,0,20,0" VerticalTextAlignment="Center" HorizontalOptions="FillAndExpand" BackgroundColor="DarkGray" Grid.Column="1" Text="{Binding ., StringFormat='Slide {0}'}"></Label>
</StackLayout>
</Frame>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</dualscreen:TwoPaneView.Pane2>
</dualscreen:TwoPaneView>
</ContentPage>

Просмотреть файл

@ -0,0 +1,50 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace Xamarin.Forms.Controls.GalleryPages.TwoPaneViewGalleries
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class CompanionPane : ContentPage
{
List<string> _dataSource;
public CompanionPane()
{
InitializeComponent();
_dataSource =
Enumerable.Range(1, 1000)
.Select(i => $"{i}")
.ToList();
twoPaneView.TallModeConfiguration = Xamarin.Forms.DualScreen.TwoPaneViewTallModeConfiguration.TopBottom;
cv.ItemsSource = _dataSource;
indicators.SelectedItem = _dataSource[0];
cv.PositionChanged += OnCarouselViewPositionChanged;
indicators.SelectionChanged += OnIndicatorsSelectionChanged;
}
void OnIndicatorsSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (indicators.SelectedItem == null)
return;
cv.Position = _dataSource.IndexOf((string)indicators.SelectedItem);
}
void OnCarouselViewPositionChanged(object sender, PositionChangedEventArgs e)
{
indicators.SelectedItem = _dataSource[e.CurrentPosition];
indicators.ScrollTo(e.CurrentPosition);
}
}
}

Просмотреть файл

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
xmlns:local="clr-namespace:Xamarin.Forms.Controls.GalleryPages.TwoPaneViewGalleries" xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:dualScreen="clr-namespace:Xamarin.Forms.DualScreen;assembly=Xamarin.Forms.DualScreen"
mc:Ignorable="d"
x:Class="Xamarin.Forms.Controls.GalleryPages.TwoPaneViewGalleries.DualViewListPage">
<dualScreen:TwoPaneView>
<dualScreen:TwoPaneView.Pane1>
<CollectionView SelectionMode="Single" x:Name="mapList">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="10,5,10,5">
<Frame Visual="Material" BorderColor="LightGray">
<StackLayout Padding="5">
<Label FontSize="Title" Text="{Binding Title}"></Label>
</StackLayout>
</Frame>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</dualScreen:TwoPaneView.Pane1>
<dualScreen:TwoPaneView.Pane2>
<local:DualViewMap x:Name="mapPage"></local:DualViewMap>
</dualScreen:TwoPaneView.Pane2>
</dualScreen:TwoPaneView>
</ContentPage>

Просмотреть файл

@ -0,0 +1,99 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.DualScreen;
using Xamarin.Forms.Xaml;
namespace Xamarin.Forms.Controls.GalleryPages.TwoPaneViewGalleries
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class DualViewListPage
{
DualViewMapPage mapPagePushed;
bool IsSpanned => DualScreenInfo.Current.SpanMode != TwoPaneViewMode.SinglePane;
public DualViewListPage()
{
InitializeComponent();
mapList.SelectionChanged += OnTitleSelected;
mapPagePushed = new DualViewMapPage();
mapList.ItemsSource = new List<MapItem>
{
new MapItem("New York", 40.7128f, -74.0060f),
new MapItem("Seattle", 47.6062f, -122.3425f),
new MapItem("Palo Alto", 37.444184f, -122.161059f),
new MapItem("San Francisco", 37.7542f, -122.4471f)
};
}
async void OnTitleSelected(object sender, SelectionChangedEventArgs e)
{
if (e.CurrentSelection == null || e.CurrentSelection.Count == 0)
return;
UpdateMapItem();
await SetupViews();
}
public MapItem SelectedItem { get; set; }
void UpdateMapItem()
{
var item = mapList.SelectedItem as MapItem ?? (mapList.ItemsSource as IList<MapItem>)[0];
SelectedItem = item;
if (SelectedItem != null)
{
mapPage.UpdateMap(item);
mapPagePushed.UpdateMap(item);
}
}
async Task SetupViews()
{
if (IsSpanned && !DualScreenInfo.Current.IsLandscape)
UpdateMapItem();
if (SelectedItem == null)
return;
if (!IsSpanned || DualScreenInfo.Current.IsLandscape)
{
if (!Navigation.NavigationStack.Contains(mapPagePushed))
{
await Navigation.PushAsync(mapPagePushed);
}
}
}
protected override void OnAppearing()
{
if (!IsSpanned)
mapList.SelectedItem = null;
DualScreenInfo.Current.PropertyChanged += OnFormsWindowPropertyChanged;
}
protected override void OnDisappearing()
{
base.OnDisappearing();
DualScreenInfo.Current.PropertyChanged -= OnFormsWindowPropertyChanged;
}
async void OnFormsWindowPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(DualScreenInfo.Current.SpanMode) || e.PropertyName == nameof(DualScreenInfo.Current.IsLandscape))
{
await SetupViews();
}
}
}
}

Просмотреть файл

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="Xamarin.Forms.Controls.GalleryPages.TwoPaneViewGalleries.DualViewMap">
<ContentView.Content>
<WebView x:Name="webView" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" />
</ContentView.Content>
</ContentView>

Просмотреть файл

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace Xamarin.Forms.Controls.GalleryPages.TwoPaneViewGalleries
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class DualViewMap : ContentView
{
public DualViewMap()
{
InitializeComponent();
webView.Source = $"file:///android_asset/googlemap.html";
}
public void UpdateMap(MapItem item)
=> webView.Source = $"file:///android_asset/googlemap.html?lat={item.Lat}&lng={item.Lng}";
}
}

Просмотреть файл

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
xmlns:local="clr-namespace:Xamarin.Forms.Controls.GalleryPages.TwoPaneViewGalleries" xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="Xamarin.Forms.Controls.GalleryPages.TwoPaneViewGalleries.DualViewMapPage">
<local:DualViewMap x:Name="map"></local:DualViewMap>
</ContentPage>

Просмотреть файл

@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace Xamarin.Forms.Controls.GalleryPages.TwoPaneViewGalleries
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class DualViewMapPage : ContentPage
{
public DualViewMapPage()
{
InitializeComponent();
}
public void UpdateMap(MapItem item)
=> map.UpdateMap(item);
}
}

Просмотреть файл

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Xamarin.Forms.Controls.GalleryPages.TwoPaneViewGalleries
{
public class MapItem
{
public MapItem(string title, double lat, double lng)
{
Title = title;
Lat = lat;
Lng = lng;
}
public string Title { get; set; }
public double Lat { get; set; }
public double Lng { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
xmlns:local="clr-namespace:Xamarin.Forms.Controls.GalleryPages.TwoPaneViewGalleries" xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="Xamarin.Forms.Controls.GalleryPages.TwoPaneViewGalleries.ExtendCanvas">
<ContentPage.Content>
<Grid>
<WebView x:Name="webView" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" />
<SearchBar x:Name="searchBar" Placeholder="Find a place..." BackgroundColor="DarkGray" Opacity="0.8" HorizontalOptions="FillAndExpand" VerticalOptions="Start" />
</Grid>
</ContentPage.Content>
</ContentPage>

Просмотреть файл

@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace Xamarin.Forms.Controls.GalleryPages.TwoPaneViewGalleries
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ExtendCanvas
{
public ExtendCanvas()
{
InitializeComponent();
searchBar.SearchButtonPressed += SearchBar_SearchButtonPressed;
webView.Source = "file:///android_asset/googlemapsearch.html";
StartSearch();
}
private void SearchBar_SearchButtonPressed(object sender, EventArgs e)
{
StartSearch();
}
void StartSearch()
{
var place = searchBar?.Text ?? string.Empty;
webView.Source = "file:///android_asset/googlemapsearch.html?place=" + System.Web.HttpUtility.UrlEncode(place);
}
}
}

Просмотреть файл

@ -23,16 +23,17 @@ namespace Xamarin.Forms.Controls.GalleryPages.TwoPaneViewGalleries
protected override void OnAppearing()
{
base.OnAppearing();
DualScreenInfo.PropertyChanged += DualScreenInfo_PropertyChanged;
DualScreenInfo.PropertyChanged += OnInfoPropertyChanged;
UpdateColumns();
}
protected override void OnDisappearing()
{
base.OnDisappearing();
DualScreenInfo.PropertyChanged -= DualScreenInfo_PropertyChanged;
DualScreenInfo.PropertyChanged -= OnInfoPropertyChanged;
}
void DualScreenInfo_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
void UpdateColumns()
{
if (DualScreenInfo.SpanningBounds.Length > 0)
{
@ -50,6 +51,12 @@ namespace Xamarin.Forms.Controls.GalleryPages.TwoPaneViewGalleries
OnPropertyChanged(nameof(Column1Width));
OnPropertyChanged(nameof(Column2Width));
OnPropertyChanged(nameof(Column3Width));
}
void OnInfoPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
UpdateColumns();
}
public double Column1Width { get; set; }

Просмотреть файл

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<Grid xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="Xamarin.Forms.Controls.GalleryPages.TwoPaneViewGalleries.Details">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Image Margin="16, 16, 0, 0" WidthRequest="100" HeightRequest="100" Source="ic_movie_black_24dp"></Image>
<Grid Grid.Column="1" VerticalOptions="Center">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label FontSize="Title" FontAttributes="Bold" Text="Title:"></Label>
<Label FontSize="Title" Grid.Row="1" FontAttributes="Bold" Text="Details:"></Label>
<Label Grid.Column="1" Text="{Binding Title}" VerticalTextAlignment="Center"></Label>
<Label Grid.Column="1" Grid.Row="1" Text="{Binding Details}" VerticalTextAlignment="Center"></Label>
</Grid>
</Grid>

Просмотреть файл

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace Xamarin.Forms.Controls.GalleryPages.TwoPaneViewGalleries
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class Details : Grid
{
public Details()
{
InitializeComponent();
}
}
}

Просмотреть файл

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Xamarin.Forms.Controls.GalleryPages.TwoPaneViewGalleries"
mc:Ignorable="d"
x:Class="Xamarin.Forms.Controls.GalleryPages.TwoPaneViewGalleries.DetailsPage"
Title="Master Details">
<local:Details></local:Details>
</ContentPage>

Просмотреть файл

@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.DualScreen;
using Xamarin.Forms.Xaml;
namespace Xamarin.Forms.Controls.GalleryPages.TwoPaneViewGalleries
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class DetailsPage
{
bool IsSpanned => DualScreenInfo.Current.SpanMode != TwoPaneViewMode.SinglePane;
public DetailsPage()
{
InitializeComponent();
}
protected override void OnAppearing()
{
base.OnAppearing();
DualScreenInfo.Current.PropertyChanged += OnFormsWindowPropertyChanged;
}
protected override void OnDisappearing()
{
base.OnDisappearing();
DualScreenInfo.Current.PropertyChanged -= OnFormsWindowPropertyChanged;
}
async void OnFormsWindowPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if(e.PropertyName == nameof(DualScreenInfo.Current.SpanMode) || e.PropertyName == nameof(DualScreenInfo.Current.IsLandscape))
{
if (IsSpanned)
await Navigation.PopAsync();
}
}
}
}

Просмотреть файл

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8" ?>
<CollectionView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="Xamarin.Forms.Controls.GalleryPages.TwoPaneViewGalleries.Master"
SelectionMode="Single">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="10,5,10,5">
<Frame Visual="Material" BorderColor="LightGray">
<StackLayout Padding="5">
<Label FontSize="Title" Text="{Binding Title}"></Label>
</StackLayout>
</Frame>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>

Просмотреть файл

@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace Xamarin.Forms.Controls.GalleryPages.TwoPaneViewGalleries
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class Master : CollectionView
{
public Master()
{
InitializeComponent();
ItemsSource = Enumerable.Range(1, 100)
.Select(x => new MasterDetailsItem(x))
.ToList();
}
}
}

Просмотреть файл

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:dualScreen="clr-namespace:Xamarin.Forms.DualScreen;assembly=Xamarin.Forms.DualScreen"
xmlns:local="clr-namespace:Xamarin.Forms.Controls.GalleryPages.TwoPaneViewGalleries"
mc:Ignorable="d"
x:Class="Xamarin.Forms.Controls.GalleryPages.TwoPaneViewGalleries.MasterDetail">
<dualScreen:TwoPaneView MinWideModeWidth="4000" MinTallModeHeight="4000">
<dualScreen:TwoPaneView.Pane1>
<local:Master x:Name="masterPage"></local:Master>
</dualScreen:TwoPaneView.Pane1>
<dualScreen:TwoPaneView.Pane2>
<local:Details x:Name="detailsPage"></local:Details>
</dualScreen:TwoPaneView.Pane2>
</dualScreen:TwoPaneView>
</ContentPage>

Просмотреть файл

@ -0,0 +1,82 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.DualScreen;
using Xamarin.Forms.Xaml;
namespace Xamarin.Forms.Controls.GalleryPages.TwoPaneViewGalleries
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MasterDetail
{
bool IsSpanned => DualScreenInfo.Current.SpanMode != TwoPaneViewMode.SinglePane;
DetailsPage detailsPagePushed;
public MasterDetail()
{
InitializeComponent();
masterPage.SelectionChanged += OnTitleSelected;
detailsPagePushed = new DetailsPage();
}
private void OnTitleSelected(object sender, SelectionChangedEventArgs e)
{
if (e.CurrentSelection == null || e.CurrentSelection.Count == 0)
return;
SetBindingContext();
SetupViews();
}
void SetBindingContext()
{
var bindingContext = masterPage.SelectedItem ?? (masterPage.ItemsSource as IList<MasterDetailsItem>)[0];
detailsPagePushed.BindingContext = bindingContext;
detailsPage.BindingContext = bindingContext;
}
async void SetupViews()
{
if (IsSpanned && !DualScreenInfo.Current.IsLandscape)
SetBindingContext();
if (detailsPage.BindingContext == null)
return;
if (!IsSpanned)
{
if (!Navigation.NavigationStack.Contains(detailsPagePushed))
{
await Navigation.PushAsync(detailsPagePushed);
}
}
}
protected override void OnAppearing()
{
if (!IsSpanned)
masterPage.SelectedItem = null;
DualScreenInfo.Current.PropertyChanged += OnFormsWindowPropertyChanged;
}
protected override void OnDisappearing()
{
base.OnDisappearing();
DualScreenInfo.Current.PropertyChanged -= OnFormsWindowPropertyChanged;
}
void OnFormsWindowPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(DualScreenInfo.Current.SpanMode) || e.PropertyName == nameof(DualScreenInfo.Current.IsLandscape))
{
SetupViews();
}
}
}
}

Просмотреть файл

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Xamarin.Forms.Controls.GalleryPages.TwoPaneViewGalleries
{
public class MasterDetailsItem
{
public MasterDetailsItem(int x)
{
Title = $"Item {x}";
Details = $"This is item {x}";
}
public string Title { get; set; }
public string Details { get; set; }
}
}

Просмотреть файл

@ -6,11 +6,11 @@
mc:Ignorable="d"
x:Class="Xamarin.Forms.Controls.GalleryPages.TwoPaneViewGalleries.OpenCompactWindow">
<ContentPage.Content>
<StackLayout>
<Button Text="Open Compact Window"
<StackLayout x:Name="layout">
<Button x:Name="button" Text="Open Compact Window"
Clicked="Button_Clicked"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand" />
VerticalOptions="Start"
HorizontalOptions="Start" />
</StackLayout>
</ContentPage.Content>
</ContentPage>

Просмотреть файл

@ -12,9 +12,25 @@ namespace Xamarin.Forms.Controls.GalleryPages.TwoPaneViewGalleries
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class OpenCompactWindow : ContentPage
{
DualScreen.DualScreenInfo info;
public OpenCompactWindow()
{
InitializeComponent();
info = new DualScreen.DualScreenInfo(layout);
info.PropertyChanged += Info_PropertyChanged;
}
private void Info_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (info.SpanningBounds.Length > 0)
{
var bounds = info.SpanningBounds[0];
layout.Padding = new Thickness(bounds.Width / 2 - button.Width / 2, bounds.Height / 2 - button.Height / 2, 0, 0);
}
else
{
layout.Padding = new Thickness(0);
}
}
async void Button_Clicked(object sender, EventArgs e)
@ -29,7 +45,7 @@ namespace Xamarin.Forms.Controls.GalleryPages.TwoPaneViewGalleries
{
Children =
{
new Label(){ Text = "rabbit"},new Label(){ Text = "rabbit"},new Label(){ Text = "rabbit"},new Label(){ Text = "rabbit"},new Label(){ Text = "rabbit"},new Label(){ Text = "rabbit"},new Label(){ Text = "rabbit"},new Label(){ Text = "rabbit"},new Label(){ Text = "rabbit"},new Label(){ Text = "rabbit"},new Label(){ Text = "rabbit"},new Label(){ Text = "rabbit"},new Label(){ Text = "rabbit"},new Label(){ Text = "rabbit"},new Label(){ Text = "rabbit"},
new Label(){ Text = "Welcome to your Compact Mode Window" },
button
},
BackgroundColor = Color.Yellow,
@ -39,13 +55,13 @@ namespace Xamarin.Forms.Controls.GalleryPages.TwoPaneViewGalleries
layout.BatchCommitted += Layout_BatchCommitted;
page.Content = layout;
page.Content = new ScrollView() { Content = layout };
var args = await DualScreen.DualScreenHelper.OpenCompactMode(page);
button.Command = new Command(async () =>
{
await args.Close();
await args.CloseAsync();
});
}

Просмотреть файл

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="Xamarin.Forms.Controls.GalleryPages.TwoPaneViewGalleries.TwoPage"
xmlns:local="clr-namespace:Xamarin.Forms.Controls.GalleryPages.TwoPaneViewGalleries"
Visual="Material"
x:Name="mainPage">
<Grid x:Name="layout">
<CollectionView x:Name="cv" BackgroundColor="LightGray">
<CollectionView.ItemsLayout>
<LinearItemsLayout
SnapPointsAlignment="Start"
SnapPointsType="MandatorySingle"
Orientation="Horizontal"
ItemSpacing="{Binding Source={x:Reference mainPage}, Path=HingeWidth}" />
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<Frame BackgroundColor="LightGray" Padding="0" Margin="0"
WidthRequest="{Binding Source={x:Reference mainPage}, Path=ContentWidth}"
HeightRequest="{Binding Source={x:Reference mainPage}, Path=ContentHeight}">
<Frame Margin="20" BackgroundColor="White">
<Label FontSize="Large" Text="{Binding .}" VerticalTextAlignment="Center" HorizontalTextAlignment="Center" HorizontalOptions="Center" VerticalOptions="Center"></Label>
</Frame>
</Frame>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</Grid>
</ContentPage>

Просмотреть файл

@ -0,0 +1,130 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.DualScreen;
using Xamarin.Forms.Xaml;
namespace Xamarin.Forms.Controls.GalleryPages.TwoPaneViewGalleries
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class TwoPage : ContentPage
{
IItemsLayout horizontalLayout = null;
IItemsLayout verticalItemsLayout = null;
bool disableUpdates = false;
private double contentWidth;
private double contentHeight;
public DualScreenInfo DualScreenLayoutInfo { get; }
bool IsSpanned => DualScreenLayoutInfo.SpanningBounds.Length > 0;
public TwoPage()
{
InitializeComponent();
DualScreenLayoutInfo = new DualScreenInfo(layout);
cv.ItemsSource =
Enumerable.Range(0, 1000)
.Select(i => $"Page {i}")
.ToList();
}
protected override void OnAppearing()
{
DualScreenLayoutInfo.PropertyChanged += OnFormsWindowPropertyChanged;
DualScreenInfo.Current.PropertyChanged += OnFormsWindowPropertyChanged;
SetupColletionViewLayout();
}
protected override void OnDisappearing()
{
base.OnDisappearing();
DualScreenLayoutInfo.PropertyChanged -= OnFormsWindowPropertyChanged;
DualScreenInfo.Current.PropertyChanged -= OnFormsWindowPropertyChanged;
}
void OnFormsWindowPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (Content == null || disableUpdates)
return;
SetupColletionViewLayout();
if (e.PropertyName == nameof(DualScreenInfo.Current.HingeBounds))
{
OnPropertyChanged(nameof(HingeWidth));
}
}
public double ContentHeight
{
get => contentHeight;
set
{
if (contentHeight == value)
return;
contentHeight = value;
OnPropertyChanged(nameof(ContentHeight));
}
}
public double ContentWidth
{
get => contentWidth;
set
{
if (contentWidth == value)
return;
contentWidth = value;
OnPropertyChanged(nameof(ContentWidth));
}
}
public double Pane1Height => IsSpanned ? (DualScreenLayoutInfo.SpanningBounds[0].Height) : layout.Height;
public double Pane2Height => IsSpanned ? (DualScreenLayoutInfo.SpanningBounds[1].Height) : 0d;
public double HingeWidth => DualScreenLayoutInfo?.HingeBounds.Width ?? DualScreenInfo.Current?.HingeBounds.Width ?? 0d;
void SetupColletionViewLayout()
{
ContentWidth = IsSpanned ? (DualScreenLayoutInfo.SpanningBounds[0].Width) : layout.Width;
ContentHeight = (!DualScreenLayoutInfo.IsLandscape) ? Pane1Height : Pane1Height + Pane2Height;
disableUpdates = true;
if (verticalItemsLayout == null)
{
horizontalLayout = cv.ItemsLayout;
verticalItemsLayout = new LinearItemsLayout(ItemsLayoutOrientation.Vertical)
{
SnapPointsAlignment = SnapPointsAlignment.Start,
SnapPointsType = SnapPointsType.None
};
}
if (!DualScreenLayoutInfo.IsLandscape)
{
if (cv.ItemsLayout != horizontalLayout)
{
cv.ItemsLayout = horizontalLayout;
}
}
else
{
if (cv.ItemsLayout != verticalItemsLayout)
{
cv.ItemsLayout = verticalItemsLayout;
}
}
disableUpdates = false;
}
}
}

Просмотреть файл

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Xamarin.Forms.DualScreen;assembly=Xamarin.Forms.DualScreen"
xmlns:twoPaneGallery="clr-namespace:Xamarin.Forms.Controls.GalleryPages.TwoPaneViewGalleries"
mc:Ignorable="d"
x:Class="Xamarin.Forms.Controls.GalleryPages.TwoPaneViewGalleries.TwoPanePropertiesGallery">
<ContentPage.Content>
<local:TwoPaneView x:Name="twoPaneView"
MinTallModeHeight="{Binding Source={x:Reference MinTallModeHeight}, Path=Value}"
MinWideModeWidth="{Binding Source={x:Reference MinWideModeWidth}, Path=Value}"
PanePriority="{Binding Source={x:Reference PanePriority}, Path=SelectedItem, Mode=TwoWay}"
TallModeConfiguration="{Binding Source={x:Reference TallModeConfiguration}, Path=SelectedItem}"
WideModeConfiguration="{Binding Source={x:Reference WideModeConfiguration}, Path=SelectedItem}">
<local:TwoPaneView.Pane1>
<StackLayout Padding="20">
<Label Text="MinTallModeHeight"></Label>
<Slider x:Name="MinTallModeHeight" ></Slider>
<Label Text="MinWideModeWidth"></Label>
<Slider x:Name="MinWideModeWidth"></Slider>
<Label Text="Pane1Length"></Label>
<Slider x:Name="Pane1Length" Maximum="1" Value="1"></Slider>
<Label Text="Pane2Length"></Label>
<Slider x:Name="Pane2Length" Maximum="1" Value="1"></Slider>
<Picker Title="PanePriority" x:Name="PanePriority" SelectedIndex="0">
</Picker>
<Picker Title="TallModeConfiguration" x:Name="TallModeConfiguration" SelectedIndex="1">
</Picker>
<Picker Title="WideModeConfiguration" x:Name="WideModeConfiguration" SelectedIndex="1">
</Picker>
<Label Text="Hinge Angle (only works on Android):"></Label>
<twoPaneGallery:HingeAngleLabel />
</StackLayout>
</local:TwoPaneView.Pane1>
<local:TwoPaneView.Pane2>
<StackLayout BackgroundColor="Yellow">
<Label Text="Secondary Content"></Label>
<Button Text="Reset" Clicked="OnReset"></Button>
</StackLayout>
</local:TwoPaneView.Pane2>
</local:TwoPaneView>
</ContentPage.Content>
</ContentPage>

Просмотреть файл

@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace Xamarin.Forms.Controls.GalleryPages.TwoPaneViewGalleries
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class TwoPanePropertiesGallery : ContentPage
{
public TwoPanePropertiesGallery()
{
InitializeComponent();
Pane1Length.ValueChanged += PaneLengthChanged;
Pane2Length.ValueChanged += PaneLengthChanged;
PanePriority.ItemsSource = Enum.GetValues(typeof(DualScreen.TwoPaneViewPriority));
TallModeConfiguration.ItemsSource = Enum.GetValues(typeof(DualScreen.TwoPaneViewTallModeConfiguration));
WideModeConfiguration.ItemsSource = Enum.GetValues(typeof(DualScreen.TwoPaneViewWideModeConfiguration));
twoPaneView.PanePriority = DualScreen.TwoPaneViewPriority.Pane1;
Pane1Length.Value = 0.5;
Pane2Length.Value = 0.5;
}
void PaneLengthChanged(object sender, ValueChangedEventArgs e)
{
twoPaneView.Pane1Length = new GridLength(Pane1Length.Value, GridUnitType.Star);
twoPaneView.Pane2Length = new GridLength(Pane2Length.Value, GridUnitType.Star);
}
protected override void OnAppearing()
{
base.OnAppearing();
Setup(Width, Height);
PanePriority.SelectedIndex = 0;
TallModeConfiguration.SelectedIndex = 1;
WideModeConfiguration.SelectedIndex = 1;
}
void Setup(double width, double height)
{
if (width <= 0 || height <= 0)
return;
MinTallModeHeight.Maximum = height;
MinWideModeWidth.Maximum = width;
}
protected override void OnSizeAllocated(double width, double height)
{
base.OnSizeAllocated(width, height);
Setup(width, height);
}
void OnReset(object sender, EventArgs e)
{
twoPaneView.PanePriority = DualScreen.TwoPaneViewPriority.Pane1;
Pane1Length.Value = 0.5;
Pane2Length.Value = 0.5;
}
}
public class HingeAngleLabel : Label { }
}

Просмотреть файл

@ -14,15 +14,40 @@ namespace Xamarin.Forms.Controls.GalleryPages.TwoPaneViewGalleries
mdp.IsPresented = false;
}
Content = new StackLayout
var twoPaneView = new DualScreen.TwoPaneView()
{
MinTallModeHeight = 0,
MinWideModeWidth = 0
};
var pane1 = new StackLayout
{
Children =
{
GalleryBuilder.NavButton("TwoPanePropertiesGallery", () => new TwoPanePropertiesGallery(), Navigation),
GalleryBuilder.NavButton("Master Details Sample", () => new MasterDetail(), Navigation),
GalleryBuilder.NavButton("Companion Pane", () => new CompanionPane(), Navigation),
GalleryBuilder.NavButton("ExtendCanvas Sample", () => new ExtendCanvas(), Navigation),
GalleryBuilder.NavButton("DualViewMapPage Sample", () => new DualViewMapPage(), Navigation),
}
};
var pane2 = new StackLayout
{
Children =
{
GalleryBuilder.NavButton("Nested TwoPaneView Split Across Hinge", () => new NestedTwoPaneViewSplitAcrossHinge(), Navigation),
GalleryBuilder.NavButton("Open Picture in Picture Window", () => new OpenCompactWindow(), Navigation),
GalleryBuilder.NavButton("DualScreenInfo with non TwoPaneView", () => new GridUsingDualScreenInfo(), Navigation),
GalleryBuilder.NavButton("eReader Samples", () => new TwoPage(), Navigation),
}
};
twoPaneView.Pane1 = pane1;
twoPaneView.Pane2 = pane2;
Content = twoPaneView;
}
}
}

Просмотреть файл

@ -72,6 +72,12 @@
<EmbeddedResource Update="GalleryPages\TwoPaneViewGalleries\OpenCompactWindow.xaml">
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
</EmbeddedResource>
<EmbeddedResource Update="GalleryPages\TwoPaneViewGalleries\TwoPage.xaml">
<Generator>MSBuild:Compile</Generator>
</EmbeddedResource>
<EmbeddedResource Update="GalleryPages\TwoPaneViewGalleries\TwoPanePropertiesGallery.xaml">
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
</EmbeddedResource>
</ItemGroup>
<Target Name="CreateControllGalleryConfig" BeforeTargets="Build">

Просмотреть файл

@ -12,9 +12,10 @@ namespace Xamarin.Forms.DualScreen
bool IsSpanned { get; }
bool IsLandscape { get; }
Rectangle GetHinge();
Size ScaledScreenSize { get; }
Point? GetLocationOnScreen(VisualElement visualElement);
DeviceInfo DeviceInfo { get; }
void WatchForChangesOnLayout(VisualElement visualElement);
void StopWatchingForChangesOnLayout(VisualElement visualElement);
object WatchForChangesOnLayout(VisualElement visualElement, Action action);
void StopWatchingForChangesOnLayout(VisualElement visualElement, object handle);
Task<int> GetHingeAngleAsync();
}
}

Просмотреть файл

@ -1,4 +1,5 @@
using System;
using System.Threading.Tasks;
using Xamarin.Forms.Internals;
namespace Xamarin.Forms.DualScreen.UnitTests
@ -19,6 +20,8 @@ namespace Xamarin.Forms.DualScreen.UnitTests
public DeviceInfo DeviceInfo { get; set; }
public Size ScaledScreenSize => DeviceInfo.ScaledScreenSize;
public event EventHandler OnScreenChanged;
public void Dispose()
@ -41,20 +44,20 @@ namespace Xamarin.Forms.DualScreen.UnitTests
public Point? SetLocationOnScreen(Point point) => _location = point;
public void WatchForChangesOnLayout(VisualElement visualElement)
public object WatchForChangesOnLayout(VisualElement visualElement, Action action)
{
visualElement.BatchCommitted += OnLayoutChangesCommited;
EventHandler<EventArg<VisualElement>> handler = (_, __) => action();
visualElement.BatchCommitted += handler;
return handler;
}
public void StopWatchingForChangesOnLayout(VisualElement visualElement)
public void StopWatchingForChangesOnLayout(VisualElement visualElement, object handle)
{
visualElement.BatchCommitted -= OnLayoutChangesCommited;
if(handle is EventHandler<EventArg<VisualElement>> eh)
visualElement.BatchCommitted -= eh;
}
void OnLayoutChangesCommited(object sender, EventArg<VisualElement> e)
{
OnScreenChanged?.Invoke(this, EventArgs.Empty);
}
public Task<int> GetHingeAngleAsync() => Task.FromResult(0);
}
internal class TestDualScreenServiceLandscape : TestDualScreenService

Просмотреть файл

@ -176,5 +176,57 @@ namespace Xamarin.Forms.DualScreen.UnitTests
Assert.AreEqual(0, twoPaneViewNested.Pane1.X);
Assert.AreEqual(0, twoPaneViewNested.Pane2.X);
}
[Test]
public void SpanningBoundsLandscape()
{
var testDualScreenService = new TestDualScreenServiceLandscape();
testDualScreenService.IsSpanned = true;
testDualScreenService.SetLocationOnScreen(new Point(0, 400));
var result = new StackLayout() { IsPlatformEnabled = true };
result.Layout(new Rectangle(0, 400, testDualScreenService.ScaledScreenSize.Width, 200));
DualScreenInfo info = new DualScreenInfo(result, testDualScreenService);
var top = info.SpanningBounds[0];
Assert.AreEqual(0, top.X);
Assert.AreEqual(0, top.Y);
Assert.AreEqual(testDualScreenService.ScaledScreenSize.Width, top.Width);
Assert.AreEqual(90, top.Height);
var bottom = info.SpanningBounds[1];
Assert.AreEqual(0, bottom.X);
Assert.AreEqual(110, bottom.Y);
Assert.AreEqual(testDualScreenService.ScaledScreenSize.Width, bottom.Width);
Assert.AreEqual(90, bottom.Height);
}
[Test]
public void SpanningBoundsPortrait()
{
var testDualScreenService = new TestDualScreenServicePortrait();
testDualScreenService.IsSpanned = true;
testDualScreenService.SetLocationOnScreen(new Point(400, 0));
var result = new StackLayout() { IsPlatformEnabled = true };
result.Layout(new Rectangle(400, 0, 200, testDualScreenService.ScaledScreenSize.Height));
DualScreenInfo info = new DualScreenInfo(result, testDualScreenService);
var left = info.SpanningBounds[0];
Assert.AreEqual(0, left.X);
Assert.AreEqual(0, left.Y);
Assert.AreEqual(90, left.Width);
Assert.AreEqual(testDualScreenService.ScaledScreenSize.Height, left.Height);
var right = info.SpanningBounds[1];
Assert.AreEqual(110, right.X);
Assert.AreEqual(0, right.Y);
Assert.AreEqual(90, right.Width);
Assert.AreEqual(testDualScreenService.ScaledScreenSize.Height, right.Height);
}
}
}

Просмотреть файл

@ -7,6 +7,7 @@ namespace Xamarin.Forms.DualScreen
{
public class CompactModeArgs : EventArgs
{
Func<Task> _close;
public CompactModeArgs(Func<Task> close, bool success)
{
if(close == null)
@ -14,11 +15,11 @@ namespace Xamarin.Forms.DualScreen
close = () => Task.Delay(0);
}
Close = close;
_close = close;
Success = success;
}
public Func<Task> Close { get; }
public Task CloseAsync() => _close();
public bool Success { get; }
}
}

Просмотреть файл

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace Xamarin.Forms.DualScreen
{
public partial class DualScreenInfo : INotifyPropertyChanged
{
public Task<int> GetHingeAngleAsync() => DualScreenService.GetHingeAngleAsync();
}
}

Просмотреть файл

@ -7,16 +7,19 @@ using System.Threading.Tasks;
namespace Xamarin.Forms.DualScreen
{
public class DualScreenInfo : INotifyPropertyChanged
{
static Lazy<DualScreenInfo> _dualScreenInfo { get; } = new Lazy<DualScreenInfo>(OnCreate);
public event PropertyChangedEventHandler PropertyChanged;
Rectangle[] _spanningBounds;
Rectangle _hingeBounds;
bool _isLandscape;
TwoPaneViewMode _spanMode;
public partial class DualScreenInfo : INotifyPropertyChanged
{
static Lazy<DualScreenInfo> _dualScreenInfo { get; } = new Lazy<DualScreenInfo>(OnCreate);
public event PropertyChangedEventHandler PropertyChanged;
Rectangle[] _spanningBounds;
Rectangle _hingeBounds;
bool _isLandscape;
TwoPaneViewMode _spanMode;
TwoPaneViewLayoutGuide _twoPaneViewLayoutGuide;
IDualScreenService _dualScreenService;
public static DualScreenInfo Current => _dualScreenInfo.Value;
IDualScreenService DualScreenService =>
_dualScreenService ?? DependencyService.Get<IDualScreenService>() ?? NoDualScreenServiceImpl.Instance;
public DualScreenInfo(Layout layout) : this(layout, null)
{
@ -24,6 +27,7 @@ namespace Xamarin.Forms.DualScreen
internal DualScreenInfo(Layout layout, IDualScreenService dualScreenService)
{
_dualScreenService = dualScreenService;
if (layout == null)
{
_twoPaneViewLayoutGuide = TwoPaneViewLayoutGuide.Instance;
@ -36,94 +40,95 @@ namespace Xamarin.Forms.DualScreen
}
public Rectangle[] SpanningBounds
{
get => GetSpanningBounds();
set
{
SetProperty(ref _spanningBounds, value);
}
}
{
get => GetSpanningBounds();
set
{
SetProperty(ref _spanningBounds, value);
}
}
public Rectangle HingeBounds
{
get => GetHingeBounds();
set
{
SetProperty(ref _hingeBounds, value);
}
}
public Rectangle HingeBounds
{
get => GetHingeBounds();
set
{
SetProperty(ref _hingeBounds, value);
}
}
public bool IsLandscape
{
get => GetIsLandscape();
set
{
SetProperty(ref _isLandscape, value);
}
}
public bool IsLandscape
{
get => GetIsLandscape();
set
{
SetProperty(ref _isLandscape, value);
}
}
public TwoPaneViewMode SpanMode
{
get => GetSpanMode();
set
{
SetProperty(ref _spanMode, value);
}
}
public TwoPaneViewMode SpanMode
{
get => GetSpanMode();
set
{
SetProperty(ref _spanMode, value);
}
}
Rectangle[] GetSpanningBounds()
{
var guide = _twoPaneViewLayoutGuide;
var hinge = guide.Hinge;
Rectangle[] GetSpanningBounds()
{
var guide = _twoPaneViewLayoutGuide;
var hinge = guide.Hinge;
guide.UpdateLayouts();
if (hinge == Rectangle.Zero)
return new Rectangle[0];
if (hinge == Rectangle.Zero)
return new Rectangle[0];
if (guide.Pane2 == Rectangle.Zero)
return new Rectangle[0];
if (guide.Pane2 == Rectangle.Zero)
return new Rectangle[0];
if(IsLandscape)
return new[] { guide.Pane1, new Rectangle(0, hinge.Height + guide.Pane1.Height, guide.Pane2.Width, guide.Pane2.Height) };
else
return new[] { guide.Pane1, new Rectangle(hinge.Width + guide.Pane1.Width, 0, guide.Pane2.Width, guide.Pane2.Height) };
}
return new[] { guide.Pane1, guide.Pane2 };
}
Rectangle GetHingeBounds()
{
var guide = _twoPaneViewLayoutGuide;
return guide.Hinge;
}
Rectangle GetHingeBounds()
{
var guide = _twoPaneViewLayoutGuide;
guide.UpdateLayouts();
return guide.Hinge;
}
bool GetIsLandscape() => _twoPaneViewLayoutGuide.IsLandscape;
bool GetIsLandscape() => _twoPaneViewLayoutGuide.IsLandscape;
TwoPaneViewMode GetSpanMode() => _twoPaneViewLayoutGuide.Mode;
TwoPaneViewMode GetSpanMode() => _twoPaneViewLayoutGuide.Mode;
static DualScreenInfo OnCreate()
{
DualScreenInfo dualScreenInfo = new DualScreenInfo(null);
static DualScreenInfo OnCreate()
{
DualScreenInfo dualScreenInfo = new DualScreenInfo(null);
dualScreenInfo._twoPaneViewLayoutGuide.WatchForChanges();
dualScreenInfo._twoPaneViewLayoutGuide.PropertyChanged += dualScreenInfo.OnTwoPaneViewLayoutGuideChanged;
return dualScreenInfo;
}
return dualScreenInfo;
}
void OnTwoPaneViewLayoutGuideChanged(object sender, PropertyChangedEventArgs e)
{
SpanningBounds = GetSpanningBounds();
IsLandscape = GetIsLandscape();
HingeBounds = GetHingeBounds();
SpanMode = GetSpanMode();
}
void OnTwoPaneViewLayoutGuideChanged(object sender, PropertyChangedEventArgs e)
{
SpanningBounds = GetSpanningBounds();
IsLandscape = GetIsLandscape();
HingeBounds = GetHingeBounds();
SpanMode = GetSpanMode();
}
bool SetProperty<T>(ref T backingStore, T value,
[CallerMemberName]string propertyName = "",
Action onChanged = null)
{
if (EqualityComparer<T>.Default.Equals(backingStore, value))
return false;
bool SetProperty<T>(ref T backingStore, T value,
[CallerMemberName]string propertyName = "",
Action onChanged = null)
{
if (EqualityComparer<T>.Default.Equals(backingStore, value))
return false;
backingStore = value;
onChanged?.Invoke();
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
return true;
}
}
backingStore = value;
onChanged?.Invoke();
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
return true;
}
}
}

Просмотреть файл

@ -3,6 +3,7 @@ using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using Android.App;
using Android.Util;
using Android.Views;
using Microsoft.Device.Display;
using Xamarin.Forms;
@ -25,16 +26,17 @@ namespace Xamarin.Forms.DualScreen
internal class DualScreenServiceImpl : IDualScreenService, IDisposable
{
public event EventHandler OnScreenChanged;
GenericGlobalLayoutListener _genericGlobalLayoutListener;
ScreenHelper _helper;
bool _isDuo = false;
HingeSensor _hingeSensor;
static Activity _mainActivity;
static DualScreenServiceImpl _HingeService;
int _hingeAngle;
Rectangle _hingeLocation;
bool _isDisposed;
bool _isLandscape;
Size _pixelScreenSize;
object _hingeAngleLock = new object();
TaskCompletionSource<int> _gettingHingeAngle;
Activity MainActivity
{
@ -45,7 +47,6 @@ namespace Xamarin.Forms.DualScreen
public DualScreenServiceImpl()
{
_HingeService = this;
_genericGlobalLayoutListener = new GenericGlobalLayoutListener(() => OnScreenChanged?.Invoke(this, EventArgs.Empty));
if (_mainActivity != null)
Init(_mainActivity);
}
@ -78,56 +79,116 @@ namespace Xamarin.Forms.DualScreen
_HingeService._helper = new ScreenHelper();
}
if (_HingeService._hingeSensor != null)
{
_HingeService._hingeSensor.OnSensorChanged -= _HingeService.OnSensorChanged;
_HingeService._hingeSensor.StopListening();
}
_HingeService._isDuo = _HingeService._helper.Initialize(_HingeService.MainActivity);
if (_HingeService._isDuo)
{
_HingeService._hingeSensor = new HingeSensor(_HingeService.MainActivity);
_HingeService._hingeSensor.OnSensorChanged += _HingeService.OnSensorChanged;
_HingeService._hingeSensor.StartListening();
}
}
void ConfigurationChanged(object sender, EventArgs e)
{
bool screenChanged = false;
if (_isLandscape != IsLandscape)
OnScreenChanged?.Invoke(this, e);
_isLandscape = IsLandscape;
}
void OnSensorChanged(object sender, HingeSensor.HingeSensorChangedEventArgs e)
{
if (_hingeLocation != GetHinge())
{
_hingeLocation = GetHinge();
_isLandscape = IsLandscape;
screenChanged = true;
}
if (_hingeAngle != e.HingeAngle)
OnScreenChanged?.Invoke(this, EventArgs.Empty);
if (_mainActivity != null)
{
using (DisplayMetrics display = _mainActivity.Resources.DisplayMetrics)
{
var scalingFactor = display.Density;
_pixelScreenSize = new Size(display.WidthPixels, display.HeightPixels);
var newSize = new Size(_pixelScreenSize.Width / scalingFactor, _pixelScreenSize.Height / scalingFactor);
_hingeAngle = e.HingeAngle;
if (newSize != ScaledScreenSize)
{
ScaledScreenSize = newSize;
screenChanged = true;
}
}
}
if(screenChanged)
OnScreenChanged?.Invoke(this, e);
}
public void Dispose()
{
if (_hingeSensor != null)
{
_hingeSensor.OnSensorChanged -= OnSensorChanged;
_hingeSensor.StopListening();
}
if (_isDisposed)
return;
_isDisposed = true;
// make sure the one shot task is cleared out if it's running
SetHingeAngle(0);
StopListeningForHingeChanges();
}
public Size ScaledScreenSize
{
get;
private set;
}
public bool IsSpanned
=> _isDuo && (_helper?.IsDualMode ?? false);
public DeviceInfo DeviceInfo => Device.info;
void StartListeningForHingeChanges()
{
if (_hingeSensor == null)
return;
_hingeSensor.OnSensorChanged += OnSensorChanged;
_hingeSensor.StartListening();
}
void StopListeningForHingeChanges()
{
if (_hingeSensor == null)
return;
_hingeSensor.OnSensorChanged -= OnSensorChanged;
_hingeSensor.StopListening();
}
void OnSensorChanged(object sender, HingeSensor.HingeSensorChangedEventArgs e)
{
SetHingeAngle(e.HingeAngle);
}
void SetHingeAngle(int hingeAngle)
{
TaskCompletionSource<int> toSet = null;
lock (_hingeAngleLock)
{
StopListeningForHingeChanges();
toSet = _gettingHingeAngle;
_gettingHingeAngle = null;
}
if (toSet != null)
toSet.SetResult(hingeAngle);
}
public Task<int> GetHingeAngleAsync()
{
lock (_hingeAngleLock)
{
if (_gettingHingeAngle == null)
{
_gettingHingeAngle = new TaskCompletionSource<int>();
StartListeningForHingeChanges();
}
}
return _gettingHingeAngle.Task;
}
public Rectangle GetHinge()
{
@ -137,7 +198,7 @@ namespace Xamarin.Forms.DualScreen
var rotation = ScreenHelper.GetRotation(_helper.Activity);
var hinge = _helper.DisplayMask.GetBoundingRectsForRotation(rotation).FirstOrDefault();
var hingeDp = new Rectangle(PixelsToDp(hinge.Left), PixelsToDp(hinge.Top), PixelsToDp(hinge.Width()), PixelsToDp(hinge.Height()));
return hingeDp;
}
@ -170,23 +231,65 @@ namespace Xamarin.Forms.DualScreen
return new Point(view.View.Context.FromPixels(location[0]), view.View.Context.FromPixels(location[1]));
}
public void WatchForChangesOnLayout(VisualElement visualElement)
public object WatchForChangesOnLayout(VisualElement visualElement, Action action)
{
if (action == null)
return null;
var view = Platform.Android.Platform.GetRenderer(visualElement);
var androidView = view?.View;
if (view?.View == null)
return;
if (androidView == null || !androidView.IsAlive())
return null;
view.View.ViewTreeObserver.AddOnGlobalLayoutListener(_genericGlobalLayoutListener);
ViewTreeObserver.IOnGlobalLayoutListener listener = null;
listener = new GenericGlobalLayoutListener(() =>
{
if (!androidView.IsAlive())
{
action = null;
androidView = null;
try
{
_mainActivity?.Window?.DecorView?.RootView?.ViewTreeObserver?.RemoveOnGlobalLayoutListener(listener);
}
catch
{
// just in case something along the call path here is disposed of
}
return;
}
action?.Invoke();
});
androidView.ViewTreeObserver.AddOnGlobalLayoutListener(listener);
return listener;
}
public void StopWatchingForChangesOnLayout(VisualElement visualElement)
public void StopWatchingForChangesOnLayout(VisualElement visualElement, object handle)
{
var view = Platform.Android.Platform.GetRenderer(visualElement);
if (view?.View == null)
if (handle == null)
return;
view.View.ViewTreeObserver.RemoveOnGlobalLayoutListener(_genericGlobalLayoutListener);
var view = Platform.Android.Platform.GetRenderer(visualElement);
var androidView = view?.View;
if (androidView == null || !androidView.IsAlive())
return;
if (handle is ViewTreeObserver.IOnGlobalLayoutListener vto)
{
try
{
view.View.ViewTreeObserver.RemoveOnGlobalLayoutListener(vto);
}
catch
{
// just in case something along the call path here is disposed of
}
}
}
}
}

Просмотреть файл

@ -24,13 +24,22 @@ namespace Xamarin.Forms.DualScreen
public DualScreenService()
{
}
if(Window.Current != null)
Window.Current.SizeChanged += OnCurrentSizeChanged;
}
public bool IsSpanned
public Task<int> GetHingeAngleAsync() => Task.FromResult(0);
void OnCurrentSizeChanged(object sender, Windows.UI.Core.WindowSizeChangedEventArgs e)
{
OnScreenChanged?.Invoke(this, EventArgs.Empty);
}
public bool IsSpanned
{
get
{
var visibleBounds = ApplicationView.GetForCurrentView().VisibleBounds;
var visibleBounds = Window.Current.Bounds;
if (visibleBounds.Height > 1200 || visibleBounds.Width > 1200)
return true;
@ -51,7 +60,16 @@ namespace Xamarin.Forms.DualScreen
}
}
public void Dispose()
public Size ScaledScreenSize
{
get
{
Windows.Foundation.Rect windowSize = Window.Current.Bounds;
return new Size(windowSize.Width, windowSize.Height);
}
}
public void Dispose()
{
}
@ -86,29 +104,34 @@ namespace Xamarin.Forms.DualScreen
return new Point(screenCoords.X, screenCoords.Y);
}
public void WatchForChangesOnLayout(VisualElement visualElement)
public object WatchForChangesOnLayout(VisualElement visualElement, Action action)
{
var view = Platform.UWP.Platform.GetRenderer(visualElement);
if (view?.ContainerElement == null)
return null;
EventHandler<object> layoutUpdated = (_, __) =>
{
action();
};
view.ContainerElement.LayoutUpdated += layoutUpdated;
return layoutUpdated;
}
public void StopWatchingForChangesOnLayout(VisualElement visualElement, object handle)
{
if (handle == null)
return;
var view = Platform.UWP.Platform.GetRenderer(visualElement);
if (view?.ContainerElement == null)
return;
view.ContainerElement.LayoutUpdated += OnContainerElementLayoutUpdated;
}
public void StopWatchingForChangesOnLayout(VisualElement visualElement)
{
var view = Platform.UWP.Platform.GetRenderer(visualElement);
if (view?.ContainerElement == null)
return;
view.ContainerElement.LayoutUpdated -= OnContainerElementLayoutUpdated;
}
void OnContainerElementLayoutUpdated(object sender, object e)
{
OnScreenChanged?.Invoke(this, EventArgs.Empty);
if(handle is EventHandler<object> handler)
view.ContainerElement.LayoutUpdated -= handler;
}
}
}

Просмотреть файл

@ -15,19 +15,24 @@ namespace Xamarin.Forms.DualScreen
{
}
public bool IsSpanned => false;
public Task<int> GetHingeAngleAsync() => Task.FromResult(0);
public bool IsSpanned => false;
public bool IsLandscape => Device.info.CurrentOrientation.IsLandscape();
public DeviceInfo DeviceInfo => Device.info;
#pragma warning disable CS0067
public event EventHandler OnScreenChanged;
#pragma warning restore CS0067
public void Dispose()
public void Dispose()
{
}
public Rectangle GetHinge()
public Size ScaledScreenSize => Device.info.ScaledScreenSize;
public Rectangle GetHinge()
{
return Rectangle.Zero;
}
@ -37,19 +42,27 @@ namespace Xamarin.Forms.DualScreen
return null;
}
public void WatchForChangesOnLayout(VisualElement visualElement)
public object WatchForChangesOnLayout(VisualElement visualElement, Action action)
{
visualElement.BatchCommitted += OnLayoutChangesCommited;
if (action == null)
return null;
EventHandler<EventArg<VisualElement>> layoutUpdated = (_, __) =>
{
action();
};
visualElement.BatchCommitted += layoutUpdated;
return layoutUpdated;
}
public void StopWatchingForChangesOnLayout(VisualElement visualElement)
public void StopWatchingForChangesOnLayout(VisualElement visualElement, object handle)
{
visualElement.BatchCommitted -= OnLayoutChangesCommited;
}
if (handle == null)
return;
void OnLayoutChangesCommited(object sender, EventArg<VisualElement> e)
{
OnScreenChanged?.Invoke(this, EventArgs.Empty);
if (handle is EventHandler<EventArg<VisualElement>> handler)
visualElement.BatchCommitted -= handler;
}
}
}

Просмотреть файл

@ -58,14 +58,20 @@ namespace Xamarin.Forms.DualScreen
ViewMode _currentMode;
bool _hasMeasured = false;
bool _updatingMode = false;
bool _performingLayout = false;
bool _processPendingChange = false;
Rectangle _layoutGuidePane1;
Rectangle _layoutGuidePane2;
TwoPaneViewMode _layoutGuideMode;
Rectangle _layoutGuideHinge;
bool _layoutGuideIsLandscape;
double _previousWidth = -1;
double _previousHeight = -1;
public static readonly BindableProperty TallModeConfigurationProperty
= BindableProperty.Create("TallModeConfiguration", typeof(TwoPaneViewTallModeConfiguration), typeof(TwoPaneView), defaultValue: TwoPaneViewTallModeConfiguration.TopBottom, propertyChanged: OnJustInvalidateLayout);
= BindableProperty.Create("TallModeConfiguration", typeof(TwoPaneViewTallModeConfiguration), typeof(TwoPaneView), defaultValue: TwoPaneViewTallModeConfiguration.TopBottom, propertyChanged: TwoPaneViewLayoutPropertyChanged);
public static readonly BindableProperty WideModeConfigurationProperty
= BindableProperty.Create("WideModeConfiguration", typeof(TwoPaneViewWideModeConfiguration), typeof(TwoPaneView), defaultValue: TwoPaneViewWideModeConfiguration.LeftRight, propertyChanged: OnJustInvalidateLayout);
= BindableProperty.Create("WideModeConfiguration", typeof(TwoPaneViewWideModeConfiguration), typeof(TwoPaneView), defaultValue: TwoPaneViewWideModeConfiguration.LeftRight, propertyChanged: TwoPaneViewLayoutPropertyChanged);
public static readonly BindableProperty Pane1Property
= BindableProperty.Create("Pane1", typeof(View), typeof(TwoPaneView), propertyChanged: (b, o, n) => OnPanePropertyChanged(b, o, n, 0));
@ -79,19 +85,19 @@ namespace Xamarin.Forms.DualScreen
public static readonly BindableProperty ModeProperty = ModePropertyKey.BindableProperty;
public static readonly BindableProperty PanePriorityProperty
= BindableProperty.Create("PanePriority", typeof(TwoPaneViewPriority), typeof(TwoPaneView), defaultValue: TwoPaneViewPriority.Pane1, propertyChanged: OnJustInvalidateLayout);
= BindableProperty.Create("PanePriority", typeof(TwoPaneViewPriority), typeof(TwoPaneView), defaultValue: TwoPaneViewPriority.Pane1, propertyChanged: TwoPaneViewLayoutPropertyChanged);
public static readonly BindableProperty MinTallModeHeightProperty
= BindableProperty.Create("MinTallModeHeight", typeof(double), typeof(TwoPaneView), defaultValueCreator: OnMinModePropertyCreate, propertyChanged: OnJustInvalidateLayout);
= BindableProperty.Create("MinTallModeHeight", typeof(double), typeof(TwoPaneView), defaultValueCreator: OnMinModePropertyCreate, propertyChanged: TwoPaneViewLayoutPropertyChanged);
public static readonly BindableProperty MinWideModeWidthProperty
= BindableProperty.Create("MinWideModeWidth", typeof(double), typeof(TwoPaneView), defaultValueCreator: OnMinModePropertyCreate, propertyChanged: OnJustInvalidateLayout);
= BindableProperty.Create("MinWideModeWidth", typeof(double), typeof(TwoPaneView), defaultValueCreator: OnMinModePropertyCreate, propertyChanged: TwoPaneViewLayoutPropertyChanged);
public static readonly BindableProperty Pane1LengthProperty
= BindableProperty.Create("Pane1Length", typeof(GridLength), typeof(TwoPaneView), defaultValue: GridLength.Star, propertyChanged: OnJustInvalidateLayout);
= BindableProperty.Create("Pane1Length", typeof(GridLength), typeof(TwoPaneView), defaultValue: GridLength.Star, propertyChanged: TwoPaneViewLayoutPropertyChanged);
public static readonly BindableProperty Pane2LengthProperty
= BindableProperty.Create("Pane2Length", typeof(GridLength), typeof(TwoPaneView), defaultValue: GridLength.Star, propertyChanged: OnJustInvalidateLayout);
= BindableProperty.Create("Pane2Length", typeof(GridLength), typeof(TwoPaneView), defaultValue: GridLength.Star, propertyChanged: TwoPaneViewLayoutPropertyChanged);
public event EventHandler ModeChanged;
@ -111,17 +117,11 @@ namespace Xamarin.Forms.DualScreen
((TwoPaneView)bindable).ModeChanged?.Invoke(bindable, EventArgs.Empty);
}
static void OnJustInvalidateLayout(BindableObject bindable, object oldValue, object newValue)
static void TwoPaneViewLayoutPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var b = (TwoPaneView)bindable;
if (!b._performingLayout && !b._updatingMode)
{
b.UpdateMode();
}
else
{
b._processPendingChange = true;
}
b.UpdateMode();
}
static void OnPanePropertyChanged(BindableObject bindable, object oldValue, object newValue, int paneIndex)
@ -134,7 +134,7 @@ namespace Xamarin.Forms.DualScreen
else
twoPaneView._content2.Content = newView;
OnJustInvalidateLayout(bindable, null, null);
twoPaneView.UpdateMode();
}
public double MinTallModeHeight
@ -233,22 +233,52 @@ namespace Xamarin.Forms.DualScreen
base.OnIsPlatformEnabledChanged();
if (IsPlatformEnabled)
{
TwoPaneViewLayoutGuide.WatchForChanges();
TwoPaneViewLayoutGuide.PropertyChanged += OnTwoPaneViewLayoutGuide;
_twoPaneViewLayoutGuide.WatchForChanges();
_twoPaneViewLayoutGuide.PropertyChanged += OnTwoPaneViewLayoutGuide;
}
else
{
TwoPaneViewLayoutGuide.StopWatchingForChanges();
TwoPaneViewLayoutGuide.PropertyChanged -= OnTwoPaneViewLayoutGuide;
_twoPaneViewLayoutGuide.StopWatchingForChanges();
_twoPaneViewLayoutGuide.PropertyChanged -= OnTwoPaneViewLayoutGuide;
}
}
TwoPaneViewLayoutGuide TwoPaneViewLayoutGuide => _twoPaneViewLayoutGuide;
void OnTwoPaneViewLayoutGuide(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
OnJustInvalidateLayout(this, null, null);
if (_twoPaneViewLayoutGuide.Pane1 == _layoutGuidePane1 &&
_twoPaneViewLayoutGuide.Pane2 == _layoutGuidePane2 &&
_twoPaneViewLayoutGuide.Mode == _layoutGuideMode &&
_twoPaneViewLayoutGuide.Hinge == _layoutGuideHinge &&
_twoPaneViewLayoutGuide.IsLandscape == _layoutGuideIsLandscape)
{
return;
}
_layoutGuidePane1 = _twoPaneViewLayoutGuide.Pane1;
_layoutGuidePane2 = _twoPaneViewLayoutGuide.Pane2;
_layoutGuideMode = _twoPaneViewLayoutGuide.Mode;
_layoutGuideHinge = _twoPaneViewLayoutGuide.Hinge;
_layoutGuideIsLandscape = _twoPaneViewLayoutGuide.IsLandscape;
UpdateMode();
}
protected override void OnSizeAllocated(double width, double height)
{
if (!_updatingMode &&
width > 0 &&
height > 0 &&
width != _previousWidth &&
height != _previousHeight)
{
_previousWidth = width;
_previousHeight = height;
UpdateMode(false);
}
base.OnSizeAllocated(width, height);
}
protected override void LayoutChildren(double x, double y, double width, double height)
@ -262,8 +292,12 @@ namespace Xamarin.Forms.DualScreen
UpdateMode();
}
void UpdateMode()
void UpdateMode(bool invalidateLayout = true)
{
// controls hasn't fully been created yet
if (RowDefinitions.Count != 3 || ColumnDefinitions.Count != 3)
return;
if (_updatingMode)
{
_processPendingChange = true;
@ -280,11 +314,11 @@ namespace Xamarin.Forms.DualScreen
_hasMeasured = true;
TwoPaneViewLayoutGuide.UpdateLayouts();
_twoPaneViewLayoutGuide.UpdateLayouts();
if (TwoPaneViewLayoutGuide.Mode != TwoPaneViewMode.SinglePane)
if (_twoPaneViewLayoutGuide.Mode != TwoPaneViewMode.SinglePane)
{
if (TwoPaneViewLayoutGuide.Mode == TwoPaneViewMode.Wide)
if (_twoPaneViewLayoutGuide.Mode == TwoPaneViewMode.Wide)
{
// Regions are laid out horizontally
if (WideModeConfiguration != TwoPaneViewWideModeConfiguration.SinglePane)
@ -292,7 +326,7 @@ namespace Xamarin.Forms.DualScreen
newMode = (WideModeConfiguration == TwoPaneViewWideModeConfiguration.LeftRight) ? ViewMode.LeftRight : ViewMode.RightLeft;
}
}
else if (TwoPaneViewLayoutGuide.Mode == TwoPaneViewMode.Tall)
else if (_twoPaneViewLayoutGuide.Mode == TwoPaneViewMode.Tall)
{
// Regions are laid out vertically
if (TallModeConfiguration != TwoPaneViewTallModeConfiguration.SinglePane)
@ -368,12 +402,8 @@ namespace Xamarin.Forms.DualScreen
}
else
{
InvalidateLayout();
Device.BeginInvokeOnMainThread(() =>
{
TwoPaneViewLayoutGuide.UpdateLayouts();
if (invalidateLayout)
InvalidateLayout();
});
}
}
finally
@ -396,46 +426,56 @@ namespace Xamarin.Forms.DualScreen
Rectangle pane2 = _twoPaneViewLayoutGuide.Pane2;
bool isLayoutSpanned = _twoPaneViewLayoutGuide.Mode != TwoPaneViewMode.SinglePane;
columnMiddle.Width = new GridLength(0, GridUnitType.Absolute);
rowMiddle.Height = new GridLength(0, GridUnitType.Absolute);
if (newMode == ViewMode.LeftRight || newMode == ViewMode.RightLeft)
{
columnLeft.Width = ((newMode == ViewMode.LeftRight) ? Pane1Length : Pane2Length);
columnRight.Width = ((newMode == ViewMode.LeftRight) ? Pane2Length : Pane1Length);
}
else
{
columnLeft.Width = new GridLength(1, GridUnitType.Star);
columnRight.Width = new GridLength(0, GridUnitType.Absolute);
}
if (newMode == ViewMode.TopBottom || newMode == ViewMode.BottomTop)
{
rowTop.Height = ((newMode == ViewMode.TopBottom) ? Pane1Length : Pane2Length);
rowBottom.Height = ((newMode == ViewMode.TopBottom) ? Pane2Length : Pane1Length);
}
else
{
rowTop.Height = new GridLength(1, GridUnitType.Star);
rowBottom.Height = new GridLength(0, GridUnitType.Absolute);
}
if (TwoPaneViewLayoutGuide.Mode != TwoPaneViewMode.SinglePane && newMode != ViewMode.Pane1Only && newMode != ViewMode.Pane2Only)
if (_twoPaneViewLayoutGuide.Mode != TwoPaneViewMode.SinglePane && newMode != ViewMode.Pane1Only && newMode != ViewMode.Pane2Only)
{
Rectangle hinge = _twoPaneViewLayoutGuide.Hinge;
if (TwoPaneViewLayoutGuide.Mode == TwoPaneViewMode.Wide)
if (_twoPaneViewLayoutGuide.Mode == TwoPaneViewMode.Wide)
{
columnMiddle.Width = new GridLength(hinge.Width, GridUnitType.Absolute);
columnLeft.Width = new GridLength(pane1.Width, GridUnitType.Absolute);
columnRight.Width = new GridLength(pane2.Width, GridUnitType.Absolute);
rowMiddle.Height = new GridLength(0, GridUnitType.Absolute);
rowTop.Height = GridLength.Star;
rowBottom.Height = new GridLength(0, GridUnitType.Absolute);
}
else
{
rowMiddle.Height = new GridLength(hinge.Height, GridUnitType.Absolute);
rowTop.Height = new GridLength(pane1.Height, GridUnitType.Absolute);
rowBottom.Height = new GridLength(pane2.Height, GridUnitType.Absolute);
columnMiddle.Width = new GridLength(0, GridUnitType.Absolute);
columnLeft.Width = GridLength.Star;
columnRight.Width = new GridLength(0, GridUnitType.Absolute);
}
}
else
{
columnMiddle.Width = new GridLength(0, GridUnitType.Absolute);
rowMiddle.Height = new GridLength(0, GridUnitType.Absolute);
if (newMode == ViewMode.LeftRight || newMode == ViewMode.RightLeft)
{
columnLeft.Width = ((newMode == ViewMode.LeftRight) ? Pane1Length : Pane2Length);
columnRight.Width = ((newMode == ViewMode.LeftRight) ? Pane2Length : Pane1Length);
}
else
{
columnLeft.Width = new GridLength(1, GridUnitType.Star);
columnRight.Width = new GridLength(0, GridUnitType.Absolute);
}
if (newMode == ViewMode.TopBottom || newMode == ViewMode.BottomTop)
{
rowTop.Height = ((newMode == ViewMode.TopBottom) ? Pane1Length : Pane2Length);
rowBottom.Height = ((newMode == ViewMode.TopBottom) ? Pane2Length : Pane1Length);
}
else
{
rowTop.Height = new GridLength(1, GridUnitType.Star);
rowBottom.Height = new GridLength(0, GridUnitType.Absolute);
}
}

Просмотреть файл

@ -26,7 +26,7 @@ namespace Xamarin.Forms.DualScreen
public event PropertyChangedEventHandler PropertyChanged;
List<string> _pendingPropertyChanges = new List<string>();
Rectangle _absoluteLayoutPosition;
bool _isWatchingForChanges = false;
object _watchHandle = null;
TwoPaneViewLayoutGuide()
{
@ -44,49 +44,37 @@ namespace Xamarin.Forms.DualScreen
_dualScreenService = dualScreenService;
if(_layout != null)
{
UpdateLayouts();
_layout.PropertyChanged += OnLayoutPropertyChanged;
_layout.PropertyChanging += OnLayoutPropertyChanging;
}
}
void CheckIsPlatformEnabled()
void OnLayoutPropertyChanging(object sender, PropertyChangingEventArgs e)
{
if (_layout == null)
return;
if (_layout.IsPlatformEnabled)
{
if (!_isWatchingForChanges)
{
WatchForChanges();
_isWatchingForChanges = true;
}
}
else
{
if (_isWatchingForChanges)
{
StopWatchingForChanges();
_isWatchingForChanges = false;
}
}
if (e.PropertyName == "Renderer")
StopWatchingForChanges();
}
void OnLayoutPropertyChanged(object sender, PropertyChangedEventArgs e) => CheckIsPlatformEnabled();
void OnLayoutPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Renderer")
WatchForChanges();
}
public void WatchForChanges()
{
StopWatchingForChanges();
DualScreenService.OnScreenChanged += OnScreenChanged;
if (_layout != null)
{
DualScreenService.WatchForChangesOnLayout(_layout);
}
if (DualScreenService.DeviceInfo is INotifyPropertyChanged npc)
{
npc.PropertyChanged += OnDeviceInfoChanged;
_watchHandle = DualScreenService.WatchForChangesOnLayout(_layout, () => OnScreenChanged(DualScreenService, EventArgs.Empty));
if (_watchHandle == null)
return;
}
DualScreenService.OnScreenChanged += OnScreenChanged;
}
public void StopWatchingForChanges()
@ -95,12 +83,8 @@ namespace Xamarin.Forms.DualScreen
if (_layout != null)
{
DualScreenService.StopWatchingForChangesOnLayout(_layout);
}
if (DualScreenService.DeviceInfo is INotifyPropertyChanged npc)
{
npc.PropertyChanged -= OnDeviceInfoChanged;
DualScreenService.StopWatchingForChangesOnLayout(_layout, _watchHandle);
_watchHandle = null;
}
}
@ -112,8 +96,6 @@ namespace Xamarin.Forms.DualScreen
return;
}
CheckIsPlatformEnabled();
var screenPosition = DualScreenService.GetLocationOnScreen(_layout);
if (screenPosition == null)
return;
@ -127,15 +109,6 @@ namespace Xamarin.Forms.DualScreen
}
}
void OnDeviceInfoChanged(object sender, PropertyChangedEventArgs args)
{
CheckIsPlatformEnabled();
if (args.PropertyName == nameof(DualScreenService.DeviceInfo.CurrentOrientation))
{
UpdateLayouts();
}
}
public bool IsLandscape
{
get => DualScreenService.IsLandscape;
@ -195,7 +168,7 @@ namespace Xamarin.Forms.DualScreen
Rectangle containerArea;
if (_layout == null)
{
containerArea = new Rectangle(Point.Zero, DualScreenService.DeviceInfo.ScaledScreenSize);
containerArea = new Rectangle(Point.Zero, DualScreenService.ScaledScreenSize);
}
else
{
@ -210,7 +183,7 @@ namespace Xamarin.Forms.DualScreen
Rectangle containerArea;
if (_layout == null)
{
containerArea = new Rectangle(Point.Zero, DualScreenService.DeviceInfo.ScaledScreenSize);
containerArea = new Rectangle(Point.Zero, DualScreenService.ScaledScreenSize);
}
else
{
@ -227,8 +200,6 @@ namespace Xamarin.Forms.DualScreen
internal void UpdateLayouts()
{
CheckIsPlatformEnabled();
Rectangle containerArea = GetContainerArea();
if (containerArea.Width <= 0)
{

Просмотреть файл

@ -23,7 +23,14 @@ namespace Xamarin.Forms.Platform.Android
void ElementMeasureInvalidated(object sender, System.EventArgs e)
{
RequestLayout();
if (this.IsAlive())
{
RequestLayout();
}
else if(sender is VisualElement ve)
{
ve.MeasureInvalidated -= ElementMeasureInvalidated;
}
}
internal IVisualElementRenderer VisualElementRenderer => Content;