I created a control in Xamarin forms that flips views(Like a card style). both these views have some sort of input (EX: List of buttons) that if you interact with will flip the "Card" control to the next view. I was able to get this working with Android, but when I test with IOS the controls seems to be disabled and I am not able to hit any events. Now I did solve a similar problem before by using the e.NativeView.UserInteractionEnabled property. The only issue is that this property can only be use when initializing the view, I want to be able to use something similar that is more dynamic.
A little progress on the IOS issue.
<?xml version="1.0" encoding="utf-8" ?>
<TemplatedView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="minto.qm.mobile.Views.Controls.PanelView">
<TemplatedView.ControlTemplate>
<ControlTemplate>
<AbsoluteLayout>
//the last item in this list will be the dominant control on runtime
//the other views input will not work
<ContentView Content="{TemplateBinding BackContent}" AbsoluteLayout.LayoutBounds="1,1,1,1" AbsoluteLayout.LayoutFlags="WidthProportional,HeightProportional,PositionProportional" AnchorX="0.5" AnchorY="0.5"></ContentView>
<ContentView Content="{TemplateBinding FrontContent}" AbsoluteLayout.LayoutBounds="1,1,1,1" AbsoluteLayout.LayoutFlags="WidthProportional,HeightProportional,PositionProportional" AnchorX="0.5" AnchorY="0.5"></ContentView>
</AbsoluteLayout>
</ControlTemplate>
</TemplatedView.ControlTemplate>
</TemplatedView>
Does anyone know of any functionality like this?
update:
here is the behind code for the control:
public partial class PanelView : TemplatedView
{
public event EventHandler FrontContentAppeared;
public event EventHandler BackContentAppeared;
public static readonly BindableProperty FrontContentProperty = BindableProperty.Create(nameof(FrontContent), typeof(View), typeof(PanelView), defaultValue: null, propertyChanged: OnFrontContentChanged);
public static readonly BindableProperty BackContentProperty = BindableProperty.Create(nameof(BackContent), typeof(View), typeof(PanelView), defaultValue: null, propertyChanged: OnBackContentChanged);
public static readonly BindableProperty SwitchViewProperty = BindableProperty.Create(nameof(SwitchView), typeof(bool), typeof(PanelView), defaultValue: false, propertyChanged: OnSwitchViewChanged);
private bool _isFrontView = true;
public PanelView()
{
InitializeComponent();
}
private async void SwitchCurrentView()
{
if (_isFrontView)
{
BackContent.IsVisible = true;
//BackContent.InputTransparent = true;
FrontContent.Unfocus();
BackContent.Focus();
await Task.WhenAll(
FrontContent.FadeTo(0, 500, Easing.Linear),
BackContent.FadeTo(1, 500, Easing.Linear),
this.RotateYTo(GetRotation(0, 180), 250, Easing.Linear)
);
FrontContent.IsVisible = false;
//FrontContent.InputTransparent = false;
BackContentAppeared?.Invoke(this, EventArgs.Empty);
}
else
{
FrontContent.IsVisible = true;
//FrontContent.InputTransparent = true;
FrontContent.Focus();
BackContent.Unfocus();
await Task.WhenAll(
FrontContent.FadeTo(1, 500, Easing.Linear),
BackContent.FadeTo(0, 500, Easing.Linear),
this.RotateYTo(GetRotation(180, 180), 250, Easing.Linear)
);
BackContent.IsVisible = false;
//BackContent.InputTransparent = false;
FrontContentAppeared?.Invoke(this, EventArgs.Empty);
}
_isFrontView = !_isFrontView;
SwitchView = false;
}
private static void OnFrontContentChanged(BindableObject bindable, object oldValue, object newValue)
{
var self = (PanelView)bindable;
self.SetFrontContentView((View)newValue);
}
private static void OnBackContentChanged(BindableObject bindable, object oldValue, object newValue)
{
var self = (PanelView)bindable;
self.SetBackContentView((View)newValue);
}
private static void OnSwitchViewChanged(BindableObject bindable, object oldValue, object newValue)
{
var self = (PanelView)bindable;
self.SwitchView = (bool)newValue;
if (self.SwitchView)
{
self.SwitchCurrentView();
}
}
private void SetFrontContentView(View view)
{
FrontContent = view;
if (!_isFrontView)
{
FrontContent.IsVisible = false;
view.FadeTo(0, 1, Easing.Linear);
}
}
private void SetBackContentView(View view)
{
view.FadeTo(0, 1, Easing.Linear);
view.RotateYTo(180, 1, Easing.Linear);
BackContent = view;
if (_isFrontView)
{
BackContent.IsVisible = false;
}
}
private double GetRotation(double start, double amount)
{
var rotation = (start + amount) % 360;
return rotation;
}
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
if (FrontContent != null)
{
SetInheritedBindingContext(FrontContent, BindingContext);
}
if (BackContent != null)
{
SetInheritedBindingContext(BackContent, BindingContext);
}
}
public View FrontContent
{
get { return (View)GetValue(FrontContentProperty); }
set { SetValue(FrontContentProperty, value); }
}
public View BackContent
{
get { return (View)GetValue(BackContentProperty); }
set { SetValue(BackContentProperty, value); }
}
public bool SwitchView
{
get { return (bool)GetValue(SwitchViewProperty); }
set { SetValue(SwitchViewProperty, value); }
}
}
the animation runs when the bindable bool variable SwitchView changes
private void OnRoomTypeTapped(object sender, GliderItemTappedEventArgs e)
{
if (e.IsSelected && e.IsSelected == e.LastSelection)
{
PanelView.SwitchView = true;
}
}
UPDATE:
Xaml Page example. note: (Base:ExtendedPage is a custom contentpage) and (controls:Glider is are custom view with list of buttons on them)
<?xml version="1.0" encoding="utf-8" ?>
<base:ExtendedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:minto.qm.mobile.Views.Controls;assembly=minto.qm.mobile"
xmlns:base="clr-namespace:minto.qm.mobile.Views.Pages.Base;assembly=minto.qm.mobile"
x:Class="minto.qm.mobile.Views.Pages.RoomPage"
Navigation="{Binding Navigation, Mode=OneWayToSource}"
Title="Room">
<base:ExtendedPage.Content>
<ScrollView>
<StackLayout Spacing="0">
<ContentView BackgroundColor="#3498DB" Padding="10">
<Label Text="{Binding SelectedRoomName}" FontAttributes="Bold" HorizontalTextAlignment="Center" VerticalTextAlignment="Center"></Label>
</ContentView>
<StackLayout Spacing="5" Padding="10, 20, 10, 10">
<controls:PanelView x:Name="PanelView">
<controls:PanelView.FrontContent>
<controls:Glider ItemsSource="{Binding RoomTypes}" DisplayProperty="Name" SelectedIndex="0" ItemSelected="OnRoomTypeSelected" ItemTapped="OnRoomTypeTapped" Orientation="Vertical" Lines="3" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand"></controls:Glider>
</controls:PanelView.FrontContent>
<controls:PanelView.BackContent>
<controls:Glider ItemsSource="{Binding Rooms}" DisplayProperty="Name" SelectedIndex="0" ItemSelected="OnRoomSelected" ItemTapped="OnRoomTapped" Orientation="Horizontal" Lines="5" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand"></controls:Glider>
</controls:PanelView.BackContent>
</controls:PanelView>
<StackLayout Orientation="Horizontal">
<Label Text="Room annotation:" FontAttributes="Bold" Margin="5, 0, 0, 0" VerticalTextAlignment="Center"></Label>
<Button Text="<" FontAttributes="Bold" HorizontalOptions="EndAndExpand" VerticalOptions="Center" BackgroundColor="Transparent" WidthRequest="50" HeightRequest="50" Clicked="OnExpandButtonClicked" FontSize="Medium" AnchorX="0.5" AnchorY="0.5"></Button>
</StackLayout>
<Entry x:Name="AnnotationEditor" HorizontalOptions="FillAndExpand" BackgroundColor="#4D4D4D" TextChanged="Entry_OnTextChanged"/>
<Button Text="Next" Command="{Binding NextPageCommand}" HorizontalOptions="FillAndExpand" />
</StackLayout>
</StackLayout>
</ScrollView>
</base:ExtendedPage.Content>
<base:ExtendedPage.Overlay>
<controls:Tombstone Token="{Binding DeficiencyToken}" BackgroundColor="#444444"></controls:Tombstone>
</base:ExtendedPage.Overlay>
<base:ExtendedPage.ToolbarItems>
<ToolbarItem Text="Details" Order="Primary" Priority="0" Clicked="DetailsClicked"></ToolbarItem>
</base:ExtendedPage.ToolbarItems>
</base:ExtendedPage>
public partial class RoomPage : ExtendedPage
{
public ViewModels.Pages.RoomPage ViewModel => _vm ?? (_vm = BindingContext as ViewModels.Pages.RoomPage);
private ViewModels.Pages.RoomPage _vm;
private bool _buttonExpanded;
public RoomPage()
{
InitializeComponent();
BindingContext = new ViewModels.Pages.RoomPage();
}
protected override void OnAppearing()
{
base.OnAppearing();
ViewModel.OnAppearing();
HandleCaching();
}
private async void HandleCaching()
{
await Task.Run(() =>
{
var pageCache = Services.Caches.Pages.GetInstance();
pageCache.Preload(nameof(InspectionGalleryPage), new InspectionGalleryPage());
});
}
private void DetailsClicked(object sender, EventArgs e)
{
ShowOverlay = !ShowOverlay;
}
private void Entry_OnTextChanged(object sender, TextChangedEventArgs e)
{
ViewModel.RoomAnnotations = e.NewTextValue;
}
private void OnRoomTypeSelected(object sender, GliderItemSelectedEventArgs e)
{
ViewModel.SelectedRoomTypeName = e.Item.ToString();
}
private void OnRoomTypeTapped(object sender, GliderItemTappedEventArgs e)
{
if (e.IsSelected && e.IsSelected == e.LastSelection)
{
PanelView.SwitchView = true;
}
}
private void OnRoomSelected(object sender, GliderItemSelectedEventArgs e)
{
ViewModel.SelectedRoomName = e.Item.ToString();
}
private void OnRoomTapped(object sender, GliderItemTappedEventArgs e)
{
if (e.IsSelected && e.IsSelected == e.LastSelection)
{
PanelView.SwitchView = true;
}
}
private void AnimateHeight(View view, double end)
{
view.Animate("Expander", value => view.HeightRequest = value, view.Height, end, 2, 250, Easing.Linear);
}
private void OnExpandButtonClicked(object sender, EventArgs e)
{
var button = (Button) sender;
if (_buttonExpanded)
{
button.RotateTo(0, 250, Easing.Linear);
AnimateHeight(AnnotationEditor, 45.5);
}
else
{
button.RotateTo(-90, 250, Easing.Linear);
AnimateHeight(AnnotationEditor, 300);
}
_buttonExpanded = !_buttonExpanded;
}
}