Add padding to button renderer with Image on Android Xamarin - android

I'm having trouble padding an button with Image on Android. I have it working using Insets on iOS but can't achieve the same thing on Android.
Here is my Android renderer code:
public class PaddedButtonRenderer : ButtonRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
base.OnElementChanged(e);
if (Control != null)
{
var button = (PaddedButton)Element;
UpdatePadding();
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == nameof(PaddedButton.Padding))
{
UpdatePadding();
}
}
private void UpdatePadding()
{
var element = Element as PaddedButton;
if (element != null)
{
Control.SetPadding(
(int)element.Padding,
(int)element.Padding,
(int)element.Padding,
(int)element.Padding);
}
}
}
Here is my working iOS button:
public class PaddedButtonRenderer : ButtonRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
base.OnElementChanged(e);
if (Control != null)
{
var button = (PaddedButton)Element;
Control.ContentEdgeInsets = new UIEdgeInsets((nfloat)button.Padding, (nfloat)button.Padding, (nfloat)button.Padding, (nfloat)button.Padding);
}
}
}
Any suggestions? I'm starting to think padding is the wrong attribute to be changing on Android. Thanks.

Add padding to button renderer with Image on Android Xamarin
I test your code, it did not work, but you could refer to #Dbl's Answer, it works fine both on Android and iOS.
First, make sure you have define the Padding in your PaddedButton :
public class EnhancedButton : Button
{
public static BindableProperty PaddingProperty = BindableProperty.Create(nameof(Padding), typeof(Thickness), typeof(EnhancedButton), default(Thickness), defaultBindingMode: BindingMode.OneWay);
public Thickness Padding
{
get { return (Thickness)GetValue(PaddingProperty); }
set { SetValue(PaddingProperty, value); }
}
}
Second, in your UpdatePadding() method, when you using this.Control.SetPadding(), modify your code like this :
private void UpdatePadding()
{
var element = this.Element as EnhancedButton;
if (element != null)
{
this.Control.SetPadding(
(int)element.Padding.Left,
(int)element.Padding.Top,
(int)element.Padding.Right,
(int)element.Padding.Bottom
);
}
}

Related

Xamarin Forms : disposed object problem with an Android renderer

Using an Android renderer for a Frame inside a page in Xamarin Forms, I need to change the position of this object after the size allocation of the page.
The page being in a tab in a Shell, when I change tabs and I come back I get the exception 'Cannot access a disposed object' in the renderer.
The exception occurs on this line of UpdatePos:
SetY(20);
My problem has been reproduced with the code below :
The page :
public partial class TestPage : ContentPage
{
public partial class Container : Frame
{
public delegate void PosChangedEvent();
public event PosChangedEvent HandlerPosUpdated;
public void Update()
{
HandlerPosUpdated?.Invoke();
}
}
Container _container = null;
public TestPage()
{
InitializeComponent();
_container = new Container()
{
Content = new myView()
};
main_layout.Children.Add(_container);
}
protected override void OnSizeAllocated(double width, double height)
{
base.OnSizeAllocated(width, height);
_container.Update();
}
}
The renderer :
public class ContainerRenderer : ViewRenderer<Frame, Android.Views.View>
{
public ContainerRenderer(Context context) : base(context)
{}
public void UpdatePos()
{
SetY(20); // System.ObjectDisposedException: 'Cannot access a disposed object. Object name: 'ContainerRenderer'.'
}
protected override void OnElementChanged(ElementChangedEventArgs<Frame> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
TestPage.Container view = e.NewElement as TestPage.Container;
if (view != null)
{
view.HandlerPosUpdated += UpdatePos;
}
}
if (e.OldElement != null)
{
TestPage.Container view = e.OldElement as TestPage.Container;
if (view != null)
{
view.HandlerPosUpdated -= UpdatePos;
}
}
}
}
How this exception could be avoided ?
Any hints are welcome!
Remove that handler when the custom renderer is disposed:
private bool disposedValue;
protected override void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
RemoveHandlerPosUpdated();
}
disposedValue = true;
}
base.Dispose(disposing);
}
private void RemoveHandlerPosUpdated()
{
if (Element != null)
{
TestPage.Container view = Element as TestPage.Container;
if (view != null)
{
view.HandlerPosUpdated -= UpdatePos;
}
}
}
If that doesn't fix it, then may need to do something in TestPage.Container class, to remove any handlers attached to HandlerPosUpdated. Details TBD.

