Xamarin forms - Style in android - xaml

I've implemented dark mode support recently in my Xamarin app following this tutorial : https://devblogs.microsoft.com/xamarin/modernizing-ios-apps-dark-mode-xamarin/
I've set my Label Colors using a Style defined in App.xaml like this :
<Style x:Key="label" TargetType="Label">
<Setter Property="TextColor" Value="{DynamicResource label}"/>
</Style>
<Style x:Key="labelLight" TargetType="Label">
<Setter Property="TextColor" Value="{DynamicResource labelLight}"/>
</Style>
Where the colors label and labelLight are defined in a ResourceDictionary named LightTheme.xaml and DarkTheme.xaml.
I also implemented a custom renderer for iOS like this:
public class ThemeRenderer : Xamarin.Forms.Platform.iOS.PageRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
if (e.OldElement != null || Element == null)
return;
SetTheme();
}
public override void TraitCollectionDidChange(UITraitCollection previousTraitCollection)
{
base.TraitCollectionDidChange(previousTraitCollection);
if (TraitCollection.UserInterfaceStyle != previousTraitCollection.UserInterfaceStyle)
SetTheme();
}
private void SetTheme()
{
if (TraitCollection.UserInterfaceStyle == UIUserInterfaceStyle.Dark)
{
App.Current.Resources = new DarkTheme();
App.AppTheme = Theme.Dark;
}
else
{
App.Current.Resources = new LightTheme();
App.AppTheme = Theme.Light;
}
}
}
This is where I get in trouble, my Android version of the App doesn't recognize these colors (everything is in LightGray) and my App doesn't run anymore under iOS 12 and lower. I have no clue what to do to fix this.

For example in android activity
[Activity(Label = "#string/app_name", Theme = "#style/MyTheme", MainLauncher =true)]
public class MainActivity : AppCompatActivity{
...
}
then in your Resources/values/styles.xml :
<!-- Base application theme. -->
<style name="MyTheme" parent="Theme.AppCompat.DayNight.DarkActionBar"> // base theme to use Theme.AppCompat.DayNight
<item name="colorPrimary">#color/colorPrimary</item>
<item name="colorPrimaryDark">#color/colorPrimaryDark</item>
<item name="colorAccent">#color/colorAccent</item>
<item name="android:colorPrimaryDark">#color/colorPrimaryDark</item>
<item name="android:colorPrimary">#ff29b6f6</item>
</style>
and you could read this.

Related

Why is MenuFlyout.Items always null in Closing event?

I can successfully add a MenuFlyoutItem to a MenuFlyout dynamically in the ContextMenuFlyout_Opening event, but when I try to remove it in ContextMenuFlyout_Closing the MenuFlyout.Items is always null and the MFI isnt found and removed.
Any ideas why this is so?
<Page.Resources>
<MenuFlyout
x:Key="ListViewContextMenu"
Closing="{x:Bind ViewModel.ContextMenuFlyout_Closing}"
Opening="{x:Bind ViewModel.ContextMenuFlyout_Opening}">
<MenuFlyoutItem
Click="{x:Bind ViewModel.EditParty}"
Icon="Edit"
Text="Edit" />
<MenuFlyoutItem Text="Open in new window">
<MenuFlyoutItem.Icon>
<FontIcon
FontFamily="Segoe MDL2 Assets"
FontSize="40"
Glyph="ξΆ§" />
</MenuFlyoutItem.Icon>
</MenuFlyoutItem>
<MenuFlyoutSeparator />
<MenuFlyoutItem
Click="MenuFlyoutItem_Click"
Icon="Delete"
Text="Delete" />
</MenuFlyout>
</Page.Resources>
ViewModel event handlers
public void ContextMenuFlyout_Opening(object sender, object e)
{
MenuFlyout flyout = sender as MenuFlyout;
if (flyout != null)
{
// If party.IsConflict = true then add the MFI
if (SelectedTalent.IsConflict)
{
flyout.Items.Add(new MenuFlyoutItem()
{
Icon = new FontIcon() { Glyph = "\uEC4F" },
Text = "Resolve Conflict"
});
}
}
}
public void ContextMenuFlyout_Closing(object sender, object e)
{
// Remove 'Resolve Conflict' MFI if its found
MenuFlyout flyout = sender as MenuFlyout;
if (flyout != null)
{
var rc = flyout.Items.FirstOrDefault(o => o.Name == "Resolve Conflict");
if (rc != null)
{
flyout.Items.Remove(rc);
}
}
}
ListView that uses the MenuFlyout
<ListView
ContextFlyout="{StaticResource ListViewContextMenu}"
Based on your code, it seems that you are trying to get the MenuFlyoutItem object by name. But you forget to give the MenuFlyoutItem object a Name when you add it to the MenuFlyout, you just added a Text property.
flyout.Items.Add(new MenuFlyoutItem()
{
Icon = new FontIcon() { Glyph = "\uEC4F" },
Text = "Resolve Conflict",
Name = "Resolve Conflict",
});

