Search code examples
androidandroid-softkeyboardandroid-dialogfragmentdialogfragment

DialogFragment not resizing for soft keyboard on >= API 24


I have been googling and searching Stack Overflow 2 days, but haven't found something that could help me.

I'm trying to implement a DialogFragment that's fullscreen on smartphones and a modal dialog on tablets.

This is how it is supposed to look (taken on an API level 23 device):

How it's supposed to look

This is how it looks on an API level 24 device:

How it's looking

This works with the following code on devices with API level <= 23 I tested it on multiple real and emulated devices.

TestDialogFragment.java:

public class TestDialogFragment extends DialogFragment
{
    private boolean isTablet;

    public TestDialogFragment()
    {
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);

        isTablet = getResources().getBoolean(R.bool.isTablet); // defaults to false, true >= sw600dp
        if (isTablet)
        {
            setStyle(DialogFragment.STYLE_NO_TITLE, android.R.style.Theme_Holo_Light_Dialog_NoActionBar_MinWidth);
        } else
        {
            setStyle(STYLE_NO_FRAME, android.R.style.Theme_Holo_Light);
        }
        setCancelable(false);
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle
        savedInstanceState)
    {
        if (!isTablet)
        {
            Window window = getDialog().getWindow();
            if (window != null)
            {
                window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
            }
        }

        return inflater.inflate(R.layout.dialog_fragment, container, false);
    }

    @Override
    public void onStart()
    {
        super.onStart();
        if (isTablet)
        {
            Dialog d = getDialog();
            if (d != null)
            {
                int width = ViewGroup.LayoutParams.WRAP_CONTENT;
                int height = ViewGroup.LayoutParams.MATCH_PARENT;
                d.getWindow().setLayout(width, height);
            }
        }
    }
}

dialog_fragment.xml:

<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.Toolbar
        android:id="@+id/dialog_toolbar"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:background="@color/colorPrimary"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginRight="8dp"
            android:ellipsize="end"
            android:fontFamily="sans-serif-medium"
            android:maxLines="2"
            android:paddingBottom="14dp"
            android:paddingTop="14dp"
            android:text="@string/dialog_title"
            android:textSize="20sp" />

    </android.support.v7.widget.Toolbar>

    <EditText
        android:id="@+id/edit_text"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginLeft="4dp"
        android:layout_marginRight="4dp"
        android:ems="10"
        android:hint="@string/edit_text_1"
        android:inputType="text"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@+id/progress_bar"
        app:layout_constraintTop_toBottomOf="@+id/dialog_toolbar"
        app:theme="@style/AppTheme" />

    <ProgressBar
        android:id="@+id/progress_bar"
        style="?android:progressBarStyle"
        android:layout_width="24dp"
        android:layout_height="0dp"
        android:layout_marginLeft="4dp"
        android:layout_marginRight="4dp"
        android:visibility="invisible"
        app:layout_constraintBottom_toBottomOf="@id/edit_text"
        app:layout_constraintHorizontal_bias="1.0"
        app:layout_constraintLeft_toRightOf="@id/edit_text"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="@id/edit_text"
        app:layout_constraintWidth_default="wrap"
        tools:visibility="visible" />

    <android.support.v7.widget.RecyclerView
        android:id="@+id/results_recycler_view"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/edit_text"/>

    <TextView
        android:id="@+id/no_results_text_view"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginTop="32dp"
        android:gravity="center"
        android:text="@string/no_results"
        android:visibility="gone"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/edit_text"
        app:layout_constraintVertical_bias="0.0"
        app:layout_constraintHeight_default="wrap"
        tools:visibility="visible" />
</android.support.constraint.ConstraintLayout>

I can't figure out why this doesn't also work on API level 24 and up. I see no changes to this behaviour in the release notes for Android 24.

Is there anything wrong with my code or have I overlooked anything?

Thanks in advance for your help!


