I wrote a service with implementations per platform, to change the color of the StatusBar for my application.
Currently, each ContentPage will have to set its own color, in xaml:
<ContentPage xmlns:helpers="clr-namespace:MyApp.Helpers"
helpers:StatusBarHelper.StatusBarColor="{StaticResource MyColor}">
Where the helper calls the service with the specified color.
Otherwise, the color will remain from the previous page that set it.
What I want is that the StatusBar will receive the same color as the page's background.
Is there an easier way to achieve this?
You could check the following solution
in Forms
public interface IStatusBarStyleManager
{
void SetColoredStatusBar(string hexColor);
}
Setup the Status bar color with this line
DependencyService.Get<IStatusBarStyleManager>().SetColoredStatusBar("#2196F3");
in Android
[assembly: Xamarin.Forms.Dependency(typeof(StatusBarStyleManager))]
namespace ShaXam.Droid.DependencyServices
{
public class StatusBarStyleManager : IStatusBarStyleManager
{
public void SetColoredStatusBar(string hexColor)
{
if (Build.VERSION.SdkInt >= BuildVersionCodes.M)
{
Device.BeginInvokeOnMainThread(() =>
{
var currentWindow = GetCurrentWindow();
currentWindow.DecorView.SystemUiVisibility = 0;
currentWindow.SetStatusBarColor(Android.Graphics.Color.ParseColor(hexColor);
});
}
}
public void SetWhiteStatusBar()
{
if (Build.VERSION.SdkInt >= BuildVersionCodes.M)
{
Device.BeginInvokeOnMainThread(() =>
{
var currentWindow = GetCurrentWindow();
currentWindow.DecorView.SystemUiVisibility = (StatusBarVisibility)SystemUiFlags.LightStatusBar;
currentWindow.SetStatusBarColor(Android.Graphics.Color.White);
});
}
}
Window GetCurrentWindow()
{
var window = CrossCurrentActivity.Current.Activity.Window;
// clear FLAG_TRANSLUCENT_STATUS flag:
window.ClearFlags(WindowManagerFlags.TranslucentStatus);
// add FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS flag to the window
window.AddFlags(WindowManagerFlags.DrawsSystemBarBackgrounds);
return window;
}
}
}
in iOS
[assembly: Dependency(typeof(StatusBarStyleManager))]
namespace ShaXam.iOS.DependencyServices
{
public class StatusBarStyleManager : IStatusBarStyleManager
{
public void SetColoredStatusBar(string hexColor)
{
Device.BeginInvokeOnMainThread(() =>
{
if (UIDevice.CurrentDevice.CheckSystemVersion(13, 0))
{
UIView statusBar = new UIView(UIApplication.SharedApplication.KeyWindow.WindowScene.StatusBarManager.StatusBarFrame);
statusBar.BackgroundColor = Color.FromHex(hexColor).ToUIColor();
UIApplication.SharedApplication.KeyWindow.AddSubview(statusBar);
}
else
{
UIView statusBar = UIApplication.SharedApplication.ValueForKey(new NSString("statusBar")) as UIView;
if (statusBar.RespondsToSelector(new ObjCRuntime.Selector("setBackgroundColor:")))
{
statusBar.BackgroundColor = Color.FromHex(hexColor).ToUIColor();
}
}
UIApplication.SharedApplication.SetStatusBarStyle(UIStatusBarStyle.LightContent, false);
GetCurrentViewController().SetNeedsStatusBarAppearanceUpdate();
});
}
public void SetWhiteStatusBar()
{
Device.BeginInvokeOnMainThread(() =>
{
if (UIDevice.CurrentDevice.CheckSystemVersion(13, 0))
{
UIView statusBar = new UIView(UIApplication.SharedApplication.KeyWindow.WindowScene.StatusBarManager.StatusBarFrame);
statusBar.BackgroundColor = UIColor.White;
UIApplication.SharedApplication.KeyWindow.AddSubview(statusBar);
}
else
{
UIView statusBar = UIApplication.SharedApplication.ValueForKey(new NSString("statusBar")) as UIView;
if (statusBar.RespondsToSelector(new ObjCRuntime.Selector("setBackgroundColor:")))
{
statusBar.BackgroundColor = UIColor.White;
}
}
UIApplication.SharedApplication.SetStatusBarStyle(UIStatusBarStyle.DarkContent, false);
GetCurrentViewController().SetNeedsStatusBarAppearanceUpdate();
});
}
UIViewController GetCurrentViewController()
{
var window = UIApplication.SharedApplication.KeyWindow;
var vc = window.RootViewController;
while (vc.PresentedViewController != null)
vc = vc.PresentedViewController;
return vc;
}
}
}
Related
My goal is to add an OK button on iOS numeric keyboard; I can achieve that very easily with a custom renderer :
public class ExtendedEntryRenderer : EntryRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (Element == null)
{
return;
}
// Check only for Numeric keyboard
if (this.Element.Keyboard == Keyboard.Numeric)
{
this.AddDoneButton();
}
}
/// <summary>
/// <para>Add toolbar with Done button</para>
/// </summary>
protected void AddDoneButton()
{
var toolbar = new UIToolbar(new RectangleF(0.0f, 0.0f, 50.0f, 44.0f));
var doneButton = new UIBarButtonItem(UIBarButtonSystemItem.Done, delegate
{
this.Control.ResignFirstResponder();
var baseEntry = this.Element.GetType();
((IEntryController)Element).SendCompleted();
});
toolbar.Items = new UIBarButtonItem[] {
new UIBarButtonItem (UIBarButtonSystemItem.FlexibleSpace),
doneButton
};
this.Control.InputAccessoryView = toolbar;
}
}
but my question is how can we add this keyboard behavior on a Xamarin prompt dialog.
await DisplayPromptAsync("Title", "Content", keyboard: Keyboard.Numeric);
If you want to customize the Keyboard of AlertView in iOS , you could implement it by using DependencyService
in Forms
create a Interface
public interface IDisplayPrompt
{
void DisplayPrompt(string Title,string Content,Keyboard keyboard,Action<string> SubmitAction,Action CancelAction);
}
in iOS
using System;
using app55;
using app55.iOS;
using Xamarin.Forms;
using UIKit;
using System.Drawing;
[assembly: Dependency(typeof(DisplayPromptImplement))]
namespace app55.iOS
{
public class DisplayPromptImplement:IDisplayPrompt
{
public DisplayPromptImplement()
{
}
public void DisplayPrompt(string Title, string Content, Keyboard keyboard, Action<string> SubmitAction, Action CancelAction)
{
UIAlertController alertController = UIAlertController.Create(Title,Content,UIAlertControllerStyle.Alert);
UIAlertAction OKAction = UIAlertAction.Create("OK",UIAlertActionStyle.Default,(action)=> {
//click OK Button
var content = alertController.TextFields[0].Text;
SubmitAction.Invoke(content);
});
UIAlertAction DismissAction = UIAlertAction.Create("Cancel", UIAlertActionStyle.Cancel, (action) => {
//click Cancel Button
CancelAction.Invoke();
});
alertController.AddTextField((field)=> {
if (keyboard == Keyboard.Numeric)
field.KeyboardType = UIKeyboardType.NumberPad;
AddDoneButton(field);
});
alertController.AddAction(OKAction);
alertController.AddAction(DismissAction);
UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(alertController,true,null);
}
protected void AddDoneButton(UITextField field)
{
var toolbar = new UIToolbar(new RectangleF(0.0f, 0.0f, 50.0f, 44.0f));
var doneButton = new UIBarButtonItem(UIBarButtonSystemItem.Done, delegate
{
field.ResignFirstResponder();
});
toolbar.Items = new UIBarButtonItem[] {
new UIBarButtonItem (UIBarButtonSystemItem.FlexibleSpace),
doneButton
};
field.InputAccessoryView = toolbar;
}
}
}
Now in Forms we could invoked it like following
void Button_Clicked(System.Object sender, System.EventArgs e)
{
if(Device.RuntimePlatform=="iOS")
{
DependencyService.Get<IDisplayPrompt>().DisplayPrompt("Title", "Please Input Message", Keyboard.Numeric, (content) =>
{
/// get the content that you input
label.Text = content.ToString();
}, null);
}
else
{
// other platform
//...await DisplayPromptAsync
}
}
Screen Shot
I have a xamarin forms application and I have been able to change the navigationbar color. How can I change the statusbar color crossplatform? In the image below you can see the green navigationpagebar background color. Above that it's blue, I want to change the color of that. How can I achieve this crossplatform in xamarin forms?
You could use DependencyService .
in share project , define the interface
public interface IStatusBarColor
{
void SetColoredStatusBar(string color);
}
in Android
Firstly , install the plugin CurrentActivity from nuegt , check https://github.com/jamesmontemagno/CurrentActivityPlugin
using Android.OS;
using Android.Views;
using App24.Droid;
using App24;
using Xamarin.Forms;
using Plugin.CurrentActivity;
[assembly: Dependency(typeof(SetStatusBarColorImplemention))]
namespace App24.Droid
{
public class SetStatusBarColorImplemention : IStatusBarColor
{
public SetStatusBarColorImplemention()
{
}
public void SetColoredStatusBar(string color)
{
if (Build.VERSION.SdkInt >= BuildVersionCodes.M)
{
Device.BeginInvokeOnMainThread(() =>
{
var currentWindow = GetCurrentWindow();
currentWindow.DecorView.SystemUiVisibility = 0;
currentWindow.SetStatusBarColor(Android.Graphics.Color.ParseColor(color));
});
}
}
Window GetCurrentWindow()
{
var window = CrossCurrentActivity.Current.Activity.Window;
window.ClearFlags(WindowManagerFlags.TranslucentStatus);
window.AddFlags(WindowManagerFlags.DrawsSystemBarBackgrounds);
return window;
}
}
}
in iOS
using App24;
using App24.iOS;
using Foundation;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using ObjCRuntime;
using CoreGraphics;
[assembly: Dependency(typeof(SetStatusBarColorImplemention))]
namespace App24.iOS
{
public class SetStatusBarColorImplemention : IStatusBarColor
{
public void SetColoredStatusBar(string hexColor)
{
if(UIDevice.CurrentDevice.CheckSystemVersion(13, 0))
{
UIWindow window = UIApplication.SharedApplication.KeyWindow;
UIView view = new UIView(window.WindowScene.StatusBarManager.StatusBarFrame);
window.AddSubview(view);
Device.BeginInvokeOnMainThread(() =>
{
if (view.RespondsToSelector(new Selector("setBackgroundColor:")))
{
view.BackgroundColor = Color.FromHex(hexColor).ToUIColor();
}
UIApplication.SharedApplication.SetStatusBarStyle(UIStatusBarStyle.LightContent, false);
topViewControllerWithRootViewController(UIApplication.SharedApplication.KeyWindow.RootViewController).SetNeedsStatusBarAppearanceUpdate();
});
}
else
{
Device.BeginInvokeOnMainThread(() =>
{
UIView statusBar = UIApplication.SharedApplication.ValueForKey(new NSString("statusBar")) as UIView;
if (statusBar.RespondsToSelector(new Selector("setBackgroundColor:")))
{
statusBar.BackgroundColor = Color.FromHex(hexColor).ToUIColor();
}
UIApplication.SharedApplication.SetStatusBarStyle(UIStatusBarStyle.LightContent, false);
topViewControllerWithRootViewController(UIApplication.SharedApplication.KeyWindow.RootViewController).SetNeedsStatusBarAppearanceUpdate();
});
}
}
UIViewController topViewControllerWithRootViewController(UIViewController rootViewController)
{
if (rootViewController is UITabBarController)
{
UITabBarController tabBarController = (UITabBarController)rootViewController;
return topViewControllerWithRootViewController(tabBarController.SelectedViewController);
}
else if (rootViewController is UINavigationController)
{
UINavigationController navigationController = (UINavigationController)rootViewController;
return topViewControllerWithRootViewController(navigationController.VisibleViewController);
}
else if (rootViewController.PresentedViewController != null)
{
UIViewController presentedViewController = rootViewController.PresentedViewController;
return topViewControllerWithRootViewController(presentedViewController);
}
else
{
return rootViewController;
}
}
}
}
Now invoked the line as you want .
DependencyService.Get<IStatusBarColor>().SetColoredStatusBar("#00ff00"); // set the color of bar as green
To my knowledge you need to set the statusbar color on every platform separatly.
There are alot of questions like this here on StackOverflow and Google that can help you with that.
For Android:
check your styles.xml in Resources -> values
look for something like <item name="android:statusBarColor">#000000</item> to set the color
For iOS:
in your AppDelegate.cs look for the FinishedLaunsching-Method.
You can change the Style with UIApplication.SharedApplication.SetStatusBarStyle(UIStatusBarStyle.DarkContent, false);
I have a ListView in Xamarin.Forms of this way :
this.listView = new ListView();
this.listView.HasUnevenRows = true;
var dataTemplate = new DataTemplate(() =>
{
return new ViewCell { View = new CustomButtonTemplate()};
});
this.listView.ItemTemplate = dataTemplate;
CustomButtonTemplate.xaml
<local:CustomButton
Margin="6"
Padding="0"
HeightRequest="-1"
WidthRequest="-1"
Style="{StaticResource Title_LabelStyle}"
Text="{Binding DisplayText}" />
I also got one button renderer but dont work (without HeightRequest,WidthRequest,Padding dont work either):
[assembly: ExportRenderer(typeof(CustomButton), typeof(CustomButtonMultilineRenderer))]
namespace SGUK.ClassAction.IOS.Renderers
{
public class CustomButtonMultilineRenderer : ButtonRenderer
{
public CustomButtonMultilineRenderer()
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Button> e)
{
base.OnElementChanged(e);
if (this.Control != null)
{
this.Control.TitleLabel.LineBreakMode = UILineBreakMode.WordWrap;
this.Control.TitleEdgeInsets = new UIEdgeInsets(0, 10, 0, 10);
this.Control.TitleLabel.TextAlignment = UITextAlignment.Center;
this.Control.HorizontalAlignment = UIControlContentHorizontalAlignment.Center;
}
}
}
}
(with MaterialButtonRenderer dont work either)
The auto height with HasUnevenRows=true works fine on iOS if not using a custom renderer. If using a custom renderer, then it is up to the renderer to set the height of the cell, you have to calculate your own row height in the GetHeightForRow method in the custom renderer.
[assembly: ExportRenderer(typeof(ListView), typeof(MyLVRenderer))]
namespace App79.iOS
{
public class MyLVRenderer : ListViewRenderer
{
//UITableViewSource originalSource;
protected override void OnElementChanged(ElementChangedEventArgs<ListView> e)
{
base.OnElementChanged(e);
UITableViewSource originalSource = (UIKit.UITableViewSource)Control.Source;
Control.Source = new MyLVSource(originalSource, e.NewElement);
}
}
public class MyLVSource : UITableViewSource
{
UITableViewSource originalSource;
ListView myListView;
public MyLVSource(UITableViewSource origSource, ListView myListV)
{
originalSource = origSource;
myListView = myListV;
}
public override nint RowsInSection(UITableView tableview, nint section)
{
return originalSource.RowsInSection(tableview, section);
}
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{
return originalSource.GetCell(tableView, indexPath);
}
public override nfloat GetHeightForFooter(UITableView tableView, nint section)
{
return originalSource.GetHeightForFooter(tableView, section);
}
public override nfloat GetHeightForRow(UITableView tableView, NSIndexPath indexPath)
{
nfloat origHeight = originalSource.GetHeightForRow(tableView, indexPath);
// calculate your own row height here
ObservableCollection<Employee> employees = myListView.ItemsSource as ObservableCollection<Employee>;
string displayName = employees[indexPath.Row].DisplayName;
nfloat height = MeasureTextSize(displayName,UIScreen.MainScreen.Bounds.Size.Width-50,UIFont.SystemFontSize,null);
return height;
}
public nfloat MeasureTextSize(string text, double width, double fontSize, string fontName = null)
{
var nsText = new NSString(text);
var boundSize = new SizeF((float)width, float.MaxValue);
var options = NSStringDrawingOptions.UsesFontLeading | NSStringDrawingOptions.UsesLineFragmentOrigin;
if (fontName == null)
{
fontName = "HelveticaNeue";
}
var attributes = new UIStringAttributes
{
Font = UIFont.FromName(fontName, (float)fontSize)
};
var sizeF = nsText.GetBoundingRect(boundSize, options, attributes, null).Size;
//return new Xamarin.Forms.Size((double)sizeF.Width, (double)sizeF.Height);
return sizeF.Height + 5;
}
}
}
Here is the result:
I uploaded a sample here and you can check.
I know the default DatePicker already has a bottom line, but I'm trying to add a bottom line to the DatePicker in the custom renderer code (for some purpose).
I can set a full border of my GradientDrawable object by myGradientDrawable.SetStroke(3, myColor); but I don't know how to add only the bottom line so anyone can help me please?
Try this:
public class CustomPickerRenderer : PickerRenderer
{
public CustomPickerRenderer(Context context) : base(context)
{
}
private AlertDialog alert;
private CustomPicker element;
private int selectedIndex;
public LayerDrawable AddPickerStyles(string imagePath)
{
ColorDrawable borderColorDrawable = new ColorDrawable(Xamarin.Forms.Color.FromHex("#43addf").ToAndroid());
ColorDrawable backgroundColorDrawable = new ColorDrawable(Xamarin.Forms.Color.FromHex("#7e1b80").ToAndroid());
Drawable[] drawables = new Drawable[]
{
borderColorDrawable, backgroundColorDrawable
};
LayerDrawable layerDrawable = new LayerDrawable(drawables);
layerDrawable.SetLayerInset(1, 0, 0, 0, 5);
return layerDrawable;
}
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
{
base.OnElementChanged(e);
element = (CustomPicker)this.Element;
if (Control != null && this.Element != null)
{
Control.Background = AddPickerStyles(element.Image);
}
}
}
so i managed to get a stock navigation drawer to work and the action bar title to change with the fragments selected. I've also managed to get the backstack working easy peasy.
What i can't figure out how to do is get the action bar title to change back with the back click. google documentations says to add a onBackStackChangedListener:
getSupportFragmentManager().addOnBackStackChangedListener(
new FragmentManager.OnBackStackChangedListener() {
public void onBackStackChanged() {
// Update your UI here.
}
});
but i'm at a lost where to place it? they say when i .commit to changes so i assumed it was placed after
if (id == R.id.nav_spatial_awareness) {
setTitle("Spatial Awareness");
SpatialAwareness spatialAwarenessFragment = new SpatialAwareness();
android.support.v4.app.FragmentManager spatialAwarenessManager = getSupportFragmentManager();
spatialAwarenessManager.beginTransaction()
.addToBackStack(null)
.replace(R.id.main_content_layout, spatialAwarenessFragment, spatialAwarenessFragment.getTag())
.commit();
but that didn't work, this is what i tried and all i get is red squigglies
if (id == R.id.nav_spatial_awareness) {
setTitle("Spatial Awareness");
SpatialAwareness spatialAwarenessFragment = new SpatialAwareness();
android.support.v4.app.FragmentManager spatialAwarenessManager = getSupportFragmentManager();
spatialAwarenessManager.beginTransaction()
.addToBackStack(null)
.replace(R.id.main_content_layout, spatialAwarenessFragment, spatialAwarenessFragment.getTag())
.commit();
getSupportFragmentManager().addOnBackStackChangedListener(
new FragmentManager.OnBackStackChangedListener() {
public void onBackStackChanged() {
setTitle("Spatial Awareness");
}
});
please help me noob
so i tried this
if (id == R.id.nav_spatial_awareness) {
setTitle("Spatial Awareness");
final SpatialAwareness spatialAwarenessFragment = new SpatialAwareness();
android.support.v4.app.FragmentManager spatialAwarenessManager = getSupportFragmentManager();
spatialAwarenessManager.beginTransaction()
.addToBackStack(null)
.replace(R.id.main_content_layout, spatialAwarenessFragment, "spatialAwarenessFragmentTag")
.commit();
getSupportFragmentManager().addOnBackStackChangedListener(
new FragmentManager.OnBackStackChangedListener() {
public void onBackStackChanged() {
android.app.Fragment currentBackStackFragment = getFragmentManager().findFragmentByTag("spatialAwarenessFragmentTag");
if(currentBackStackFragment instanceof SpatialAwareness){
setTitle("Spatial");
}
}
});
i gave my fragment a tag and then tried matching the instance and then changing the title, still no good :(
Yes, the solution as mentioned here is to add a FragmentManager.OnBackStackChangedListener to your activity's FragmentManager.
Here is an example from a project I worked on:
(I have a navigation drawer with 7 Fragments and the OverviewFragment is the initial one that opens when the MainActivity opens)
My MainActivity:
public class MainActivity extends AppCompatActivity
implements NavigationView.OnNavigationItemSelectedListener, FragmentManager.OnBackStackChangedListener {
...
#Override
protected void onCreate(Bundle savedInstanceState) {
...
NavigationView navigationView = findViewById(R.id.nav_view);
navigationView.setNavigationItemSelectedListener(this);
if (savedInstanceState == null) {
// open the default fragment
OverviewFragment fragment = new OverviewFragment();
getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container, fragment).commit();
setTitle(R.string.title_fragment_overview);
}
getSupportFragmentManager().addOnBackStackChangedListener(this);
}
#Override
public boolean onNavigationItemSelected(MenuItem item) {
// Handle navigation view item clicks here.
Fragment fragment = null;
switch (item.getItemId()) {
case R.id.nav_overview:
fragment = new OverviewFragment();
break;
case R.id.nav_schedule:
fragment = new ScheduleFragment();
break;
case R.id.nav_all_tasks:
fragment = new AllTasksFragment();
break;
case R.id.nav_announcements:
fragment = new AnnouncementFragment();
break;
case R.id.nav_my_courses:
fragment = new MyCoursesFragment();
break;
case R.id.nav_map:
fragment = new MapFragment();
break;
case R.id.nav_settings:
fragment = new SettingsFragment();
break;
default:
fragment = new OverviewFragment();
}
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragment_container, fragment)
.addToBackStack(null)
.commit();
drawer.closeDrawer(GravityCompat.START);
return true;
}
#Override
public void onBackStackChanged() {
FragmentManager fragmentManager = getSupportFragmentManager();
Fragment currentFragment =
fragmentManager.findFragmentById(R.id.fragment_container);
if (currentFragment instanceof OverviewFragment) {
setTitle(R.string.title_fragment_overview);
navigationView.getMenu().findItem(R.id.nav_overview).setChecked(true);
}
else if (currentFragment instanceof ScheduleFragment) {
setTitle(R.string.title_fragment_schedule);
navigationView.getMenu().findItem(R.id.nav_schedule).setChecked(true);
}
else if (currentFragment instanceof AllTasksFragment) {
setTitle(R.string.title_fragment_all_tasks);
navigationView.getMenu().findItem(R.id.nav_all_tasks).setChecked(true);
}
else if (currentFragment instanceof MyCoursesFragment) {
setTitle(R.string.title_fragment_my_courses);
navigationView.getMenu().findItem(R.id.nav_my_courses).setChecked(true);
}
else if (currentFragment instanceof AnnouncementFragment) {
setTitle(R.string.title_fragment_announcements);
navigationView.getMenu().findItem(R.id.nav_announcements).setChecked(true);
}
else if (currentFragment instanceof MapFragment) {
setTitle(R.string.title_fragment_map);
navigationView.getMenu().findItem(R.id.nav_map).setChecked(true);
}
else if (currentFragment instanceof SettingsFragment) {
setTitle(R.string.title_fragment_settings);
navigationView.getMenu().findItem(R.id.nav_settings).setChecked(true);
}
}
}
Here's the entire project: https://github.com/FCI-E-campus/fci-e-campus-android