2

I have an activity with bunch of controls (EditText, Spinner etc.), with one of the EditText's having a custom keyboard. Here is how my XML looks like

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/masterRelativeLayout"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <ScrollView
        android:id="@+id/mainScrollview"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:fillViewport="true" >

        <LinearLayout
            android:id="@+id/childLinearLayout"
            android:layout_width="match_parent"
            android:orientation="vertical"
            android:layout_height="wrap_content">

            <!-- Many EditText's, Spinners here -->

        </LinearLayout>
    </ScrollView>

    <android.inputmethodservice.KeyboardView
        android:id="@+id/myKeyboardView"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:focusable="true"
        android:focusableInTouchMode="true"
        android:keyPreviewLayout ="@layout/kbdpreview"
        android:layout_alignParentBottom="true"
        android:visibility="gone" />
</RelativeLayout>

Notice that the keyboard view is bottom-aligned, so that it shows up at the bottom of the screen.

The custom keyboard gets hidden and shown on the specific EditText as follows:

These methods are called when a specific EditText in the activity is touched.

private OnTouchListener m_onTouchListenerNotationText = new OnTouchListener()
{
    @Override
    public boolean onTouch(View v, MotionEvent event)
    {
        if (v == m_notationText)
        {
            // Hide the default keyboard
            getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
            m_notationText.onTouchEvent(event);
            m_customKeyboard.showCustomKeyboard(v);
            AdjustScrollView();
            return true;    // Done with the event
        }
        return false;
    }
};

Whenever the custom keyboard appears on the screen, it hides some of the controls in my view. However, Android framework does not know that this keyboard is being shown so the scrollview does not adjust. The controls remain hidden behind the custom keyboard. Hence, I have implemented a callback in the custom keyboard which gets called when the keyboard is visible.

// This listener is in showCustomKeyboard(...) function
// Set a static variable m_height and tell the activity to adjust the scrollview after keyboard becomes VISIBLE
mKeyboardView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener()
{
    @SuppressLint("NewApi")
    @SuppressWarnings("deprecation")
    @Override
    public void onGlobalLayout()
    {
        int currentapiVersion = android.os.Build.VERSION.SDK_INT;
        if (currentapiVersion >= android.os.Build.VERSION_CODES.JELLY_BEAN)
        {
            mKeyboardView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
        }
        else
        {
            mKeyboardView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
        }
        m_height = mKeyboardView.getHeight();
        Log.d(TAG, "Custom Keyboard now visible, height = " + m_height);
        mHostActivity.AdjustScrollView();
    }
});

// This listener is in hideCustomKeyboard(...) function
// Set a static variable m_height and tell the activity to adjust the scrollview after keyboard becomes HIDDEN
view.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener()
{
    @SuppressLint("NewApi")
    @SuppressWarnings("deprecation")
    @Override
    public void onGlobalLayout()
    {
        int currentapiVersion = android.os.Build.VERSION.SDK_INT;
        if (currentapiVersion >= android.os.Build.VERSION_CODES.JELLY_BEAN)
        {
            view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
        }
        else
        {
            view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
        }
        m_height = 0;
        Log.d(TAG, "Custom Keyboard now hidden");
        mHostActivity.AdjustScrollView();
    }
});

Finally, the scrollview adjustment happens as follows:

public synchronized void AdjustScrollView()
{
    RelativeLayout rl = (RelativeLayout) findViewById(R.id.masterRelativeLayout);
    int masterHeight = rl.getHeight();
    Log.d(TAG, "Scrollview master relative layout height = " + masterHeight);

    LinearLayout ll = (LinearLayout) findViewById(R.id.childLinearLayout);
    int childHeight = ll.getHeight();
    Log.d(TAG, "Scrollview child linear layout height = " + childHeight);

    ScrollView scrollView = (ScrollView) findViewById(R.id.mainScrollview);
    if (masterHeight - childHeight - m_customKeyboard.m_height <= 0)
    {
        // Need to adjust scrollview's height
        scrollView.getLayoutParams().height = masterHeight - m_customKeyboard.m_height;
        Log.d(TAG, "Setting scrollview.layoutparams.height = " + (masterHeight - m_customKeyboard.m_height));
    }
    else if (m_customKeyboard.m_height == 0)
    {
        // Need to adjust scrollview's height to full screen
        scrollView.getLayoutParams().height = childHeight;
        Log.d(TAG, "Setting scrollview.layoutparams.height = " + childHeight);
    }
}

