flipping card update

Eduardo Gomez Romero 1,315 Reputation points
2025-03-05T23:41:45.6166667+00:00

I create a live tile

using System.Diagnostics;

namespace LiveTileControl.Controls;

public partial class LiveTileControl : ContentView {

    private bool _isFlipping;

    public LiveTileControl() {
        InitializeComponent();
        UpdateTitlePosition();
        UpdateTileSize();

        // Initial states
        FrontView.IsVisible = true;
        FrontView.RotationY = 0;

        BackView.IsVisible = false;
        BackView.RotationY = 0;

    }

    #region title properties

    public static readonly BindableProperty TitleProperty = BindableProperty.Create(
        nameof(Title),
        typeof(string),
        typeof(LiveTileControl));

    public string Title {
        get => (string)GetValue(TitleProperty);
        set => SetValue(TitleProperty, value);
    }

    public static readonly BindableProperty TitleFontSizeProperty = BindableProperty.Create(
        nameof(TitleFontSize),
        typeof(int),
        typeof(LiveTileControl), 14);

    public int TitleFontSize {
        get => (int)GetValue(TitleFontSizeProperty);
        set => SetValue(TitleFontSizeProperty, value);
    }

    public static readonly BindableProperty TitleFontColorProperty = BindableProperty.Create(
        nameof(TitleFontColor),
        typeof(Color),
        typeof(LiveTileControl));

    public Color TitleFontColor {
        get => (Color)GetValue(TitleFontColorProperty);
        set => SetValue(TitleFontColorProperty, value);
    }

    public static readonly BindableProperty TitleFontFamilyProperty = BindableProperty.Create(
        nameof(TitleFontFamily),
        typeof(string),
        typeof(LiveTileControl),
        "Default");

    public string TitleFontFamily {
        get => (string)GetValue(TitleFontFamilyProperty);
        set => SetValue(TitleFontFamilyProperty, value);
    }

    public static readonly BindableProperty TitleFontAttributesProperty = BindableProperty.Create(
        nameof(TitleFontAttributes),
        typeof(FontAttributes),
        typeof(LiveTileControl));

    public FontAttributes TitleFontAttributes {
        get => (FontAttributes)GetValue(TitleFontAttributesProperty);
        set => SetValue(TitleFontAttributesProperty, value);
    }

    public static readonly BindableProperty TitlePositionProperty = BindableProperty.Create(
        nameof(TitlePosition),
        typeof(TitlePositions),
        typeof(LiveTileControl),
        TitlePositions.LowerLeft, propertyChanged: OnTitlePositionChanged);

    private static void OnTitlePositionChanged(BindableObject bindable, object oldValue, object newValue) {
        if(bindable is LiveTileControl control && newValue is TitlePositions) {
            control.UpdateTitlePosition();
        }
    }

    private void UpdateTitlePosition() {
        switch(TitlePosition) {
            case TitlePositions.UpperLeft:
                TitleLabel.HorizontalOptions = LayoutOptions.Start;
                TitleLabel.VerticalOptions = LayoutOptions.Start;
                break;
            case TitlePositions.UpperRight:
                TitleLabel.HorizontalOptions = LayoutOptions.End;
                TitleLabel.VerticalOptions = LayoutOptions.Start;
                break;
            case TitlePositions.LowerLeft:
                TitleLabel.HorizontalOptions = LayoutOptions.Start;
                TitleLabel.VerticalOptions = LayoutOptions.End;
                break;
            case TitlePositions.LowerRight:
                TitleLabel.HorizontalOptions = LayoutOptions.End;
                TitleLabel.VerticalOptions = LayoutOptions.End;
                break;
            case TitlePositions.Center:
                TitleLabel.HorizontalOptions = LayoutOptions.Center;
                TitleLabel.VerticalOptions = LayoutOptions.Center;
                break;
            default:
                Debug.WriteLine("Unknown TitlePosition");
                break;
        }
    }

    public TitlePositions TitlePosition {
        get => (TitlePositions)GetValue(TitlePositionProperty);
        set => SetValue(TitlePositionProperty, value);
    }

    public static readonly BindableProperty TitleMarginProperty = BindableProperty.Create(
        nameof(TitleMargin), typeof(Thickness),
        typeof(LiveTileControl),
        new Thickness(5, 0, 0, 5));

