Xamarin: change StatusBarColor during navigation - xamarin

In the MainActivity OnCreate, I set the color of the StatusBar using:
Window.SetStatusBarColor(Resources.GetColor(Resource.Color.colorPrimary));
For the specific pages, I need to set the StatusBar color trasparent.
Is possible to do that in a Android custom rendered class?
EDIT:
my OnLayout method on custom ANdorid
protected override void OnLayout(bool changed, int l, int t, int r, int b)
{
CustomNavigation.IgnoreLayoutChange = true;
base.OnLayout(changed, l, t, r, b);
CustomNavigation.IgnoreLayoutChange = false;
int containerHeight = b - t;
PageController.ContainerArea = new Rectangle(0, 0, Context.FromPixels(r - l), Context.FromPixels(containerHeight));
if (Element?.Navigation?.NavigationStack.Count == 1)
{
CustomNavigation.BarBackgroundColor = Color.Transparent;
//HERE I NEED TO HAVE STATUS AR TRANSPARENT
}
if (Element?.Navigation?.NavigationStack.Count > 1)
{
PageController.ContainerArea = new Rectangle(0, 60, Context.FromPixels(r - l), Context.FromPixels(containerHeight));
CustomNavigation.BarBackgroundColor = Color.FromHex("#006CA6");
}
for (var i = 0; i < ChildCount; i++)
{
AView child = GetChildAt(i);
if (child is Android.Support.V7.Widget.Toolbar)
{
continue;
}
child.Layout(0, 0, r, b);
}
}

Status bar appearance is about its background and text colours. Both properties have their own limitations on different platforms, however, we could manipulate both with the solution described below.
Our goal is simple, we want to be able to switch the status bar appearance between LightTheme and DarkTheme at runtime:
Define an interface in your shared code:
public interface IStatusBarStyleManager
{
void SetLightTheme();
void SetDarkTheme();
}
Since Android Lollipop (21) it is possible to set a custom status bar background colour by simply defining it in style.xml with a key colorPrimaryDark or programmatically, Since Android M (23) it is possible to set a predefined status bar text colour theme to light or dark.
Android code:
public class StatusBarStyleManager : IStatusBarStyleManager
{
public void SetDarkTheme()
{
if (Build.VERSION.SdkInt >= BuildVersionCodes.M)
{
Device.BeginInvokeOnMainThread(() =>
{
var currentWindow = GetCurrentWindow();
currentWindow.DecorView.SystemUiVisibility = 0;
currentWindow.SetStatusBarColor(Android.Graphics.Color.DarkCyan);
});
}
}
public void SetLightTheme()
{
if (Build.VERSION.SdkInt >= BuildVersionCodes.M)
{
Device.BeginInvokeOnMainThread(() =>
{
var currentWindow = GetCurrentWindow();
currentWindow.DecorView.SystemUiVisibility = (StatusBarVisibility)SystemUiFlags.LightStatusBar;
currentWindow.SetStatusBarColor(Android.Graphics.Color.LightGreen);
});
}
}
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;
}
}
I am using the Current Activity Plugin by James Montemagno to get the reference of the current activity.
iOS code:
In iOS the status bar background colour by default matching the colour of the navigation bar. In other words, we don’t have to explicitly set the background colour of the status bar if we want it to match the background colour of the navigation bar. Since iOS 7 it is possible to set a predefined status bar text colour theme to light or dark. However, we will have to manipulate the Info.plist. Since status bar behaviour is determined by view controllers by default, we have to disable this:
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
Next, we can define a default text colour theme:
<key>UIStatusBarStyle</key>
<string>UIStatusBarStyleDefault</string>
public class StatusBarStyleManager : IStatusBarStyleManager
{
public void SetDarkTheme()
{
Device.BeginInvokeOnMainThread(() =>
{
UIApplication.SharedApplication.SetStatusBarStyle(UIStatusBarStyle.LightContent, false);
GetCurrentViewController().SetNeedsStatusBarAppearanceUpdate();
});
}
public void SetLightTheme()
{
Device.BeginInvokeOnMainThread(() =>
{
UIApplication.SharedApplication.SetStatusBarStyle(UIStatusBarStyle.Default, false);
GetCurrentViewController().SetNeedsStatusBarAppearanceUpdate();
});
}
UIViewController GetCurrentViewController()
{
var window = UIApplication.SharedApplication.KeyWindow;
var vc = window.RootViewController;
while (vc.PresentedViewController != null)
vc = vc.PresentedViewController;
return vc;
}
}
Goodluck
Revert in case of queries.

