Shell Navigation Bar Has Shadow property (#8408)
authorShane Neuville <shneuvil@microsoft.com>
Wed, 20 Nov 2019 11:51:30 +0000 (04:51 -0700)
committerRui Marinho <me@ruimarinho.net>
Wed, 20 Nov 2019 11:51:30 +0000 (11:51 +0000)
* add shadow api for shell nav bar

* - ios fixes

* - ios fixes

* - name fix android and simplify types on ios

* - change to listener

* - cleanup

* - remove toolbar

* - fix ios to propagate nav bar on page push

* - fix android crash

Xamarin.Forms.Controls/XamStore/Views/StorePages.cs
Xamarin.Forms.Core/Internals/PropertyPropagationExtensions.cs
Xamarin.Forms.Core/Shell/Shell.cs
Xamarin.Forms.Platform.Android/GenericGlobalLayoutListener.cs [new file with mode: 0644]
Xamarin.Forms.Platform.Android/Renderers/ShellToolbarTracker.cs
Xamarin.Forms.Platform.Android/Xamarin.Forms.Platform.Android.csproj
Xamarin.Forms.Platform.iOS/Renderers/IShellNavBarAppearanceTracker.cs
Xamarin.Forms.Platform.iOS/Renderers/SafeShellNavBarAppearanceTracker.cs
Xamarin.Forms.Platform.iOS/Renderers/ShellNavBarAppearanceTracker.cs
Xamarin.Forms.Platform.iOS/Renderers/ShellSectionRenderer.cs
Xamarin.Forms.Platform.iOS/Xamarin.Forms.Platform.iOS.csproj

index f480b2b..3ae6457 100644 (file)
@@ -302,11 +302,15 @@ namespace Xamarin.Forms.Controls.XamStore
 
                        Content = new ScrollView { Content = grid };
 
-                       //var listView = new ListView();
-                       //listView.ItemsSource = Enumerable.Range(0, 1000).ToList();
 
-                       //Content = listView;
-               }
+            grid.Children.Add(MakeButton("Hide Nav Shadow",
+                    () => Shell.SetNavBarHasShadow(this, false)),
+                1, 21);
+
+            grid.Children.Add(MakeButton("Show Nav Shadow",
+                    () => Shell.SetNavBarHasShadow(this, true)),
+                2, 21);
+        }
 
                Switch _navBarVisibleSwitch;
                Switch _tabBarVisibleSwitch;
index 97f3753..5a7abec 100644 (file)
@@ -18,6 +18,9 @@ namespace Xamarin.Forms.Internals
                        if (propertyName == null || propertyName == Shell.NavBarIsVisibleProperty.PropertyName)
                                BaseShellItem.PropagateFromParent(Shell.NavBarIsVisibleProperty, element);
 
+                       if (propertyName == null || propertyName == Shell.NavBarHasShadowProperty.PropertyName)
+                               BaseShellItem.PropagateFromParent(Shell.NavBarHasShadowProperty, element);
+
                        if (propertyName == null || propertyName == Shell.TabBarIsVisibleProperty.PropertyName)
                                BaseShellItem.PropagateFromParent(Shell.TabBarIsVisibleProperty, element);
 
index f362704..62b07b3 100644 (file)
@@ -32,6 +32,10 @@ namespace Xamarin.Forms
                public static readonly BindableProperty NavBarIsVisibleProperty =
                        BindableProperty.CreateAttached("NavBarIsVisible", typeof(bool), typeof(Shell), true);
 
+               public static readonly BindableProperty NavBarHasShadowProperty =
+                       BindableProperty.CreateAttached("NavBarHasShadow", typeof(bool), typeof(Shell), default(bool),
+                               defaultValueCreator: (b) => Device.RuntimePlatform == Device.Android);
+
                public static readonly BindableProperty SearchHandlerProperty =
                        BindableProperty.CreateAttached("SearchHandler", typeof(SearchHandler), typeof(Shell), null, BindingMode.OneTime,
                                propertyChanged: OnSearchHandlerPropertyChanged);
@@ -71,6 +75,9 @@ namespace Xamarin.Forms
                public static bool GetNavBarIsVisible(BindableObject obj) => (bool)obj.GetValue(NavBarIsVisibleProperty);
                public static void SetNavBarIsVisible(BindableObject obj, bool value) => obj.SetValue(NavBarIsVisibleProperty, value);
 