    public Thickness TitleMargin {
        get => (Thickness)GetValue(TitleMarginProperty);
        set => SetValue(TitleMarginProperty, value);
    }

    public enum TitlePositions {
        UpperLeft,
        UpperRight,
        LowerLeft,
        LowerRight,
        Center
    }

    #endregion

    #region contols properties

    public static readonly BindableProperty TileWidthProperty = BindableProperty.Create(
        nameof(TileWidth),
        typeof(int),
        typeof(LiveTileControl));

    public int TileWidth {
        get => (int)GetValue(TileWidthProperty);
        set => SetValue(TileWidthProperty, value);
    }

    public static readonly BindableProperty TileHeightProperty = BindableProperty.Create(
        nameof(TileHeight),
        typeof(int),
        typeof(LiveTileControl));

    public int TileHeight {
        get => (int)GetValue(TileHeightProperty);
        set => SetValue(TileHeightProperty, value);
    }

    public static readonly BindableProperty TileTransparencyProperty = BindableProperty.Create(
        nameof(TileTransparency),
        typeof(double),
        typeof(LiveTileControl),
        propertyChanged: OnTransparencyChanged);

    public double TileTransparency {
        get => (double)GetValue(TileTransparencyProperty);
        set => SetValue(TileTransparencyProperty, value);
    }

    public static readonly BindableProperty TileColorProperty = BindableProperty.Create(
        nameof(TileColor),
        typeof(Color),
        typeof(LiveTileControl));

    public Color TileColor {
        get => (Color)GetValue(TileColorProperty);
        set => SetValue(TileColorProperty, value);
    }

    public static readonly BindableProperty TileSizeProperty = BindableProperty.Create(
    nameof(TileSize),
    typeof(TileSizes),
    typeof(LiveTileControl),
    TileSizes.Medium, // Default to Medium
    propertyChanged: OnTileSizeChanged);

    public TileSizes TileSize {
        get => (TileSizes)GetValue(TileSizeProperty);
        set => SetValue(TileSizeProperty, value);
    }

    public static readonly BindableProperty DataMemberProperty = BindableProperty.Create(
    nameof(DataMember),
    typeof(string),
    typeof(LiveTileControl),
    "No Data",
    propertyChanged: OnDataMemberChanged);

    private static void OnDataMemberChanged(BindableObject bindable, object oldValue, object newValue) {
        if(bindable is LiveTileControl control) {
            Debug.WriteLine("Data changed to: " + newValue);
            control.FlipTile(); // Automatically flip the tile when DataMember changes
        }
    }

    public string DataMember {
        get => (string)GetValue(DataMemberProperty);
        set => SetValue(DataMemberProperty, value);
    }

    public static readonly BindableProperty FlipDurationProperty = BindableProperty.Create(
        nameof(FlipDuration),
        typeof(int),
        typeof(LiveTileControl),
        500);

    public int FlipDuration {
        get => (int)GetValue(FlipDurationProperty);
        set => SetValue(FlipDurationProperty, value);
    }


    private static void OnTileSizeChanged(BindableObject bindable, object oldValue, object newValue) {
        if(bindable is LiveTileControl control && newValue is TileSizes) {
            control.UpdateTileSize();
        }
    }

    private static void OnTransparencyChanged(BindableObject bindable, object oldValue, object newValue) {
        if(bindable is LiveTileControl control && newValue is double transparency) {
            control.UpdateBackgroundTransparency(transparency);
        }
    }

    private void UpdateBackgroundTransparency(double transparency) {
        float alpha = (float)Math.Clamp(transparency / 100.0, 0.0, 1.0);
        var baseColor = TileColor ?? Colors.Transparent;
        TileColor = baseColor.WithAlpha(alpha);
    }

    private void UpdateTileSize() {
        switch(TileSize) {
            case TileSizes.Small:
                TileWidth = 71;
                TileHeight = 71;
                IconFontSize = 38;
                TitleLabel.IsVisible = false;
                break;
            case TileSizes.Medium:
                TileWidth = 150;
                TileHeight = 150;
                IconFontSize = 72;
                TitleLabel.IsVisible = true;
                break;
            case TileSizes.Wide:
                TileWidth = 310;
                TileHeight = 150;
                IconFontSize = 110;
                TitleLabel.IsVisible = true;
                break;
            case TileSizes.Large:
                TileWidth = 310;
                TileHeight = 310;
                IconFontSize = 132;
                TitleLabel.IsVisible = true;
                break;
            default:
                Debug.WriteLine("Unknown TileSize");
                break;
        }
    }

