Cet article a été écrit il y a plus d'un an : son contenu peut être dépassé.
Petit souci que j’ai rencontré dernièrement avec un BottomSheetDialogFragment, un EditText et le clavier Android.
Note : cette solution ne semble plus valide aujourd’hui.
Problématique
L’idée est de proposer une popup de partage par mail. Pour que ce soit plus sympa qu’une bête fenêtre centrée, j’ai utilisé un BottomSheetDialogFragment dans lequel j’ai mis un layout custom avec un EditText. L’idée est, lorsqu’on appuie sur le bouton d’ouverture de la popup, que le focus soit donné directement à l’EditText avec le clavier ouvert, la popup au dessus.

Affichage désiré du clavier sur BottomSheetDialogFragment
Mais bien évidemment, ça ne fonctionnait pas. Soit le clavier ne s’affichait pas et il était nécessaire pour l’utilisateur d’appuyer explicitement sur l’EditText pour le voir, soit le clavier masquait en partie la popup.

Mauvais affichage du clavier sur BottomSheetDialogFragment

Pas d'affichage du clavier sur BottomSheetDialogFragment
Solution
Après avoir un peu galéré, j’ai fini par obtenir un résultat satisfaisant avec le fragment suivant :
public class SendBottomSheetFragment extends BottomSheetDialogFragment {
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_send, container, false);
}
@Override
public void setupDialog(final Dialog dialog, int style) {
super.setupDialog(dialog, style);
//Permet de redimensionner la fenêtre quand le clavier apparaît.
//Si le mode n'est pas activé, le clavier n'est pas visible.
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
//Traitement à effectuer une fois le dialog affiché
dialog.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(DialogInterface dialogInterface) {
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
//Récupération du container parent qui gère le comportement du BottomSheetDialog
// /!\ l'id peut changer dans les versions futures de la librairie de support
BottomSheetDialog bottomSheetDialog = (BottomSheetDialog) dialog;
FrameLayout bottomSheet = (FrameLayout) bottomSheetDialog.findViewById(android.support.design.R.id.design_bottom_sheet);
//Force l'ouverture maximale de la bottomSheet
BottomSheetBehavior.from(bottomSheet).setState(BottomSheetBehavior.STATE_EXPANDED);
}
}, 250); //Délai pour être sûr que le clavier est bien affiché lorsque l'on demandera l'ouverture maximale.
}
});
}
}
SendBottomSheetFragment.java
Et le reste du code qui va bien :
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.NestedScrollView
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="match_parent" >
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="10dp">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Bla bla bla"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="20dp"/>
<EditText
android:id="@+id/editText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="email"
android:inputType="textEmailAddress"
android:imeOptions="actionNext"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView"
android:layout_marginTop="10dp"/>
<Button
android:id="@+id/route.send.send"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Envoyer"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/editText"
android:layout_marginTop="10dp"
android:layout_marginRight="20dp"
android:layout_marginEnd="20dp"
android:background="@color/colorAccent"
android:textColor="@android:color/white"/>
</android.support.constraint.ConstraintLayout>
</android.support.v4.widget.NestedScrollView>
layout xml
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button openButton = (Button) findViewById(R.id.button);
openButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//Affichage de la bottom sheet au clic sur bouton.
BottomSheetDialogFragment bottomSheetDialogFragment = new SendBottomSheetFragment();
bottomSheetDialogFragment.show(getSupportFragmentManager(), bottomSheetDialogFragment.getTag());
}
});
}
}
MainActivity.java
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Push Me"/>
</android.support.constraint.ConstraintLayout>
main_layout.xml
À noter plusieurs points :
- Attention à la pérennité : l’id récupéré correspond bien pour le moment à la version de la support librairie utilisée (la version 26.0.0) mais ça peut évoluer ensuite.
- Il manque des contrôles de nullité des objets, notamment sur le getWindow() (je n’ai pas voulu surcharger le code).
- Le Handler peut être facultatif : sur un code simple, ça fonctionnait sans. Le délai est également arbitraire et correspondait à mon cas d’usage. Selon la complexité de l’application, il peut être plus ou moins long (attention à ne pas dépasser 500 ms quand même, c’est trop long autrement).
- Dans de rares cas, ça ne fonctionnait pas, le clavier masquait toujours en partie la vue. Cela se produisait essentiellement lorsque l’on sortait de veille directement sur l’application, et qu’on appuyait directement sur le bouton. Si l’on attendait quelques secondes, le problème n’avait plus lieu. Je suppose qu’en sortie de veille, le clavier est un peu plus long à apparaître et donc le délai du Handler devait être passé. Dans le cas de mon application, au vue des usages, ce n’était pas forcément problématique. Un contournement possible est d’ajouter un second handler avec un délai plus important (par exemple, 700ms) qui fait la même chose que le premier : si le premier ne fonctionne pas, le second prendra la relève, l’utilisateur aura juste l’impression que ça a mis du temps à se faire (mais ça commence à ressembler à du bricolage).
- Il est sûrement possible de contourner le problème en gérant tout manuellement via BottomSheetBehavior. Dans mon cas, cette solution a toutefois engendré d’autres complications et je suis revenu au BottomSheetDialogFragment.
- Testé sur devices de la version 16 à la version 25 du SDK Android. Cela ne semble plus être fonctionnel sur les versions suivantes.