+               public static bool GetNavBarHasShadow(BindableObject obj) => (bool)obj.GetValue(NavBarHasShadowProperty);
+               public static void SetNavBarHasShadow(BindableObject obj, bool value) => obj.SetValue(NavBarHasShadowProperty, value);
+
                public static SearchHandler GetSearchHandler(BindableObject obj) => (SearchHandler)obj.GetValue(SearchHandlerProperty);
                public static void SetSearchHandler(BindableObject obj, SearchHandler handler) => obj.SetValue(SearchHandlerProperty, handler);
 
diff --git a/Xamarin.Forms.Platform.Android/GenericGlobalLayoutListener.cs b/Xamarin.Forms.Platform.Android/GenericGlobalLayoutListener.cs
new file mode 100644 (file)
index 0000000..c525023
--- /dev/null
@@ -0,0 +1,34 @@
+using System;
+using Android.Views;
+using Object = Java.Lang.Object;
+
+namespace Xamarin.Forms.Platform.Android
+{
+       internal class GenericGlobalLayoutListener : Object, ViewTreeObserver.IOnGlobalLayoutListener
+       {
+               Action _callback;
+
+               public GenericGlobalLayoutListener(Action callback)
+               {
+                       _callback = callback;
+               }
+
+               public void OnGlobalLayout()
+               {
+                       _callback?.Invoke();
+               }
+
+               protected override void Dispose(bool disposing)
+               {
+                       Invalidate();
+                       base.Dispose(disposing);
+               }
+
+               // I don't want our code to dispose of this class I'd rather just let the natural
+               // process manage the life cycle so we don't dispose of this too early
+               internal void Invalidate()
+               {
+                       _callback = null;
+               }
+       }
+}
\ No newline at end of file
index f11e1b3..166d754 100644 (file)
@@ -18,6 +18,7 @@ using R = Android.Resource;
 using Toolbar = Android.Support.V7.Widget.Toolbar;
 using ADrawableCompat = Android.Support.V4.Graphics.Drawable.DrawableCompat;
 using ATextView = global::Android.Widget.TextView;
+using Android.Support.Design.Widget;
 
 namespace Xamarin.Forms.Platform.Android
 {
@@ -50,13 +51,19 @@ namespace Xamarin.Forms.Platform.Android
                //assume the default
                Color _tintColor = Color.Default;
                Toolbar _toolbar;
+               AppBarLayout _appBar;
+               float _appBarElevation;
+               GenericGlobalLayoutListener _globalLayoutListener;
 
                public ShellToolbarTracker(IShellContext shellContext, Toolbar toolbar, DrawerLayout drawerLayout)
                {
                        _shellContext = shellContext ?? throw new ArgumentNullException(nameof(shellContext));
                        _toolbar = toolbar ?? throw new ArgumentNullException(nameof(toolbar));
                        _drawerLayout = drawerLayout ?? throw new ArgumentNullException(nameof(drawerLayout));
+                       _appBar = _toolbar.Parent.GetParentOfType<AppBarLayout>();
 
+                       _globalLayoutListener = new GenericGlobalLayoutListener(() => UpdateNavBarHasShadow(Page));
+                       _appBar.ViewTreeObserver.AddOnGlobalLayoutListener(_globalLayoutListener);
                        _toolbar.SetNavigationOnClickListener(this);
                        ((IShellController)_shellContext.Shell).AddFlyoutBehaviorObserver(this);
                }
@@ -139,6 +146,11 @@ namespace Xamarin.Forms.Platform.Android
 
                        if (disposing)
                        {
+                               if (_appBar.IsAlive() && _appBar.ViewTreeObserver.IsAlive())
+                                       _appBar.ViewTreeObserver.RemoveOnGlobalLayoutListener(_globalLayoutListener);
+
+                               _globalLayoutListener.Invalidate();
+
                                if (_backButtonBehavior != null)
                                        _backButtonBehavior.PropertyChanged -= OnBackButtonBehaviorChanged;
                                ((IShellController)_shellContext?.Shell)?.RemoveFlyoutBehaviorObserver(this);
@@ -156,6 +168,7 @@ namespace Xamarin.Forms.Platform.Android
                                _drawerToggle?.Dispose();
                        }
 
+                       _globalLayoutListener = null;
                        _backButtonBehavior = null;
                        SearchHandler = null;
                        _shellContext = null;
@@ -163,6 +176,7 @@ namespace Xamarin.Forms.Platform.Android
                        _searchView = null;
                        Page = null;
                        _toolbar = null;
+                       _appBar = null;
                        _drawerLayout = null;
                        base.Dispose(disposing);
                }
