Skip to content

Commit

Permalink
Improved templated indicator view
Browse files Browse the repository at this point in the history
  • Loading branch information
kubaflo committed Sep 15, 2024
1 parent 3de5be9 commit 0386734
Show file tree
Hide file tree
Showing 7 changed files with 212 additions and 2 deletions.
24 changes: 24 additions & 0 deletions src/Controls/src/Core/IndicatorView/IndicatorStackLayout.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.ComponentModel;
using Microsoft.Maui.Controls.Shapes;
using Microsoft.Maui.Graphics;
Expand All @@ -7,6 +8,8 @@ namespace Microsoft.Maui.Controls
internal class IndicatorStackLayout : StackLayout
{
readonly IndicatorView _indicatorView;
internal event EventHandler<(IndicatorView, double, double)>? TemplateSizeChanged;

public IndicatorStackLayout(IndicatorView indicatorView)
{
_indicatorView = indicatorView;
Expand All @@ -20,6 +23,27 @@ protected override void OnChildAdded(Element child)

if (child is View view)
{
if(!_indicatorView.templatedItemWidth.HasValue || !_indicatorView.templatedItemHeight.HasValue)
{
NotifyTemplateSizeChanged();

void OnViewSizeChanged(object? sender, EventArgs e)
{
if(sender is View view)
{
NotifyTemplateSizeChanged();
view.SizeChanged -= OnViewSizeChanged;
}
}

void NotifyTemplateSizeChanged()
{
var width = Math.Max(view.WidthRequest, view.Width) + view.Margin.Left + view.Margin.Right;
var height = Math.Max(view.HeightRequest, view.Height) + view.Margin.Top + view.Margin.Bottom;
TemplateSizeChanged?.Invoke(this, (_indicatorView, width, height));
}
}

var tapGestureRecognizer = new TapGestureRecognizer
{
Command = new Command(sender => _indicatorView.Position = Children.IndexOf(sender)),
Expand Down
29 changes: 27 additions & 2 deletions src/Controls/src/Core/IndicatorView/IndicatorView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@ public IEnumerable ItemsSource

IPlatformSizeService _platformSizeService;

internal double? templatedItemWidth;
internal double? templatedItemHeight;

protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
{
if (IndicatorTemplate == null)
Expand All @@ -150,15 +153,35 @@ static void UpdateIndicatorLayout(IndicatorView indicatorView, object newValue)
{
if (newValue != null)
{
indicatorView.IndicatorLayout = new IndicatorStackLayout(indicatorView) { Spacing = DefaultPadding };
IndicatorStackLayout indicatorStackLayout = new IndicatorStackLayout(indicatorView) { Spacing = DefaultPadding };
indicatorView.IndicatorLayout = indicatorStackLayout;
indicatorStackLayout.TemplateSizeChanged += OnTemplateSizeChanged;
}
else if (indicatorView.IndicatorLayout is not null)
{
(indicatorView.IndicatorLayout as IndicatorStackLayout)?.Remove();
var indicatorStackLayout = indicatorView.IndicatorLayout as IndicatorStackLayout;
indicatorStackLayout.TemplateSizeChanged -= OnTemplateSizeChanged;
indicatorStackLayout?.Remove();
indicatorView.IndicatorLayout = null;
}
}

static void OnTemplateSizeChanged(object sender, (IndicatorView indicatorView, double width, double height) args)
{
args.indicatorView.templatedItemWidth = args.width;
args.indicatorView.templatedItemHeight = args.height;
SetTemplatedIndicatorSize(args.indicatorView);
}

static void SetTemplatedIndicatorSize(IndicatorView indicatorView)
{
if (indicatorView.IndicatorLayout is null || !indicatorView.templatedItemWidth.HasValue || !indicatorView.templatedItemHeight.HasValue)
return;

indicatorView.WidthRequest = indicatorView.templatedItemWidth.Value * indicatorView.Count + DefaultPadding * (indicatorView.Count - 1);
indicatorView.HeightRequest = indicatorView.templatedItemHeight.Value;
}

void ResetItemsSource(IEnumerable oldItemsSource)
{
if (oldItemsSource is INotifyCollectionChanged oldCollection)
Expand All @@ -177,6 +200,7 @@ void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
if (sender is ICollection collection)
{
Count = collection.Count;
SetTemplatedIndicatorSize(this);
return;
}
var count = 0;
Expand All @@ -186,6 +210,7 @@ void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
count++;
}
Count = count;
SetTemplatedIndicatorSize(this);
}

Paint IIndicatorView.IndicatorColor => IndicatorColor?.AsPaint();
Expand Down
80 changes: 80 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue21980.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Maui.Controls.Sample.Issues.Issue21980">
<ContentPage.Resources>
<Style x:Key="IndicatorViewTemplateStyle" TargetType="Image">
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal">
<VisualState.Setters>
<Setter Property="Source" Value="indicator_unselected" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Selected">
<VisualState.Setters>
<Setter Property="Source" Value="indicator_selected" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
</ContentPage.Resources>
<VerticalStackLayout>
<Grid>
<CarouselView
ItemsSource="{Binding Images}"
IndicatorView="IndicatorView"
HorizontalScrollBarVisibility="Never"
BackgroundColor="#ececec"
WidthRequest="300"
HeightRequest="450"
Grid.Row="0">
<CarouselView.ItemTemplate>
<DataTemplate>
<Grid>
<Image
Source="{Binding .}"
WidthRequest="300"
HeightRequest="450"/>
</Grid>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
<Border
HorizontalOptions="Center"
VerticalOptions="End"
Margin="16"
Padding="3"
BackgroundColor="White"
StrokeShape="RoundRectangle 12"
StrokeThickness="0"
Grid.Row="0">
<IndicatorView
x:Name="IndicatorView"
IndicatorColor="Transparent"
SelectedIndicatorColor="Transparent"
Padding="0">
<IndicatorView.IndicatorTemplate>
<DataTemplate>
<Image
HeightRequest="25"
WidthRequest="25"
Margin="0"
Style="{StaticResource IndicatorViewTemplateStyle}" />
</DataTemplate>
</IndicatorView.IndicatorTemplate>
</IndicatorView>
</Border>
</Grid>
<Button
AutomationId="button"
Text="Change Source"
Command="{Binding ChangeSourceCommand}"
HorizontalOptions="Center"
WidthRequest="200"
Margin="0,16"/>
</VerticalStackLayout>
</ContentPage>
56 changes: 56 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue21980.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace Maui.Controls.Sample.Issues;

[XamlCompilation(XamlCompilationOptions.Compile)]
[Issue(IssueTracker.Github, 21980, "IndicatorView with DataTemplate does not render correctly", PlatformAffected.All)]
public partial class Issue21980 : ContentPage
{
public Issue21980()
{
InitializeComponent();
BindingContext = new Issue21980ViewModel();
}
}

public class Issue21980ViewModel : INotifyPropertyChanged
{
private readonly IReadOnlyList<string> _images1 = new List<string>
{
"dotnet_bot.png",
"dotnet_bot.png",
};

private readonly IReadOnlyList<string> _images2 = new List<string>()
{
"dotnet_bot.png",
"dotnet_bot.png",
"dotnet_bot.png",
"dotnet_bot.png",
};

private IReadOnlyList<string> _images = [];
public IReadOnlyList<string> Images
{
get => _images;
set
{
_images = value;
OnPropertyChanged();
}
}

public Command ChangeSourceCommand { get; set; }

public Issue21980ViewModel()
{
Images = _images1;
ChangeSourceCommand = new Command(() => Images = Images?.Count != _images1.Count ? _images1 : _images2);
}

public event PropertyChangedEventHandler PropertyChanged;

void OnPropertyChanged([CallerMemberName] string propertyName = "") =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using NUnit.Framework;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.TestCases.Tests.Issues
{
public class Issue21980 : _IssuesUITest
{
public override string Issue => "IndicatorView with DataTemplate does not render correctly";

public Issue21980(TestDevice testDevice) : base(testDevice)
{
}

[Test]
[Category(UITestCategories.IndicatorView)]
public void IndicatorViewShouldRenderCorrectly()
{
App.WaitForElement("button");
App.Click("button");

VerifyScreenshot();
}
}
}

0 comments on commit 0386734

Please sign in to comment.