Editing the NavigationPage back button size in xamarin forms - user-interface

My aim is to change increase the size of the back arrow button that comes with the NavigationPage class. I have found out that we are able to change its color, but not it's size. Is it possible to do that in xamarin forms or can i atleast use a custom icon there that will suit my UI requirements.
<Style TargetType="NavigationPage">
<Setter Property="BarTextColor" Value="Red" />
<Setter Property="BarBackgroundColor" Value="Black" />
</Style>
Here i am able to set color for the "BarTextColor" but not it's size. Any help is appreciated.

NavigationPage back button is icon, so you can replace this icon using another bigger icon.
For Android platform, if your MainActivity type is FormsAppCompatActivity, you could custom a PageRenderer and change the Toolbar's NavigationIcon.
[assembly: ExportRenderer(typeof(ContentPage), typeof(NavigationPageRendererDroid))]
namespace FormsSample.Droid
{
public class NavigationPageRendererDroid : PageRenderer
{
public NavigationPageRendererDroid(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Page> e)
{
base.OnElementChanged(e);
var context = (Activity)this.Context;
var toolbar = context.FindViewById<AndroidX.AppCompat.Widget.Toolbar>(Droid.Resource.Id.toolbar);
toolbar.NavigationIcon = AndroidX.Core.Content.ContextCompat.GetDrawable(context, Resource.Drawable.c1);
}
}
}
Please note: ToolBar in Android platform> Resource folder> Layout type is androidx.appcompat.widget.Toolbar
If your ToolBar is other type, you can take a look:
How to change navigation page back button in xamarin forms
Update:
I also reproduce the same problem as you, from I provide the thread , you can follow the last solution, custom a NavigationPageRenderer, override the OnPushAsync method to set the Toolbar's icon.
Firstly, create a class named CustomNavigationPage in the .NET Standard project, and this class inherits from NavigationPage to make our custom renderer applied to that class.
public class CustomNavigationPage : NavigationPage
{
public CustomNavigationPage(Page startupPage) : base(startupPage)
{
}
}
Secondly, in Android project, create class named NavigationPageRenderer, override the OnPushAsync method.
[assembly: ExportRenderer(typeof(CustomNavigationPage), typeof(NavigationPageRenderer))]
namespace FormsSample.Droid
{
public class NavigationPageRenderer : Xamarin.Forms.Platform.Android.AppCompat.NavigationPageRenderer
{
public Activity context;
public NavigationPageRenderer(Context context) : base(context)
{
}
protected override Task<bool> OnPushAsync(Page view, bool animated)
{
var retVal = base.OnPushAsync(view, animated);
var context = (Activity)this.Context;
var toolbar = context.FindViewById<AndroidX.AppCompat.Widget.Toolbar>(Droid.Resource.Id.toolbar);
if (toolbar != null)
{
if (toolbar.NavigationIcon != null)
{
toolbar.NavigationIcon = AndroidX.Core.Content.ContextCompat.GetDrawable(context, Resource.Drawable.c1);
toolbar.Title = "back";
}
}
return retVal;
}
}
}
Finally, in App.xaml.csfile, set the MainPage to NavigtaionPage,
MainPage = new CustomNavigationPage(new simplecontrol.Page41());

Related

How to hide clear button Icon inside SearchBar control Xamarin forms