Related

Webview OnProgressChanged and progressbar

In a Xamarin application I have a CustomWebView renderer; I'm injecting in the view and displaying a progressbar in OnProgressChanged event of the WebChromeClient with the following code.
Init:
var progressBar = new Android.Widget.ProgressBar(_context, null, Android.Resource.Attribute.ProgressBarStyleHorizontal);
Control.SetWebViewClient(new CusWebViewClient($"javascript: {JavascriptFunction}"));
Control.SetWebChromeClient(new CusWebChromeClient(progressBar));
Control.AddView(progressBar);
CusWebChromeClient:
public class CusWebChromeClient : WebChromeClient
{
Android.Widget.ProgressBar progressBar;
public CusWebChromeClient(Android.Widget.ProgressBar progressBar)
{
this.progressBar = progressBar;
}
public override void OnProgressChanged(Android.Webkit.WebView view, int newProgress)
{
if (newProgress < 100 && progressBar.Visibility == ViewStates.Gone)
{
progressBar.Visibility = ViewStates.Visible;
}
progressBar.SetProgress(newProgress, true);
if (newProgress == 100)
{
//progressBar.Visibility = ViewStates.Gone;
}
}
}
The issue is that the progress bar is displayed really small like:
I need to display it with full display width and with a more heigth.
You forgot to set the width of progress bar.
You could use the screen width to set the progress bar in custom renderer. After that, it would be okay.
var width = (int)Application.Current.MainPage.Width;
Please note, 'Application' is an ambiguous reference between 'Android.App.Application' and 'Xamarin.Forms.Application'. You could add reference like below to fix it.
using Application = Xamarin.Forms.Application;
Change:
Control.AddView(progressBar);
To:
Control.AddView(progressBar, width, 30);

ActionBar With Tabbar in Xamarin forms for all(android, iOs and WinPhone)

i need to Create a ActionBar with TabbedLayout control in xamarin forms, In xamarin Android i did that Easily but now they want in both platform IOS and Android using Xamarin forms.please share any Example or Give me suggestion for how to make the custom Controls in Xamari Froms.
Below i have attached the Image how i need Action bar with Tabbed layout.
If you are using Xamarin.Forms the Tabbed page, for Android tabbar items will in the top. For iOS, you have to create a renderer to achieve it. However, Showing Tabbar items in the top are against User guidelines of iOS.
Create custom render, override ViewDidLayoutSubviews and add the following lines code.
[assembly: ExportRenderer(typeof(ExtendedTabbedPage), typeof(ExtendedTabbedPageRenderer))]
namespace ExtendedTabbedPage.Pages
{
public class ExtendedTabbedPageRenderer : TabbedRenderer
{
private ExtendedTabbedPage Page => (ExtendedTabbedPage)Element;
public ExtendedTabbedPageRenderer()
{
}
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
var page = (ExtendedTabbedPage)Element;
page.CurrentPageChanged += Page_CurrentPageChanged;
}
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
Page_CurrentPageChanged();
}
public override void ViewDidLayoutSubviews()
{
base.ViewDidLayoutSubviews();
SetTabPostition();
}
void SetTabPostition()
{
if (Element == null)
return;
var element = Element as ExtendedTabbedPage;
this.TabBar.InvalidateIntrinsicContentSize();
nfloat tabSize = 74.0f;
UIInterfaceOrientation orientation = UIApplication.SharedApplication.StatusBarOrientation;
if (UIInterfaceOrientation.LandscapeLeft == orientation || UIInterfaceOrientation.LandscapeRight == orientation)
{
tabSize = 32.0f;
}
CGRect tabFrame = this.TabBar.Frame;
CGRect viewFrame = this.View.Frame;
tabFrame.Height = tabSize;
tabFrame.Y = this.View.Frame.Y;
this.TabBar.Frame = tabFrame;
this.TabBar.ContentMode = UIViewContentMode.Top;
PageController.ContainerArea = new Rectangle(0, tabFrame.Y + tabFrame.Height, viewFrame.Width, viewFrame.Height - tabFrame.Height);
this.TabBar.SetNeedsUpdateConstraints();
}
void Page_CurrentPageChanged()
{
var current = Tabbed.CurrentPage;
//if Tab is more than 5 then more will appear in iOS
if (current == null)
{
CGRect tabFrm = this.TabBar.Frame;
if (this.MoreNavigationController != null)
{
var morenavframe = this.MoreNavigationController.View.Frame;
morenavframe.Y = tabFrm.Y + tabFrm.Height;
this.MoreNavigationController.View.Frame = morenavframe;
foreach (var morecontroller in this.MoreNavigationController.ViewControllers)
{
var morecontframe = morecontroller.View.Frame;
morecontframe.Y = morenavframe.Y + morenavframe.Height;
morecontroller.View.Frame = tabFrm;
}
}
return;
}
var controller = Platform.GetRenderer(current);
if (controller == null)
return;
var frame = controller.ViewController.View.Frame;
CGRect tabFrame = this.TabBar.Frame;
frame.Y = (tabFrame.Y + tabFrame.Height);
controller.ViewController.View.Frame = frame;
this.View.Frame = frame;
}
public override void ViewDidAppear(bool animated)
{
base.ViewDidAppear(animated);
Page_CurrentPageChanged();
}
}
}
To get a tabbed layout in Xamarin.Forms you'll usually use a TabbedPage. This will give you the tabs you show on Android. On iOS and Windows you'll get the native alternative. This means you'll get the tabs on the bottom of the screen on iOS and on Windows you'll get the tabs on top (similar, but exactly like on Android). See this illustration from the Xamarin docs:
If you want to create your own version you can implement your own version of the MultiPage class.