    public enum TileSizes {
        Small,    // Corresponds to Small tile size
        Medium,   // Corresponds to Medium tile size
        Wide,     // Corresponds to Wide tile size
        Large     // Corresponds to Large tile size
    }

    #endregion

    #region Icon Properties

    public static readonly BindableProperty IconGlyphProperty = BindableProperty.Create(
        nameof(IconGlyph),
        typeof(string),
        typeof(LiveTileControl));

    public string IconGlyph {
        get => (string)GetValue(IconGlyphProperty);
        set => SetValue(IconGlyphProperty, value);
    }

    public static readonly BindableProperty IconFontFamilyProperty = BindableProperty.Create(
        nameof(IconFontFamily),
        typeof(string),
        typeof(LiveTileControl));

    public string IconFontFamily {
        get => (string)GetValue(IconFontFamilyProperty);
        set => SetValue(IconFontFamilyProperty, value);
    }

    public static readonly BindableProperty IconFontSizeProperty = BindableProperty.Create(
        nameof(IconFontSize),
        typeof(double),
        typeof(LiveTileControl),
        24.0); // Default size

    public double IconFontSize {
        get => (double)GetValue(IconFontSizeProperty);
        set => SetValue(IconFontSizeProperty, value);
    }

    public static readonly BindableProperty IconFontColorProperty = BindableProperty.Create(
        nameof(IconFontColor),
        typeof(Color),
        typeof(LiveTileControl),
        Colors.Black); // Default color

    public Color IconFontColor {
        get => (Color)GetValue(IconFontColorProperty);
        set => SetValue(IconFontColorProperty, value);
    }

    #endregion


    private void FlipTile() {
        var frontToBackAnimation = new Animation(
            (f) => FrontView.RotationY = f,
            0, 90, Easing.Linear);

        var backToFrontAnimation = new Animation(
            (f) => BackView.RotationY = f,
            -90, 0, Easing.Linear);

        frontToBackAnimation.Commit(
            this,
            "FrontToBack",
            16,
            (uint)(FlipDuration / 2),
            Easing.Linear,
            (finished, canceled) => {
                // Visibility toggle logic
                FrontView.IsVisible = false;
                BackView.IsVisible = true;
                BackView.RotationY = -90; // Ensure back view rotation starts correctly

                // Commit back-to-front animation
                backToFrontAnimation.Commit(
                    this, "BackToFront", 16, (uint)(FlipDuration / 2), Easing.Linear);
            });
    }

    public async Task UpdateTileSafely(string newData) {
        if(_isFlipping) {
            return;
        }

        _isFlipping = true; // Lock flipping
        DataMember = newData; // Update the content displayed on the back of the tile
        FlipTile(); // Trigger the flip animation
        await Task.Delay(FlipDuration); // Wait for the animation to complete
        _isFlipping = false; // Unlock flipping
    }
}

   <Grid
       BackgroundColor="{Binding TileColor}"
       BindingContext="{Binding Source={Reference Tile}}"
       HeightRequest="{Binding TileHeight}"
       WidthRequest="{Binding TileWidth}">
       <!--  Front View  -->
       <Grid
           x:Name="FrontView"
           IsVisible="True">
           <Label
               FontFamily="{Binding IconFontFamily}"
               FontSize="{Binding IconFontSize}"
               HorizontalOptions="Center"
               Text="{Binding IconGlyph}"
               TextColor="{Binding IconFontColor}"
               VerticalOptions="Center" />
           <Label
               x:Name="TitleLabel"
               Margin="{Binding TitleMargin}"
               FontAttributes="{Binding TitleFontAttributes}"
               FontFamily="{Binding TitleFontFamily}"
               FontSize="{Binding TitleFontSize}"
               HorizontalOptions="Start"
               Text="{Binding Title}"
               TextColor="{Binding TitleFontColor}"
               VerticalOptions="Start" />
       </Grid>
       <!--  Back View  -->
       <Grid
           x:Name="BackView"
           BackgroundColor="{Binding TileColor}"
           IsVisible="False">
           <Label
               FontAttributes="{Binding TitleFontAttributes}"
               FontSize="{Binding TitleFontSize}"
               HorizontalOptions="Center"
               Text="{Binding DataMember}"
               TextColor="{Binding TitleFontColor}"
               VerticalOptions="Center" />
       </Grid>
   </Grid>

