6

I'm developing a phonegap application with version 2.9.0;

The layout was fully tested in desktop browser using RWD Bookmarklet(http://responsive.victorcoulon.fr/) and worked fine. However, when tested in mobile devices or the emulator, the layout broke. After a little bit testing, I found out that the problem was the status bar height. Changed the application to fullscreen, problem solved.

But now, when i focus on an input field, the screen is not being adjusted, so, the keyboard covers the input field!

After looking all the questions and related problems, I found this one, that makes sense to me, but i wanted to know if there is a way to make the adjust pan work with fullscreen, so i don't need to adjust all my components height, calculate different status bar heights based on devices, etc.

Codes

form.html

<form id="login-form">
    <div class="form-group">
       <input type="text" name="login" class="form-control" id="login"
                        placeholder="xxxxxxx@example.com">
    </div>
    <div class="form-group">
      <input type="password" name="pass" class="form-control"
                        id="password" placeholder="*******">
    </div>
    <a class="pull-right login-btn" id="btn-login" href="#"><span
                    class="image-replacement"></span></a> 
    <a class="pull-right login-btn" id="btn-cadastro" href="#"><span class="image-replacement"></span></a>
</form>

Android Manifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:windowSoftInputMode="adjustPan"
      package="com.com.app" android:versionName="1.0" android:versionCode="1" android:hardwareAccelerated="true">
    <supports-screens
        android:largeScreens="true"
        android:normalScreens="true"
        android:smallScreens="true"
        android:xlargeScreens="true"
        android:resizeable="true"
        android:anyDensity="true"
        />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.RECORD_VIDEO"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />   
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />   
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.BROADCAST_STICKY" />


<application android:icon="@drawable/icon" android:label="@string/app_name"
    android:hardwareAccelerated="true"
    android:debuggable="true">
    <activity android:name="App" android:label="@string/app_name"
            android:theme="@android:style/Theme.Black.NoTitleBar"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale"
            android:windowSoftInputMode="stateVisible|adjustPan">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
</application>

<uses-sdk android:minSdkVersion="7" android:targetSdkVersion="17"/>
</manifest> 

App.java

package com.com.app;

import org.apache.cordova.Config;
import org.apache.cordova.DroidGap;

import android.os.Bundle;
import android.view.WindowManager;

public class BDH extends DroidGap
{
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        // Set by <content src="index.html" /> in config.xml

        getWindow().setFlags(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN, WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST);

        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);

        super.loadUrl(Config.getStartUrl());
        //super.loadUrl("file:///android_asset/www/index.html")
    }
}
Community
  • 1
  • 1
Alexandre Wiechers Vaz
  • 1,587
  • 2
  • 18
  • 21
  • 1
    check this: http://stackoverflow.com/questions/19849462/phonegap-android-how-to-adjust-layout-in-full-screen-mode-when-softkeyboard-is – Jorge Marmolejo Nov 08 '13 at 00:34

3 Answers3

2

UPDATE

Following up on my original answer, the below JavaScript will work on windowSoftInputMode="adjustResize", however, it will not work on "adjustPan" because the JavaScript resize() event will not be fired when the keyboard is shown.

However, as mentioned here, you can capture the keyboard shown event on the Java side by hooking a GlobalLayoutListener into the ViewTreeObserver:

package com.com.app;

import org.apache.cordova.Config;
import org.apache.cordova.DroidGap;

import android.os.Bundle;
import android.view.WindowManager;

public class BDH extends DroidGap
{
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        // Set by <content src="index.html" /> in config.xml

        getWindow().setFlags(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN, WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST);

        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);

        super.loadUrl(Config.getStartUrl());
        //super.loadUrl("file:///android_asset/www/index.html")

        final View activityRootView = ((ViewGroup) findViewById(android.R.id.content)).getChildAt(0);

        activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() 
            {
                // r will be populated with the coordinates of your view that area still visible.
                Rect r = new Rect();

                activityRootView.getWindowVisibleDisplayFrame(r);

                int heightDiff = activityRootView.getRootView().getHeight() - (r.bottom - r.top);

                // If more than 100 pixels, its probably a keyboard...
                if (heightDiff > 100) 
                {
                                    // Fire off a function to the JavaScript.
                    this.sendJavascript("try { onKeyboardShowing(); } catch (e) {};");
                }
            }
        });
    }
}

