Show entire bottom sheet with EditText above Keyboard

I'm implementing a UI where a bottom sheet will appear above the keyboard with an EditText for the user to enter a value. The problem is the View is being partially overlapped by the keyboard, covering up the bottom of the bottom sheet.

Here is the Bottom Sheet and no keyboard.

Bottom Sheet

Here is the Bottom Sheet with the keyboard showing.

enter image description here

What's the best method to ensure the entire Bottom Sheet is shown?

Thanks.

72338 次浏览

A BottomSheetDialog can be helpful for this. it will open with Softkeyboard open with focus on edit text.But user can still close the Softkeyboard and Dialog will be reset to Bottom. Again focusing will make dialog appear at top of Softkeyboard.

 public void showDialog()  {
final BottomSheetDialog dialog=new BottomSheetDialog(this);
dialog.setContentView(R.layout.item_dialog);
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
dialog.show();
}

You can make the BottomSheetDialog expanded over keyboard . But for this you need to call it after SoftKeyboard Open. the Expand code is .

 BottomSheetDialog d = (BottomSheetDialog) dialog;
FrameLayout bottomSheet = (FrameLayout) d.findViewById(android.support.design.R.id.design_bottom_sheet);
BottomSheetBehavior.from(bottomSheet).setState(BottomSheetBehavior.STATE_EXPANDED);

I have tested it on DialogInterface.OnShowListener() but its not working . Tested with it 1 second delay its working . But Delay is not the solution . You need to figure out the on which action you should expand the dialog.

 final BottomSheetDialog dialog=new BottomSheetDialog(this);
dialog.setContentView(R.layout.item_dialog);
dialog.getWindow().setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE|
WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
FrameLayout bottomSheet = (FrameLayout) dialog.findViewById(android.support.design.R.id.design_bottom_sheet);
BottomSheetBehavior.from(bottomSheet).setState(BottomSheetBehavior.STATE_EXPANDED);
}
},2000);
dialog.show();

For this, I found an AlertDialog worked best. While it doesn't sit flush against the bottom or side of the screen, it still looks good enough.

First, create the AlertDialog with your view.

val view = LayoutInflater.from(context).inflate(R.layout.alert, null)


dialog = AlertDialog.Builder(context)
.setView(view)
.create()

Next, set the gravity.

    dialog.window.attributes.gravity = Gravity.BOTTOM

And finally, show it.

dialog.show()

You can also bind the keyboard to stay with the dialog, by using an onDismissListener.

After showing the AlertDialog, I force up the keyboard.

Call this method, passing in your EditText.

fun showKeyboard(view: View?) {
if (view == null) return;


val imm = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY);
}

And for dismissing within the onDismissListener.

private fun hideKeyboard() {
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.toggleSoftInput(InputMethodManager.HIDE_IMPLICIT_ONLY, 0)
}
dialog = new BottomSheetDialog(getContext(), R.style.BottomSheetDialog);
dialog.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
BottomSheetDialog d = (BottomSheetDialog) dialog;
FrameLayout bottomSheet = d.findViewById(R.id.design_bottom_sheet);
BottomSheetBehavior bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet);
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
}
},0);
}
});

This code works fine at Fragment's onCreateView method (thanks for ADM)

Just reposting @jblejder from this question Keyboard hides BottomSheetDialogFragment since it worked for me, to make it easier for others to find:

The most convenient way that I found to change this is by creating style:

<style name="DialogStyle" parent="Theme.Design.Light.BottomSheetDialog">
<item name="android:windowIsFloating">false</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:windowSoftInputMode">adjustResize</item>
</style>

And set this in onCreate method of your BottomSheetDialogFragment:

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(DialogFragment.STYLE_NORMAL, R.style.DialogStyle)
}

This is how it looks on my device:

enter image description here

==== UPDATE ====

As already mentioned in the Comments a few times, you might also need to set the state of the BottomSheetDialog to STATE_EXPANDED like in Nordknight's answer below

dialog = new BottomSheetDialog(getContext(), R.style.BottomSheetDialog);
dialog.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
BottomSheetDialog d = (BottomSheetDialog) dialog;
FrameLayout bottomSheet = d.findViewById(R.id.design_bottom_sheet);
BottomSheetBehavior bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet);
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
}
},0);
}
});

My answer might be useful for someone who is still looking for solution. If keyboard is covering edittext in BottomSheetDialogFragment then in setupDialog() method create instance of a class KeyboardUtil and pass your rootview.

    @Override
public void setupDialog(final Dialog dialog, int style) {
super.setupDialog(dialog, style);
View view = View.inflate(getActivity(), R.layout.reopen_dialog_layout, null);
new KeyboardUtil(getActivity(), view);
}