How to keep keyboard opened after focus other(specific) view in Xamarin.Forms Android / iOS? [duplicate]

Entry.unfocus/Entry.completed hides keyboard, how to cancel it?
I have a page with some entries and when I press keyboard enter key, I want the keyboard not hides. How to do that with PCL project (Android e iOS)?
Just to point out another solution for Android. In case you want to keep always visible the keyboard for a specific Editor Renderer, you need to override the following methods in the MainActivity class:
private bool _lieAboutCurrentFocus;
public override bool DispatchTouchEvent(MotionEvent ev)
{
var focused = CurrentFocus;
bool customEntryRendererFocused = focused != null && focused.Parent is YourCustomEditorRenderer;
_lieAboutCurrentFocus = customEntryRendererFocused;
var result = base.DispatchTouchEvent(ev);
_lieAboutCurrentFocus = false;
return result;
}
public override Android.Views.View CurrentFocus
{
get
{
if (_lieAboutCurrentFocus)
{
return null;
}
return base.CurrentFocus;
}
}
You can find a more detail explanation here
Hope this helps.
Regards
If you want to do that from the PCL there's a nice and easy way to navigate through your entries and keep them focused one after the other (If this is what you're looking for, and not just keep keyboard open)
Let's say you have around 5 entries in your page, and you want to cycle through them when user presses the done or enter key.
CurrentPage.FindByName<Entry>("FirstEntry").Completed += (o, args) =>
{
CurrentPage.FindByName<Entry>("SecondEntry").Focus();
};
CurrentPage.FindByName<Entry>("SecondEntry").Completed += (o, args) =>
{
CurrentPage.FindByName<Entry>("ThirdEntry").Focus();
};
CurrentPage.FindByName<Entry>("ThirdEntry").Completed += (o, args) =>
{
CurrentPage.FindByName<Entry>("ForthEntry").Focus();
};
CurrentPage.FindByName<Entry>("ForthEntry").Completed += (o, args) =>
{
CurrentPage.FindByName<Entry>("FifthEntry").Focus();
};
CurrentPage.FindByName<Entry>("FifthEntry").Completed += (o, args) =>
{
//Keep going or execute your command, you got the drill..
};
You can add this to your ViewIsAppearing or Init method.
Recently i did something similar. I want to keep keyboard always open in a page and not to hide when a button clicked. To accomplish this, i followed different ways both on iOS and Android.
iOS
In iOS, i created a custom editor renderer
public class CustomEditorRenderer : EditorRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
{
base.OnElementChanged(e);
var element = this.Element as CustomEditor;
Control.InputAccessoryView = null;
Control.ShouldEndEditing += DisableHidingKeyboard;
MessagingCenter.Subscribe<ReportEventDetailPage>(this, "FocusKeyboardStatus", (sender) =>
{
if (Control != null)
{
Control.ShouldEndEditing += EnableHidingKeyboard;
}
MessagingCenter.Unsubscribe<ReportEventDetailPage>(this, "FocusKeyboardStatus");
});
}
private bool DisableHidingKeyboard(UITextView textView)
{
return false;
}
private bool EnableHidingKeyboard(UITextView textView)
{
return true;
}
}
In this piece of code:
Control.ShouldEndEditing += DisableHidingKeyboard; makes keyboard always opened after focusing custom editor. However, the keyboard does not hide when changing current page to another page. To solve this problem i used MessagingCenter and when dissapering of the current page i send a message to hide keyboard.
Android
For Android, i created a keyboard helper interface and implemented it.
Here is my interface:
public interface IKeyboardHelper
{
void ShowKeyboard();
void HideKeyboard();
}
Keyboard Helper class for Android:
public class KeyboardHelper : IKeyboardHelper
{
public void ShowKeyboard()
{
var context = Forms.Context;
var inputMethodManager = context.GetSystemService(Context.InputMethodService) as InputMethodManager;
if (inputMethodManager != null && context is Activity)
{
var activity = context as Activity;
var token = activity.CurrentFocus?.WindowToken;
inputMethodManager.ToggleSoftInput(ShowFlags.Forced, HideSoftInputFlags.ImplicitOnly);
}
}
public void HideKeyboard()
{
var context = Forms.Context;
var inputMethodManager = context.GetSystemService(Context.InputMethodService) as InputMethodManager;
if (inputMethodManager != null && context is Activity)
{
var activity = context as Activity;
var token = activity.CurrentFocus?.WindowToken;
inputMethodManager.HideSoftInputFromWindow(token, HideSoftInputFlags.None);
activity.Window.DecorView.ClearFocus();
}
}
in Constructor of the current page:
else if (Device.OS == TargetPlatform.Android)
{
MessagingCenter.Send(this, "AndroidFocusEditor");
}
and OnAppearing method of the current page:
protected override void OnAppearing()
{
base.OnAppearing();
if (Device.OS == TargetPlatform.Android)
{
DependencyService.Get<IKeyboardHelper>().ShowKeyboard();
//EventEditor.Focus();
MessagingCenter.Subscribe<ReportEventDetailPage>(this, "AndroidFocusEditor", (sender) => {
Device.BeginInvokeOnMainThread(async () => {
await Task.Run(() => Task.Delay(1));
EventEditor.Focus();
MessagingCenter.Unsubscribe<ReportEventDetailPage>(this, "AndroidFocusEditor");
});
});
}
else if (Device.OS == TargetPlatform.iOS)
{
EventEditor.Focus();
}
}
One last thing: if user clicks another button on the page, keyboard is hiding. To prevent this i followed this link and it really helped me a lot
Keep Keyboard Open For Android
In case you have a custom Keyboard, you can implement a "show" and a "hide" method on android renderer.
Then on your page, show keyboard on your custom control without hiding it. You can hide it when changing page, by overriding OnBackButtonPressed.
In OnBackButtonPressed, send a message using MessagingCenter. Then subscribe to it on your custom control constructor.
Declare an EventHandler that you invoke in the callback method.
Subscribe to this event on your android custom entry renderer and hide the keyboard there.
I had a similar problem and handled it like below:
using System.Runtime.CompilerServices;
using Xamarin.Forms;
public class CustomEntry: Entry
{
public static readonly BindableProperty KeyboardAliveProperty =
BindableProperty.Create(nameof(KeyboardAliveType), typeof(KeyboardAliveType),
typeof(CustomEntry), KeyboardAliveType.Default);
public KeyboardAliveType KeyboardAliveType
{
get { return (KeyboardAliveType)GetValue(KeyboardAliveProperty); }
set { SetValue( KeyboardAliveProperty, value);}
}
}
public enum KeyboardAliveType
{
Default =0,
OnCompleted = 1,
OnButtonClicked = 2,
OnCompletedAndButtonClicked = 3
}
Renderer for Android:
using System;
using Android.Content;
using Android.OS;
using Android.Views;
using Android.Views.InputMethods;
using Android.Widget;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Entry = Xamarin.Forms.Entry;
[assembly: ExportRenderer(typeof(CustomEntry), typeof(CustomEntryRenderer))]
/// <summary>
/// Allow and support changes to Border styling and Keyboard with Custom Entry.
/// </summary>
public class CustomEntryRenderer: EntryRenderer, TextView.IOnEditorActionListener
{
private ImeAction _currentInputImeFlag;
public CustomEntryRenderer(Context context) : base(context)
{
//do nothiing
}
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (e.NewElement!=null)
{
}
}
bool TextView.IOnEditorActionListener.OnEditorAction(TextView v, ImeAction actionId, KeyEvent e)
{
// Fire Completed and dismiss keyboard for hardware / physical keyboards
if (actionId == ImeAction.Done || actionId == _currentInputImeFlag ||
(actionId == ImeAction.ImeNull && e.KeyCode == Keycode.Enter && e.Action == KeyEventActions.Up))
{
global::Android.Views.View nextFocus = null;
if (_currentInputImeFlag == ImeAction.Next)
{
nextFocus = FocusSearch(v, FocusSearchDirection.Forward);
}
if (nextFocus != null)
{
nextFocus.RequestFocus();
if (!nextFocus.OnCheckIsTextEditor())
{
if (Element is CustomEntry cE)
{
if (cE.KeyboardAliveType != KeyboardAliveType.OnCompleted &&
cE.KeyboardAliveType != KeyboardAliveType.OnCompletedAndButtonClicked)
{
v.HideKeyboard();
}
}
}
}
else
{
EditText.ClearFocus();
if (Element is CustomEntry cE)
{
if (cE.KeyboardAliveType != KeyboardAliveType.OnCompleted &&
cE.KeyboardAliveType != KeyboardAliveType.OnCompletedAndButtonClicked)
{
v.HideKeyboard();
}
}
}
((IEntryController)Element).SendCompleted();
}
return true;
}
}
internal static class CustomEntryRendererExtensions
{
internal static void HideKeyboard(this Android.Views.View inputView, bool overrideValidation = false)
{
if (inputView == null)
throw new ArgumentNullException(nameof(inputView) + " must be set before the keyboard can be hidden.");
using (var inputMethodManager = (InputMethodManager)inputView.Context?.GetSystemService(Context.InputMethodService))
{
if (!overrideValidation && !(inputView is EditText || inputView is TextView || inputView is SearchView))
throw new ArgumentException("inputView should be of type EditText, SearchView, or TextView");
IBinder windowToken = inputView.WindowToken;
if (windowToken != null && inputMethodManager != null)
inputMethodManager.HideSoftInputFromWindow(windowToken, HideSoftInputFlags.None);
}
}
}
In MainActivity.cs
private bool _lieAboutCurrentFocus;
public override bool DispatchTouchEvent(MotionEvent ev)
{
var focused = CurrentFocus;
if (focused?.Parent is CustomEntryRenderer cer)
{
if (cer.Element is CustomEntry cEntry)
{
if (cEntry.KeyboardAliveType == KeyboardAliveType.OnButtonClicked ||
cEntry.KeyboardAliveType == KeyboardAliveType.OnCompletedAndButtonClicked)
{
_lieAboutCurrentFocus = true;
}
}
}
var result = base.DispatchTouchEvent(ev);
_lieAboutCurrentFocus = false;
return result;
}
public override Android.Views.View CurrentFocus
{
get
{
if (_lieAboutCurrentFocus)
{
return null;
}
return base.CurrentFocus;
}
}
Renderer for UWP:
using System;
using System.ComponentModel;
using System.Reflection;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.System;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Input;
using Xamarin.Forms;
using Xamarin.Forms.Platform.UWP;
[assembly: ExportRenderer(typeof(CustomEntry), typeof(CustomEntryRenderer))]
public class CustomEntryRenderer : EntryRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (Control != null)
{
// Remove the EventHandler set for KeyUp, and add my custom EventHandler.
// Had to do it this way (using WindowsRuntimeMarshal) because the Delegate that
// I want to remove from the KeyUp event is marked private in a different assembly, so no way to access it directly.
// This way I can customize how the keyboard behaves when the Enter key is pressed.
/*Done the best I can for UWP.*/
var keyUpRuntimeEvent = this.Control.GetType().GetRuntimeEvent("KeyUp");
Action<EventRegistrationToken> removeEventHandlerAction =
(Action<EventRegistrationToken>)Delegate.CreateDelegate(typeof(Action<EventRegistrationToken>),
this.Control,
keyUpRuntimeEvent.RemoveMethod);
WindowsRuntimeMarshal.RemoveAllEventHandlers(removeEventHandlerAction);
this.Control.KeyUp += TextBoxOnKeyUp;
this.Control.PreventKeyboardDisplayOnProgrammaticFocus = false;
// Just to make sure that keyboard is up when the Entry is focused.
Control.GotFocus += (sender, args) =>
{
AttemptToForceKeyboardToShow(Control);
};
Control.TextChanged += (sender, args) =>
{
if (Control.FocusState != FocusState.Unfocused)
{
AttemptToForceKeyboardToShow(Control);
}
};
}
}
protected override void Dispose(bool disposing)
{
if (disposing && Control != null)
{
Control.KeyUp -= TextBoxOnKeyUp;
}
base.Dispose(disposing);
}
private void TextBoxOnKeyUp(object sender, KeyRoutedEventArgs args)
{
if (args?.Key != VirtualKey.Enter)
{
return;
}
if (Element.ReturnType == ReturnType.Next)
{
FocusManager.TryMoveFocus(FocusNavigationDirection.Next);
}
else
{
/*Done the best I can for UWP.*/
if (Element is CustomEntry cE)
{
if (cE.KeyboardAliveType != KeyboardAliveType.OnCompleted &&
cE.KeyboardAliveType != KeyboardAliveType.OnCompletedAndButtonClicked)
{
//Hide the soft keyboard; this matches the behavior of Forms on Android/iOS
Windows.UI.ViewManagement.InputPane.GetForCurrentView().TryHide();
}
}
}
((IEntryController)Element).SendCompleted();
}
private void AttemptToForceKeyboardToShow(FormsTextBox control)
{
try
{
var inputPane = InputPane.GetForUIContext(control.UIContext);
var keyboardShowSuccess = inputPane?.TryShow();
if (keyboardShowSuccess == null || !keyboardShowSuccess.Value)
{
Console.WriteLine("Attempt to force Keyboard to show failed on Windows.");
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
See here.

How to change navigation page back button in xamarin forms

I am trying to change back arrow image in navigation page. For this in android app i created navigation page renderer and then using method toolbar.SetNavigationIcon and its not working, but when i use toolbar.SetLogo its working fine.
protected override void OnElementChanged(ElementChangedEventArgs<NavigationPage> e)
{
base.OnElementChanged(e);
toolbar.SetNavigationIcon(Resource.Drawable.arrow);
toolbar.SetLogo(Resource.Drawable.arrow);
}
public override void OnViewAdded(Android.Views.View child)
{
base.OnViewAdded(child);
if (child.GetType() == typeof(Android.Support.V7.Widget.Toolbar))
{
toolbar = (Android.Support.V7.Widget.Toolbar)child;
toolbar.ChildViewAdded += Toolbar_ChildViewAdded;
}
}
I also tried add image to app:navigationIcon in toolbar.axml, and it shows great in designer
my arrow
But, when i starting my app i have the same standart arrow icon
enter image description here
If your MainActivity is FormsApplicationActivity, you could refer to this example :
https://github.com/jessejiang0214/ChangeBackIconInXF/tree/master/Droid
If your MainActivity type is FormsAppCompatActivity, you could custom a PageRenderer and change the Toolbar's NavigationIcon.
For example :
[assembly: ExportRenderer(typeof(ContentPage), typeof(NavigationPageRendererDroid))]
...
public class NavigationPageRendererDroid : PageRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Page> e)
{
base.OnElementChanged(e);
var context = (Activity)Xamarin.Forms.Forms.Context;
var toolbar = context.FindViewById<Android.Support.V7.Widget.Toolbar>(Droid.Resource.Id.toolbar);
toolbar.NavigationIcon = Android.Support.V4.Content.ContextCompat.GetDrawable(context, Resource.Drawable.Back);
}
}
Usage :
MainPage = new NavigationPage(new MainPage());
...
//When click a button in MainPage, navigate to another page
private async void Button_Clicked(object sender, EventArgs e)
{
await Navigation.PushAsync(new TestPage());
}
Effect.
Update :
When you use Navigation.PushAsync() method navigate to another page, the system will automatically update the Toolbar's icon, you could find in the source code :
protected virtual Task<bool> OnPushAsync(Page view, bool animated)
{
return SwitchContentAsync(view, animated);
}
Task<bool> SwitchContentAsync(Page page, bool animated, bool removed = false, bool popToRoot = false)
{
...
UpdateToolbar();
...
}
void UpdateToolbar()
{
...
bool isNavigated = ((INavigationPageController)Element).StackDepth > 1;
if (isNavigated)
{
...
if (NavigationPage.GetHasBackButton(Element.CurrentPage))
{
//Set the navigation back icon < =================== !!! =-=
var icon = new DrawerArrowDrawable(activity.SupportActionBar.ThemedContext);
icon.Progress = 1;
bar.NavigationIcon = icon;
}
}
...
}
Solution :
So we have no choice but to custom a NavigationPageRenderer, override the OnPushAsync method to set the Toolbar's icon.
using AToolbar = Android.Support.V7.Widget.Toolbar;
[assembly: ExportRenderer(typeof(CustomNavigationPage), typeof(NavigationPageRendererDroid))] // APPCOMP
...
public class NavigationPageRendererDroid : Xamarin.Forms.Platform.Android.AppCompat.NavigationPageRenderer // APPCOMP
{
public AToolbar toolbar;
public Activity context;
protected override Task<bool> OnPushAsync(Page view, bool animated)
{
var retVal = base.OnPushAsync(view, animated);
context = (Activity)Xamarin.Forms.Forms.Context;
toolbar = context.FindViewById<Android.Support.V7.Widget.Toolbar>(Droid.Resource.Id.toolbar);
if (toolbar != null)
{
if (toolbar.NavigationIcon != null)
{
toolbar.NavigationIcon = Android.Support.V4.Content.ContextCompat.GetDrawable(context, Resource.Drawable.Back);
//toolbar.SetNavigationIcon(Resource.Drawable.Back);
}
}
return retVal;
}
}
The CustomNavigationPage are defined in PCL :
public class CustomNavigationPage : NavigationPage
{
public CustomNavigationPage(Page startupPage) : base(startupPage)
{
}
}
Usage :
public App()
{
InitializeComponent();
MainPage = new CustomNavigationPage(new MainPage());
}
...
// In MainPage
private async void Button_Clicked(object sender, EventArgs e)
{
await Navigation.PushAsync(new TestPage());
}
I solved this the next way:
In my MainActivity i am added static toolbar property and identified it in OnCreateOptionsMenu
public static Toolbar ToolBar { get; private set; }
public override bool OnCreateOptionsMenu(IMenu menu)
{
ToolBar = FindViewById<Toolbar>(Resource.Id.toolbar);
ToolBar.SetNavigationIcon(Resource.Drawable.arrow);
return base.OnCreateOptionsMenu(menu);
}
protected override void OnCreate(Bundle bundle)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
....
}
Then in PageRenderer:
protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
{
base.OnElementChanged(e);
MainActivity.ToolBar?.SetNavigationIcon(Resource.Drawable.arrow);
}
But!! From 2 ways i have bad effect with redrawing
Usage:
async void tapImage_Tapped(object sender, EventArgs e)
{
await Navigation.PushAsync(new ChooseGenrePage(_listGenres));
}