@@ -202,6 +216,7 @@ namespace Xamarin.Forms.Platform.Android
                                UpdateLeftBarButtonItem();
                                UpdateToolbarItems();
                                UpdateNavBarVisible(_toolbar, newPage);
+                               UpdateNavBarHasShadow(newPage);
                                UpdateTitleView();
                        }
                }
@@ -215,6 +230,8 @@ namespace Xamarin.Forms.Platform.Android
                                UpdateToolbarItems();
                        else if (e.PropertyName == Shell.NavBarIsVisibleProperty.PropertyName)
                                UpdateNavBarVisible(_toolbar, Page);
+                       else if (e.PropertyName == Shell.NavBarHasShadowProperty.PropertyName)
+                               UpdateNavBarHasShadow(Page);
                        else if (e.PropertyName == Shell.BackButtonBehaviorProperty.PropertyName)
                        {
                                var backButtonHandler = Shell.GetBackButtonBehavior(Page);
@@ -411,6 +428,24 @@ namespace Xamarin.Forms.Platform.Android
                        toolbar.Visibility = navBarVisible ? ViewStates.Visible : ViewStates.Gone;
                }
 
+               void UpdateNavBarHasShadow(Page page)
+               {
+                       if (page == null || !_appBar.IsAlive())
+                               return;
+
+                       if (Shell.GetNavBarHasShadow(page))
+                       {
+                               if (_appBarElevation > 0)
+                                       _appBar.SetElevation(_appBarElevation);
+                       }
+                       else
+                       {
+                               // 4 is the default
+                               _appBarElevation = _appBar.Context.ToPixels(4);
+                               _appBar.SetElevation(0f);
+                       }
+               }
+
                protected virtual void UpdatePageTitle(Toolbar toolbar, Page page)
                {
                        _toolbar.Title = page.Title;
index afe184e..4614e93 100644 (file)
     <Compile Include="Extensions\FlowDirectionExtensions.cs" />
     <Compile Include="AppCompat\ImageButtonRenderer.cs" />
     <Compile Include="FastRenderers\ImageElementManager.cs" />
+    <Compile Include="GenericGlobalLayoutListener.cs" />
     <Compile Include="GestureManager.cs" />
     <Compile Include="FastRenderers\LabelRenderer.cs" />
     <Compile Include="FastRenderers\VisualElementRenderer.cs" />
index 43d043a..800cc3c 100644 (file)
@@ -9,5 +9,6 @@ namespace Xamarin.Forms.Platform.iOS
                void ResetAppearance(UINavigationController controller);
                void SetAppearance(UINavigationController controller, ShellAppearance appearance);
                void UpdateLayout(UINavigationController controller);
+               void SetHasShadow(UINavigationController controller, bool hasShadow);
        }
 }
\ No newline at end of file
index 92ba8d3..bd94ee7 100644 (file)
@@ -1,64 +1,12 @@
-using UIKit;
+using System;
+using System.ComponentModel;
+using CoreGraphics;
+using UIKit;
 
 namespace Xamarin.Forms.Platform.iOS
 {
-       public class SafeShellNavBarAppearanceTracker : IShellNavBarAppearanceTracker
+       [EditorBrowsable(EditorBrowsableState.Never)]
+       public class SafeShellNavBarAppearanceTracker : ShellNavBarAppearanceTracker
        {
-               UIColor _defaultBarTint;
-               UIColor _defaultTint;
-               UIStringAttributes _defaultTitleAttributes;
-
-               public void UpdateLayout(UINavigationController controller)
-               {
-               }
-
-               public void ResetAppearance(UINavigationController controller)
-               {
-                       if (_defaultTint != null)
-                       {
-                               var navBar = controller.NavigationBar;
-                               navBar.TintColor = _defaultTint;
-                               navBar.TitleTextAttributes = _defaultTitleAttributes;
-                       }
-               }
-
-               public void SetAppearance(UINavigationController controller, ShellAppearance appearance)
-               {
-                       var background = appearance.BackgroundColor;
-                       var foreground = appearance.ForegroundColor;
-                       var titleColor = appearance.TitleColor;
-
-                       var navBar = controller.NavigationBar;
-
-                       if (_defaultTint == null)
-                       {
-                               _defaultBarTint = navBar.BarTintColor;
-                               _defaultTint = navBar.TintColor;
-                               _defaultTitleAttributes = navBar.TitleTextAttributes;
-                       }
-
-                       if (!background.IsDefault)
-                               navBar.BarTintColor = background.ToUIColor();
-                       if (!foreground.IsDefault)
-                               navBar.TintColor = foreground.ToUIColor();
-                       if (!titleColor.IsDefault)
-                       {
-                               navBar.TitleTextAttributes = new UIStringAttributes
-                               {
-                                       ForegroundColor = titleColor.ToUIColor()
-                               };
-                       }
-               }
-
-               #region IDisposable Support
-               protected virtual void Dispose(bool disposing)
-               {
-               }
-
-               public void Dispose()
-               {
-                       Dispose(true);
-               }
-               #endregion
        }
 }