So when the keyboard is shown, you can fire a function to the JavaScript, indicating the keyboard is being shown, then you can adjust the screen:

var fieldFocused = null;

function onKeyboardShowing()
{
    if(fieldFocused != null)
    {
        $('body').scrollTo(fieldFocused, 500, {offset:-50});
    }
}

$(document).on('focus', 'input, textarea', function() {
    fieldFocused = $(this);
});

$(document).on('blur', 'input, textarea', function() {
    fieldFocused = null;
});

ORIGINAL ANSWER

We had a horrible time trying to fix the Android soft keyboard covering the input fields. The solution I came up with is by no means 'nice', however it worked...

You will need jQuery and also another jQuery plugin called jQuery.ScrollTo by Ariel Flesher, found here.

Now add this to the JavaScript:

var fieldFocused = null;

$(window).resize(function(e) {
    if(fieldFocused != null)
    {
        $('body').scrollTo(fieldFocused, 500, {offset:-50});
    }
});

$(document).on('focus', 'input, textarea', function() {
    fieldFocused = $(this);
});

$(document).on('blur', 'input, textarea', function() {
    fieldFocused = null;
});

When a textarea/input is brought into focus, the DOM element is assigned to a variable. When the window is resized, the window is scrolled to bring the DOM element to the top.

We have used android:windowSoftInputMode="adjustResize" in the Android manifest.

As I have said this is not the most elegant of fixes, but we have implemented into our PhoneGap app and it works.

Community
  • 1
  • 1
kieranroneill
  • 849
  • 7
  • 9
  • So sad that phonegap doesn't provide a fix for this bug :/ Thanks for the answer. First time i tried to use android:windowSoftInputMode="adjustResize" with fullscreen the app crashed, but I'm going to try it and tell if it works – Alexandre Wiechers Vaz Sep 04 '13 at 20:22
  • Unfortunately, this didn't worked for me, because android doesn't enable screen resize on full screen apps :/ – Alexandre Wiechers Vaz Sep 06 '13 at 18:30
  • 1
    If you must use `android:windowSoftInputMode="adjustPan"` my only suggestion is that you follow the advice in this [post](http://stackoverflow.com/a/9108219/1287405). Then combine it with my answer so that when you detect the keyboard being shown call the code snippet nested in the `resize()` event. I will update my answer with this suggestion. – kieranroneill Sep 09 '13 at 09:01
  • Thank you very much for the update. Didn't solve my problem, although it helped me to find a solution – Alexandre Wiechers Vaz Sep 11 '13 at 21:33
  • confused, which file should I add the java stuff? – Jamie Hutber Oct 13 '13 at 20:46
  • @Jamie, the Java part goes in the `Activity`'s `onCreate` method; `DroidGap` is a subclass of the `Activity` class. – kieranroneill Oct 14 '13 at 09:36
  • Hi is it possible that make the scroll to stick on the top of the keyboard? – user998405 Jun 10 '14 at 19:31
2

Although it may be not the better way to fix it, i've found a solution. Detect the events and communicate to JS was not working for me, neither with window.scrollTo nor with the jQuery plugin. Unfortunately, my time is short and i preferred to do it in Java directly. As far as i have time, i'll refactor it and develop a plugin based on this solution. As the code gets updated, i'll update it here too. Here it goes:

    /**
     * 
     *  Due to a well known bug on Phonegap¹, android softKeyboard adjustPan functionality wasn't working
     *  as expected when an input field recieved focus. The common workaround(Change to adjustResize and),
     *  however, was not applicable, due to an Android bug² that crashes fullscreen apps when in adjustResize mode.
     *  This is an workaround, to detect when the softKeyboard is activated and then programatically scroll 
     *  whenever it needs;
     *
     *  During the development proccess i came across an annoying behavior on android, that were making the
     *  input field dispatch onFocusChange twice when focus was cleared, when it should dispatch only once.
     *  The first one, without focus(Expected behavior), the second one WITH focus(Dafuq?), causing it to
     *  not scroll back on blur. My workaround was to only enable it to set a flag(lostFocus parameter), and
     *  only allow the method to calculate the scroll size IF the element had not lost it's focus;
     * 
     *  ¹ - http://stackoverflow.com/questions/11968420/softkeyboard-in-phonegap-covers-input-elements
     *  ² - http://stackoverflow.com/questions/7417123/android-how-to-adjust-layout-in-full-screen-mode-when-softkeyboard-is-visible
     **/

    final View activityRootView = ((ViewGroup) findViewById(android.R.id.content)).getChildAt(0);

    activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout(){

            View focused = appView.findFocus();
            activityRootView.getWindowVisibleDisplayFrame(r);

            if(focused instanceof TextView){

                if(focused.getOnFocusChangeListener() == null){
                    focused.setOnFocusChangeListener(new OnFocusChangeListener() {

                        @Override
                        public void onFocusChange(View v, boolean hasFocus) {
                            if(!hasFocus){
                                activityRootView.scrollTo(0,0);
                                lostFocus = true;
                                showKeyBoard = false;
                            }else{
                                showKeyBoard = true;
                            }
                        }

                    });
                }

                /**
                 * 
                 *  Really tricky one to find, that was the only way i found to detect when this listener call came from
                 *  the buggy input focus gain. If the element had lost its focus, r(A Rect representing the screen visible area)
                 *  would be the total height, what means that there would be no keyboard to be shown, as far as the screen
                 *  was completely visible.
                 * 
                 **/
                if(showKeyBoard || r.top != activityRootView.getHeight()){

                    int heightDiff = 0;
                    int keyBoardSize = 0;
                    int scrollTo = 0;


                    heightDiff = activityRootView.getRootView().getHeight() - focused.getTop();
                    keyBoardSize = activityRootView.getRootView().getHeight() - r.bottom;

                    if((keyBoardSize < focused.getBottom() && keyBoardSize > 0) && !lostFocus){
                        scrollTo = focused.getBottom() - keyBoardSize;
                    }


                    if(scrollTo == 0){
                        activityRootView.scrollTo(0,scrollTo);
                        lostFocus = false;
                        showKeyBoard = true;
                    }else if(heightDiff < r.bottom){
                        activityRootView.scrollTo(0, scrollTo);
                        lostFocus = false;
                        showKeyBoard = false;
                    }
                }
            }
        }
    });

