Skip to content

Commit 6f5ca12

Browse files
IndicatorView IndicatorTemplate Binding (#19004)
* 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]>
1 parent 7c7f512 commit 6f5ca12

File tree

3 files changed

+108
-48
lines changed

3 files changed

+108
-48
lines changed

src/Controls/samples/Controls.Sample/Pages/Controls/IndicatorPage.xaml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,14 @@
121121
x:Name="indicatorView"
122122
IndicatorColor="LightGray"
123123
SelectedIndicatorColor="DarkGray"
124-
HorizontalOptions="Center" />
124+
HorizontalOptions="Center">
125+
<IndicatorView.IndicatorTemplate>
126+
<DataTemplate>
127+
<Label
128+
Text="{Binding }"/>
129+
</DataTemplate>
130+
</IndicatorView.IndicatorTemplate>
131+
</IndicatorView>
125132
</StackLayout>
126133
</Grid>
127134
</views:BasePage.Content>

src/Controls/src/Core/IndicatorView/IndicatorStackLayout.cs

Lines changed: 46 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,35 @@
1-
#nullable disable
2-
using System;
3-
using System.Collections;
4-
using System.Collections.Specialized;
51
using System.ComponentModel;
2+
using Microsoft.Maui.Controls.Shapes;
63
using Microsoft.Maui.Graphics;
74