Solution

  • I managed to at least get the contents of the dialog to be always visible by expanding this class from Mike Penz's MaterialDrawer library. This isn't the prettiest solution, but atleast everything in the dialog can be interacted with with the keyboard open.

    Here is the code:

    public class KeyboardUtil
    {
        private View activityDecorView;
    
        private View dialogDecorView;
    
        private View contentView;
    
        private int diff;
    
        //a small helper to allow showing the editText focus
        ViewTreeObserver.OnGlobalLayoutListener activityListener = new ViewTreeObserver.OnGlobalLayoutListener()
        {
            @Override
            public void onGlobalLayout()
            {
                Rect r = new Rect();
                //r will be populated with the coordinates of your view that area still visible.
                activityDecorView.getWindowVisibleDisplayFrame(r);
    
                //get screen height and calculate the difference with the usable area from  r
                int height = activityDecorView.getContext().getResources().getDisplayMetrics().heightPixels;
                diff = height - r.bottom;
    
                //no difference means no keyboard, so remove padding
                if (diff == 0)
                {
                    //check if the padding is != 0 (if yes reset the padding)
                    if (contentView.getPaddingBottom() != 0)
                    {
                        //reset the padding of the contentView
                        contentView.setPadding(0, 0, 0, 0);
                    }
                }
            }
        };
    
        ViewTreeObserver.OnGlobalLayoutListener dialogListener = new ViewTreeObserver.OnGlobalLayoutListener()
        {
            @Override
            public void onGlobalLayout()
            {
                //if it could be a keyboard and the view hasn't shrunk on it's own add the padding to the view
                if (diff != 0 && activityDecorView.getContext().getResources().getDisplayMetrics().heightPixels - diff >
                        contentView.getHeight())
                {
                    //the view shrunk on it's own. remove padding
    
                    //check if the padding is != 0 (if yes reset the padding)
                    if (contentView.getPaddingBottom() != 0)
                    {
                        //reset the padding of the contentView
                        contentView.setPadding(0, 0, 0, 0);
                    }
                } else
                {
                    // if the usable screen height differs from the total screen height we assume that it shows a
                    // keyboard now
                    //check if the padding is 0 (if yes set the padding for the keyboard)
                    if (contentView.getPaddingBottom() != diff)
                    {
                        //set the padding of the contentView for the keyboard
                        contentView.setPadding(0, 0, 0, diff);
                    }
                }
            }
        };
    
        public KeyboardUtil(Activity act, View contentView)
        {
            this.activityDecorView = act.getWindow().getDecorView();
            this.contentView = contentView;
    
            //only required on newer android versions. it was working on API level 19
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
            {
                activityDecorView.getViewTreeObserver().addOnGlobalLayoutListener(activityListener);
            }
        }
    
        public KeyboardUtil(Activity act, Dialog dialog, View contentView)
        {
            this(act, contentView);
    
            if (dialog.getWindow() != null)
            {
                this.dialogDecorView = dialog.getWindow().getDecorView();
    
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
                {
                    dialogDecorView.getViewTreeObserver().addOnGlobalLayoutListener(dialogListener);
                }
            }
        }
    
        public void enable()
        {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
            {
                activityDecorView.getViewTreeObserver().addOnGlobalLayoutListener(activityListener);
                if (dialogDecorView != null)
                {
                    dialogDecorView.getViewTreeObserver().addOnGlobalLayoutListener(dialogListener);
                }
            }
        }
    
        public void disable()
        {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
            {
                activityDecorView.getViewTreeObserver().removeOnGlobalLayoutListener(activityListener);
                if (dialogDecorView != null)
                {
                    dialogDecorView.getViewTreeObserver().removeOnGlobalLayoutListener(dialogListener);
                }
            }
        }
    
    
        /**
         * Helper to hide the keyboard
         *
         * @param act
         */
        public static void hideKeyboard(Activity act)
        {
            if (act != null && act.getCurrentFocus() != null)
            {
                InputMethodManager inputMethodManager = (InputMethodManager) act.getSystemService(Activity
                        .INPUT_METHOD_SERVICE);
                inputMethodManager.hideSoftInputFromWindow(act.getCurrentFocus().getWindowToken(), 0);
            }
        }
    }