Elaboration on r, lostFocus and showKeyboard

r is a Rect object, that gets filled by the method getWindowVisibleDisplayFrame(Rect r)

From the Docs:

Retrieve the overall visible display size in which the window this view is attached to has been positioned in. This takes into account screen decorations above the window, for both cases where the window itself is being position inside of them or the window is being placed under then and covered insets are used for the window to position its content inside. In effect, this tells you the available area where content can be placed and remain visible to users.

So, if the keyboard is shown, r.bottom would be different from the rootView height.

showKeyboard and lostFocus are two workarounds to get reliably the correct focus/blur behavior. showKeyboard is simple, only a flag to tell the application if it should or should not scroll. Theoretically, it'd work, however, i came across an annoying bug, that caused the input field to be focused immediately after his lost of focus, before the soft keyboard hide (Only internally in the application, on device, the element didn't gain focus and the keyboard was already hidden). To solve it, i've used lostFocus to tell the application when it really has lost focus and only allow it to calculate where to scroll if the element hadn't lost its focus.

Alexandre Wiechers Vaz
  • 1,587
  • 2
  • 18
  • 21
  • Hi, can you elaborate more on what is "lostFocus", "showKeyBoard" and "r" are? thanks – Arief Sep 25 '13 at 10:45
  • Sure! I've updated the answer with some detailed explanation on these points. – Alexandre Wiechers Vaz Sep 26 '13 at 16:37
  • That's work fine for me on ANDROID version 2.3 , 4.04... BUT.... on Android 4.1.x on a Samsung Galaxy S3 or others THAT'S NOT WORK!!!!!!! Anybody knows why? (sorry for my bad english) I need help! thanks! – rocanroldani Nov 05 '13 at 17:00
  • @gazapko thanks for your response! I verified that about a week ago and unfortunately, I still have no workaround. It seems that some change in the API changes the element that get focused... However, I'm still looking for this workaround and I hope that I'll be able to find something soon – Alexandre Wiechers Vaz Nov 05 '13 at 19:48
0

just posted it the solution at: http://www.stackoverflow.com/questions/19849462/

Jorge Marmolejo
  • 574
  • 6
  • 17