I am using xamarin forms SearchBar control. I want to remove clear button x icon without using custom renderer.
<controls:ExSearchBar
x:Name="entrySearch"
BackgroundColor="White"
CornerRadius="6"
BorderWidth="1"
HeightRequest="45"
Text="{Binding SearchText}"
HorizontalOptions="FillAndExpand"
Placeholder="search">
</controls:ExSearchBar>
This is ExSearchBar control in shared project
public class ExSearchBar : SearchBar
{
public static readonly BindableProperty ElevationProperty = BindableProperty.Create(nameof(Elevation), typeof(float), typeof(ExFrame), default(float));
public float Elevation
{
get { return (float)GetValue(ElevationProperty); }
set { SetValue(ElevationProperty, value); }
}
}
How can I do that?
The situation you are describing is the exact reason why Xamarin Forms ships with the ability to create custom renderers. The forms team define the UI elements in abstract (seperate from their native implementation) and when there is a specific feature that is not defined in their API, you must go down to the platform level to change it.
You can also use an Effect to achieve the same result, I have provided a custom renderer for iOS & Android to show you how you would go about achieving the UI you desire:
iOS:
public class SearchBarButtonRenderer : SearchBarRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<SearchBar> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
Control.SearchTextField.ClearButtonMode = UITextFieldViewMode.Never;
}
}
}
Really simple, just remove the clear button from the underlying UITextField
Android
public class SearchBarButtonRenderer : SearchBarRenderer
{
private readonly Context _context;
public SearchBarButtonRenderer(Context context)
: base(context)
{
_context = context;
}
protected override void OnElementChanged(ElementChangedEventArgs<SearchBar> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
// Get Search Close Button Drawable
var closeButtonId = Resources.GetIdentifier("android:id/search_close_btn", null, null);
var searchEditText = Control.FindViewById<ImageView>(closeButtonId);
// Get Close Button Drawable To Replace Existing Drawable
var closeDrawable = GetCloseButtonDrawable() as VectorDrawable;
if (closeDrawable is null) return;
// Apply Transparent Color To Drawable (To Make Invisible)
var buttonColor = Xamarin.Forms.Color.Transparent.ToAndroid();
closeDrawable.SetTint(buttonColor);
// Set Drawable On Control
searchEditText.SetImageDrawable(closeDrawable);
}
}
private Drawable GetCloseButtonDrawable()
{
return ContextCompat.GetDrawable(_context, Resource.Drawable.abc_ic_clear_material);
}
}
A little bit of a fiddle, find the close button drawable and replace it with a custom styled drawable

Custom NavigationRenderer for back button not called while navigating back in xamarin forms application

Here is the code for the custom renderer i used to assign a custom icon as my back button.
namespace MyProjectName.Droid.Renderers
{
public class MyNavigationRenderer: PageRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Page> e)
{
base.OnElementChanged(e);
var context = (Activity)Xamarin.Forms.Forms.Context;
var toolbar = context.FindViewById<AndroidX.AppCompat.Widget.Toolbar>(Droid.Resource.Id.toolbar);
toolbar.NavigationIcon = AndroidX.Core.Content.ContextCompat.GetDrawable(context, Resource.Drawable.bbutton_nav);
}
}
}
This code successfully replaces the native back arrow icon with my custom bbutton_nav. When i navigate forward(Navigate.PushAsync()), the custom icon appears on all the upcoming screens. But when i click on the back icon to go back one page(Navigate.PopAsync()), the old native back arrow reappears instead of the new custom icon that was set by the renderer. When i tried debugging , i found out that the renderer class was not getting called when navigating back(Navigation.PopAsync()).
Any help on how to mitigate this issue is appreciated. Thanks
Create a custom renderer for NavigationPage instead of Page , and override the OnLayout method .
Android will change the detault icon back in UpdateToolbar method , and OnLayout method is triggered every time while current page is changed.
Android Solution
[assembly: ExportRenderer(typeof(NavigationPage), typeof(MyNavigationRenderer))]
namespace FormsApp.Droid
{
public class MyNavigationRenderer : NavigationPageRenderer
{
Context _context;
AndroidX.AppCompat.Widget.Toolbar _toolbar;
public MyNavigationRenderer(Context context) : base(context)
{
_context = context;
}
public override void OnViewAdded(Android.Views.View child)
{
base.OnViewAdded(child);
if (child.GetType() == typeof(AndroidX.AppCompat.Widget.Toolbar))
{
_toolbar = (AndroidX.AppCompat.Widget.Toolbar)child;
_toolbar.SetNavigationIcon(Resource.Drawable.bbutton_nav);
}
}
protected override void OnLayout(bool changed, int l, int t, int r, int b)
{
base.OnLayout(changed, l, t, r, b);
if (_toolbar != null)
{
if (_toolbar.NavigationIcon != null)
{
_toolbar.NavigationIcon = AndroidX.Core.Content.ContextCompat.GetDrawable(_context, Resource.Drawable.bbutton_nav);
}
}
}
}
}
Refer to https://forums.xamarin.com/discussion/183344/how-to-change-navigation-back-button-icon .
iOS Solution
[assembly: ExportRenderer(typeof(NavigationPage), typeof(MyRenderer))]
namespace FormsApp.iOS
{
class MyRenderer : NavigationRenderer
{
public override void ViewDidLayoutSubviews()
{
base.ViewDidLayoutSubviews();
if (this.NavigationBar.TopItem.BackBarButtonItem == null)
{
this.NavigationBar.BackIndicatorImage = UIImage.FromFile("dots.png");
this.NavigationBar.BackIndicatorTransitionMaskImage = UIImage.FromFile("dots.png");
this.NavigationBar.TopItem.BackBarButtonItem = new UIBarButtonItem("", UIBarButtonItemStyle.Plain, null);
}
}
}
}

