Thursday, June 23, 2016

Soft keyboard hiding entry fields in Android: A quick fix.

Soft keyboard hiding Entry fields in Android: A quick fix.

One of the issues i faced with Xamarin Forms in Android was that, when i had a page with multiple entries, and one of them gets focused, the page doesn't resize, the soft keyboard hides the entries and you are not able to scroll to keep filling the form.

Luckily! I found a fix.

Follow these steps:

1. Add this class to your MainActivity.cs

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;
    }
}


2. Add this line to MainActivity OnCreate() method after Xamarin.Forms Init:

global::Xamarin.Forms.Forms.Init (this, savedInstanceState);

AndroidBug5497WorkaroundForXamarinAndroid.assistActivity(this);

3. The next step involves wrapping the content of the page you want to be resizable with ScrollView

<ScrollView>
    <ContentPage.Content>
    </ContentPage.Content>
</ScrollView>


4. With this pre-conditions in place, you are ready to do the trick.

First, create custom entry renderers for the pages that you want to be resized/scrollable when soft keyboard is present.

Let's say i have a Login page and i want it to resize when username field gets focused, so i can scroll to see the password field and the login button (like iOS behavior).

Create a custom entry like this:

public class LoginEntry : Entry
{
}


Then implement this custom control on each platform. The control won't do anything specific in iOS, but it will do a trick in Android to invoke the platform specific window resize when any field gets focused:

[assembly: ExportRenderer(typeof(LoginEntry), typeof(LoginEntryRenderer))]
namespace YourNamespace
{
    public class LoginEntryRenderer : EntryRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
        {
            base.OnElementChanged(e);

            if (e.NewElement == null) return;

            // This line do the trick
            Control.FocusChange += Control_FocusChange;
        }

        void Control_FocusChange(object sender, FocusChangeEventArgs e)
        {
            if (e.HasFocus)
                (Forms.Context as Activity).Window.SetSoftInputMode(SoftInput.AdjustResize);

            else
                (Forms.Context as Activity).Window.SetSoftInputMode(SoftInput.AdjustNothing);
  
        }
    }
}


As you noticed, i am attaching a delegate event when the control focus change.

If the control HasFocus, i add AdjustResize flag, which combined with the class we added to MainActivity in Step 1, will do the trick. Also, when control lost focus i add AdjustNothing flag to return things to normal.

Code, Eat, Sleep!