diff --git a/Docs/MAUI/A_android.png b/Docs/MAUI/A_android.png new file mode 100644 index 0000000..e204b08 Binary files /dev/null and b/Docs/MAUI/A_android.png differ diff --git a/Docs/MAUI/A_ios.png b/Docs/MAUI/A_ios.png new file mode 100644 index 0000000..715620c Binary files /dev/null and b/Docs/MAUI/A_ios.png differ diff --git a/Docs/MAUI/M_android.png b/Docs/MAUI/M_android.png new file mode 100644 index 0000000..4048fe1 Binary files /dev/null and b/Docs/MAUI/M_android.png differ diff --git a/Docs/MAUI/M_ios.png b/Docs/MAUI/M_ios.png new file mode 100644 index 0000000..291b91c Binary files /dev/null and b/Docs/MAUI/M_ios.png differ diff --git a/Docs/MAUI/U_android.png b/Docs/MAUI/U_android.png new file mode 100644 index 0000000..5c2cbe3 Binary files /dev/null and b/Docs/MAUI/U_android.png differ diff --git a/Docs/MAUI/U_ios.png b/Docs/MAUI/U_ios.png new file mode 100644 index 0000000..8ffeba1 Binary files /dev/null and b/Docs/MAUI/U_ios.png differ diff --git a/Docs/MAUI/banner.png b/Docs/MAUI/banner.png new file mode 100644 index 0000000..0fe51e4 Binary files /dev/null and b/Docs/MAUI/banner.png differ diff --git a/Docs/logo_maui.png b/Docs/logo_maui.png new file mode 100644 index 0000000..b6fd1d1 Binary files /dev/null and b/Docs/logo_maui.png differ diff --git a/Maui.Tabs/BadgeView.cs b/Maui.Tabs/BadgeView.cs index 9f6c509..0fa2639 100644 --- a/Maui.Tabs/BadgeView.cs +++ b/Maui.Tabs/BadgeView.cs @@ -36,7 +36,12 @@ public class BadgeView : Border nameof(BadgePadding), typeof(Thickness), typeof(BadgeView), - defaultValueCreator: (b) => new Thickness(6, 2)); + defaultValueCreator: +#if __ANDROID__ + (b) => new Thickness(5, 2)); +#else + (b) => new Thickness(4, 2)); +#endif public static readonly BindableProperty ShowIndicatorProperty = BindableProperty.Create( nameof(ShowIndicator), @@ -48,8 +53,6 @@ public class BadgeView : Border public BadgeView() { - _paddingInternalChange = true; - Padding = new Thickness(6, 2); Margin = new Thickness(0, 4, 0, 0); BackgroundColor = Colors.Red; @@ -60,7 +63,11 @@ public BadgeView() { HorizontalOptions = LayoutOptions.Center, VerticalOptions = LayoutOptions.Center, +#if __ANDROID__ Margin = new Thickness(0, -1.5, 0, 0), +#else + Margin = new Thickness(0, -1, 0, 0), +#endif BindingContext = this, }; @@ -169,35 +176,15 @@ private void Update() if (ShowIndicator) { - double margin = (TextSize + BadgePadding.VerticalThickness) / 2; - - TranslationX = HorizontalOptions.Alignment switch - { - LayoutAlignment.Start => margin, - LayoutAlignment.End => -margin, - _ => 0, - }; - - TranslationY = VerticalOptions.Alignment switch - { - LayoutAlignment.Start => margin, - LayoutAlignment.End => -margin, - _ => 0, - }; - HeightRequest = 10; WidthRequest = 10; StrokeShape = new RoundRectangle { CornerRadius = new CornerRadius(5) }; return; } - else - { - TranslationX = 0; - TranslationY = 0; - WidthRequest = -1; - HeightRequest = -1; - UpdateCornerRadius(); - } + + WidthRequest = -1; + HeightRequest = -1; + UpdateCornerRadius(); _paddingInternalChange = true; Padding = BadgePadding; diff --git a/Maui.Tabs/MaterialUnderlinedTabItem.xaml b/Maui.Tabs/MaterialUnderlinedTabItem.xaml index c05b7ca..b70ec55 100644 --- a/Maui.Tabs/MaterialUnderlinedTabItem.xaml +++ b/Maui.Tabs/MaterialUnderlinedTabItem.xaml @@ -1,10 +1,11 @@  - + @@ -43,29 +44,33 @@ - - - - + - + diff --git a/Maui.Tabs/Maui.Tabs.csproj b/Maui.Tabs/Maui.Tabs.csproj index 6a70bec..8d13f48 100644 --- a/Maui.Tabs/Maui.Tabs.csproj +++ b/Maui.Tabs/Maui.Tabs.csproj @@ -22,9 +22,9 @@ $(AssemblyName) ($(TargetFramework)) - 2.3.0.0 - 2.3.0.0 - 2.3.0 + 3.0.0 + 3.0.0 + 3.0.0 true en @@ -37,15 +37,15 @@ Sharpnado.Tabs.Maui https://github.com/roubachof/Sharpnado.Tabs https://github.com/roubachof/Sharpnado.Tabs - images\logo_2_1.png + images\logo_maui.png + docs\ReadMe.md maui dotnetmaui xamarin.forms android ios uwp netstandard tabs segmented control bottombar fixed tabs scrollable tabs badge - First MAUI release \o/ "Pure" MAUI Tabs (no renderers) - Including fixed tabs, scrollable tabs, bottom tabs, badge, segmented control, custom tabs, button tabs, ... + The Ultimate Tabs customization solution for .net MAUI, including fixed tabs, scrollable tabs, bottom tabs, badge, segmented control, custom tabs, button tabs, ... Pure MAUI Tabs: * Fixed tabs (android tabs style) @@ -64,7 +64,7 @@ ## Installation - * In Core project, in `App.xaml.cs`: + * In Core project, in `MauiProgram.cs`: ```csharp public static MauiApp CreateMauiApp() @@ -72,7 +72,7 @@ var builder = MauiApp.CreateBuilder(); builder .UseMauiApp() - .UseSharpnadoTabs(); + .UseSharpnadoTabs(loggerEnabled: false); } ``` @@ -88,11 +88,12 @@ true true snupkg - 2.3.0.0 + 3.0.0 - + + diff --git a/Maui.Tabs/Platforms/Android/TouchEffectPlatform.cs b/Maui.Tabs/Platforms/Android/TouchEffectPlatform.cs index 279d192..e618481 100644 --- a/Maui.Tabs/Platforms/Android/TouchEffectPlatform.cs +++ b/Maui.Tabs/Platforms/Android/TouchEffectPlatform.cs @@ -39,7 +39,7 @@ protected override void OnAttached() { if (Container is not ViewGroup group) { - throw new InvalidOperationException("Container must be a ViewGroup"); + throw new InvalidOperationException("Touch color effect requires to be attached to a container like a ContentView or a layout (Grid, StackLayout, etc...)"); } View.Clickable = true; diff --git a/Maui.Tabs/Platforms/iOS/TouchEffectPlatform.cs b/Maui.Tabs/Platforms/iOS/TouchEffectPlatform.cs index 8244f6b..02397ce 100644 --- a/Maui.Tabs/Platforms/iOS/TouchEffectPlatform.cs +++ b/Maui.Tabs/Platforms/iOS/TouchEffectPlatform.cs @@ -1,17 +1,15 @@ using System.ComponentModel; -using UIKit; using Microsoft.Maui.Controls.Compatibility.Platform.iOS; - using Microsoft.Maui.Controls.Platform; - -using Sharpnado.Tabs.Effects; -using Sharpnado.Tabs.Effects.iOS; +using ObjCRuntime; using Sharpnado.Tabs.Effects.iOS.GestureCollectors; using Sharpnado.Tabs.Effects.iOS.GestureRecognizers; +using UIKit; namespace Sharpnado.Tabs.Effects.iOS { - public class TouchEffectPlatform : PlatformEffect { - public bool IsDisposed => (Container as IVisualElementRenderer)?.Element == null; + public class TouchEffectPlatform : PlatformEffect + { + public bool IsDisposed => Container == null || Container.Handle == NativeHandle.Zero; public UIView View => Control ?? Container; UIView _layer; @@ -46,14 +44,17 @@ protected override void OnDetached() { void OnTouch(TouchGestureRecognizer.TouchArgs e) { switch (e.State) { case TouchGestureRecognizer.TouchState.Started: + InternalLogger.Debug($"OnTouch Started"); BringLayer(); break; case TouchGestureRecognizer.TouchState.Ended: + InternalLogger.Debug($"OnTouch Ended"); EndAnimation(); break; case TouchGestureRecognizer.TouchState.Cancelled: + InternalLogger.Debug($"OnTouch Cancelled"); if (!IsDisposed && _layer != null) { _layer.Layer.RemoveAllAnimations(); _layer.Alpha = 0; @@ -77,11 +78,13 @@ void UpdateEffectColor() { return; } + InternalLogger.Debug($"UpdateEffectColor"); _alpha = color.Alpha < 1.0 ? 1 : (float)0.3; _layer.BackgroundColor = color.ToUIColor(); } void BringLayer() { + InternalLogger.Debug($"BringLayer"); _layer.Layer.RemoveAllAnimations(); _layer.Alpha = _alpha; View.BringSubviewToFront(_layer); @@ -89,6 +92,7 @@ void BringLayer() { void EndAnimation() { if (!IsDisposed && _layer != null) { + InternalLogger.Debug($"EndAnimation"); _layer.Layer.RemoveAllAnimations(); UIView.Animate(0.225, () => { diff --git a/Maui.Tabs/Platforms/iOS/TouchGestureRecognizer.cs b/Maui.Tabs/Platforms/iOS/TouchGestureRecognizer.cs index 42a7836..c2817e6 100644 --- a/Maui.Tabs/Platforms/iOS/TouchGestureRecognizer.cs +++ b/Maui.Tabs/Platforms/iOS/TouchGestureRecognizer.cs @@ -119,7 +119,7 @@ public override bool ShouldReceiveTouch(UIGestureRecognizer recognizer, UITouch return false; } - return touch.View == _view; + return touch.View.IsDescendantOfView(_view); } } } \ No newline at end of file diff --git a/Maui.Tabs/ReadMe.md b/Maui.Tabs/ReadMe.md new file mode 100644 index 0000000..cfb514a --- /dev/null +++ b/Maui.Tabs/ReadMe.md @@ -0,0 +1,98 @@ +# Pure MAUI Tabs +* Fixed tabs (android tabs style) +* Scrollable tabs +* Vertical tabs +* Material design tabs (top and leading icon) +* Lazy and Delayed views +* Support for SVG images +* Segmented tabs +* Custom shadows (neumorphism ready) +* Badges on tabs +* Circle button in tab bar +* Bottom bar tabs (ios tabs style) +* Custom tabs (be creative just implement TabItem) +* Independent ViewSwitcher +* Bindable with ItemsSource + +## Installation + +* In Core project, in `MauiProgram.cs`: + +```csharp +public static MauiApp CreateMauiApp() +{ + var builder = MauiApp.CreateBuilder(); + builder + .UseMauiApp() + .UseSharpnadoTabs(loggerEnabled: false); +} +``` + +## Usage + +First create a `TabHostView` which will contains all your tabs: + +```xml + + + + + + + + + + + + + + + + + +``` + + +Then bind the `SelectedIndex` with a `ViewSwitcher` that will accordingly select your views. + +```xml + + + + + + +``` \ No newline at end of file diff --git a/Maui.Tabs/UnderlinedTabItem.xaml b/Maui.Tabs/UnderlinedTabItem.xaml index 15dab43..5470815 100644 --- a/Maui.Tabs/UnderlinedTabItem.xaml +++ b/Maui.Tabs/UnderlinedTabItem.xaml @@ -25,8 +25,6 @@ diff --git a/MauiSample/App.xaml b/MauiSample/App.xaml index d105f42..c9e9eb7 100644 --- a/MauiSample/App.xaml +++ b/MauiSample/App.xaml @@ -1,8 +1,8 @@ - - + + xmlns:local="clr-namespace:MauiSample"> diff --git a/MauiSample/MainPage.xaml b/MauiSample/MainPage.xaml index a5da4a9..44e97e6 100644 --- a/MauiSample/MainPage.xaml +++ b/MauiSample/MainPage.xaml @@ -53,12 +53,11 @@ AccentColor="{StaticResource Primary}" Animate="True" UseActivityIndicator="True" /> - - - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MauiSample/Presentation/Views/TabI.xaml.cs b/MauiSample/Presentation/Views/TabI.xaml.cs new file mode 100644 index 0000000..b7a6b2e --- /dev/null +++ b/MauiSample/Presentation/Views/TabI.xaml.cs @@ -0,0 +1,15 @@ +using Sharpnado.Tabs; +using System.Windows.Input; + +namespace MauiSample.Presentation.Views; + +public partial class TabI : ContentView +{ + public TabI() + { + InitializeComponent(); + + string tabsVersion = typeof(TabHostView).Assembly.GetName().Version.ToString(); + Version.Markdown += tabsVersion; + } +} \ No newline at end of file diff --git a/MauiSample/Presentation/Views/TabM.xaml b/MauiSample/Presentation/Views/TabM.xaml index ffb01e6..f6b96ff 100644 --- a/MauiSample/Presentation/Views/TabM.xaml +++ b/MauiSample/Presentation/Views/TabM.xaml @@ -151,7 +151,7 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MauiSample/Presentation/Views/TabU.xaml.cs b/MauiSample/Presentation/Views/TabU.xaml.cs new file mode 100644 index 0000000..92d1eb8 --- /dev/null +++ b/MauiSample/Presentation/Views/TabU.xaml.cs @@ -0,0 +1,16 @@ +using System.Windows.Input; + +namespace MauiSample.Presentation.Views; + +public partial class TabU : ContentView +{ + private int _tapCount = 0; + + public TabU() + { + InitializeComponent(); + } + + public ICommand TapMeCommand => + new Command(() => TapMeLabel.Text = _tapCount++ % 2 == 1 ? "Wait, what?" : "Stop that!"); +} \ No newline at end of file diff --git a/MauiSample/Resources/AppIcon/logo.png b/MauiSample/Resources/AppIcon/logo.png new file mode 100644 index 0000000..42446b3 Binary files /dev/null and b/MauiSample/Resources/AppIcon/logo.png differ diff --git a/MauiSample/Resources/Images/delayed_view.png b/MauiSample/Resources/Images/delayed_view.png new file mode 100644 index 0000000..073cb68 Binary files /dev/null and b/MauiSample/Resources/Images/delayed_view.png differ diff --git a/MauiSample/Resources/Splash/logo.png b/MauiSample/Resources/Splash/logo.png new file mode 100644 index 0000000..42446b3 Binary files /dev/null and b/MauiSample/Resources/Splash/logo.png differ diff --git a/README.md b/README.md index a4cb005..c226233 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,26 @@ -# Sharpnado.Tabs +# Sharpnado.Tabs for MAUI and Xamarin.Forms + +