Xamarin WebView GestureRecognizer not working

I have an awkward problem with GestureRecognizers on Xamarin WebView:
Although the documentation any some questions/answers here and in Xamarin Forum say that WebView GestureRecognizers should all work, I can't get it to fire any event.
My XAML code looks like this:
<StackLayout BackgroundColor="LightGray" >
<WebView x:Name="webView" VerticalOptions="FillAndExpand" >
<WebView.GestureRecognizers>
<SwipeGestureRecognizer Direction="Left" Swiped="onSwiped"/>
</WebView.GestureRecognizers>
<WebView.Source>
<HtmlWebViewSource Html="{Binding HTML}" />
</WebView.Source>
</WebView>
</StackLayout>
Alternatives treid so far:
Same GestureRecognizer on the Title of the same page: works
Same GestureRecognizer on a ListView of another page: works
Tried Nuget package Vapolia.XamarinGestures which also didn't work on the webview
Tried to put the GestureRecoginzer on the StackLayout around the WebView: didn't work either.
Tried it on iOS device and simulator. Normally iOS should be the easy part here...
What I actually want to achieve: with a swipe left move forward to another (programatically defined) web page.
I assume those gestures are somehow absorbed by the webview for regular navigation, but I was wondering why some examples would say that all gestures work on the webview.
An alternative could be to add that target webpage to the webview history stack on the "forward" path.. but not sure how to do that.
Anyone has some hints?
You could use Custom Renderer to add the swipe event on specific platform. And handle them in Forms .
in Forms
create a CustomWebView
public class CustomWebView : WebView
{
public event EventHandler SwipeLeft;
public event EventHandler SwipeRight;
public void OnSwipeLeft() =>
SwipeLeft?.Invoke(this, null);
public void OnSwipeRight() =>
SwipeRight?.Invoke(this, null);
}
in Android
using Android.Content;
using Android.Views;
using App11;
using App11.Droid;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(CustomWebView), typeof(MyWebViewRenderer))]
namespace App11.Droid
{
public class MyWebViewRenderer : WebViewRenderer
{
public MyWebViewRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
Control.SetOnTouchListener(new MyOnTouchListener((CustomWebView)Element));
}
}
public class MyOnTouchListener : Java.Lang.Object, Android.Views.View.IOnTouchListener
{
float oldX;
float newX;
CustomWebView myWebView;
public MyOnTouchListener(CustomWebView webView)
{
myWebView = webView;
}
public bool OnTouch(Android.Views.View v, MotionEvent e)
{
if (e.Action == MotionEventActions.Down)
{
oldX = e.GetX(0);
}
if (e.Action == MotionEventActions.Up)
{
newX = e.GetX();
if (newX - oldX > 0)
{
myWebView.OnSwipeRight();
}
else
{
myWebView.OnSwipeLeft();
}
}
return false;
}
}
}
in iOS
using App11;
using App11.iOS;
using Foundation;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using ObjCRuntime;
[assembly: ExportRenderer(typeof(CustomWebView), typeof(MyWebViewRenderer))]
namespace App11.iOS
{
public class MyWebViewRenderer:WkWebViewRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
if(e.NewElement!=null)
{
this.BackgroundColor = UIColor.Red;
UISwipeGestureRecognizer leftgestureRecognizer = new UISwipeGestureRecognizer(this,new Selector("SwipeEvent:"));
leftgestureRecognizer.Direction = UISwipeGestureRecognizerDirection.Left;
UISwipeGestureRecognizer rightgestureRecognizer = new UISwipeGestureRecognizer(this, new Selector("SwipeEvent:"));
rightgestureRecognizer.Direction = UISwipeGestureRecognizerDirection.Right;
leftgestureRecognizer.Delegate = new MyWebViewDelegate();
rightgestureRecognizer.Delegate = new MyWebViewDelegate();
this.AddGestureRecognizer(leftgestureRecognizer);
this.AddGestureRecognizer(rightgestureRecognizer);
}
}
[Export("SwipeEvent:")]
void SwipeEvent(UISwipeGestureRecognizer recognizer)
{
var webview = Element as CustomWebView;
if(recognizer.Direction == UISwipeGestureRecognizerDirection.Left)
{
webview.OnSwipeLeft();
}
else if(recognizer.Direction == UISwipeGestureRecognizerDirection.Right)
{
webview.OnSwipeRight();
}
}
}
public class MyWebViewDelegate: UIGestureRecognizerDelegate
{
public override bool ShouldRecognizeSimultaneously(UIGestureRecognizer gestureRecognizer, UIGestureRecognizer otherGestureRecognizer)
{
return false;
}
}
}
Now you just need to use it like
<local:CustomWebView x:Name="browser"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
SwipeLeft="browser_SwipeLeft"
SwipeRight="browser_SwipeRight">
There was an additional trick to make it finally work. All the above (correct) solution was ignored due to my Xamarin MasterDetailPage setup.
This was capturing all horizontal swipes and not putting them through to the HybridWebView.
MasterDetailPage.IsGestureEnabled = false;
finally fixed it and enabled the swipe gestures in my WebView.