created the xaml in mainpage

 <control:LiveTileControl
     Title="My live tile"
     DataMember="{Binding Data}"
     IconFontColor="White"
     IconFontFamily="FluentUI"
     IconGlyph="{Static font:FluentUI.people_12_regular}"
     TileColor="Green"
     TileSize="Large"
     TileTransparency="10"
     TitleFontSize="14"
     TitlePosition="LowerLeft" />

and the viewModel

    public partial class MainPageViewModel : ObservableObject {

        private readonly ApiService _apiService;

        [ObservableProperty]
        private string _data;

        public MainPageViewModel(ApiService apiService) {

            _apiService = apiService;
            _data = "No data yet....";

            InitializeAsync();
        }

        private async void InitializeAsync() {
            await StartFetchingData();
        }

        private async Task StartFetchingData() {

            var apiUrl = "https://jsonplaceholder.typicode.com/posts/1";

            while(true) {

                try {

                    var newData = await _apiService.FetchAndFormatData(apiUrl);
                    Debug.WriteLine($"Fetched new data: {newData}");
                    Data = newData; // This should trigger the tile flip

                } catch(Exception e) {

                    Debug.WriteLine(e.Message);
                }

                await Task.Delay(30000);

            }
        }
    }
}

the problem is that the live tile, has to flip to the front and back every 500 milliseconds and it doesn't flip, I want it to flip when I receive new data.

But because I am in testing phase I want to flip contently betwen the front and back

rigth now it stays on the backview

https://reccloud.com/u/0ydjbni

.NET MAUI
.NET MAUI
A Microsoft open-source framework for building native device applications spanning mobile, tablet, and desktop.
3,982 questions
{count} votes

1 answer

Sort by: Most helpful
  1. Wenyan Zhang (Shanghai Wicresoft Co,.Ltd.) 35,996 Reputation points Microsoft External Staff
    2025-03-06T07:27:01.9966667+00:00

    Hello,

    Please modify the FlipTile method like the following:

    private void FlipTile()
    {
         var frontToBackAnimation = new Animation(
             (f) => this.RotationY = f,
             0, 90, Easing.Linear);
     
         var backToFrontAnimation = new Animation(
             (f) => this.RotationY = f,
             -90, 0, Easing.Linear);
     
         frontToBackAnimation.Commit(
             this,
             "FrontToBack",
             16,
             (uint)(FlipDuration / 2),
             Easing.Linear,
             (finished, canceled) => {
                 // Visibility toggle logic
                 FrontView.IsVisible = !FrontView.IsVisible;
                  BackView.IsVisible = !BackView.IsVisible;
                 // this.RotationY = -90; // Ensure back view rotation starts correctly
     
                // Commit back-to - front animation
                 backToFrontAnimation.Commit(
                            this, "BackToFront", 16, (uint)(FlipDuration / 2), Easing.Linear, (finished, canceled) =>
                            {
                                //If you remove the following, the card will go back next time
     
                                FrontView.IsVisible = !FrontView.IsVisible;
                               BackView.IsVisible = !BackView.IsVisible;
     
                            });
             });
    }
    

    Update

    //MyLiveTitleControl is the name of the control

    //<control:LiveTileControl x:Name="MyLiveTitleControl"

    public partial class MainPage : ContentPage {
        IDispatcherTimer timer;
        public MainPage(MainPageViewModel mainPageViewModel) {
            InitializeComponent();
            BindingContext = mainPageViewModel;
            timer = Dispatcher.CreateTimer();
            timer.Interval = TimeSpan.FromSeconds(10);
            MainThread.BeginInvokeOnMainThread(() =>
            {
                timer.Tick += async (s, e) =>
                {
                   await MyLiveTitleControl.UpdateTileSafely("test");
                };
            });
            timer.Start();
        }
     
    }
    

    Best Regards,

    Wenyan Zhang


    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment".

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.


Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.