+ +

-

Get it from NuGet: [![Nuget](https://img.shields.io/nuget/v/Sharpnado.Tabs.svg)](https://www.nuget.org/packages/Sharpnado.Tabs) -| Supported platforms | -|----------------------------| -| :heavy_check_mark: Android | -| :heavy_check_mark: iOS | -| :heavy_check_mark: UWP | +[![Nuget](https://img.shields.io/nuget/v/Sharpnado.Maui.Tabs.svg)](https://www.nuget.org/packages/Sharpnado.Maui.Tabs) +| Supported MAUI platforms | Supported XF platforms | +|----------------------------|----------------------------| +| :heavy_check_mark: Android |:heavy_check_mark: Android | +| :heavy_check_mark: iOS |:heavy_check_mark: iOS | +| :question: Windows |:heavy_check_mark: UWP | +| :question: Mac | +* MAUI version * Fully customizable * Underlined tabs, bottom tabs, Segmented control, scrollable tabs +* Lazy and Delayed views * Material tabs specs full implementation * SVG support thanks to GeometryIcon * Badge on tabs @@ -22,6 +29,20 @@ Get it from NuGet: * Shadows included in TabHost * Bindable + + + + + + + + + + + +
MAUI sample
+ + @@ -135,14 +156,33 @@ Get it from NuGet: ## Sample app -The tabs components are presented in the Silly! app in the following repository: +For dotnet MAUI, you can just have a look at the `MauiSample` solution. -https://github.com/roubachof/Xamarin-Forms-Practices +For Xamarin.Forms the tabs components are presented in the Silly! app in the following repository: -If you want to know how to use them, it's the best place to start. +https://github.com/roubachof/Xamarin-Forms-Practices ## Installation +### MAUI + +```csharp +public static class MauiProgram +{ + public static MauiApp CreateMauiApp() + { + var builder = MauiApp.CreateBuilder(); + builder + .UseMauiApp() + .UseSharpnadoTabs(loggerEnable: false); + + return builder.Build(); + } +} +``` + +### Xamarin.Forms + Because Tabs uses platform-specific effects like tinted images and tap feedback color, you must install the nuget package in all your targeted platforms projects (netstandard, ios, android, uwp). * In Core project in `App.xaml.cs`: @@ -178,7 +218,49 @@ public App() Xamarin.Forms.Forms.Init(e, rendererAssemblies); ``` -## Shadows +## Version 3.0 (MAUI only) + +### Attached Properties + +You can use `Commands.Tap` to add a tap gesture to any view. + +The `TouchEffect.Color` property will set a native touch feedback with the desired color. + +### DelayedView + +You probably know the `LazyView` by now, the `DelayedView` is an evolution of it enabling full control of your UI building times. + +Using a `DelayedView` will reduce application startup time by deferring the UI building of your components by some milliseconds (the value can be configured). + +All you have is wrap your view in a `DelayedView` inside your `ViewSwitcher`, or even anywhere in your app. + +```xml + + + + + + +``` + +It can totally work outside of the `Tabs` context. + + +## Shadows (Xamarin.Forms only) The `TabHostView` inherits directly from `Shadows`. It means you can add as many shades as you like to your tab bar. It behaves exactly the same as the `Shadows` component. diff --git a/Tabs/Tabs/TabHostView.cs b/Tabs/Tabs/TabHostView.cs index d10ee43..256d910 100644 --- a/Tabs/Tabs/TabHostView.cs +++ b/Tabs/Tabs/TabHostView.cs @@ -332,7 +332,7 @@ private static void SelectedIndexPropertyChanged(BindableObject bindable, object var tabHostView = (TabHostView)bindable; int selectedIndex = (int)newvalue; - if (selectedIndex < 0) + if (selectedIndex < 0 || tabHostView._fromTabItemTapped) { return; } @@ -558,17 +558,21 @@ private void UpdateSelectedIndex(int selectedIndex) InternalLogger.Debug(Tag, () => $"SelectedIndex: {SelectedIndex}"); } + private bool _fromTabItemTapped; + private void OnTabItemTapped(object tappedItem) { var selectedIndex = _selectableTabs.IndexOf((TabItem)tappedItem); - if (!_selectableTabs[selectedIndex].IsSelectable) + if (selectedIndex == -1 || !_selectableTabs[selectedIndex].IsSelectable) { return; } + _fromTabItemTapped = true; UpdateSelectedIndex(selectedIndex); RaiseSelectedTabIndexChanged(new SelectedPositionChangedEventArgs(selectedIndex)); + _fromTabItemTapped = false; } private void UpdateTabType() @@ -698,11 +702,15 @@ private void OnTabsCollectionChanged(object sender, NotifyCollectionChangedEvent private void AddTapCommand(TabItem tabItem) { - if (DeviceInfo.Platform == DevicePlatform.iOS || DeviceInfo.Platform == DevicePlatform.WinUI) + if (tabItem is TabButton) + { + return; + } + + if (Device.RuntimePlatform == Device.UWP) { tabItem.GestureRecognizers.Add( - new TapGestureRecognizer { Command = TabItemTappedCommand, CommandParameter = tabItem } - ); + new TapGestureRecognizer { Command = TabItemTappedCommand, CommandParameter = tabItem }); } else { @@ -710,16 +718,10 @@ private void AddTapCommand(TabItem tabItem) Sharpnado.Tabs.Effects.TouchEffect.SetColor(tabItem, tabItem.SelectedTabColor); Sharpnado.Tabs.Effects.Commands.SetTap(tabItem, TabItemTappedCommand); Sharpnado.Tabs.Effects.Commands.SetTapParameter(tabItem, tabItem); - - tabItem.Effects.Add(new Sharpnado.Tabs.Effects.TouchRoutingEffect()); - tabItem.Effects.Add(new Sharpnado.Tabs.Effects.CommandsRoutingEffect()); #else ViewEffect.SetTouchFeedbackColor(tabItem, tabItem.SelectedTabColor); TapCommandEffect.SetTap(tabItem, TabItemTappedCommand); TapCommandEffect.SetTapParameter(tabItem, tabItem); - - tabItem.Effects.Add(new ViewStyleEffect()); - tabItem.Effects.Add(new TapCommandRoutingEffect()); #endif } } @@ -1065,7 +1067,7 @@ private void RaiseTabButtons() { // We always want our TabButton with the highest Z-index #if NET6_0_OR_GREATER - tabButton.ZIndex = 100; + tabButton.ZIndex = 1000; #else _grid.RaiseChild(tabButton); #endif