Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
TabNavigationHost.cs
1using System;
4using System.Collections.Specialized;
5using System.ComponentModel;
6using System.Threading.Tasks;
7using Microsoft.Maui.ApplicationModel;
8using Microsoft.Maui.Controls;
9
11{
15 public class TabNavigationHost : ContentView
16 {
20 public static readonly BindableProperty ItemsSourceProperty =
21 BindableProperty.Create(nameof(ItemsSource), typeof(IEnumerable), typeof(TabNavigationHost), default(IEnumerable), propertyChanged: OnItemsSourcePropertyChanged);
22
26 public static readonly BindableProperty ItemTemplateProperty =
27 BindableProperty.Create(nameof(ItemTemplate), typeof(DataTemplate), typeof(TabNavigationHost), default(DataTemplate), propertyChanged: OnItemTemplatePropertyChanged);
28
32 public static readonly BindableProperty BadgeTemplateProperty =
33 BindableProperty.Create(nameof(BadgeTemplate), typeof(DataTemplate), typeof(TabNavigationHost), default(DataTemplate), propertyChanged: OnBadgeTemplatePropertyChanged);
34
38 public static readonly BindableProperty SelectedIndexProperty =
39 BindableProperty.Create(nameof(SelectedIndex), typeof(int), typeof(TabNavigationHost), 0, BindingMode.TwoWay, propertyChanged: OnSelectedIndexPropertyChanged);
40
44 public static readonly BindableProperty SelectedItemProperty =
45 BindableProperty.Create(nameof(SelectedItem), typeof(object), typeof(TabNavigationHost), default(object), BindingMode.TwoWay, propertyChanged: OnSelectedItemPropertyChanged);
46
50 public static readonly BindableProperty TabPlacementProperty =
51 BindableProperty.Create(nameof(TabPlacement), typeof(TabPlacement), typeof(TabNavigationHost), TabPlacement.Bottom, propertyChanged: OnTabPlacementPropertyChanged);
52
56 public static readonly BindableProperty IsScrollableProperty =
57 BindableProperty.Create(nameof(IsScrollable), typeof(bool), typeof(TabNavigationHost), false, propertyChanged: OnIsScrollablePropertyChanged);
58
62 public static readonly BindableProperty TouchEffectTypeProperty =
63 BindableProperty.Create(nameof(TouchEffectType), typeof(TabTouchEffectType), typeof(TabNavigationHost), TabTouchEffectType.None);
64
68 public static readonly BindableProperty AnimationStyleProperty =
69 BindableProperty.Create(nameof(AnimationStyle), typeof(TabAnimationStyle), typeof(TabNavigationHost), TabAnimationStyle.None);
70
71 private readonly Grid fixedGrid;
72 private readonly ScrollView scrollView;
73 private readonly StackLayout scrollLayout;
74 private readonly List<TabNavigationItemContainer> itemContainers;
75 private readonly List<object?> items;
76 private int currentSelectedIndex = -1;
77
78 private INotifyCollectionChanged? itemsSourceNotifier;
79 private bool isInternalSelectionChange;
80 private bool isApplyingSelection;
81
86 {
87 this.fixedGrid = new Grid
88 {
89 RowSpacing = 0,
90 ColumnSpacing = 0,
91 RowDefinitions = { new RowDefinition { Height = GridLength.Star } },
92 ColumnDefinitions = { new ColumnDefinition { Width = GridLength.Star } }
93 };
94
95 this.scrollLayout = new StackLayout
96 {
97 Orientation = StackOrientation.Horizontal,
98 Spacing = 0
99 };
100
101 this.scrollView = new ScrollView
102 {
103 Orientation = ScrollOrientation.Horizontal,
104 Content = this.scrollLayout
105 };
106
107 this.itemContainers = new List<TabNavigationItemContainer>();
108 this.items = new List<object?>();
109
110 this.Content = this.fixedGrid;
111 }
112
116 public IEnumerable? ItemsSource
117 {
118 get => (IEnumerable?)this.GetValue(ItemsSourceProperty);
119 set => this.SetValue(ItemsSourceProperty, value);
120 }
121
125 public DataTemplate? ItemTemplate
126 {
127 get => (DataTemplate?)this.GetValue(ItemTemplateProperty);
128 set => this.SetValue(ItemTemplateProperty, value);
129 }
130
134 public DataTemplate? BadgeTemplate
135 {
136 get => (DataTemplate?)this.GetValue(BadgeTemplateProperty);
137 set => this.SetValue(BadgeTemplateProperty, value);
138 }
139
143 public int SelectedIndex
144 {
145 get => (int)this.GetValue(SelectedIndexProperty);
146 set => this.SetValue(SelectedIndexProperty, value);
147 }
148
152 public object? SelectedItem
153 {
154 get => this.GetValue(SelectedItemProperty);
155 set => this.SetValue(SelectedItemProperty, value);
156 }
157
162 {
163 get => (TabPlacement)this.GetValue(TabPlacementProperty);
164 set => this.SetValue(TabPlacementProperty, value);
165 }
166
170 public bool IsScrollable
171 {
172 get => (bool)this.GetValue(IsScrollableProperty);
173 set => this.SetValue(IsScrollableProperty, value);
174 }
175
180 {
181 get => (TabTouchEffectType)this.GetValue(TouchEffectTypeProperty);
182 set => this.SetValue(TouchEffectTypeProperty, value);
183 }
184
189 {
190 get => (TabAnimationStyle)this.GetValue(AnimationStyleProperty);
191 set => this.SetValue(AnimationStyleProperty, value);
192 }
193
194 private static void OnItemsSourcePropertyChanged(BindableObject bindable, object oldValue, object newValue)
195 {
196 TabNavigationHost Host = (TabNavigationHost)bindable;
197 Host.OnItemsSourceChanged(oldValue as IEnumerable, newValue as IEnumerable);
198 }
199
200 private static void OnItemTemplatePropertyChanged(BindableObject bindable, object oldValue, object newValue)
201 {
202 TabNavigationHost Host = (TabNavigationHost)bindable;
203 if (!Equals(oldValue, newValue))
204 {
205 Host.RebuildItems();
206 }
207 }
208
209 private static void OnBadgeTemplatePropertyChanged(BindableObject bindable, object oldValue, object newValue)
210 {
211 TabNavigationHost Host = (TabNavigationHost)bindable;
212 if (!Equals(oldValue, newValue))
213 {
214 Host.RebuildItems();
215 }
216 }
217
218 private static void OnSelectedIndexPropertyChanged(BindableObject bindable, object oldValue, object newValue)
219 {
220 TabNavigationHost Host = (TabNavigationHost)bindable;
221 if (Host.isInternalSelectionChange)
222 {
223 return;
224 }
225
226 int OldIndex = (int)oldValue;
227 int NewIndex = (int)newValue;
228
229 if (OldIndex == NewIndex && Host.isApplyingSelection)
230 {
231 return;
232 }
233
234 Host.ApplySelectionFromProperty(NewIndex);
235 }
236
237 private static void OnSelectedItemPropertyChanged(BindableObject bindable, object oldValue, object newValue)
238 {
239 TabNavigationHost Host = (TabNavigationHost)bindable;
240 if (Host.isInternalSelectionChange)
241 {
242 return;
243 }
244
245 if (ReferenceEquals(oldValue, newValue))
246 {
247 return;
248 }
249
250 Host.ApplySelectionFromSelectedItem(newValue);
251 }
252
253 private static void OnTabPlacementPropertyChanged(BindableObject bindable, object oldValue, object newValue)
254 {
255 TabNavigationHost Host = (TabNavigationHost)bindable;
256 if (!Equals(oldValue, newValue))
257 {
258 Host.UpdatePresenterRoot();
259 Host.RebuildItems();
260 }
261 }
262
263 private static void OnIsScrollablePropertyChanged(BindableObject bindable, object oldValue, object newValue)
264 {
265 TabNavigationHost Host = (TabNavigationHost)bindable;
266 if (!Equals(oldValue, newValue))
267 {
268 Host.UpdatePresenterRoot();
269 Host.RebuildItems();
270 }
271 }
272
273 private void OnItemsSourceChanged(IEnumerable? OldItems, IEnumerable? NewItems)
274 {
275 this.DetachItemsSourceNotifier(OldItems as INotifyCollectionChanged);
276 this.AttachItemsSourceNotifier(NewItems as INotifyCollectionChanged);
277 this.RebuildItems();
278 }
279
280 private void AttachItemsSourceNotifier(INotifyCollectionChanged? Notifier)
281 {
282 if (Notifier is null)
283 {
284 this.itemsSourceNotifier = null;
285 return;
286 }
287
288 this.itemsSourceNotifier = Notifier;
289 this.itemsSourceNotifier.CollectionChanged += this.OnItemsSourceCollectionChanged;
290 }
291
292 private void DetachItemsSourceNotifier(INotifyCollectionChanged? Notifier)
293 {
294 if (Notifier is null)
295 {
296 return;
297 }
298
299 Notifier.CollectionChanged -= this.OnItemsSourceCollectionChanged;
300 if (ReferenceEquals(this.itemsSourceNotifier, Notifier))
301 {
302 this.itemsSourceNotifier = null;
303 }
304 }
305
306 private void OnItemsSourceCollectionChanged(object? Sender, NotifyCollectionChangedEventArgs E)
307 {
308 if (MainThread.IsMainThread)
309 {
310 this.RebuildItems();
311 }
312 else
313 {
314 MainThread.BeginInvokeOnMainThread(this.RebuildItems);
315 }
316 }
317
318 private void RebuildItems()
319 {
320 this.isApplyingSelection = true;
321
322 try
323 {
324 this.ClearPresenter();
325
326 this.items.Clear();
327 foreach (TabNavigationItemContainer Container in this.itemContainers)
328 {
329 Container.Dispose();
330 }
331
332 this.itemContainers.Clear();
333
334 if (this.ItemsSource is null)
335 {
336 this.SetSelectionInternal(-1, null);
337 return;
338 }
339
340 int Index = 0;
341 foreach (object? Item in this.ItemsSource)
342 {
343 this.items.Add(Item);
344 TabNavigationItemContainer Container = this.CreateContainer(Item, Index);
345 this.itemContainers.Add(Container);
346 this.AddContainerToPresenter(Container);
347 Index++;
348 }
349
350 this.EnsureSelectionConsistency();
351 }
352 finally
353 {
354 this.isApplyingSelection = false;
355 }
356 }
357
358 private void ClearPresenter()
359 {
360 if (this.IsScrollable)
361 {
362 this.scrollLayout.Children.Clear();
363 }
364 else
365 {
366 this.fixedGrid.Children.Clear();
367 this.fixedGrid.RowDefinitions.Clear();
368 this.fixedGrid.ColumnDefinitions.Clear();
369
370 if (this.IsHorizontalPlacement())
371 {
372 this.fixedGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Star });
373 }
374 else
375 {
376 this.fixedGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Star });
377 }
378 }
379 }
380
381 private void AddContainerToPresenter(TabNavigationItemContainer Container)
382 {
383 if (this.IsScrollable)
384 {
385 if (!this.scrollLayout.Children.Contains(Container))
386 {
387 this.scrollLayout.Children.Add(Container);
388 }
389 }
390 else
391 {
392 if (this.IsHorizontalPlacement())
393 {
394 this.fixedGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Star });
395 int ColumnIndex = this.fixedGrid.ColumnDefinitions.Count - 1;
396 Grid.SetColumn(Container, ColumnIndex);
397 Grid.SetRow(Container, 0);
398 }
399 else
400 {
401 this.fixedGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Star });
402 int RowIndex = this.fixedGrid.RowDefinitions.Count - 1;
403 Grid.SetRow(Container, RowIndex);
404 Grid.SetColumn(Container, 0);
405 }
406
407 if (!this.fixedGrid.Children.Contains(Container))
408 {
409 this.fixedGrid.Children.Add(Container);
410 }
411 }
412 }
413
414 private TabNavigationItemContainer CreateContainer(object? Item, int Index)
415 {
416 DataTemplate Template = this.ItemTemplate ?? this.ResolveDefaultTemplate();
417 View ContentView = this.CreateContentView(Template, Item);
418
419 TabNavigationItemContainer Container = new(this, Item, ContentView, Index);
420
421 if (!this.IsScrollable)
422 {
423 if (this.IsHorizontalPlacement())
424 {
425 Container.HorizontalOptions = LayoutOptions.Fill;
426 }
427 else
428 {
429 Container.VerticalOptions = LayoutOptions.Fill;
430 }
431 }
432
433 this.ApplyBadgeTemplate(Container);
434
435 return Container;
436 }
437
438 private DataTemplate ResolveDefaultTemplate()
439 {
440 string ResourceKey = this.TabPlacement switch
441 {
442 TabPlacement.Top => "DefaultTopUnderlineTabTemplate",
443 TabPlacement.Left => "DefaultVerticalTabTemplate",
444 TabPlacement.Right => "DefaultVerticalTabTemplate",
445 _ => "DefaultBottomPillTabTemplate"
446 };
447
448 if (Application.Current?.Resources.TryGetValue(ResourceKey, out object? ResourceValue) ?? false)
449 {
450 if (ResourceValue is DataTemplate Template)
451 {
452 return Template;
453 }
454 }
455
456 return new DataTemplate(() =>
457 {
458 Label TitleLabel = new()
459 {
460 HorizontalOptions = LayoutOptions.Center,
461 VerticalOptions = LayoutOptions.Center,
462 FontSize = 14
463 };
464
465 TitleLabel.SetBinding(Label.TextProperty, nameof(TabDefinition.Title));
466 TitleLabel.SetDynamicResource(Label.TextColorProperty, "ContentPrimaryWL");
467 return TitleLabel;
468 });
469 }
470
471 private View CreateContentView(DataTemplate Template, object? BindingContext)
472 {
473 object? Created = Template.CreateContent();
474
475 if (Created is ViewCell CreatedCell && CreatedCell.View is not null)
476 {
477 CreatedCell.View.BindingContext = BindingContext;
478 return CreatedCell.View;
479 }
480
481 if (Created is View CreatedView)
482 {
483 CreatedView.BindingContext = BindingContext;
484 return CreatedView;
485 }
486
487 throw new InvalidOperationException("Tab item template must produce a View.");
488 }
489
490 private void ApplyBadgeTemplate(TabNavigationItemContainer Container)
491 {
492 if (this.BadgeTemplate is null)
493 {
494 Container.SetCustomBadge(null);
495 return;
496 }
497
498 View BadgeView = this.CreateContentView(this.BadgeTemplate, Container.Item);
499 Container.SetCustomBadge(BadgeView);
500 }
501
502 private void EnsureSelectionConsistency()
503 {
504 if (this.items.Count == 0)
505 {
506 this.SetSelectionInternal(-1, null);
507 return;
508 }
509
510 int ExistingIndex = this.SelectedIndex;
511
512 if (ExistingIndex >= 0 && ExistingIndex < this.items.Count)
513 {
514 object? ExistingItem = this.items[ExistingIndex];
515 this.SetSelectionInternal(ExistingIndex, ExistingItem);
516 return;
517 }
518
519 if (this.SelectedItem is not null)
520 {
521 int LocatedIndex = this.GetIndexOfItem(this.SelectedItem);
522 if (LocatedIndex >= 0)
523 {
524 object? LocatedItem = this.items[LocatedIndex];
525 this.SetSelectionInternal(LocatedIndex, LocatedItem);
526 return;
527 }
528 }
529
530 this.SetSelectionInternal(0, this.items[0]);
531 }
532
533 private void SetSelectionInternal(int Index, object? Item)
534 {
535 this.isInternalSelectionChange = true;
536
537 try
538 {
539 this.SetValue(SelectedIndexProperty, Index);
540 this.SetValue(SelectedItemProperty, Item);
541 }
542 finally
543 {
544 this.isInternalSelectionChange = false;
545 }
546
547 this.ApplySelectionStates(Index);
548 }
549
550 private void ApplySelectionStates(int SelectedIndex)
551 {
552 int PreviousIndex = this.currentSelectedIndex;
553 this.currentSelectedIndex = SelectedIndex;
554
555 for (int i = 0; i < this.itemContainers.Count; i++)
556 {
557 TabNavigationItemContainer Container = this.itemContainers[i];
558 bool IsSelected = i == SelectedIndex && SelectedIndex >= 0;
559 Container.ApplySelectionState(IsSelected);
560
561 if (Container.Item is TabDefinition Definition)
562 {
563 Definition.IsSelected = IsSelected;
564 }
565 }
566
567 this.RunSelectionAnimation(PreviousIndex, SelectedIndex);
568 }
569
570 private void ApplySelectionFromProperty(int NewIndex)
571 {
572 if (NewIndex < 0 || NewIndex >= this.items.Count)
573 {
574 this.SetSelectionInternal(-1, null);
575 return;
576 }
577
578 object? Item = this.items[NewIndex];
579 this.SetSelectionInternal(NewIndex, Item);
580 }
581
582 private void ApplySelectionFromSelectedItem(object? SelectedItem)
583 {
584 if (SelectedItem is null)
585 {
586 this.SetSelectionInternal(-1, null);
587 return;
588 }
589
590 int FoundIndex = this.GetIndexOfItem(SelectedItem);
591 if (FoundIndex >= 0)
592 {
593 object? Item = this.items[FoundIndex];
594 this.SetSelectionInternal(FoundIndex, Item);
595 }
596 }
597
598 private int GetIndexOfItem(object? Item)
599 {
600 for (int i = 0; i < this.items.Count; i++)
601 {
602 object? Candidate = this.items[i];
603 if (ReferenceEquals(Candidate, Item) || Equals(Candidate, Item))
604 {
605 return i;
606 }
607 }
608
609 return -1;
610 }
611
612 private async Task HandleItemTappedAsync(TabNavigationItemContainer Container)
613 {
614 if (Container.Item is TabDefinition Definition && !Definition.IsEnabled)
615 {
616 return;
617 }
618
619 await this.ApplyTouchFeedbackAsync(Container);
620
621 this.SetSelectionInternal(Container.Index, Container.Item);
622
623 if (Container.Item is TabDefinition Descriptor && Descriptor.Command is not null)
624 {
625 if (Descriptor.Command.CanExecute(Descriptor.CommandParameter ?? Descriptor))
626 {
627 Descriptor.Command.Execute(Descriptor.CommandParameter ?? Descriptor);
628 }
629 }
630 }
631
632 private Task ApplyTouchFeedbackAsync(TabNavigationItemContainer Container)
633 {
634 switch (this.TouchEffectType)
635 {
636 case TabTouchEffectType.OpacityHighlight:
637 return this.AnimateOpacityAsync(Container);
638 case TabTouchEffectType.Scale:
639 return this.AnimateScaleAsync(Container);
640 default:
641 return Task.CompletedTask;
642 }
643 }
644
645 private async Task AnimateOpacityAsync(VisualElement Element)
646 {
647 const double TargetOpacity = 0.6;
648 const uint Duration = 80;
649
650 double OriginalOpacity = Element.Opacity;
651 await Element.FadeToAsync(TargetOpacity, Duration, Easing.SinOut);
652 await Element.FadeToAsync(OriginalOpacity, Duration, Easing.SinIn);
653 }
654
655 private async Task AnimateScaleAsync(VisualElement Element)
656 {
657 const double TargetScale = 0.92;
658 const uint Duration = 80;
659
660 double OriginalScale = Element.Scale;
661 await Element.ScaleToAsync(TargetScale, Duration, Easing.CubicOut);
662 await Element.ScaleToAsync(OriginalScale, Duration, Easing.CubicIn);
663 }
664
665 private void UpdatePresenterRoot()
666 {
667 bool IsHorizontal = this.IsHorizontalPlacement();
668
669 if (this.IsScrollable)
670 {
671 this.scrollLayout.Orientation = IsHorizontal ? StackOrientation.Horizontal : StackOrientation.Vertical;
672 this.scrollView.Orientation = IsHorizontal ? ScrollOrientation.Horizontal : ScrollOrientation.Vertical;
673
674 if (!ReferenceEquals(this.Content, this.scrollView))
675 {
676 this.Content = this.scrollView;
677 }
678 }
679 else
680 {
681 if (IsHorizontal)
682 {
683 this.fixedGrid.RowDefinitions.Clear();
684 this.fixedGrid.ColumnDefinitions.Clear();
685 this.fixedGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Star });
686 }
687 else
688 {
689 this.fixedGrid.RowDefinitions.Clear();
690 this.fixedGrid.ColumnDefinitions.Clear();
691 this.fixedGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Star });
692 }
693
694 if (!ReferenceEquals(this.Content, this.fixedGrid))
695 {
696 this.Content = this.fixedGrid;
697 }
698 }
699 }
700
701 private bool IsHorizontalPlacement()
702 {
703 return this.TabPlacement == TabPlacement.Top || this.TabPlacement == TabPlacement.Bottom;
704 }
705
706 private void RunSelectionAnimation(int PreviousIndex, int SelectedIndex)
707 {
708 if (this.AnimationStyle == TabAnimationStyle.None)
709 {
710 return;
711 }
712
713 if (SelectedIndex < 0 || SelectedIndex >= this.itemContainers.Count)
714 {
715 return;
716 }
717
718 TabNavigationItemContainer TargetContainer = this.itemContainers[SelectedIndex];
719
720 switch (this.AnimationStyle)
721 {
722 case TabAnimationStyle.FadeScale:
723 _ = this.AnimateFadeScaleAsync(TargetContainer);
724 break;
725 case TabAnimationStyle.UnderlineSlide:
726 this.AnimateUnderlineTransition(PreviousIndex, SelectedIndex);
727 break;
728 }
729 }
730
731 private void AnimateUnderlineTransition(int PreviousIndex, int SelectedIndex)
732 {
733 if (PreviousIndex == SelectedIndex)
734 {
735 return;
736 }
737
738 // Default templates rely on visual state transitions to animate underline elements.
739 }
740
741 private async Task AnimateFadeScaleAsync(VisualElement Element)
742 {
743 const double TargetScale = 1.05;
744 const uint ScaleInDuration = 120;
745 const uint ScaleOutDuration = 140;
746
747 double OriginalScale = Element.Scale;
748
749 await Element.ScaleToAsync(TargetScale, ScaleInDuration, Easing.CubicOut);
750 await Element.ScaleToAsync(OriginalScale, ScaleOutDuration, Easing.CubicIn);
751 }
752
753 private sealed class TabNavigationItemContainer : ContentView, IDisposable
754 {
755 private readonly TabNavigationHost host;
756 private readonly TapGestureRecognizer tapRecognizer;
757 private readonly INotifyPropertyChanged? notifier;
758 private bool disposed;
759 private View? customBadgeView;
760
761 public TabNavigationItemContainer(TabNavigationHost Host, object? Item, View Content, int Index)
762 {
763 this.host = Host;
764 this.Item = Item;
765 this.Index = Index;
766 this.tapRecognizer = new TapGestureRecognizer();
767 this.tapRecognizer.Tapped += this.OnTapped;
768 this.GestureRecognizers.Add(this.tapRecognizer);
769
770 this.Content = Content;
771 this.BindingContext = Item;
772 Content.BindingContext = Item;
773
774 if (Item is TabDefinition Definition)
775 {
776 this.IsEnabled = Definition.IsEnabled;
777 }
778
779 if (Item is INotifyPropertyChanged NotifyPropertyChanged)
780 {
781 this.notifier = NotifyPropertyChanged;
782 this.notifier.PropertyChanged += this.OnItemPropertyChanged;
783 }
784
785 this.Padding = new Thickness(0);
786 this.Margin = new Thickness(0);
787 }
788
789 public int Index { get; }
790
791 public object? Item { get; }
792
793 public void ApplySelectionState(bool isSelected)
794 {
795 VisualStateManager.GoToState(this, isSelected ? "Selected" : "Normal");
796 }
797
798 public void SetCustomBadge(View? badgeView)
799 {
800 this.customBadgeView = badgeView;
801
802 Layout? BadgeHost = this.Content.FindByName<Layout>("CustomBadgeHost");
803 if (BadgeHost is null)
804 {
805 return;
806 }
807
808 BadgeHost.Children.Clear();
809
810 if (badgeView is null)
811 {
812 return;
813 }
814
815 badgeView.BindingContext = this.BindingContext;
816 BadgeHost.Children.Add(badgeView);
817 }
818
819 protected override void OnBindingContextChanged()
820 {
821 base.OnBindingContextChanged();
822
823 if (this.customBadgeView is not null)
824 {
825 this.customBadgeView.BindingContext = this.BindingContext;
826 }
827 }
828
829 private void OnItemPropertyChanged(object? Sender, PropertyChangedEventArgs E)
830 {
831 if (string.Equals(E.PropertyName, nameof(TabDefinition.IsEnabled), StringComparison.Ordinal) && this.Item is TabDefinition Definition)
832 {
833 this.IsEnabled = Definition.IsEnabled;
834 }
835 }
836
837 private void OnTapped(object? Sender, TappedEventArgs E)
838 {
839 _ = this.host.HandleItemTappedAsync(this);
840 }
841
842 public void Dispose()
843 {
844 if (this.disposed)
845 {
846 return;
847 }
848
849 this.disposed = true;
850 this.tapRecognizer.Tapped -= this.OnTapped;
851 this.GestureRecognizers.Remove(this.tapRecognizer);
852
853 if (this.notifier is not null)
854 {
855 this.notifier.PropertyChanged -= this.OnItemPropertyChanged;
856 }
857 }
858 }
859 }
860}
Visual host that renders a collection of tabs and keeps its selection in sync with external consumers...
static readonly BindableProperty ItemsSourceProperty
Backing store for the ItemsSource property.
DataTemplate? BadgeTemplate
Gets or sets an optional badge template used by the built-in tab templates.
object? SelectedItem
Gets or sets the currently selected item.
static readonly BindableProperty SelectedItemProperty
Backing store for the SelectedItem property.
TabTouchEffectType TouchEffectType
Gets or sets the touch effect applied to tab interactions.
DataTemplate? ItemTemplate
Gets or sets the template used to create tab visuals for each item.
static readonly BindableProperty AnimationStyleProperty
Backing store for the AnimationStyle property.
static readonly BindableProperty TouchEffectTypeProperty
Backing store for the TouchEffectType property.
static readonly BindableProperty BadgeTemplateProperty
Backing store for the BadgeTemplate property.
TabPlacement TabPlacement
Gets or sets the placement of the tab host (horizontal or vertical layout).
int SelectedIndex
Gets or sets the zero-based index of the selected tab.
bool IsScrollable
Gets or sets a value indicating whether the tab list should be scrollable instead of distributing ite...
TabAnimationStyle AnimationStyle
Gets or sets the animation style used when the selected tab changes.
static readonly BindableProperty IsScrollableProperty
Backing store for the IsScrollable property.
IEnumerable? ItemsSource
Gets or sets the collection of items rendered as tabs.
TabNavigationHost()
Initializes a new instance of the TabNavigationHost class.
static readonly BindableProperty ItemTemplateProperty
Backing store for the ItemTemplate property.
static readonly BindableProperty TabPlacementProperty
Backing store for the TabPlacement property.
static readonly BindableProperty SelectedIndexProperty
Backing store for the SelectedIndex property.
Definition: ImplTypes.g.cs:58
TabTouchEffectType
Defines the touch feedback effect applied when interacting with tab items.
TabAnimationStyle
Specifies the animation strategy used when the selected tab changes.
TabPlacement
Defines the placement of tab items within a TabNavigationHost.
Definition: TabPlacement.cs:7