AndroidManifest.xml has the following line for this activity:

android:windowSoftInputMode="stateAlwaysHidden|stateUnchanged|adjustResize"

This mostly works, with 2 problems:

Problem 1: When I press long press on the EditText which shows custom keyboard, sometimes there is a race condition whereby the default keyboard AND custom keyboard both show up momentarily, then the default keyboard goes away (because I am hiding it in m_onTouchListenerNotationText). But in this time, the calculation of AdjustScrollView gets messed up, because now, the master relative layout height is very small (total height - custom keyboard height - default keyboard height). So the scrollview height calculation above is wrong. The scrollview now gets confined to a very tiny area at the top of the screen, followed by white space, followed by my custom keyboard at the bottom. I worked around this problem by adding call to AdjustScrollView in touch listener for the EditText (so whenever this happens, the user can click on that tiny view and the touch listener will adjust the scrollview). This work-around is undesirable, because, then it makes the long-press useless (I want the user to be able to long-press to show the default system menu of cut/copy/paste etc.). The work-around of requiring an additional touch makes this system menu go away.

After long press in the EditText - Problem 1

Problem 2: If the custom keyboard is hidden, The scrollview does not take the entire screen size again. The bottom part of the screen, which was occupied by the custom keyboard, remains blank and the scrollview is only in the top part of the screen.

Problem 2

Abhijit
  • 89
  • 12

1 Answers1

0

The only solution I found is to use keyboard hide-show detection like in this example:

https://stackoverflow.com/a/7423586/1979882

The concept is to overwrite the background Layout to your custom and measure.

public class VLinearLayoutKeyboardListener extends LinearLayout {
     public interface IKeyboardChanged {
            void onKeyboardShown(int actualHeight, int proposedheight);
            void onKeyboardHidden(int actualHeight, int proposedheight);
        }
     private ArrayList<IKeyboardChanged> keyboardListener = new ArrayList<IKeyboardChanged>();

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public VLinearLayoutKeyboardListener(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public VLinearLayoutKeyboardListener(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public VLinearLayoutKeyboardListener(Context context) {
        super(context);
    }
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public VLinearLayoutKeyboardListener(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        // TODO Auto-generated constructor stub
    }

     public void addKeyboardStateChangedListener(IKeyboardChanged listener) {
            keyboardListener.add(listener);
        }

        public void removeKeyboardStateChangedListener(IKeyboardChanged listener) {
            keyboardListener.remove(listener);
        }
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            final int proposedheight = MeasureSpec.getSize(heightMeasureSpec);
            final int actualHeight = getHeight();

            if (actualHeight > proposedheight) {
                notifyKeyboardShown(actualHeight, proposedheight);
            } else if (actualHeight < proposedheight) {
                notifyKeyboardHidden(actualHeight, proposedheight);
            }
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }

        private void notifyKeyboardHidden(int actualHeight, int proposedheight) {
            for (IKeyboardChanged listener : keyboardListener) {
                listener.onKeyboardHidden(actualHeight, proposedheight);
            }
        }

        private void notifyKeyboardShown(int actualHeight, int proposedheight) {
            for (IKeyboardChanged listener : keyboardListener) {
                listener.onKeyboardShown(actualHeight, proposedheight);
            }
        }

}

in an Activity

    public class mAct extends Activity implements  IKeyboardChanged {
    private VLinearLayoutKeyboardListener vllkl;
    onCreate(){
    getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
    }

    @Override
          public void onDestroy() {
            super.onDestroy();
            vllkl.removeKeyboardStateChangedListener(this);

         }

    @Override
        public void onKeyboardShown(int actualHeight, int proposedheight) {
//actualHeight - old value
//proposedheight - new value
            Log.d(TAG,"onKeyboardShown(): [" + actualHeight + ", " + proposedheight + "]");
//here you can setup Views heights
        }
    @Override
        public void onKeyboardHidden(int actualHeight, int proposedheight) {
//actualHeight - old value
//proposedheight - new value
            Log.d(TAG,"onKeyboardHidden(): [" + actualHeight + ", " + proposedheight + "]");
//here you can setup Views heights
        }
    }

in XML

<?xml version="1.0" encoding="utf-8"?>
<your.package.name.VLinearLayoutKeyboardListener xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/black"
        android:orientation="vertical"
        android:id="@+id/root">
....
</your.package.name.VLinearLayoutKeyboardListener >
Community
  • 1
  • 1
Vyacheslav
  • 26,359
  • 19
  • 112
  • 194