Skip to content

Commit

Permalink
IndicatorView IndicatorTemplate Binding (#19004)
Browse files Browse the repository at this point in the history
* IndicatorView IndicatorTemplate Binding

* Update to use BindableLayout

* Rename method

* Add tests, replaced event with command

* Update IndicatorStackLayout.cs

* Fix compilation issue

---------

Co-authored-by: Rui Marinho <[email protected]>
  • Loading branch information
VladislavAntonyuk and rmarinho authored Apr 5, 2024
1 parent 7c7f512 commit 6f5ca12
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,14 @@
x:Name="indicatorView"
IndicatorColor="LightGray"
SelectedIndicatorColor="DarkGray"
HorizontalOptions="Center" />
HorizontalOptions="Center">
<IndicatorView.IndicatorTemplate>
<DataTemplate>
<Label
Text="{Binding }"/>
</DataTemplate>
</IndicatorView.IndicatorTemplate>
</IndicatorView>
</StackLayout>
</Grid>
</views:BasePage.Content>
Expand Down
93 changes: 46 additions & 47 deletions src/Controls/src/Core/IndicatorView/IndicatorStackLayout.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,35 @@
#nullable disable
using System;
using System.Collections;
using System.Collections.Specialized;
using System.ComponentModel;
using Microsoft.Maui.Controls.Shapes;
using Microsoft.Maui.Graphics;

namespace Microsoft.Maui.Controls
{
internal class IndicatorStackLayout : StackLayout
{
IndicatorView _indicatorView;
readonly IndicatorView _indicatorView;
public IndicatorStackLayout(IndicatorView indicatorView)
{
_indicatorView = indicatorView;
Orientation = StackOrientation.Horizontal;
_indicatorView.PropertyChanged += _indicatorViewPropertyChanged;
_indicatorView.PropertyChanged += IndicatorViewPropertyChanged;
}

void _indicatorViewPropertyChanged(object sender, PropertyChangedEventArgs e)
protected override void OnChildAdded(Element child)
{
base.OnChildAdded(child);

if (child is View view)
{
var tapGestureRecognizer = new TapGestureRecognizer
{
Command = new Command(sender => _indicatorView.Position = Children.IndexOf(sender)),
CommandParameter = view
};
view.GestureRecognizers.Add(tapGestureRecognizer);
}
}

void IndicatorViewPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == IndicatorView.IndicatorsShapeProperty.PropertyName
|| e.PropertyName == IndicatorView.IndicatorTemplateProperty.PropertyName)
Expand Down Expand Up @@ -53,8 +65,7 @@ internal void ResetIndicators()
try
{
BatchBegin();
Children.Clear();
AddExtraIndicatorItems();
BindIndicatorItems();
}
finally
{
Expand All @@ -75,11 +86,10 @@ internal void ResetIndicatorCount(int oldCount)

if (oldCount > _indicatorView.Count)
{
RemoveRedundantIndicatorItems();
return;
}

AddExtraIndicatorItems();
BindIndicatorItems();
}
finally
{
Expand All @@ -100,6 +110,10 @@ void ResetIndicatorStylesNonBatch()
var selectedIndex = position >= maxVisible ? maxVisible - 1 : position;
bool isSelected = index == selectedIndex;
var visualElement = Children[index] as VisualElement;
if (visualElement is null)
{
return;
}

visualElement.BackgroundColor = isSelected
? GetColorOrDefault(_indicatorView.SelectedIndicatorColor, Colors.Gray)
Expand All @@ -115,49 +129,34 @@ void ResetIndicatorStylesNonBatch()
IsVisible = indicatorCount > 1 || !_indicatorView.HideSingle;
}

Color GetColorOrDefault(Color color, Color defaultColor) => color ?? defaultColor;
Color GetColorOrDefault(Color? color, Color defaultColor) => color ?? defaultColor;

void AddExtraIndicatorItems()
void BindIndicatorItems()
{
var indicatorCount = _indicatorView.Count;
var indicatorMaximumVisible = _indicatorView.MaximumVisible;
var indicatorSize = _indicatorView.IndicatorSize;
var indicatorTemplate = _indicatorView.IndicatorTemplate;

var oldCount = Children.Count;
for (var i = 0; i < indicatorCount - oldCount && i < indicatorMaximumVisible - oldCount; i++)
var indicatorSize = _indicatorView.IndicatorSize > 0 ? _indicatorView.IndicatorSize : 10;
var indicatorTemplate = _indicatorView.IndicatorTemplate ??= new DataTemplate(() => new Border
{
var size = indicatorSize > 0 ? indicatorSize : 10;
var indicator = indicatorTemplate?.CreateContent() as View ?? new Frame
Padding = 0,
VerticalOptions = LayoutOptions.Center,
HorizontalOptions = LayoutOptions.Center,
WidthRequest = indicatorSize,
HeightRequest = indicatorSize,
StrokeShape = new RoundRectangle()
{
Padding = 0,
HasShadow = false,
BorderColor = Colors.Transparent,
VerticalOptions = LayoutOptions.Center,
HorizontalOptions = LayoutOptions.Center,
WidthRequest = size,
HeightRequest = size,
CornerRadius = _indicatorView.IndicatorsShape == IndicatorShape.Circle ? (float)size / 2 : 0
};
var tapGestureRecognizer = new TapGestureRecognizer();
tapGestureRecognizer.Tapped += (sender, args) => _indicatorView.Position = Children.IndexOf(sender as View);
indicator.GestureRecognizers.Add(tapGestureRecognizer);
Children.Add(indicator);
}
}

void RemoveRedundantIndicatorItems()
{
var indicatorCount = _indicatorView.Count;
while (Children.Count > indicatorCount)
{
Children.RemoveAt(0);
}
CornerRadius = _indicatorView.IndicatorsShape == IndicatorShape.Circle
? (float)indicatorSize / 2
: 0,
Stroke = Colors.Transparent
}
});

BindableLayout.SetItemsSource(this, _indicatorView.ItemsSource);
BindableLayout.SetItemTemplate(this, indicatorTemplate);
}

