Reputation: 23831
I want to add a gradient to both parts of the menu of an Xamarin app. Place where I want the gradient are marked with "here" - I'm just after ToolBar for now...
In order to attempt this, I have created the following class
public class GradientHeaderNavigationPage : NavigationPage
{
public GradientHeaderNavigationPage() { }
public GradientHeaderNavigationPage(Page root) : base(root) { }
public static readonly BindableProperty LeftColorProperty =
BindableProperty.Create(propertyName: nameof(LeftColor),
returnType: typeof(Color),
declaringType: typeof(GradientHeaderNavigationPage),
defaultValue: Color.FromHex("#92CD8C")); //Color.Accent);
public static readonly BindableProperty RightColorProperty =
BindableProperty.Create(propertyName: nameof(RightColor),
returnType: typeof(Color),
declaringType: typeof(GradientHeaderNavigationPage),
defaultValue: Color.FromHex("#17AEC6")); //Color.Accent);
public Color LeftColor
{
get { return (Color)GetValue(LeftColorProperty); }
set { SetValue(LeftColorProperty, value); }
}
public Color RightColor
{
get { return (Color)GetValue(RightColorProperty); }
set { SetValue(RightColorProperty, value); }
}
}
Then for Android (we can worry about iOS later) I have added the following custom renderer
[assembly: ExportRenderer(typeof(GradientHeaderNavigationPage), typeof(GradientHeaderNavigationRenderer))]
namespace Drax.Droid.Renderers
{
public class GradientHeaderNavigationRenderer : NavigationRenderer
{
public GradientHeaderNavigationRenderer(Context context) : base(context) { }
protected override void OnElementChanged(ElementChangedEventArgs<NavigationPage> e)
{
base.OnElementChanged(e);
if (e.OldElement != null || Element == null)
return;
var control = (GradientHeaderNavigationPage)Element;
var context = (MainActivity)Context;
context.ActionBar.SetBackgroundDrawable(
new GradientDrawable(GradientDrawable.Orientation.RightLeft,
new int[] { control.RightColor.ToAndroid(), control.LeftColor.ToAndroid() }));
}
}
}
and added to the /Resources/drawable
folder the file gradient_header.xml
<?xml version="1.0" encoding="utf-8" ?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<gradient
android:angle="180"
android:startColor="#92CD8C"
android:endColor="#17AEC6"
android:type="linear" />
</shape>
and in /Resources/layout/ToolBar.axml
I have added android:background="@drawable/header_gradient"
<android.support.v7.widget.Toolbar
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/header_gradient"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
android:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
Now, in my App.cs file (I am using Prism for MVVM) I have
[assembly: XamlCompilation(XamlCompilationOptions.Compile)]
namespace Drax
{
public partial class App : PrismApplication
{
public App(IPlatformInitializer initializer) : base(initializer) { }
protected override async void OnInitialized()
{
try
{
InitializeComponent();
var result = await NavigationService.NavigateAsync(
new Uri("/DraxMasterDetailPage/NavigationPage/MapPage"));
if (!result.Success)
SetMainPageFromException(result.Exception);
}
catch (Exception e)
{
Console.WriteLine($"Android DEBUG:: {e.Message}");
}
}
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
RegisterServices(containerRegistry);
RegisterViews(containerRegistry);
}
private void RegisterServices(IContainerRegistry containerRegistry)
{
containerRegistry.Register<ILoggerFacade, Services.DebugLogger>();
}
private void RegisterViews(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterForNavigation<NavigationPage>();
containerRegistry.RegisterForNavigation<DraxMasterDetailPage>();
containerRegistry.RegisterForNavigation<MapPage>();
}
private void SetMainPageFromException(Exception ex)
{
var layout = new StackLayout
{
Padding = new Thickness(40)
};
layout.Children.Add(new Label
{
Text = ex?.GetType()?.Name ?? "Unknown Error encountered",
FontAttributes = FontAttributes.Bold,
HorizontalOptions = LayoutOptions.Center
});
layout.Children.Add(new ScrollView
{
Content = new Label
{
Text = $"{ex}",
LineBreakMode = LineBreakMode.WordWrap
}
});
MainPage = new ContentPage
{
Content = layout
};
}
}
}
with my DraxMasterDetailPage
as
<MasterDetailPage x:Class="Drax.Views.DraxMasterDetailPage"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:Controls="clr-namespace:Drax.Controls">
<MasterDetailPage.Master>
<NavigationPage Title="Drax">
<x:Arguments>
<ContentPage Title="Menu">
<StackLayout Padding="40">
</StackLayout>
</ContentPage>
</x:Arguments>
</NavigationPage>
</MasterDetailPage.Master>
</MasterDetailPage>
I have tried to change NavigationPage
to GradientHeaderNavigationPage
but this throws a runtime exception of
System.NullReferenceException: Object reference not set to an instance of an object.
on this line in the Adroid renderer
context.ActionBar.SetBackgroundDrawable(
new GradientDrawable(GradientDrawable.Orientation.RightLeft,
new int[] { control.RightColor.ToAndroid(), control.LeftColor.ToAndroid() }));
ActionBar
is null.
How can I amend this code to show the gradient ToolBar?
Upvotes: 2
Views: 513
Reputation: 6643
You have changed the toolbar's background through the gradient_header.xml
and ToolBar.axml
. It means the whole app's toolbar has been changed. You can comment out the Naviagtion Renderer to see the effect.
If you want to adjust it using the code, you should manipulate the ContentPage
's custom renderer instead of the NavigationPage
's.
Firstly, define some methods to find the toolbar using code in MainActivity
:
private static MainActivity instance;
public static View RootFindViewById<T>(int id) where T : View
{
return instance.FindViewById<T>(id);
}
public MainActivity()
{
instance = this;
}
Then you could change the toolbar's background dynamically:
[assembly: ExportRenderer(typeof(ContentPage), typeof(CustomPageRenderer))]
namespace PrismDemo.Droid
{
public class CustomPageRenderer : PageRenderer
{
public CustomPageRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
{
base.OnElementChanged(e);
var toolbar = MainActivity.RootFindViewById<Toolbar>(Resource.Id.toolbar);
if (toolbar == null) return;
toolbar.SetBackground(new GradientDrawable(GradientDrawable.Orientation.RightLeft,
new int[] { Color.Red.ToAndroid(), Color.Blue.ToAndroid() }));
}
}
}
Here, I used hard code for setting the color and apply the custom renderer for all of the content pages. You could adjust it to your specified page and define the color bindable property there.
Alternatively, I think you can utilize the MessagingCenter to trigger the toolbar adjusting code.
Upvotes: 1
Reputation: 9990
For me this worked for toolbar:
var toolbar = FindViewById<Android.Support.V7.Widget.Toolbar>(Resource.Id.toolbar);
if (toolbar == null)
return;
However, it is inside OnLayout
. I can't remember for sure at the moment, but it is likely that you can find toolbar properly only there.
Upvotes: 1