85
namespace Microsoft.Maui.Controls
96
{
107
internal class IndicatorStackLayout : StackLayout
118
{
12-
IndicatorView _indicatorView;
9+
readonly IndicatorView _indicatorView;
1310
public IndicatorStackLayout(IndicatorView indicatorView)
1411
{
1512
_indicatorView = indicatorView;
1613
Orientation = StackOrientation.Horizontal;
17-
_indicatorView.PropertyChanged += _indicatorViewPropertyChanged;
14+
_indicatorView.PropertyChanged += IndicatorViewPropertyChanged;
1815
}
1916

20-
void _indicatorViewPropertyChanged(object sender, PropertyChangedEventArgs e)
17+
protected override void OnChildAdded(Element child)
18+
{
19+
base.OnChildAdded(child);
20+
21+
if (child is View view)
22+
{
23+
var tapGestureRecognizer = new TapGestureRecognizer
24+
{
25+
Command = new Command(sender => _indicatorView.Position = Children.IndexOf(sender)),
26+
CommandParameter = view
27+
};
28+
view.GestureRecognizers.Add(tapGestureRecognizer);
29+
}
30+
}
31+
32+
void IndicatorViewPropertyChanged(object? sender, PropertyChangedEventArgs e)
2133
{
2234
if (e.PropertyName == IndicatorView.IndicatorsShapeProperty.PropertyName
2335
|| e.PropertyName == IndicatorView.IndicatorTemplateProperty.PropertyName)
@@ -53,8 +65,7 @@ internal void ResetIndicators()
5365
try
5466
{
5567
BatchBegin();
56-
Children.Clear();
57-
AddExtraIndicatorItems();
68+
BindIndicatorItems();
5869
}
5970
finally
6071
{
@@ -75,11 +86,10 @@ internal void ResetIndicatorCount(int oldCount)
7586

7687
if (oldCount > _indicatorView.Count)
7788
{
78-
RemoveRedundantIndicatorItems();
7989
return;
8090
}
8191

82-
AddExtraIndicatorItems();
92+
BindIndicatorItems();
8393
}
8494
finally
8595
{
@@ -100,6 +110,10 @@ void ResetIndicatorStylesNonBatch()
100110
var selectedIndex = position >= maxVisible ? maxVisible - 1 : position;
101111
bool isSelected = index == selectedIndex;
102112
var visualElement = Children[index] as VisualElement;
113+
if (visualElement is null)
114+
{
115+
return;
116+
}
103117

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

118-
Color GetColorOrDefault(Color color, Color defaultColor) => color ?? defaultColor;
132+
Color GetColorOrDefault(Color? color, Color defaultColor) => color ?? defaultColor;
119133

120-
void AddExtraIndicatorItems()
134+
void BindIndicatorItems()
121135
{
122-
var indicatorCount = _indicatorView.Count;
123-
var indicatorMaximumVisible = _indicatorView.MaximumVisible;
124-
var indicatorSize = _indicatorView.IndicatorSize;
125-
var indicatorTemplate = _indicatorView.IndicatorTemplate;
126-
127-
var oldCount = Children.Count;
128-
for (var i = 0; i < indicatorCount - oldCount && i < indicatorMaximumVisible - oldCount; i++)
136+
var indicatorSize = _indicatorView.IndicatorSize > 0 ? _indicatorView.IndicatorSize : 10;
137+
var indicatorTemplate = _indicatorView.IndicatorTemplate ??= new DataTemplate(() => new Border
129138
{
130-
var size = indicatorSize > 0 ? indicatorSize : 10;
131-
var indicator = indicatorTemplate?.CreateContent() as View ?? new Frame
139+
Padding = 0,
140+
VerticalOptions = LayoutOptions.Center,
141+
HorizontalOptions = LayoutOptions.Center,
142+
WidthRequest = indicatorSize,
143+
HeightRequest = indicatorSize,
144+
StrokeShape = new RoundRectangle()
132145
{
133-
Padding = 0,
134-
HasShadow = false,
135-
BorderColor = Colors.Transparent,
136-
VerticalOptions = LayoutOptions.Center,
137-
HorizontalOptions = LayoutOptions.Center,
138-
WidthRequest = size,
139-
HeightRequest = size,
140-
CornerRadius = _indicatorView.IndicatorsShape == IndicatorShape.Circle ? (float)size / 2 : 0
141-
};
142-
var tapGestureRecognizer = new TapGestureRecognizer();
143-
tapGestureRecognizer.Tapped += (sender, args) => _indicatorView.Position = Children.IndexOf(sender as View);
144-
indicator.GestureRecognizers.Add(tapGestureRecognizer);
145-
Children.Add(indicator);
146-
}
147-
}
148-
149-
void RemoveRedundantIndicatorItems()
150-
{
151-
var indicatorCount = _indicatorView.Count;
152-
while (Children.Count > indicatorCount)
153-
{
154-
Children.RemoveAt(0);
155-
}
146+
CornerRadius = _indicatorView.IndicatorsShape == IndicatorShape.Circle
147+
? (float)indicatorSize / 2
148+
: 0,
149+
Stroke = Colors.Transparent
150+
}
151+
});
152+
153+
BindableLayout.SetItemsSource(this, _indicatorView.ItemsSource);
154+
BindableLayout.SetItemTemplate(this, indicatorTemplate);
156155
}
157156

158157
public void Remove()
159158
{
160-
_indicatorView.PropertyChanged -= _indicatorViewPropertyChanged;
159+
_indicatorView.PropertyChanged -= IndicatorViewPropertyChanged;
161160
}
162161
}
163-
}
162+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using System.Collections.Generic;
2+
using Xunit;
3+
4+
namespace Microsoft.Maui.Controls.Core.UnitTests
5+
{
6+
public class IndicatorViewTests : BaseTestFixture
7+
{
8+
[Fact]
9+
public void IndicatorStackLayoutNoItems_ResetIndicators_ShouldHaveNoChildren()
10+
{
11+
// Arrange
12+
var indicatorView = new IndicatorView();
13+
var indicatorStackLayout = new IndicatorStackLayout(indicatorView);
14+
15+
// Act
16+
indicatorStackLayout.ResetIndicators();
17+
18+
// Assert
19+
Assert.Empty(indicatorStackLayout.Children);
20+
}
21+
22+
[Fact]
23+
public void IndicatorStackLayoutWithItems_ResetIndicators_ShouldBindChildren()
24+
{
25+
// Arrange
26+
var indicatorView = new IndicatorView() { ItemsSource = new List<string>{"item1", "item2"} };
27+
var indicatorStackLayout = new IndicatorStackLayout(indicatorView);
28+
29+
// Act
30+
indicatorStackLayout.ResetIndicators();
31+
32+
// Assert
33+
Assert.Equal(2, indicatorStackLayout.Children.Count);
34+
}
35+
36+
[Theory]
37+
[InlineData(1, 2)]
38+
[InlineData(0, 2)]
39+
[InlineData(-2, 2)]
40+
public void IndicatorStackLayout_ResetIndicatorCount_ShouldBindChildren(int oldCount, int expected)
41+
{
42+
// Arrange
43+
var indicatorView = new IndicatorView() { ItemsSource = new List<string>{"item1", "item2"} };
44+
var indicatorStackLayout = new IndicatorStackLayout(indicatorView);
45+
Assert.Empty(indicatorStackLayout.Children);
46+
47+
// Act
48+
indicatorStackLayout.ResetIndicatorCount(oldCount);
49+
50+
// Assert
51+
Assert.Equal(expected, indicatorStackLayout.Children.Count);
52+
}
53+
}
54+
}

0 commit comments

Comments
 (0)