0
votes

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?

https://forums.xamarin.com/discussion/81114/how-to-create-a-custom-controls-with-multiple-view-on-top-of-each-other

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="&#60;" 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;
        }
    }
1
It looks like one content is sitting on top of other and captures the input. Can you hide the ContentView which is currently in the back when animation ends? If I saw how you do your "flip" may be I could help more. If I want to reproduce - Is TemplatedView derived from ContentPage? Are BackContent and FrontContent Layouts with buttons?Yuri S
@YuriS I just updated with the information you requested. as well, both view contains a list of buttons that toggle on tap event.rpresley
Is xaml provide above for PanelView ? I cannot get buttons on the screen. I see empty screen. Looks like Content="{TemplateBinding FrontContent}" binding doesn't work for me.Yuri S
@YuriS The xaml above is the template of the control itself. this control takes 2 seperate views(it can have any kind of input) with a list of buttons on them. if taped it will flip. that is why you see blank on your screen. I can show you are main xaml page example.rpresley
please provide all necessary code including layouts with buttons and main page so I don't have to write it myself then I will be able to debug it quickly and help you. And side question, why you are not using ControlTempate as recommended here developer.xamarin.com/guides/xamarin-forms/templates/…Yuri S

1 Answers

0
votes

As I said "something" is covering your button. Here is the problematic declaration

  <AbsoluteLayout>
    <ContentView Content="{TemplateBinding BackContent}" BackgroundColor="Transparent" AbsoluteLayout.LayoutBounds="1,1,1,1" AbsoluteLayout.LayoutFlags="WidthProportional,HeightProportional,PositionProportional" AnchorX="0.5" AnchorY="0.5"></ContentView>
    <ContentView Content="{TemplateBinding FrontContent}" BackgroundColor="Transparent" AbsoluteLayout.LayoutBounds="1,1,1,1" AbsoluteLayout.LayoutFlags="WidthProportional,HeightProportional,PositionProportional" AnchorX="0.5" AnchorY="0.5"></ContentView>
  </AbsoluteLayout>

Your FrontContent ContentView covers BackContent ContentView. When you rotate and hide content you actually working with buttons not views. I found this design a little more complicated than it should be but regardless that was not the question. Below is the solution. When you hide you content (meaning button) also hide your Parent view and show it when you show the button.

private async void SwitchCurrentView()
{
    if (_isFrontView)
    {
        BackContent.IsVisible = true;
       ((ContentView)BackContent.Parent).IsVisible = 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;
        ((ContentView)FrontContent.Parent).IsVisible = false;//******************

        BackContentAppeared?.Invoke(this, EventArgs.Empty);
    }
    else
    {
        FrontContent.IsVisible = true;
        ((ContentView)FrontContent.Parent).IsVisible = 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;
        ((ContentView)BackContent.Parent).IsVisible = false;

        FrontContentAppeared?.Invoke(this, EventArgs.Empty);
    }

    _isFrontView = !_isFrontView;
    SwitchView = false;
}