Xamarin.Forms TabbedPage event when current tab is tapped to refresh the page

I'm using Xamarin.Forms to build an iOS/Android app, and have a TabbedPage.
If a user is already on Tab 2, and Tab2 is clicked, and I want either the tab2 to refresh, or for a function of my own to run so I can refresh it myself.
Is there a way to do this within Xamarin.Forms or a way to do it with custom renderers?
Here is how I ended up solving the issue. My TabbedPage consisted of a NavigationPage for each tab, so if you are not using navigation pages your code will have to change a little bit but only slightly. You can put your "refresh" logic inside of OnTabbarControllerItemSelected for iOS and OnTabbarControllerItemSelected for Android. Check out the code below.
Android Renderer (courtesy of Mike Ma)
using Android.Support.Design.Widget;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Xamarin.Forms.Platform.Android.AppCompat;
[assembly: ExportRenderer(typeof(MainTabbedPage), typeof(MainPageRenderer))]
namespace YourNameSpace
{
public class MainPageRenderer : TabbedPageRenderer, TabLayout.IOnTabSelectedListener
{
private MainTabbedPage _page;
protected override void OnElementChanged(ElementChangedEventArgs<TabbedPage> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
_page = (MainTabbedPage)e.NewElement;
}
else
{
_page = (MainTabbedPage)e.OldElement;
}
}
async void TabLayout.IOnTabSelectedListener.OnTabReselected(TabLayout.Tab tab)
{
await _page.CurrentPage.Navigation.PopToRootAsync();
}
}
}
iOs Renderer:
using UIKit;
using System;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(MainTabbedPage), typeof(MainPageRenderer))]
namespace YourNameSpace
{
public class MainPageRenderer : TabbedRenderer
{
private MainTabbedPage _page;
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
_page = (MainTabbedPage)e.NewElement;
}
else
{
_page = (MainTabbedPage)e.OldElement;
}
try
{
var tabbarController = (UITabBarController)this.ViewController;
if (null != tabbarController)
{
tabbarController.ViewControllerSelected += OnTabbarControllerItemSelected;
}
}
catch (Exception exception)
{
Console.WriteLine(exception);
}
}
private async void OnTabbarControllerItemSelected(object sender, UITabBarSelectionEventArgs eventArgs)
{
if (_page?.CurrentPage?.Navigation != null && _page.CurrentPage.Navigation.NavigationStack.Count > 0)
{
await _page.CurrentPage.Navigation.PopToRootAsync();
}
}
}
}
Do some research of Android Native TabLayout the click event call back function is OnTabReselected
And according to the TabbedPageRenderer source code. You can find OnTabReselected is not be implemented.
So I created a customer render for TabbedPage as following code and implement the OnTabReselected function to change the current page background :
[assembly: ExportRenderer(typeof(Page1), typeof(MyTabPageRender))]
namespace XamarinTabbedPage_Demo.Droid
{
public class MyTabPageRender : TabbedPageRenderer, TabLayout.IOnTabSelectedListener
{
protected override void OnElementChanged(ElementChangedEventArgs<TabbedPage> e)
{
base.OnElementChanged(e);
}
void TabLayout.IOnTabSelectedListener.OnTabReselected(TabLayout.Tab tab)
{
int selectedIndex = tab.Position;
if (Element.Children.Count > selectedIndex && selectedIndex >= 0)
{
Element.CurrentPage = Element.Children[selectedIndex];
if (selectedIndex == 0)
{
Element.CurrentPage.BackgroundColor = Color.Black;
}
}
}
}
}