Adding a bottom border to an Entry in Xamarin Forms iOS with an image at the end

Now before anyone ignores this as a duplicate please read till the end. What I want to achieve is this
I've been doing some googling and looking at objective c and swift responses on stackoverflow as well. And this response StackOverFlowPost seemed to point me in the right direction. The author even told me to use ClipsToBounds to clip the subview and ensure it's within the parents bounds. Now here's my problem, if I want to show an image on the right side of the entry(Gender field), I can't because I'm clipping the subview.
For clipping, I'm setting the property IsClippedToBounds="True" in the parent stacklayout for all textboxes.
This is the code I'm using to add the bottom border
Control.BorderStyle = UITextBorderStyle.None;
var myBox = new UIView(new CGRect(0, 40, 1000, 1))
{
BackgroundColor = view.BorderColor.ToUIColor(),
};
Control.AddSubview(myBox);
This is the code I'm using to add an image at the beginning or end of an entry
private void SetImage(ExtendedEntry view)
{
if (!string.IsNullOrEmpty(view.ImageWithin))
{
UIImageView icon = new UIImageView
{
Image = UIImage.FromFile(view.ImageWithin),
Frame = new CGRect(0, -12, view.ImageWidth, view.ImageHeight),
ClipsToBounds = true
};
switch (view.ImagePos)
{
case ImagePosition.Left:
Control.LeftView.AddSubview(icon);
Control.LeftViewMode = UITextFieldViewMode.Always;
break;
case ImagePosition.Right:
Control.RightView.AddSubview(icon);
Control.RightViewMode = UITextFieldViewMode.Always;
break;
}
}
}
After analysing and debugging, I figured out that when OnElementChanged function of the Custom Renderer is called, the control is still not drawn so it doesn't have a size. So I subclassed UITextField like this
public class ExtendedUITextField : UITextField
{
public UIColor BorderColor;
public bool HasBottomBorder;
public override void Draw(CGRect rect)
{
base.Draw(rect);
if (HasBottomBorder)
{
BorderStyle = UITextBorderStyle.None;
var myBox = new UIView(new CGRect(0, 40, Frame.Size.Width, 1))
{
BackgroundColor = BorderColor
};
AddSubview(myBox);
}
}
public void InitInhertedProperties(UITextField baseClassInstance)
{
TextColor = baseClassInstance.TextColor;
}
}
And passed the hasbottomborder and bordercolor parameters like this
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
var view = e.NewElement as ExtendedEntry;
if (view != null && Control != null)
{
if (view.HasBottomBorder)
{
var native = new ExtendedUITextField
{
BorderColor = view.BorderColor.ToUIColor(),
HasBottomBorder = view.HasBottomBorder
};
native.InitInhertedProperties(Control);
SetNativeControl(native);
}
}
But after doing this, now no events fire :(
Can someone please point me in the right direction. I've already built this for Android, but iOS seems to be giving me a problem.
I figured out that when OnElementChanged function of the Custom Renderer is called, the control is still not drawn so it doesn't have a size.
In older versions of Xamarin.Forms and iOS 9, obtaining the control's size within OnElementChanged worked....
You do not need the ExtendedUITextField, to obtain the size of the control, override the Frame in your original renderer:
public override CGRect Frame
{
get
{
return base.Frame;
}
set
{
if (value.Width > 0 && value.Height > 0)
{
// Use the frame size now to update any of your subview/layer sizes, etc...
}
base.Frame = value;
}
}

Background image with Carousel effect

I would like to create a layout with a fullscreen background image and some UI elements on top of it. The twist is this:
I would like the background image to swipeable like a carousel, but I would like the UI elements to stay in place. That is if I swipe the screen, the background image should slide to the side and a new image should replace it. I know about CarouselPage, but it seems to me that it won't do the trick, since a Page can have only one child which it replaces on swipe, meaning that the UI elements would be descendants of the CarouselPage and therefore would also be animated.
I am guessing I need some sort of custom renderer here, but how should I go about designing it? Should it be one fullscreen Image control replaced be another fullscreen Image control with the UI elements on top of it? And how can I do this? Or is there an all together better approach?
I am developing for iOS and Android using Xamarin.Forms.
Thanks in advance.
I don't like repeating myself much, and I think that multiple layers of actionable items can lead to confusion, but the problems appeals to me and I can see a niche for this kind of UI, so here's my take on your question.
Let's assume this is the (Xamarin.Forms.)Page you want to render with a custom carousel background:
public class FunkyPage : ContentPage
{
public IList<string> ImagePaths { get; set; }
public FunkyPage ()
{
Content = new StackLayout {
VerticalOptions = LayoutOptions.Center,
HorizontalOptions = LayoutOptions.Center,
Spacing = 12,
Children = {
new Label { Text = "Foo" },
new Label { Text = "Bar" },
new Label { Text = "Baz" },
new Label { Text = "Qux" },
}
};
ImagePaths = new List<string> { "red.png", "green.png", "blue.png", "orange.png" };
}
}
The renderer for iOS could look like this:
[assembly: ExportRenderer (typeof (FunkyPage), typeof (FunkyPageRenderer))]
public class FunkyPageRenderer : PageRenderer
{
UIScrollView bgCarousel = new UIScrollView (RectangleF.Empty) {
PagingEnabled = true,
ScrollEnabled=true
};
List<UIImageView> uiimages = new List<UIImageView> ();
protected override void OnElementChanged (VisualElementChangedEventArgs e)
{
foreach (var sub in uiimages)
sub.RemoveFromSuperview ();
uiimages.Clear ();
if (e.NewElement != null) {
var page = e.NewElement as FunkyPage;
foreach (var image in page.ImagePaths) {
var uiimage = new UIImageView (new UIImage (image));
bgCarousel.Add (uiimage);
uiimages.Add (uiimage);
}
}
base.OnElementChanged (e);
}
public override void ViewDidLoad ()
{
Add (bgCarousel);
base.ViewDidLoad ();
}
public override void ViewWillLayoutSubviews ()
{
base.ViewWillLayoutSubviews ();
bgCarousel.Frame = View.Frame;
var origin = 0f;
foreach (var image in uiimages) {
image.Frame = new RectangleF (origin, 0, View.Frame.Width, View.Frame.Height);
origin += View.Frame.Width;
}
bgCarousel.ContentSize = new SizeF (origin, View.Frame.Height);
}
}
This was tested and works. Adding a UIPageControl (the dots) is easy on top of this. Autoscrolling of the background is trivial too.
The process is similar on Android, the overrides are a bit different.

SwapChainBackgroundPanel letterboxing Monogame Windows Store App

I am porting my space shooter game from Windows Phone to Windows Store App. In WP it always play in full portrait orientation.
For the Windows Store app though while in landscape mode, I want to center the game screen with letterboxing on the left and right. The problem is I can't adjust the margin property of SwapChainBackgroundPanel so the game always aligned to the left and the black screen is on the right.
Here's my code
public Game1()
{
graphics = new GraphicsDeviceManager(this);
GamePage.Current.SizeChanged += OnWindowSizeChanged;
Content.RootDirectory = "Content";
}
private void OnWindowSizeChanged(object sender, Windows.UI.Xaml.SizeChangedEventArgs e)
{
var CurrentViewState = Windows.UI.ViewManagement.ApplicationView.Value;
double width = e.NewSize.Width;
double height = e.NewSize.Height;
// using Windows.Graphics.Display;
ResolutionScale resolutionScale = DisplayProperties.ResolutionScale;
string orientation = null;
if (ApplicationView.Value == ApplicationViewState.FullScreenLandscape)
{
orientation = "FullScreenLandscape";
//Does not work because it's start on the center of the screen
//Black screen is on the left and place the game screen on the right
GamePage.Current.HorizontalAlignment = Windows.UI.Xaml.HorizontalAlignment.Center;
//Gives error - WinRT information: Setting 'Margin' property is
//not supported on SwapChainBackgroundPanel.
GamePage.Current.Margin = new Thickness(centerMargin, 0, 0, 0);
}
else if (ApplicationView.Value == ApplicationViewState.FullScreenPortrait)
{
orientation = "FullScreenPortrait";
}
else if (ApplicationView.Value == ApplicationViewState.Filled)
{
orientation = "Filled";
}
else if (ApplicationView.Value == ApplicationViewState.Snapped)
{
orientation = "Snapped";
}
Debug.WriteLine("{0} x {1}. Scale: {2}. Orientation: {3}",
width.ToString(), height.ToString(), resolutionScale.ToString(),
orientation);
}
The GamePage.xaml is the default
<SwapChainBackgroundPanel
x:Class="SpaceShooterXW8.GamePage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:SpaceShooterXW8"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
</SwapChainBackgroundPanel>
After some researched I think I've figured it out thanks to this blog post. To those who are in a similar situation, here's what I did.
The beauty of the solution is that the letterboxing is automatically managed by the Resolution class. All I have to do is update the batch.begin() lines in my code to something like
batch.Begin(SpriteSortMode.Deferred,
null, SamplerState.LinearClamp,
null,
null,
null,
Resolution.getTransformationMatrix());
To handle resolution changes as the orientation changed I use this in my Game1.cs
public Game1()
{
graphics = new GraphicsDeviceManager(this);
GamePage.Current.SizeChanged += OnWindowSizeChanged;
Content.RootDirectory = "Content";
Resolution.Init(ref graphics);
Resolution.SetVirtualResolution(480, 800);
}
private void OnWindowSizeChanged(object sender, Windows.UI.Xaml.SizeChangedEventArgs e)
{
var CurrentViewState = Windows.UI.ViewManagement.ApplicationView.Value;
App.AppWidth = (int)e.NewSize.Width;
App.AppHeight = (int)e.NewSize.Height;
Resolution.SetResolution(App.AppWidth, App.AppHeight, true);
}
The initial values of App.AppWidth and App.AppHeight is set in the GamePage.xaml.cs.
public GamePage(string launchArguments)
{
this.InitializeComponent();
App.AppWidth = (int)Window.Current.Bounds.Width;
App.AppHeight = (int)Window.Current.Bounds.Height;
Current = this;
// Create the game.
_game = XamlGame<Game1>.Create(launchArguments, Window.Current.CoreWindow, this);
}
Both are global static property created in the App.xaml.cs
public static int AppWidth { get; set; }
public static int AppHeight { get; set; }
The only problem I've encountered so far, the mouse input does not scale to the screen resolution change. I do not have a touch screen to test unfortunately but I think touch input should scale. If anyone tested touch, please share your findings. Thanks.
Update
I've managed to scale the Mouse input using the following
public static Vector2 ScaleGesture(Vector2 position)
{
int x = (int)(position.X / (float)App.AppWidth * (float)Screen.ScreenWidth);
int y = (int)(position.Y / (float)App.AppHeight * (float)Screen.ScreenHeight);
var scaledPosition = new Vector2(x, y);
return scaledPosition;
}

Resources