Search code examples
androidandroid-layoutlistviewwindow-soft-input-modeadjustpan

"Push up" a listview but not the bar when soft input is shown


I have a layout like this in a page of a View Pager:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/black">
    <LinearLayout
        android:id="@+id/bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="24dp"
        android:layout_alignParentTop="true"
        android:orientation="horizontal"
        android:gravity="center"
        android:background="@drawable/background_gray">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="16sp"
            android:paddingTop="15dp"
            android:paddingBottom="15dp"
            android:paddingLeft="3dp" />
    </LinearLayout>
    <RelativeLayout
        android:id="@+id/container_chat_box"
        android:background="@drawable/conversation_background_gray"
        android:layout_alignParentBottom="true"
        android:layout_marginBottom="50dp">
        <EditText
            android:id="@+id/chat_box"
            style="@style/ConversationChatBox" />
    </RelativeLayout>
    <ListView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/bar"
        android:background="@android:color/white"
        android:stackFromBottom="true"
        android:transcriptMode="normal"
        android:layout_above="@id/chat_box"
        android:paddingBottom="15dp"
        android:clipToPadding="false"
        android:divider="@null"
        android:dividerHeight="0dp"
        android:listSelector="@android:color/transparent" />
</RelativeLayout>

This is an idea of how It looks like:

----------
[   bar   ]
[list view]
[list view]
[list view]
[list view]
[edit text]

When I click on the edit text, I'd like the bar to stay where it is (at the top of the screen), but the list view to be pushed up, so it looks like:

------------
[   bar   ]
[list view]
[list view]
[edit text]
[keyboard ]

What I get (the bar is pushed up and disappears):

------------
[list view]
[list view]
[list view]
[edit text]
[keyboard ]

I have this on my AndroidManifest:

android:windowSoftInputMode="adjustPan"

I tried different combinations of values for setting android:windowSoftInputMode, but I either get the keyboard to appear covering the edit text or the whole screen pushed up.

Is there a way to keep the bar in place while the listview is pushed up? Thank you in advance.


Solution

  • I was able to solve the issue, although only tested in a few phones so far.

    That's the solution:

    1) Change soft input mode to android:windowSoftInputMode="adjustResize" and add android:fitsSystemWindows="true" to the layout root view.

    2) Created a CustomInsetsLayout class that extends RelativeLayout (code bellow), so I can control what happens with the layout when the screen resizes.

    3) At this point I had an undesired black bar at the bottom of the layout. That bar was the margin bottom that was supposed to be hidden under the navigation bar but the FULLSCREEN flags responsible for that don't work with adjustResize. So I had to add code to the CustomInsetsLayout to remove the bottom margin from the root view when the screen resizes.

    That's how the CustomInsetsLayout class looks like (references and notes in the class description):

    package com.playchat.ui.full;
    
    import android.content.Context;
    import android.graphics.Rect;
    import android.os.Build;
    import android.util.AttributeSet;
    import android.view.WindowInsets;
    import android.widget.RelativeLayout;
    
    /**
     * http://stackoverflow.com/questions/21092888/windowsoftinputmode-adjustresize-not-working-with-translucent-action-navbar
     *
     * @author Kevin
     *         Date Created: 3/7/14
     *
     * https://code.google.com/p/android/issues/detail?id=63777
     *
     * When using a translucent status bar on API 19+, the window will not
     * resize to make room for input methods (i.e.
     * {@link android.view.WindowManager.LayoutParams#SOFT_INPUT_ADJUST_RESIZE} and
     * {@link android.view.WindowManager.LayoutParams#SOFT_INPUT_ADJUST_PAN} are
     * ignored).
     *
     * To work around this; override {@link #fitSystemWindows(android.graphics.Rect)},
     * capture and override the system insets, and then call through to FrameLayout's
     * implementation.
     *
     * For reasons yet unknown, modifying the bottom inset causes this workaround to
     * fail. Modifying the top, left, and right insets works as expected.
     */
    public class CustomInsetsLayout extends RelativeLayout {
        private int[] mInsets = new int[4];
    
        public CustomInsetsLayout(Context context) {
            super(context);
        }
    
        public CustomInsetsLayout(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        public CustomInsetsLayout(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
        }
    
        public final int[] getInsets() {
            return mInsets;
        }
    
        @Override
        protected final boolean fitSystemWindows(Rect insets) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                // Intentionally do not modify the bottom inset. For some reason,
                // if the bottom inset is modified, window resizing stops working.
                // TODO: Figure out why.
                mInsets[0] = insets.left;
                mInsets[1] = insets.top;
                mInsets[2] = insets.right;
    
                insets.left = 0;
                insets.top = 0;
                insets.right = 0;
    
                if (insets.bottom > 0) {
                    // Remove bottom margin from the root view
                    RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) getLayoutParams();
                    params.bottomMargin = 0;
                    setLayoutParams(params);
    
                }
            }
    
            return super.fitSystemWindows(insets);
        }
    
        @Override
        public final WindowInsets onApplyWindowInsets(WindowInsets insets) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
                if (insets.getSystemWindowInsetBottom() > 0) {
                    // Remove bottom margin from the root view
                    RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) getLayoutParams();
                    params.bottomMargin = 0;
                    setLayoutParams(params);
    
                }
                mInsets[0] = insets.getSystemWindowInsetLeft();
                mInsets[1] = insets.getSystemWindowInsetTop();
                mInsets[2] = insets.getSystemWindowInsetRight();
                return super.onApplyWindowInsets(insets.replaceSystemWindowInsets(0, 0, 0,
                    insets.getSystemWindowInsetBottom()));
    
            } else {
                return insets;
            }
        }
    }