Search code examples
androidsoft-keyboard

Move layout up when soft keyboard is shown


I'm trying to adjust the layout when the soft keyboard appears after an edit text gets focus. Right now if I have many edit text and the keyboard appears, the last edit text are hidden and I can't scroll up.

This is how my layout is builded up:

Template:

<LinearLayout>
    <LinearLayout>
        // header 1
    </LinearLayout>
    <LinearLayout>
        // header 1
    </LinearLayout>
    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="0dip"
        android:layout_weight="1"
        android:orientation="vertical">
        // where I inflate view_1
    </LinearLayout>
    <LinearLayout>
        // footer
    </LinearLayout>
</LinearLayout>

View (view_1):

<ScrollView
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:focusable="true"
        android:focusableInTouchMode="true">
        <LinearLayout>
            // ...
        </LinearLayout>
        <LinearLayout>
            // ...
        </LinearLayout>
        <LinearLayout>
            <TextView/>
            <EditText/>
            <TextView/>
            <EditText/>
            <TextView/>
            <EditText/>
            <TextView/>
            <EditText/>
        </LinearLayout>
    </LinearLayout>
</ScrollView>

I already try all kinds of combinations of android:windowSoftInputMode (on manifest.xml and programmatically). I tried to set android:isScrollContainer="false" on the scroll view, but nothing.

I also tried this answer, putting an GlobalLayoutListener in my scroll view, but the onGlobalLayout is not called when the keyboard appears. And the isKeyboardShown is always false.


Solution

  • I ended up doing it my way.

    I created a class that implements OnFocusChangeListener to handle all my EditText:

    public class EditTextFocusChangeListener implements OnFocusChangeListener {
    
        private ScrollView scrollView;
    
        public EditTextFocusChangeListener(ScrollView scrollView) {
            this.scrollView = scrollView;
        }
    
        @Override
        public void onFocusChange(View view, boolean hasFocus) {
            if(hasFocus) {
                int left = view.getLeft();
                int top = view.getTop();
                int bottom = view.getBottom();
                int keyboardHeight = scrollView.getHeight() / 3;
    
                // if the bottom of edit text is greater than scroll view height divide by 3,
                // it means that the keyboard is visible
                if (bottom > keyboardHeight)  {
                    // increase scroll view with padding
                    scrollView.setPadding(0, 0, 0, keyboardHeight);
                    // scroll to the edit text position
                    scrollView.scrollTo(left, top);
                }
            }
        }
    }
    

    Then in the activity, I setted the listener for each edit text:

    EditTextFocusChangeListener listener = new EditTextFocusChangeListener(mainScrollView);
    
    editText1 = (EditText) findViewById(R.id.editText1);
    editText1.setOnFocusChangeListener(listener);
    
    editText2 = (EditText) findViewById(R.id.editText2);
    editText2.setOnFocusChangeListener(listener);
    
    ...
    
    editTextN = (EditText) findViewById(R.id.editTextN);
    editTextN.setOnFocusChangeListener(listener); 
    

    And for the last edit text, I setted an EditorAction listerner to handle the 'Done' button on soft keyboard - to hide the keyboard and put the scroll view back to its original position:

    editTextN.setOnEditorActionListener(new OnEditorActionListener() {
            @Override
            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
                int result = actionId & EditorInfo.IME_MASK_ACTION;
                switch(result) {
                    // user taped on keyboard DONE button
                    case EditorInfo.IME_ACTION_DONE:
                        // put the scroll view back to its original position
                        mainScrollView.setPadding(0, 0, 0, 0);
                        // hide keyboard
                        ((InputMethodManager) getApplicationContext().getSystemService(Context.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(v.getWindowToken(), 0);
                        // remove focus from any edit text
                        LinearLayout scrollViewLL = (LinearLayout) mainScrollView.getChildAt(0);
                        scrollViewLL.requestFocus();
                    break;
                }
                return false;
            }
        });
    

    And finally, a way to handle when the user touches outside an edit text to hide the keyboard and put the scroll view back to its original position (found this on web and changed a little to fit my needs):

    public void setupUI(View view) {
        // Set up touch listener for non-text box views to hide keyboard.
        if (!(view instanceof EditText)) {
            view.setOnTouchListener(new OnTouchListener() {
                public boolean onTouch(View v, MotionEvent event) { 
    
                    // put the scroll view back to its original position
                    if (v instanceof ScrollView) {
                        v.setPadding(0, 0, 0, 0);
                        LinearLayout scrollViewLL = (LinearLayout) ((ScrollView) v).getChildAt(0);
                        scrollViewLL.requestFocus();
                    }
    
                    hideKeyboard();
                    return false;
                }
            });
        }
    
        // If a layout container, iterate over children and seed recursion.
        if (view instanceof ViewGroup) {
            for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
                View innerView = ((ViewGroup) view).getChildAt(i);
                setupUI(innerView);
            }
        }
    }