Create a new class

    public class KeyboardUtil {
private View decorView;
private View contentView;
//a small helper to allow showing the editText focus
ViewTreeObserver.OnGlobalLayoutListener onGlobalLayoutListener = 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.
decorView.getWindowVisibleDisplayFrame(r);


//get screen height and calculate the difference with the useable area from the r
int height = decorView.getContext().getResources().getDisplayMetrics().heightPixels;
int diff = height - r.bottom;


//if it could be a keyboard add the padding to the view
if (diff != 0) {
// if the use-able 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);
}
} else {
//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);
}
}
}
};


public KeyboardUtil(Activity act, View contentView) {
this.decorView = act.getWindow().getDecorView();
this.contentView = contentView;


//only required on newer android versions. it was working on API level 19
if (Build.VERSION.SDK_INT >= 19) {
decorView.getViewTreeObserver().addOnGlobalLayoutListener(onGlobalLayoutListener);
}
}


/**
* 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);
}
}


public void enable() {
if (Build.VERSION.SDK_INT >= 19) {
decorView.getViewTreeObserver().addOnGlobalLayoutListener(onGlobalLayoutListener);
}
}


public void disable() {
if (Build.VERSION.SDK_INT >= 19) {
decorView.getViewTreeObserver().removeOnGlobalLayoutListener(onGlobalLayoutListener);
}
}
}

This might be a redundant answer. Although just pointing out the issue. If you're using BottomSheetDialogFragment, the only way is to enable the attribute android:windowIsFloating to true. This will enable the whole window to be on top of whatever is trying to take the space behind it.

<style name="BottomSheetDialogThemeNoFloating" parent="Theme.Design.Light.BottomSheetDialog">
<item name="android:windowIsFloating">false</item>
<item name="android:windowSoftInputMode">adjustResize|stateVisible</item>
</style>

Then in your onCreate() of your dialog, set this style.

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// set the window no floating style
setStyle(DialogFragment.STYLE_NORMAL, R.style.AppRoundedBottomSheetDialogThemeNoFloating)
}

This is handy for those who frequently use bottom sheets and may want to deal with EditText and soft keyboard overlapping each other.

Note: The class KeyboardUtil by mikepenz has an issue in which on certain phones, the content view with input field is automatically pushed above keyboard despite giving bottom padding to the whole content view supplied.

This one working

 BottomSheetDialog dialog = new BottomSheetDialog(this, R.style.DialogStyle);
View sheetView = getLayoutInflater().inflate(R.layout.dialog_remark, null);
Objects.requireNonNull(dialog.getWindow())
.setSoftInputMode(SOFT_INPUT_STATE_VISIBLE);
dialog.setContentView(sheetView);
dialog.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
BottomSheetDialog d = (BottomSheetDialog) dialog;
View bottomSheetInternal = d.findViewById(com.google.android.material.R.id.design_bottom_sheet);
BottomSheetBehavior.from(bottomSheetInternal).setState(BottomSheetBehavior.STATE_EXPANDED);
}
});
dialog.show();

add this style to your styles.xml

 <style name="DialogStyle" parent="Theme.Design.Light.BottomSheetDialog">
<item name="android:windowIsFloating">false</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:windowSoftInputMode">adjustPan</item>
</style>

add your layout like this

 <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:id="@+id/scrollview"


android:layout_height="match_parent">


<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"


android:layout_margin="8dp">


<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">


<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="8dp"
android:fontFamily="@font/montserratmedium"
android:text="Add Remarks"
android:textColor="@android:color/black"
android:textSize="18sp" />


<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="24dp"
android:fontFamily="@font/montserratmedium"
android:text="Branch"
android:textColor="#8B8B8B"
android:textSize="18sp" />




<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:fontFamily="@font/montserratmedium"
android:text="BLR-CO-SINDHUBHAVAN-384"
android:textColor="@android:color/black"
android:textSize="18sp" />


<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="24dp"
android:fontFamily="@font/montserratmedium"
android:text="Enter Remarks"
android:textColor="#8B8B8B"
android:textSize="18sp" />




<EditText


android:id="@+id/input_remark"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:layout_marginTop="8dp"
android:background="@drawable/remark_inputbg"
android:gravity="start"


android:inputType="textMultiLine"
android:lines="5" />


<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">


<LinearLayout
android:id="@+id/action"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="2">


<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:layout_weight="1"
android:background="@drawable/reset_bg"
android:padding="8dp"
android:text="CANCEL" />


<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:layout_weight="1"
android:background="#4F4DBB"
android:padding="8dp"
android:text="CANCEL"
android:textColor="@android:color/white" />
</LinearLayout>
</RelativeLayout>




</LinearLayout>


</androidx.cardview.widget.CardView>


</ScrollView>

Some answers seem to do the trick better than others but will need modification when using the new material design components instead of the older support libraries while also using kotlin

Hope this will help someone.

BottomSheetDialog(this, R.style.DialogStyle).apply {
setContentView(layoutInflater.inflate(R.layout.bottom_sheet, null))
window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
findViewById<EditText>(R.id.time_et)?.requestFocus()


show()
}

layout/bottom_sheet.xml

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#ffffff"
android:padding="16dp">


<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">




<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">


<View
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />


<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="5"
android:orientation="vertical">


<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Time"
android:textColor="#000000"
android:textSize="24sp"
android:textStyle="bold" />


<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="horizontal">


<EditText
android:id="@+id/time_et"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="numberSigned"
android:minWidth="50dp"
android:text="15" />


<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:text="min" />


</LinearLayout>




</LinearLayout>


</LinearLayout>




<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:background="#000"
android:text="Save"
android:textColor="#fff" />


</LinearLayout>


</ScrollView>

styes.xml (Split for v-21 for using statusBarColor)

    <style name="DialogStyle" parent="Theme.MaterialComponents.Light.BottomSheetDialog">
<item name="android:windowIsFloating">false</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:windowSoftInputMode">adjustResize</item>
</style>
      private fun enterMobileNumberPopUp() {
val dialog = BottomSheetDialog(this,R.style.DialogStyle)
val view = layoutInflater.inflate(R.layout.layout_otp, null)
dialog.setContentView(view)
dialog.behavior.state = BottomSheetBehavior.STATE_EXPANDED
dialog.show()}
  • THIS IS THE MOST EASY WAY AND BEST WAY TO HANDLE BOTTOM SHEET DIALOG YOU CAN CALL THIS IN METHOD enter image description here
<style name="DialogStyle" parent="Theme.Design.Light.BottomSheetDialog">
<item name="android:windowIsFloating">false</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:windowSoftInputMode">adjustResize</item>

Style reference

An updated answer for those using Material Components theme, and also an improved answer to remove the need to add anything into each dialog's onCreate().

In your main AppTheme style, you can add the attribute bottomSheetDialogTheme to apply the style to all of your BottomSheetDialogFragments:

 <style name="AppTheme" parent="Theme.MaterialComponents.Light.DarkActionBar">
<item name="colorPrimary">@color/primary</item>
<item name="colorPrimaryDark">@color/primary_dark</item>
<item name="colorAccent">@color/accent</item>
<item name="bottomSheetDialogTheme">@style/BottomSheetDialogStyle</item>
</style>

So with the above, no need to add anything to your BottomSheetDialogFragment code.

And then, as previous answers, your Dialog style, noting to also match the style to the same Material Components library (or you'll get some weird looking buttons, edittexts etc):

 <style name="BottomSheetDialogStyle" parent="Theme.MaterialComponents.Light.BottomSheetDialog">
<item name="android:windowIsFloating">false</item>
<item name="android:windowSoftInputMode">adjustResize</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="colorPrimary">@color/primary</item>
<item name="colorPrimaryDark">@color/primary_dark</item>
<item name="colorAccent">@color/accent</item>
</style>

Note that I am adding my app theme colors back in here; as you can't have multiple inheritance in Android styles, you may want these colors defining here so any buttons and accents align with the rest of your app.

Kotlin, +viewBinding, +by using the accepted answer's dialog style

val bottomSheet = BottomSheetDialog(this, R.style.BottomSheetDialogStyle)
val binding = [YourSheetBinding].inflate(LayoutInflater.from(YourActivity.this))
bottomSheet.setContentView(binding.root)
bottomSheet.behavior.state = BottomSheetBehavior.STATE_EXPANDED
bottomSheet.show()

See https://stackoverflow.com/a/61813321/2914140:

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = super.onCreateDialog(savedInstanceState) as BottomSheetDialog
dialog.behavior.state = BottomSheetBehavior.STATE_EXPANDED
return dialog
}

But if a layout is not tall enough, you can use https://stackoverflow.com/a/66287187/2914140 instead. It will open BottomSheetDialog almost fullscreen:

<style name="BottomSheetDialogTheme" parent="Theme.MaterialComponents.Light.BottomSheetDialog">
<item name="android:windowIsFloating">false</item>
<item name="android:windowSoftInputMode">adjustResize|stateVisible</item>
</style>
        bottomSheetDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);

It will work for sure.

Fiddling with BottomSheetDialogFragmentwasn't really worth it. So I just changed it to a simple DialogFragment and just set its gravity to bottom:

window.setGravity(Gravity.BOTTOM);

Worked like a charm.

 override fun onStart() {
super.onStart()
bottomSheetBehavior?.skipCollapsed = true
bottomSheetBehavior?.state = BottomSheetBehavior.STATE_EXPANDED
}

Putting this in BottomSheet helped without setting styles and without ScrollView (correct me if I'm wrong or if I'm missing something)

This trick solved me

in manifest put

android:windowSoftInputMode="adjustPan"

in your activity and

        bottomSheetDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
bottomSheetDialog.getBehavior().setState(BottomSheetBehavior.STATE_EXPANDED);

Just write the following programmatically

override fun setupDialog(dialog: Dialog, style: Int) {
super.setupDialog(dialog, style)
dialog.window?.setSoftInputMode(             WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE or
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
);
}