public void Remove()
{
_indicatorView.PropertyChanged -= _indicatorViewPropertyChanged;
_indicatorView.PropertyChanged -= IndicatorViewPropertyChanged;
}
}
}
}
54 changes: 54 additions & 0 deletions src/Controls/tests/Core.UnitTests/IndicatorViewLayoutTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System.Collections.Generic;
using Xunit;

namespace Microsoft.Maui.Controls.Core.UnitTests
{
public class IndicatorViewTests : BaseTestFixture
{
[Fact]
public void IndicatorStackLayoutNoItems_ResetIndicators_ShouldHaveNoChildren()
{
// Arrange
var indicatorView = new IndicatorView();
var indicatorStackLayout = new IndicatorStackLayout(indicatorView);

// Act
indicatorStackLayout.ResetIndicators();

// Assert
Assert.Empty(indicatorStackLayout.Children);
}

[Fact]
public void IndicatorStackLayoutWithItems_ResetIndicators_ShouldBindChildren()
{
// Arrange
var indicatorView = new IndicatorView() { ItemsSource = new List<string>{"item1", "item2"} };
var indicatorStackLayout = new IndicatorStackLayout(indicatorView);

// Act
indicatorStackLayout.ResetIndicators();

// Assert
Assert.Equal(2, indicatorStackLayout.Children.Count);
}

[Theory]
[InlineData(1, 2)]
[InlineData(0, 2)]
[InlineData(-2, 2)]
public void IndicatorStackLayout_ResetIndicatorCount_ShouldBindChildren(int oldCount, int expected)
{
// Arrange
var indicatorView = new IndicatorView() { ItemsSource = new List<string>{"item1", "item2"} };
var indicatorStackLayout = new IndicatorStackLayout(indicatorView);
Assert.Empty(indicatorStackLayout.Children);

// Act
indicatorStackLayout.ResetIndicatorCount(oldCount);

// Assert
Assert.Equal(expected, indicatorStackLayout.Children.Count);
}
}
}

0 comments on commit 6f5ca12

Please sign in to comment.