\ No newline at end of file
index aab03d3..af4c7b3 100644 (file)
@@ -1,44 +1,29 @@
-using UIKit;
+using System;
+using System.ComponentModel;
+using CoreGraphics;
+using UIKit;
 
 namespace Xamarin.Forms.Platform.iOS
 {
        public class ShellNavBarAppearanceTracker : IShellNavBarAppearanceTracker
        {
-               UIView _blurView;
-               UIView _colorView;
-               UIImage _defaultBackgroundImage;
+               UIColor _defaultBarTint;
                UIColor _defaultTint;
                UIStringAttributes _defaultTitleAttributes;
-               bool _disposed = false;
+               float _shadowOpacity = float.MinValue;
+               CGColor _shadowColor;
 
-               public void UpdateLayout (UINavigationController controller)
+               public void UpdateLayout(UINavigationController controller)
                {
-                       if (_blurView?.Superview == null)
-                               return;
-
-                       var navBar = controller.NavigationBar;
-                       navBar.SendSubviewToBack(_colorView);
-                       navBar.SendSubviewToBack(_blurView);
-
-                       var frame = navBar.Frame;
-                       frame.Height += frame.Y;
-                       frame.Y = -frame.Y;
-
-                       _blurView.Frame = frame;
-                       _colorView.Frame = frame;
                }
 
                public void ResetAppearance(UINavigationController controller)
                {
-                       if (_blurView != null)
+                       if (_defaultTint != null)
                        {
                                var navBar = controller.NavigationBar;
-                               navBar.SetBackgroundImage(_defaultBackgroundImage, UIBarMetrics.Default);
                                navBar.TintColor = _defaultTint;
                                navBar.TitleTextAttributes = _defaultTitleAttributes;
-
-                               _blurView.RemoveFromSuperview();
-                               _colorView.RemoveFromSuperview();
                        }
                }
 
@@ -50,42 +35,15 @@ namespace Xamarin.Forms.Platform.iOS
 
                        var navBar = controller.NavigationBar;
 
-                       if (_blurView == null)
+                       if (_defaultTint == null)
                        {
-                               _defaultBackgroundImage = navBar.GetBackgroundImage(UIBarMetrics.Default);
+                               _defaultBarTint = navBar.BarTintColor;
                                _defaultTint = navBar.TintColor;
                                _defaultTitleAttributes = navBar.TitleTextAttributes;
-
-                               var frame = navBar.Frame;
-                               frame.Height += frame.Y;
-                               frame.Y = -frame.Y;
-
-                               var effect = UIBlurEffect.FromStyle(UIBlurEffectStyle.Regular);
-                               _blurView = new UIVisualEffectView(effect);
-                               _blurView.UserInteractionEnabled = false;
-                               _blurView.Frame = frame;
-
-                               _colorView = new UIView(frame);
-                               _colorView.UserInteractionEnabled = false;
-
-                               if (Forms.IsiOS11OrNewer)
-                               {
-                                       _blurView.Layer.ShadowColor = UIColor.Black.CGColor;
-                                       _blurView.Layer.ShadowOpacity = 1f;
-                                       _blurView.Layer.ShadowRadius = 3;
-                               }
                        }
 
-                       navBar.SetBackgroundImage(new UIImage(), UIBarMetrics.Default);
-
-                       navBar.InsertSubview(_colorView, 0);
-                       navBar.InsertSubview(_blurView, 0);
-
                        if (!background.IsDefault)
-                       {
-                               _colorView.BackgroundColor = background.ToUIColor();
-                       }
-
+                               navBar.BarTintColor = background.ToUIColor();
                        if (!foreground.IsDefault)
                                navBar.TintColor = foreground.ToUIColor();
                        if (!titleColor.IsDefault)
@@ -98,37 +56,39 @@ namespace Xamarin.Forms.Platform.iOS
                }
 
                #region IDisposable Support
-               
                protected virtual void Dispose(bool disposing)
                {
-                       if (!_disposed)
-                       {
-                               if (disposing)
-                               {
-                                       if (_blurView != null)
-                                       {
-                                               _blurView.RemoveFromSuperview();
-                                               _blurView.Dispose();
-                                       }
-
-                                       if (_colorView != null)
-                                       {
-                                               _colorView.RemoveFromSuperview();
-                                               _colorView.Dispose();
-                                       }
-                               }
-
-                               _colorView = null;
-                               _blurView = null;
-
-                               _disposed = true;
-                       }
                }
 
                public void Dispose()
                {
                        Dispose(true);
                }
+
+               public virtual void SetHasShadow(UINavigationController controller, bool hasShadow)
+               {
+                       var navigationBar = controller.NavigationBar;
+                       if (_shadowOpacity == float.MinValue)
+                       {
+                               // Don't do anything if user hasn't changed the shadow to true
+                               if (!hasShadow)
+                                       return;
+
+                               _shadowOpacity = navigationBar.Layer.ShadowOpacity;
+                               _shadowColor = navigationBar.Layer.ShadowColor;
+                       }
+
+                       if (hasShadow)
+                       {
+                               navigationBar.Layer.ShadowColor = UIColor.Black.CGColor;
+                               navigationBar.Layer.ShadowOpacity = 1.0f;
+                       }
+                       else
+                       {
+                               navigationBar.Layer.ShadowColor = _shadowColor;
+                               navigationBar.Layer.ShadowOpacity = _shadowOpacity;
+                       }
+               }
                #endregion
        }
