I need to localize the letters of the days above the datepickerdialog in android.
I already fully localized the application to the desired language. Changed the background etc. For some reason though, the letters of the days ain't changing.
I'm currently trying to alter the dialog via a CustomDatepickRenderer, but haven't found the right properties.
You could use the custom renderer to set the current configuration.
Custom Renderer:
[assembly: ExportRenderer(typeof(Xamarin.Forms.DatePicker), typeof(DatePickerDialogCustomRenderer))]
namespace App2.Droid
{
class DatePickerDialogCustomRenderer : DatePickerRenderer
{
public DatePickerDialogCustomRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.DatePicker> e)
{
base.OnElementChanged(e);
var locale = Locale.English;
this.Control.TextLocale = locale;
Resources.Configuration.SetLocale(locale);
}
}
}
The Language of the device is Chinese. Use the code to set the language of the Days Line to English.
Before:
After:
Related
My page has a margin on the left and right when viewed on a tablet device in landscape mode:
Is there any way that I can add a left and right margin to the tab area also?
Here, the better approach will be to use Padding instead of Margin. You will see why in a while.
So, to start with the implementation - you will need to harness the power of Custom renderers.
In this specific case, we will need to inherit from ShellRenderer. Also, there are some differences for Android & for iOS - for Android, you will need to override CreateBottomNavViewAppearanceTracker and for iOS - CreateTabBarAppearanceTracker
Assuming that you have followed the recommendations and named your Shell AppShell, then the 2 classes will look like this.
Android:
using Android.Content;
using TestShellTabBarMargin;
using TestShellTabBarMargin.Droid;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(AppShell), typeof(AppShellRenderer))]
namespace TestShellTabBarMargin.Droid
{
public class AppShellRenderer : ShellRenderer
{
public AppShellRenderer(Context context)
: base(context)
{
}
protected override IShellBottomNavViewAppearanceTracker CreateBottomNavViewAppearanceTracker(ShellItem shellItem)
{
return new MarginedTabBarAppearance();
}
}
}
iOS:
using TestShellTabBarMargin;
using TestShellTabBarMargin.iOS;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(AppShell), typeof(AppShellRenderer))]
namespace TestShellTabBarMargin.iOS
{
public class AppShellRenderer : ShellRenderer
{
protected override IShellTabBarAppearanceTracker CreateTabBarAppearanceTracker()
{
return new MarginedTabBarAppearance();
}
}
}
Next, you will need to create the appearance classes and inherit from the base classes (Android - ShellBottomNavViewAppearanceTracker & iOS - ShellTabBarAppearanceTracker).
NB!: You can also implement their interfaces (Android - IShellBottomNavViewAppearanceTracker & iOS - IShellTabBarAppearanceTracker) but this way you will lose all of the styles that you have already applied and then you'll have to set them by hand.
After the classes have been subclassed, the important method is SetAppearance. ResetAppearance will also work, but it is invoked in many other cases and we need to change it only once.
Here is how it looks by default on Android:
The proper implementation is to set the left & right paddings of the bottom navigation view like this:
using Android.Support.Design.Widget;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
namespace TestShellTabBarMargin.Droid
{
public class MarginedTabBarAppearance : ShellBottomNavViewAppearanceTracker
{
public MarginedTabBarAppearance(IShellContext shellContext, ShellItem shellItem)
: base(shellContext, shellItem)
{
}
public override void SetAppearance(BottomNavigationView bottomView, IShellAppearanceElement appearance)
{
base.SetAppearance(bottomView, appearance);
bottomView.SetPadding(400, 0, 400, 0);
}
}
}
End result:
If we want to set the margins, instead of the paddings, then we can modify the layoutParams of the view like this:
public override void SetAppearance(BottomNavigationView bottomView, ShellAppearance appearance)
{
if (bottomView.LayoutParameters is LinearLayout.LayoutParams layoutParams)
{
layoutParams.SetMargins(400, 0, 400, 0);
bottomView.LayoutParameters = layoutParams;
}
}
However, here it will look like this:
You can go and try to set the parent view's Background color, but the end result will be the same and with the Padding set you won't need to try to fix what is not broken.
For iOS the base flow is the same. The important method is again SetAppearance and inside it we can modify our UITabBar.
Unfortunately, I haven't found yet the proper config yet, but I will update my answer when I do. Setting the view's/frame's margins/offsets should do the work, but I suspect that the guys from Xamarin are resetting the values after the method has been executed. I bit of tinkering and trial-and-error need to happen here.
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
namespace TestShellTabBarMargin.iOS
{
public class MarginedTabBarAppearance : ShellTabBarAppearanceTracker
{
public override void SetAppearance(UITabBarController controller, ShellAppearance appearance)
{
// Modify tab bar settings
}
}
}
Edit: Setting the items' width & positioning to centered should also work and in fact is working, but only on an iPhone (on Portrait). Like I said, I suspect that the guys from Xamarin are making some updates after our changes.
This should work, but it doesn't:
public override void SetAppearance(UITabBarController controller, ShellAppearance appearance)
{
base.SetAppearance(controller, appearance);
var tabBar = controller.TabBar;
tabBar.ItemWidth = 50;
tabBar.ItemPositioning = UITabBarItemPositioning.Centered;
}
NB: Keep in mind that you will need to properly handle orientation changes and probably the device idiom (tablet or phone). According to the returned values, you can only then update the desired offsets.
You should use TabbedPageRenderer on iOS/Android platform to change the TabBar template.
For instance, Android it could be TabLayout for Android platform and TabBar for iOS
If you meant the shell tab page the first thing you should do is to implement your own ShellRenderer on platform. After that you need to override CreateTabBarAppearanceTracker method where you'll be able to create and return your own ShellTabBarAppearanceTracker(or ShellTabLayoutAppearenceTracker for Android).
After that you implement your ShellTabBarAppearanceTracker using by IShellTabBarAppearanceTracker (for iOS).
You can do it like this guy:
-Creating ShellTabBar/LayoutAppearanceTracker
I have found that on iOS, OnAppearing is called when the page literally appears on the screen, whereas on Android, it's called when it's created.
I'm using this event to lazily construct an expensive to construct view but obviously the Android behaviour defeats this.
Is there some way of knowing on Android when a screen literally appears on the screen?
You can use the event:
this.Appearing += YourPageAppearing;
Otherwise, you should use the methods of the Application class that contains the lifecycle methods:
protected override void OnStart()
{
Debug.WriteLine ("OnStart");
}
protected override void OnSleep()
{
Debug.WriteLine ("OnSleep");
}
protected override void OnResume()
{
Debug.WriteLine ("OnResume");
}
On Android, Xamarin.Forms.Page.OnAppearing is called immediately before the page's view is shown to user (not when the page is "created" (constructed)).
If you want an initial view to appear quickly, by omitting an expensive sub-view, use a binding to make that view's IsVisible initially be "false". This will keep it out of the visual tree, avoiding most of the cost of building it. Place the (invisible) view in a grid cell, whose dimensions are constant (either in DPs or "*" - anything other than "Auto".) So that layout will be "ready" for that view, when you make it visible.
APPROACH 1:
Now you just need a binding in view model that will change IsVisible to "true".
The simplest hack is to, in OnAppearing, fire an action that will change that variable after 250 ms.
APPROACH 2:
The clean alternative is to create a custom page renderer, and override "draw".
Have draw, after calling base.draw, check an action property on your page.
If not null, invoke that action, then clear it (so only happens once).
I do this by inheriting from a custom page base class:
XAML for each of my pages (change "ContentPage" to "exodus:ExBasePage"):
<exodus:ExBasePage
xmlns:exodus="clr-namespace:Exodus;assembly=Exodus"
x:Class="YourNamespace.YourPage">
...
</exodus:ExBasePage>
xaml.cs:
using Exodus;
// After creating page, change "ContentPage" to "ExBasePage".
public partial class YourPage : ExBasePage
{
...
my custom ContentPage. NOTE: Includes code not needed for this, related to iOS Safe Area and Android hardward back button:
using Xamarin.Forms;
using Xamarin.Forms.PlatformConfiguration.iOSSpecific;
namespace Exodus
{
public abstract partial class ExBasePage : ContentPage
{
public ExBasePage()
{
// Each sub-class calls InitializeComponent(); not needed here.
ExBasePage.SetupForLightStatusBar( this );
}
// Avoids overlapping iOS status bar at top, and sets a dark background color.
public static void SetupForLightStatusBar( ContentPage page )
{
page.On<Xamarin.Forms.PlatformConfiguration.iOS>().SetUseSafeArea( true );
// iOS NOTE: Each ContentPage must set its BackgroundColor to black or other dark color (when using LightContent for status bar).
//page.BackgroundColor = Color.Black;
page.BackgroundColor = Color.FromRgb( 0.3, 0.3, 0.3 );
}
// Per-platform ExBasePageRenderer uses these.
public System.Action NextDrawAction;
/// <summary>
/// Override to do something else (or to do nothing, i.e. suppress back button).
/// </summary>
public virtual void OnHardwareBackButton()
{
// Normal content page; do normal back button behavior.
global::Exodus.Services.NavigatePopAsync();
}
}
}
renderer in Android project:
using System;
using Android.Content;
using Android.Views;
using Android.Graphics;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Exodus;
using Exodus.Android;
[assembly: ExportRenderer( typeof( ExBasePage ), typeof( ExBasePageRenderer ) )]
namespace Exodus.Android
{
public class ExBasePageRenderer : PageRenderer
{
public ExBasePageRenderer( Context context ) : base( context )
{
}
protected override void OnElementChanged( ElementChangedEventArgs<Page> e )
{
base.OnElementChanged( e );
var page = Element as ExBasePage;
if (page != null)
page.firstDraw = true;
}
public override void Draw( Canvas canvas )
{
try
{
base.Draw( canvas );
var page = Element as ExBasePage;
if (page?.NextDrawAction != null)
{
page.NextDrawAction();
page.NextDrawAction = null;
}
}
catch (Exception ex)
{
// TBD: Got Disposed exception on Android Bitmap, after rotating phone (in simulator).
// TODO: Log exception.
Console.WriteLine( "ExBasePageRenderer.Draw exception: " + ex.ToString() );
}
}
}
}
To do some action after the first time the page is drawn:
public partial class YourPage : ExBasePage
{
protected override void OnAppearing()
{
// TODO: OnPlatform code - I don't have it handy.
// On iOS, we call immediately "DeferredOnAppearing();"
// On Android, we set this field, and it is done in custom renderer.
NextDrawAction = DeferredOnAppearing;
}
void DeferredOnAppearing()
{
// Whatever you want to happen after page is drawn first time:
// ((MyViewModel)BindingContext).ExpensiveViewVisible = true;
// Where MyViewModel contains:
// public bool ExpensiveViewVisible { get; set; }
// And your XAML contains:
// <ExpensiveView IsVisible={Binding ExpensiveViewVisible}" ... />
}
}
NOTE: I do this differently on iOS, because Xamarin Forms on iOS (incorrectly - not to spec) calls OnAppearing AFTER the page is drawn.
So I have OnPlatform logic. On iOS, OnAppearing immediately calls DeferredOnAppearing. On Android, the line shown is done.
Hopefully iOS will eventually be fixed to call OnAppearing BEFORE,
for consistency between the two platforms.
If so, I would then add a similar renderer for iOS.
(The current iOS implementation means there is no way to update a view before it appears a SECOND time, due to popping the nav stack.
instead, it appears with outdated content, THEN you get a chance
to correct it. This is not good.)
I need to localize DatePicker. From what I read the dialog uses system locale instead of current thread locale. Is there a workaround?
As of now, my application supports three languages user can choose from (ResourceManagers in the .NETStandard project). If the device uses different language, the whole application will be in English except for DatePicker which will be in the system language.
Both of the following are acceptable solutions:
User can choose which language they wish to use throughout the lifetime of the application
Application uses the system language if it's supported, English otherwise
Edit:
Custom renderer implementation as suggested by Raimo
public class LocaleAwareDatePickerRenderer : DatePickerRenderer{
public LocaleAwareDatePickerRenderer( Context context ) : base(context) { }
protected override EditText CreateNativeControl() {
return new EditText(Context) {TextLocale = new Locale("cs"), Focusable = false, Clickable = true, Tag = this};
}
}
Use a custom DatePickerRenderer and add these two lines to set your control's Locale:
Locale locale = new Locale(LocalizationService.GetCurrentThreadCultureInfo().TwoLetterISOLanguageName);
Control.TextLocale = locale;
I am developing a Xamarin Forms which writes successfully an image to external storage and then should use it as Background of a ContentPage.
In the constructor of the ContentPage I wrote this:
this.BackgroundImage = "/storage/emulated/0/DCIM/D72D01AEF71348CDBFEED9D0B2F259F7.jpg"
but the background image never shows.
I checked the Android Manifest and the permissions of read and write external storage are set correctly.
What am I missing?
The problem with your code is that BackgroundImage expects an image that's bundled with your app. Android implementation for updating the background image is here:
void UpdateBackgroundImage(Page view)
{
if (!string.IsNullOrEmpty(view.BackgroundImage))
this.SetBackground(Context.Resources.GetDrawable(view.BackgroundImage));
}
GetDrawable method expects an image from your application's Resources which obviously doesn't exist in your case.
What you should do, is create a custom renderer with a new BindableProperty called ExternalBackgroundImage. Then you could handle loading of the external image as a background in the Android specific custom renderer.
PCL project
Remember to change your current page from ContentPage to ExternalBackgroundImagePage so that you have access to the ExternalBackgroundImage property.
public class ExternalBackgroundImagePage : ContentPage
{
public static readonly BindableProperty ExternalBackgroundImageProperty = BindableProperty.Create("ExternalBackgroundImage", typeof(string), typeof(Page), default(string));
public string ExternalBackgroundImage
{
get { return (string)GetValue(ExternalBackgroundImageProperty); }
set { SetValue(ExternalBackgroundImageProperty, value); }
}
}
Android project
[assembly:ExportRenderer (typeof(ExternalBackgroundImagePage), typeof(ExternalBackgroundImagePageRenderer))]
namespace YourProject.Droid
{
public class ExternalBackgroundImagePageRenderer : PageRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
{
Page view = e.NewElement;
base.OnElementChanged(e);
UpdateExternalBackgroundImage(view);
}
void UpdateExternalBackgroundImage(Page view)
{
if (string.IsNullOrEmpty(view.ExternalBackgroundImage))
return;
// Retrieve a bitmap from a file
var background = BitmapFactory.DecodeFile(view.ExternalBackgroundImage);
// Convert to BitmapDrawable for the SetBackground method
var bitmapDrawable = new BitmapDrawable(background);
// Set the background image
this.SetBackground(bitmapDrawable);
}
}
}
Usage
this.ExternalBackgroundImage = "/storage/emulated/0/DCIM/D72D01AEF71348CDBFEED9D0B2F259F7.jpg"
my target is set password input on custom control in xamarin forms on .Droid project.
my custom render code:
class MyEntryRendererPassword : EntryRenderer
{
//CUSTOM entry RENDER PER ANDROID
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (Control != null)
{
Control.SetMaxHeight(150);
Control.SetTextColor(Android.Graphics.Color.Rgb(255,255,255));
Control.SetBackgroundColor(Android.Graphics.Color.Rgb(43, 50, 58));
//Control.SetRawInputType(InputTypes.TextFlagNoSuggestions | InputTypes.TextVariationVisiblePassword);
}
}
}
I have tested many codes that i saw online, but without success.
How to set Control label like password label ? I will see the dots when user writing.
Thanks.
I think you need to set the InputType property, like:
Control.InputType = Android.Text.InputTypes.TextVariationPassword |
Android.Text.InputTypes.ClassText;