Changing keyboard's ImeOptions of Xamarin.Forms.Entry in custom renderer not working on Android

What I have:
I have a custom class MyEntry derived from Xamarin.Forms.Entry and custom renderer classes MyEntryRenderer for Android and iOS.
What I want:
I want to change the keyboard's "enter"-button to a "search"-button by changing ImeOptions on Android and ReturnKeyType on iOS (see sample code). When I press the altered "search"-button, the MyEntry.Completed event should be called (like before when I pressed the un-altered "enter"-button.
What really happens:
On iOS the code works like expected. But on Android nothing happens. The event doesn't get called.
My question:
How can I achieve what I described above on Android?
Sample code:
App.cs:
namespace CustomEntry
{
public class App
{
public static Page GetMainPage()
{
MyEntry entry = new MyEntry {
VerticalOptions = LayoutOptions.CenterAndExpand,
HorizontalOptions = LayoutOptions.CenterAndExpand,
Placeholder = "Enter some text"
};
entry.Completed += delegate {
Console.WriteLine("Completed");
};
return new ContentPage {
Content = entry,
};
}
}
}
MyEntry.cs:
namespace CustomEntry
{
public class MyEntry:Entry
{
}
}
MyEntryRenderer.cs (Android):
[assembly: ExportRenderer(typeof(MyEntry), typeof(MyEntryRenderer))]
namespace CustomEntry.Android
{
public class MyEntryRenderer:EntryRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (Control != null) {
Control.ImeOptions = global::Android.Views.InputMethods.ImeAction.Search;
}
}
}
}
MyEntryRenderer.cs (iOS):
[assembly: ExportRenderer(typeof(MyEntry), typeof(MyEntryRenderer))]
namespace CustomEntry.iOS
{
public class MyEntryRenderer:EntryRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (Control != null) {
Control.ReturnKeyType = UIReturnKeyType.Search;
}
}
}
}
I found a workaround for my problem myself:
First I added an Action to my custom entry to be called when I press my "search"-button.
MyEntry.cs
namespace CustomEntry
{
public class MyEntry:Entry
{
public Action SearchPressed = delegate {
};
}
}
Second I "listen" for ImeAction.Search like this and call the Action I added to my custom entry.
MyEntryRenderer.cs (Android):
[assembly: ExportRenderer(typeof(MyEntry), typeof(MyEntryRenderer))]
namespace CustomEntry.Android
{
public class MyEntryRenderer:EntryRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (Control != null) {
Control.ImeOptions = ImeAction.Search;
Control.EditorAction += (sender, args) => {
if (args.ActionId == ImeAction.Search) {
var entry = (AddressEntry)Element;
entry.SearchPressed();
}
};
}
}
}
}
In a third class where I use MyEntry I can run some code when the "search"-button is pressed like this:
var myEntry = new MyEntry();
myEntry.SearchPressed += SomeMethod;
public class EntryExtensionRenderer : EntryRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs e)
{
base.OnElementChanged(e);
if ((Element as EntryExtension).NoSuggestionsKey)
{
Control.AutocorrectionType = UITextAutocorrectionType.No;
}
if ((Element as EntryExtension).ReturnKeyType.Equals("Done"))
{
this.AddDoneButton("Done", (EntryExtension)Element);
}
else if ((Element as EntryExtension).ReturnKeyType.Equals("Next"))
{
this.AddDoneButton("Next", (EntryExtension)Element);
}
}
protected void AddDoneButton(string button, EntryExtension entry)
{
UIToolbar toolbar = new UIToolbar(new RectangleF(0.0f, 0.0f, 50.0f, 44.0f));
var doneButton = new UIBarButtonItem();
if (button.Equals("Done")) {
doneButton = new UIBarButtonItem(UIBarButtonSystemItem.Done, delegate
{
entry.KeyPressedEnter();
});
}
if (button.Equals("Next"))
{
doneButton = new UIBarButtonItem("Next", UIBarButtonItemStyle.Done, delegate
{
entry.KeyPressedEnter();
});
}
toolbar.Items = new UIBarButtonItem[] {
new UIBarButtonItem (UIBarButtonSystemItem.FlexibleSpace),
doneButton
};
this.Control.InputAccessoryView = toolbar;
}
}

Categories

Resources