Couldnt bind color to LinearGradientBrush

I'm trying this in collection view and i want each item with a different color so I have bound color to GradientStop in xaml like this:
<BoxView.Background>
<LinearGradientBrush EndPoint="0,1">
<GradientStop Color="{Binding gradient_start_color}" Offset="0.1" />
<GradientStop Color="{Binding gradient_stop_color}" Offset="1.0" />
</LinearGradientBrush>
</BoxView.Background>
But the color is not bound and by default i get transparent background. Is there a way to bind gradientstop color?
This is a known bug in Xamarin.Forms https://github.com/xamarin/Xamarin.Forms/issues/12339, the workaround mentioned there is to change it in the code-behind rather than using data binding in xaml.
<BoxView x:Name="boxView" ...>
Color gradient_start_color;
Public Color Gradient_start_color
{
get => gradient_start_color;
set
{
gradient_start_color = value;
PropertyChanged();
UpdateBoxViewBackground();
};
}
Color gradient_stop_color;
Public Color Gradient_stop_color
{
get => gradient_stop_color;
set
{
gradient_stop_color = value;
PropertyChanged();
UpdateBoxViewBackground();
};
}
UpdateBoxViewBackground()
{
(boxView.Background as LinearGradientBrush).GradientStops[0].Color = Gradient_start_color;
(boxView.Background as LinearGradientBrush).GradientStops[1].Color = Gradient_stop_color;
}
Constructor()
{
var background = new LinearGradientBrush
{
EndPoint = new Point(0, 1),
GradientStops = new GradientStopCollection
{
new GradientStop { Color = Gradient_start_color, Offset = 0.1f },
new GradientStop { Color = Gradient_stop_color, Offset = 1.0f }
}
};
boxView.Background = background;
}

Custom MaterialFrame control with only one platform renderer

In my Xamarin.Forms app, I have MaterialFrame custom control.
iOS renderer works great and looks like:
public class MaterialFrameRenderer : FrameRenderer
{
private const int ShadowColor = 0x939393;
public override void Draw(
CGRect rect)
{
base.Draw(rect);
// Update shadow to match better material design standards of elevation
Layer.ShadowRadius = Layer.CornerRadius;
Layer.ShadowColor = ColorHelper.FromHex(ShadowColor).CGColor;
Layer.ShadowOffset = new CGSize(1, 1);
Layer.ShadowOpacity = 0.30f;
Layer.ShadowPath = UIBezierPath.FromRect(Layer.Bounds).CGPath;
Layer.MasksToBounds = false;
}
}
On Android platform I want to use implementation based on my other cross-platform control. My .net standard(shared project) implementation:
public class MaterialFrame : Frame
{
public MaterialFrame()
{
if (Device.RuntimePlatform == Device.Android)
{
Content = new MyOtherCustomControl
{
BackgroundColor = Color.Red
};
}
}
}
Unfortunately this implementation doesn't work on Android. Do you have any suggestion?
I create the MyOtherCustomControl.
<StackLayout>
<Label
HorizontalOptions="CenterAndExpand"
Text="MyOtherCustomControl"
VerticalOptions="CenterAndExpand" />
<Button />
</StackLayout>
And use it in MaterialFrame custom control.
public class MaterialFrame : Frame
{
public MaterialFrame()
{
if (Device.RuntimePlatform == Device.Android)
{
Content = new MyOtherCustomControl
{
BackgroundColor = Color.Red
};
}
}
}
Usage of MaterialFrame:
<ContentPage.Content>
<StackLayout>
<Label
HorizontalOptions="CenterAndExpand"
Text="Welcome to Xamarin.Forms!"
VerticalOptions="CenterAndExpand" />
<pages:MaterialFrame />
</StackLayout>
</ContentPage.Content>
Screenshot:

Style Trigger only working one way when bound to IsEnabled of custom class

I made a custom TimePicker and am trying to have it disabled from the start, and only enable it whenever something was done first. To make it show as disabled I use a Style trigger to change it's appearence related to it's IsEnabled property.
My problem though is that this trigger only worked in first initialization so the style looks like its disabled. but once I Enable it the trigger doesnt change the appearence back. What am I missing?
my trigger
<Style TargetType="customControlls:BorderedTimePicker">
<Style.Triggers>
<Trigger TargetType="customControlls:BorderedTimePicker" Property="IsEnabled" Value="False">
<Setter Property="BorderColor" Value="{StaticResource varColBgGrey}" />
<Setter Property="TextColor" Value="{StaticResource varColBgGrey}" />
<Setter Property="Image" Value="ClockDisabled.png" />
</Trigger>
</Style.Triggers>
To more clarify, I am sure that IsEnabled got changed since the TimePicker works after setting enabled to true, but the appearence still doesn't change.
My custom TimePicker:
public class BorderedTimePicker : TimePicker
{
#region Bindables
public static readonly BindableProperty MinimumTimeProperty = BindableProperty.Create(nameof(MinimumTime), typeof(DateTime?), typeof(BorderedTimePicker), null);
public DateTime MinimumTime
{
get => (DateTime)GetValue(MinimumTimeProperty);
set => SetValue(MinimumTimeProperty, value);
}
public static readonly BindableProperty MaximumTimeProperty = BindableProperty.Create(nameof(MaximumTime), typeof(DateTime?), typeof(BorderedTimePicker), null);
public DateTime MaximumTime
{
get => (DateTime)GetValue(MaximumTimeProperty);
set => SetValue(MaximumTimeProperty, value);
}
public static readonly BindableProperty BorderColorProperty = BindableProperty.Create(nameof(BorderColor), typeof(Color), typeof(BorderedTimePicker), Color.DarkGray, BindingMode.TwoWay);
public Color BorderColor
{
get => (Color)GetValue(BorderColorProperty);
set
{
SetValue(BorderColorProperty, value);
OnPropertyChanged();
}
}
public static readonly BindableProperty TextSizeProperty = BindableProperty.Create(nameof(TextSize), typeof(int), typeof(BorderedTimePicker), 16);
public int TextSize
{
get => (int)GetValue(TextSizeProperty);
set => SetValue(TextSizeProperty, value);
}
public static readonly BindableProperty BorderThicknessProperty = BindableProperty.Create(nameof(BorderThickness), typeof(double), typeof(BorderedTimePicker), 2.0);
public double BorderThickness
{
get => (double)GetValue(BorderThicknessProperty);
set => SetValue(BorderThicknessProperty, value);
}
public static readonly BindableProperty BorderRadiusProperty = BindableProperty.Create(nameof(BorderRadius), typeof(double), typeof(BorderedTimePicker), 20.0);
public double BorderRadius
{
get => (double)GetValue(BorderRadiusProperty);
set => SetValue(BorderRadiusProperty, value);
}
public static readonly BindableProperty PlaceholderProperty = BindableProperty.Create(nameof(Placeholder), typeof(string), typeof(BorderedTimePicker), string.Empty);
public string Placeholder
{
get => (string)GetValue(PlaceholderProperty);
set => SetValue(PlaceholderProperty, value);
}
public static readonly BindableProperty ImageProperty = BindableProperty.Create(nameof(Image), typeof(string), typeof(BorderedTimePicker), string.Empty, BindingMode.TwoWay);
public string Image
{
get => (string)GetValue(ImageProperty);
set
{
SetValue(ImageProperty, value);
OnPropertyChanged();
}
}
public static readonly BindableProperty ImageSizeProperty = BindableProperty.Create(nameof(ImageSize), typeof(int), typeof(BorderedTimePicker), 35);
public int ImageSize
{
get => (int)GetValue(ImageSizeProperty);
set => SetValue(ImageSizeProperty, value);
}
public static readonly BindableProperty IsValidProperty = BindableProperty.Create(nameof(IsValid), typeof(bool), typeof(BorderedDatePicker), true);
public bool IsValid
{
get => (bool)GetValue(IsValidProperty);
set
{
SetValue(IsValidProperty, value);
OnPropertyChanged();
}
}
public static readonly BindableProperty NullableDateProperty = BindableProperty.Create(nameof(NullableDate), typeof(DateTime?), typeof(ExtendedDatePicker), null, BindingMode.TwoWay);
public DateTime? NullableDate
{
get => (DateTime?)GetValue(NullableDateProperty);
set
{
if (value != NullableDate)
{
SetValue(NullableDateProperty, value);
UpdateDate();
}
}
}
#endregion Bindables
public BorderedTimePicker()
{
}
private void UpdateDate()
{
if (NullableDate.HasValue)
{
Time = NullableDate.Value.TimeOfDay;
}
}
protected override void OnPropertyChanged(string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if (propertyName == TimeProperty.PropertyName)
{
if (NullableDate != null) NullableDate = NullableDate.Value + Time;
}
}
EDIT: the relevant part of my viewCell where I use the trigger and the timepicker
<ViewCell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:customControlls="clr-namespace:CleanKISeCare.CustomControlls;assembly=CleanKISeCare"
xmlns:helpers="clr-namespace:Syncfusion.ListView.XForms.Helpers;assembly=Syncfusion.SfListView.XForms"
xmlns:converter="clr-namespace:CleanKISeCare.Converter;assembly=CleanKISeCare"
x:Class="CleanKISeCare.CustomControlls.DatePickerComponent"
x:Name="this">
<ViewCell.BindingContext>
<helpers:InverseBoolConverter x:Key="InverseBoolConverter" />
</ViewCell.BindingContext>
<StackLayout x:Name="mainView" BindingContext="{Binding .}" BackgroundColor="White" Padding="10">
<StackLayout.Resources>
<ResourceDictionary>
<helpers:InverseBoolConverter x:Key="InverseBoolConverter" />
<converter:StringDateTimeConverter x:Key="StringDateTimeConverter" />
<Style TargetType="customControlls:BorderedTimePicker">
<Style.Triggers>
<Trigger TargetType="customControlls:BorderedTimePicker" Property="IsEnabled" Value="False">
<Setter Property="BorderColor" Value="{StaticResource varColBgGrey}" />
<Setter Property="TextColor" Value="{StaticResource varColBgGrey}" />
<Setter Property="Image" Value="ClockDisabled.png" />
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
</StackLayout.Resources>
<Grid Margin="10"
HorizontalOptions="CenterAndExpand">
<customControlls:BorderedTimePicker
Grid.Row="0" Grid.Column="1"
x:Name="timePicker"
Image="ClockPicker.png"
TextSize="14"
WidthRequest="160"
Placeholder="Zeit"
Margin="10,0,0,0"
HorizontalOptions="StartAndExpand"
MinimumTime="{Binding MinValue, Converter={StaticResource StringDateTimeConverter}}"
MaximumTime="{Binding MaxValue, Converter={StaticResource StringDateTimeConverter}}"
NullableDate="{Binding Answer, Source={x:Reference this}, Mode=TwoWay}"
BorderColor="{StaticResource varColLightDark}"
TextColor="{StaticResource varColDark}"
Format="HH:mm" />
</Grid>
</StackLayout>
</ViewCell>
and the code behind:
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class DatePickerComponent : ViewCell
{
public DatePickerComponent()
{
InitializeComponent();
timePicker.IsEnabled = datePicker.NullableDate != null;
}
private static void OnPropertyChanged(BindableObject bindable, object oldvalue, object newvalue)
{
DatePickerComponent component = bindable as DatePickerComponent;
component?.SetDate(newvalue);
}
void SetDate(object newvalue)
{
timePicker.IsEnabled = newDate.Value.Date != defaultDate;
}
}

Xamarin reusable xaml user control and custom command

first of all sorry for my english.
I am working on a iOS and Android project using Xamarin.Form
I would like to have a 'xaml user control' reusable in different way, and I need to make it clickable with ICommand
This is the StackLayoutButton component:
<?xml version="1.0" encoding="utf-8" ?>
<StackLayout xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Installa.Controls.StackLayoutButton">
<Image x:Name="Icon" Source="{Binding Icon}" />
<Label x:Name="Text" Text="{Binding Title}" HorizontalOptions="Center" LineBreakMode="NoWrap" Font="Small" TextColor="Red" />
</StackLayout>
This is the CalendarioPage xaml page where the component is used
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:Installa.Controls;assembly=Installa"
x:Class="Installa.CalendarioPage">
<StackLayout>
<Label Text="{Binding ViewName}" Font="42" IsVisible="{Binding IsWindowsPhone}" />
<ActivityIndicator IsRunning="{Binding IsLoading}" IsVisible="{Binding IsLoading}" Color="Red" />
<controls:StackLayoutButton BindingContext="{Binding Blog}" TextColor="Blue" /> <!-- Command="{Binding SubmitCommand}" -->
<controls:StackLayoutButton BindingContext="{Binding Facebook}" TextColor="Red" /> <!-- Command="{Binding SubmitCommand}" -->
</StackLayout>
</ContentPage>
This is the CalendarioPage c# page:
public partial class CalendarioPage : ContentPage
{
private CalendarioViewModel vm;
public CalendarioPage()
{
InitializeComponent();
vm = new CalendarioViewModel();
this.BindingContext = vm;
}
}
This is the viewmodel class:
namespace Installa
{
public class CalendarioViewModel: BaseViewModel
{
public CalendarioViewModel()
{
blog = new Activity();
blog.Link = "www.google.it";
blog.Title = "Titolo del blog";
blog.Icon = "logomenu.png";
facebook = new Activity();
facebook.Title = "Tito Fbook";
facebook.Link = "www.facebook.it";
facebook.Icon = "icon.png";
ViewName = "nome della view";
IsLoading = false;
}
Activity blog = null;
public Activity Blog
{
get {return blog;}
}
Activity facebook = null;
public Activity Facebook
{
get { return facebook; }
}
string viewName = string.Empty;
public string ViewName
{
get { return viewName; }
set { SetProperty(ref viewName, value); }
}
public bool IsWindowsPhone
{
get
{
return Device.OS == TargetPlatform.WinPhone;
}
}
bool isLoading = false;
public bool IsLoading
{
get { return isLoading; }
set { SetProperty(ref isLoading, value); }
}
}
}
With Activity a simple class with:
public string Title { get; set; }
public string Link { get; set; }
public String Icon { get; set; }
Till now, all is working right, but now I need to implement the ICommand interface.
In StackLayoutButton c# code I try to add:
var tapGestureRecognizer = new TapGestureRecognizer();
tapGestureRecognizer.SetBinding(TapGestureRecognizer.CommandProperty, "TapCommand");
Icon.GestureRecognizers.Add(tapGestureRecognizer)
Text.GestureRecognizers.Add(tapGestureRecognizer)
Furthermore I try to add into CalendarioViewModel INotifyPropertyChanged and the 'OnTapped' method.
Into Activity.cs i add 'ICommand tapCommand' and the related get...but is not working.
I try even other..but I am not able to enable the tap on the StackLayoutButton components.
In wich way I should do ?
I'd like to be able to have a 'programmable' command...for example I would like browse to 'the link property' of Activity or be able to open a new view.
Thanks for help!
Update:
I was able to add TapGestureRecognizer into the xaml user control (StackLayoutButton.xaml.cs),
but I'd like to implement it in MVVM way,
using Xamarin.Forms;
namespace Installa.Controls
{
public partial class StackLayoutButton : StackLayout
{
public StackLayoutButton()
{
InitializeComponent();
TapGestureRecognizer tapGestureRecognizer = new TapGestureRecognizer
{
Command = new Command(OnMyComponentTapped),
CommandParameter = "ciao"
};
this.Icon.GestureRecognizers.Add(tapGestureRecognizer);
this.Text.GestureRecognizers.Add(tapGestureRecognizer);
}
async void OnMyComponentTapped(object parameter)
{
// do action
}
public Color TextColor
{
get { return this.Text.TextColor; }
set { this.Text.TextColor = value; }
}
public Label TextControl
{
get { return this.Text; }
set { this.Text = value; }
}
}
}
can anyone suggest me the way ?
Thanks
This is how I add gesture recognizer on my xaml view:
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding SelectEquipmentCommand}"/>
</Label.GestureRecognizers>
And this is the ICommand property in my ViewModel:
public ICommand SelectEquipmentCommand
{
get
{
return fSelectEquipmentCommand ?? (fSelectEquipmentCommand = new Command(async () => await SelectEquipmentTask()));
}
}
private async Task SelectEquipmentTask()
{
}