-}
\ No newline at end of file
+}
index 9a949d5..a76b1a1 100644 (file)
@@ -207,8 +207,8 @@ namespace Xamarin.Forms.Platform.iOS
                        if (_displayedPage != null)
                        {
                                _displayedPage.PropertyChanged += OnDisplayedPagePropertyChanged;
-                               if (!ShellSection.Stack.Contains(_displayedPage))
-                                       UpdateNavigationBarHidden();
+                               UpdateNavigationBarHidden();
+                               UpdateNavigationBarHasShadow();
                        }
                }
 
@@ -377,6 +377,8 @@ namespace Xamarin.Forms.Platform.iOS
                {
                        if (e.PropertyName == Shell.NavBarIsVisibleProperty.PropertyName)
                                UpdateNavigationBarHidden();
+                       else if (e.PropertyName == Shell.NavBarHasShadowProperty.PropertyName)
+                               UpdateNavigationBarHasShadow();
                }
 
                void PushPage(Page page, bool animated, TaskCompletionSource<bool> completionSource = null)
@@ -431,6 +433,11 @@ namespace Xamarin.Forms.Platform.iOS
                        SetNavigationBarHidden(!Shell.GetNavBarIsVisible(_displayedPage), true);
                }
 
+               void UpdateNavigationBarHasShadow()
+               {
+                       _appearanceTracker.SetHasShadow(this, Shell.GetNavBarHasShadow(_displayedPage));
+               }
+
                void UpdateShadowImages()
                {
                        NavigationBar.SetValueForKey(NSObject.FromObject(true), new NSString("hidesShadow"));
index 91f22fb..35d3dc3 100644 (file)
     <Compile Include="Renderers\ShellFlyoutRenderer.cs" />
     <Compile Include="Renderers\ShellItemRenderer.cs" />
     <Compile Include="Renderers\ShellItemTransition.cs" />
-    <Compile Include="Renderers\ShellNavBarAppearanceTracker.cs" />
     <Compile Include="Renderers\ShellPageRendererTracker.cs" />
     <Compile Include="Renderers\ShellRenderer.cs" />
     <Compile Include="Renderers\ShellScrollViewTracker.cs" />
     <Compile Include="CollectionView\CarouselViewController.cs" />
     <Compile Include="CollectionView\CarouselTemplatedCell.cs" />
     <Compile Include="Renderers\RefreshViewRenderer.cs" />
+    <Compile Include="Renderers\ShellNavBarAppearanceTracker.cs" />
   </ItemGroup>
   <ItemGroup>
     <EmbeddedResource Include="Resources\StringResources.ar.resx" />