Accommodating The On Screen Keyboard in Xamarin Forms

Most, if not all apps, will require an entry field for user input. When you are on a mobile device, selecting an entry field will cause the onscreen keyboard to appear and depending upon the platform and app settings it can cause different things to happen to your layout.

androidlandscape

Xamarin Forms Page

As a starting point, here is a simple Xamarin Forms page that creates 3 sections each with an entry field. A sample project SoftInput is available if you want to see the completed code.

<?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:SoftInput"
             x:Class="SoftInput.MainPage">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="*" />
            <RowDefinition Height="50" />
        </Grid.RowDefinitions>
        <StackLayout Grid.Row="0" BackgroundColor="#1E73BE">
            <Entry />
        </StackLayout>
        <StackLayout Grid.Row="1" BackgroundColor="#7030A0">
            <Entry />
        </StackLayout>
        <StackLayout Grid.Row="2" BackgroundColor="#00B050">
            <Entry />
        </StackLayout>
    </Grid>
</ContentPage>

Android

Android will commonly refer to the onscreen keyboard as SoftInput. By default, with Xamarin Forms, it will use AdjustPan, which will move the entire screen upwards to make room for the keyboard. This has the unfortunate side effect of making the top part of the screen unreachable when the keyboard is showing.

androiddefault

If you want the screen to resize you need to implement two pieces of code. First, in the MainActivity set the SoftInputMode to AdjustResize.

Window.SetSoftInputMode(Android.Views.SoftInput.AdjustResize);

Next, due to a bug in Android that they won’t fix, we need to implement a workaround.

public class AndroidBug5497WorkaroundForXamarinAndroid
{

    // For more information, see https://code.google.com/p/android/issues/detail?id=5497
    // To use this class, simply invoke assistActivity() on an Activity that already has its content view set.

    // CREDIT TO Joseph Johnson (http://stackoverflow.com/users/341631/joseph-johnson) for publishing the original Android solution on stackoverflow.com

    public static void assistActivity(Activity activity)
    {
        new AndroidBug5497WorkaroundForXamarinAndroid(activity);
    }

    private Android.Views.View mChildOfContent;
    private int usableHeightPrevious;
    private FrameLayout.LayoutParams frameLayoutParams;

    private AndroidBug5497WorkaroundForXamarinAndroid(Activity activity)
    {
        FrameLayout content = (FrameLayout)activity.FindViewById(Android.Resource.Id.Content);
        mChildOfContent = content.GetChildAt(0);
        ViewTreeObserver vto = mChildOfContent.ViewTreeObserver;
        vto.GlobalLayout += (object sender, EventArgs e) => {
                             possiblyResizeChildOfContent();
                             };
        frameLayoutParams = (FrameLayout.LayoutParams)mChildOfContent.LayoutParameters;
    }

    private void possiblyResizeChildOfContent()
    {
        int usableHeightNow = computeUsableHeight();
        if (usableHeightNow != usableHeightPrevious)
        {
            int usableHeightSansKeyboard = mChildOfContent.RootView.Height;
            int heightDifference = usableHeightSansKeyboard - usableHeightNow;

            frameLayoutParams.Height = usableHeightSansKeyboard - heightDifference;

            mChildOfContent.RequestLayout();
            usableHeightPrevious = usableHeightNow;
        }
    }

    private int computeUsableHeight()
    {
        Rect r = new Rect();
        mChildOfContent.GetWindowVisibleDisplayFrame(r);
        if (Build.VERSION.SdkInt < BuildVersionCodes.Lollipop)
        {
            return (r.Bottom - r.Top);
        }
        return r.Bottom;
    }
}

Then add this just below the SetSoftInputMode line.

AndroidBug5497WorkaroundForXamarinAndroid.assistActivity(this);

Landscape

When in landscape however, Android goes to FullScreen Mode as seen below.

androidlandscape

You can disable this by implementing a custom renderer for your Entry fields to set the ImeOptions flag to NoExtractUi.

[assembly: ExportRenderer(typeof(Entry), typeof(EntryRender))]
namespace SoftInput.Droid.Render
{
    using Android.Views.InputMethods;
    using Xamarin.Forms.Platform.Android;

    public class EntryRender : EntryRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
        {
            base.OnElementChanged(e);

            if (Control != null)
                Control.ImeOptions = (ImeAction)ImeFlags.NoExtractUi;
        }
    }
}

By implementing this, you will get something similar to the experience in Portrait.

iOS

The default iOS experience is one of the better experiences with the keyboard. By default it will AdjustPan upwards but you can scroll through all the content. However to get this scroll you must implement the ScrollView around the scrollable content. This has no effect on Android.

<ScrollView>
   // Existing controls in here
</ScrollView>

iosdefault

You can place controls outsize of the ScrollView to ensure they stay in view.

UWP

UWP refers to the Soft Keyboard as an Input Pane. By default UWP does an AdjustPan, the same as Android, with no scroll capability.

uwpdefault

In order to do a resize, I put together this code in your MainPage.cs

public MainPage()
{
    this.InitializeComponent();

    LoadApplication(new SoftInput.App());
    _originalHeight = ApplicationView.GetForCurrentView().VisibleBounds.Height;

    InputPane.GetForCurrentView().Showing += MainPage_Showing;
    InputPane.GetForCurrentView().Hiding += MainPage_Hiding;
}

private double _originalHeight;
private void MainPage_Hiding(InputPane sender, InputPaneVisibilityEventArgs args)
{
    this.Height = _originalHeight;
    this.VerticalAlignment = Windows.UI.Xaml.VerticalAlignment.Stretch;
}

private void MainPage_Showing(InputPane sender, InputPaneVisibilityEventArgs args)
{
    this.Height = _originalHeight - args.OccludedRect.Height;
    this.VerticalAlignment = Windows.UI.Xaml.VerticalAlignment.Top;
}

This results in this effect, but I must warn you it would need enhancing further to support rotation.

uwpresize

If anyone else has any tips and tricks for the on screen keyboard on any platform, please let me know in the comments below.

Microsoft MVP (Xamarin) | Exrin | Xamarin Forms Developer | Melbourne, Australia | Open to sponsorship to Canada or US

Related Posts

Leave A Comment?