Is there a way to center the page title on Android when using Xamarin Forms Shell?

I recently changed to Xamarin Forms and notice that the title isn't centered at the top of the page for Android devices.
Is there a way that I can do this?
Here's an example of what I mean with the title:
You can use the TitleView:
<?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:local="clr-namespace:TitleViewSample"
x:Class="TitleViewSample.MainPage">
<NavigationPage.TitleView>
<Label Text="Hello World" HorizontalTextAlignement="Center"/>
</NavigationPage.TitleView>
<ContentPage.Content>
<StackLayout>
<!-- Place new controls here -->
<Label Text="Welcome to Xamarin.Forms!"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage.Content>
</ContentPage>
https://www.andrewhoefling.com/Blog/Post/xamarin-forms-title-view-a-powerful-navigation-view
You will have to implement ShellRenderer in this case as you have Xamarin.Forms Shell Project.
using System;
using System.Collections.Specialized;
using System.ComponentModel;
using Android.App;
using Android.Content;
using Android.Content.Res;
using Android.Support.V4.Widget;
using Android.Support.V7.Widget;
using Android.Util;
using Android.Widget;
using Japanese.Droid.CustomRenderers;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Toolbar = Android.Support.V7.Widget.Toolbar;
[assembly: ExportRenderer(typeof(Xamarin.Forms.Shell), typeof(MyShellRenderer))]
namespace MyProject.Droid.CustomRenderers
{
public class MyShellRenderer : ShellRenderer
{
public MyShellRenderer(Context context) : base(context)
{
}
protected override IShellToolbarAppearanceTracker CreateToolbarAppearanceTracker()
{
return new MyShellToolbarAppearanceTracker(this);
}
protected override IShellToolbarTracker CreateTrackerForToolbar(Toolbar toolbar)
{
return new MyShellToolbarTracker(this, toolbar, ((IShellContext)this).CurrentDrawerLayout);
}
}
public class MyShellToolbarAppearanceTracker : ShellToolbarAppearanceTracker
{
public MyShellToolbarAppearanceTracker(IShellContext context) : base(context)
{
}
public override void SetAppearance(Android.Support.V7.Widget.Toolbar toolbar, IShellToolbarTracker toolbarTracker, ShellAppearance appearance)
{
base.SetAppearance(toolbar, toolbarTracker, appearance);
//Change the following code to change the icon of the Header back button.
toolbar?.SetNavigationIcon(Resource.Drawable.back);
}
}
public class MyShellToolbarTracker : ShellToolbarTracker
{
public MyShellToolbarTracker(IShellContext shellContext, Toolbar toolbar, DrawerLayout drawerLayout) : base(shellContext, toolbar, drawerLayout)
{
}
protected override void UpdateTitleView(Context context, Toolbar toolbar, View titleView)
{
base.UpdateTitleView(context, toolbar, titleView);
for (int index = 0; index < toolbar.ChildCount; index++)
{
if (toolbar.GetChildAt(index) is TextView)
{
var title = toolbar.GetChildAt(index) as TextView;
//Change the following code to change the font size of the Header title.
title.SetTextSize(ComplexUnitType.Sp, 20);
toolbar.SetTitleMargin(MainActivity.displayMetrics.WidthPixels / 4 - Convert.ToInt32(title.TextSize) - title.Text.Length / 2, 0, 0, 0);
}
}
}
}
}
Here is the code for MainActivity.cs
public class MainActivity : FormsAppCompatActivity
{
public static DisplayMetrics displayMetrics;
protected override void OnCreate(Bundle savedInstanceState)
{
Window.AddFlags(WindowManagerFlags.Fullscreen);
base.OnCreate(savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
displayMetrics = new DisplayMetrics();
WindowManager.DefaultDisplay.GetRealMetrics(displayMetrics);
LoadApplication(new App());
if (Window != null) Window.SetStatusBarColor(Android.Graphics.Color.Transparent);
if (isPhone(this)) RequestedOrientation = ScreenOrientation.Portrait;
}
}
As the title textview having wrapped width within toolbar not updating alignment on TextAlignment with center, you can update the layout params of the toolbar to matchparent and textview gravity as follows.
If the hamburger image added with custom image then need check that resoulution if that is too big just reduce it lower one
[assembly: ExportRenderer(typeof(MainPage), typeof(MyRenderer))]//MainPage - navigation page
namespace MyProject.Droid
{
public class MyRenderer: MasterDetailPageRenderer
{
public MyRenderer(Context context) : base(context)
{
}
protected override void OnLayout(bool changed, int l, int t, int r, int b)
{
base.OnLayout(changed, l, t, r, b);
var toolbar = FindViewById<AndroidX.AppCompat.Widget.Toolbar>(Resource.Id.toolbar);
for (var i = 0; i < toolbar.ChildCount; i++)
{
var title = toolbar.GetChildAt(i) as TextView;
if (title != null && !string.IsNullOrEmpty(title.Text))
{
title.TextAlignment = Android.Views.TextAlignment.Center;
title.Gravity = GravityFlags.CenterHorizontal;
var layoutParams = (AndroidX.AppCompat.Widget.Toolbar.LayoutParams)title.LayoutParameters;
layoutParams.Width = ViewGroup.LayoutParams.MatchParent;
toolbar.RequestLayout();
}
}
}
}
}

How to reduce padding between the icon and text in xamarin button

I have created a button in my xamarin application and have added an icon to it but there is too much of padding between the icon and the text as shown in the screenshot below.
How can I reduce this space between the two?
I have learnt about the custom renders for Xamarin.Android but its not working for me.
Following is the code for my custom renderer
[assembly: ExportRenderer(typeof(Button), typeof(ZeroPaddingRenderer))]
namespace MyApp.Droid
{
class ZeroPaddingRenderer : ButtonRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
base.OnElementChanged(e);
Control.SetPadding(0, Control.PaddingTop, 0, Control.PaddingBottom);
}
}
}
Here is the code for button
Button btn_login = new Button
{
VerticalOptions = LayoutOptions.Center,
Text = "Login",
};
btn_login.Image = "login.png";
Any help will be appreciated.

Resources