Définir l'état de BottomSheetDialogFragment sur développé

92

Comment définir l'état d'un fragment étendu BottomSheetDialogFragmentà étendu à l' BottomSheetBehavior#setState(STATE_EXPANDED)aide de la bibliothèque de conception de support Android (v23.2.1)?

https://code.google.com/p/android/issues/detail?id=202396 dit:

Les feuilles du bas sont définies sur STATE_COLLAPSED au début. Appelez BottomSheetBehavior # setState (STATE_EXPANDED) si vous souhaitez le développer. Notez que vous ne pouvez pas appeler la méthode avant d'afficher les dispositions.

La pratique suggérée nécessite qu'une vue soit d'abord gonflée, mais je ne sais pas comment je vais définir le BottomSheetBehaviour sur un fragment ( BottomSheetDialogFragment).

View bottomSheet = coordinatorLayout.findViewById(R.id.bottom_sheet);  
BottomSheetBehavior behavior = BottomSheetBehavior.from(bottomSheet);  
user2560886
la source

Réponses:

221

"Notez que vous ne pouvez pas appeler la méthode avant d'afficher les mises en page."

Le texte ci-dessus est l'indice.

Les boîtes de dialogue ont un écouteur qui est déclenché une fois la boîte de dialogue affichée . La boîte de dialogue ne peut pas être affichée si elle n'est pas mise en page.

Donc, dans le onCreateDialog()de votre feuille de fond modale ( BottomSheetFragment), juste avant de retourner la boîte de dialogue (ou n'importe où, une fois que vous avez une référence à la boîte de dialogue), appelez:

// This listener's onShow is fired when the dialog is shown
dialog.setOnShowListener(new DialogInterface.OnShowListener() {
    @Override
    public void onShow(DialogInterface dialog) {

        // In a previous life I used this method to get handles to the positive and negative buttons
        // of a dialog in order to change their Typeface. Good ol' days.

        BottomSheetDialog d = (BottomSheetDialog) dialog;

        // This is gotten directly from the source of BottomSheetDialog
        // in the wrapInBottomSheet() method
        FrameLayout bottomSheet = (FrameLayout) d.findViewById(android.support.design.R.id.design_bottom_sheet);

        // Right here!
        BottomSheetBehavior.from(bottomSheet)
            .setState(BottomSheetBehavior.STATE_EXPANDED);
    }
});

Dans mon cas, ma coutume BottomSheets'est avérée être:

@SuppressWarnings("ConstantConditions")
public class ShareBottomSheetFragment extends AppCompatDialogFragment {

    @NonNull @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {

        BottomSheetDialog dialog =
                new BottomSheetDialog(getActivity(), R.style.Haute_Dialog_ShareImage);

        dialog.setContentView(R.layout.dialog_share_image);

        dialog.findViewById(R.id.cancel).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dismiss();
            }
        });

        dialog.setOnShowListener(new DialogInterface.OnShowListener() {
            @Override
            public void onShow(DialogInterface dialog) {
                BottomSheetDialog d = (BottomSheetDialog) dialog;

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

        SwitchCompat switchview = (SwitchCompat) dialog.findViewById(R.id.switchview);
        switchview.setTypeface(FontCache.get(dialog.getContext(), lookup(muli, NORMAL)));

        return dialog;
    }
}

Faites-moi savoir si cela vous aide.

MISE À JOUR

Notez que vous pouvez également remplacer BottomSheetDialogFragmentcomme:

public class SimpleInitiallyExpandedBottomSheetFragment extends BottomSheetDialogFragment {

    @NonNull @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {

        BottomSheetDialog dialog = (BottomSheetDialog) super.onCreateDialog(savedInstanceState);

        dialog.setOnShowListener(new DialogInterface.OnShowListener() {
            @Override
            public void onShow(DialogInterface dialog) {
                BottomSheetDialog d = (BottomSheetDialog) dialog;

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

        // Do something with your dialog like setContentView() or whatever
        return dialog;
    }
}

Mais je ne vois vraiment pas pourquoi quelqu'un voudrait faire cela car la base BottomSheetFragmentne fait rien d'autre que de renvoyer un fichier BottomSheetDialog.

MISE À JOUR POUR ANDROIDX

Lors de l' utilisation AndroidX, la ressource trouvée précédemment à android.support.design.R.id.design_bottom_sheetpeut maintenant être trouvé à com.google.android.material.R.id.design_bottom_sheet.

efemoney
la source
Merci, j'ai essayé cette méthode. Cela donne l' BottomSheetDialogFragmentimpression que le comportement est saccadé (semble sauter des images dans l'animation d'ouverture) en passant du comportement réduit au comportement développé. Edit: Testé ceci sur les appareils Android Marshmallow et KitKat
user2560886
1
Cela fonctionne parfaitement pour moi. Pas de saut. Faites-vous autre chose que de simplement renvoyer un dialogue? J'apprécierai si vous mettez à jour votre message avec votre code afin que je puisse avoir une meilleure idée.
efemoney
5
Est-ce juste que je ne parviens pas à trouver le package android.support.design.Raprès la mise à jour des bibliothèques de support?
natario
2
J'ai également des problèmes de résolution android.support.design.R, tout comme @natario. J'utilise implementation "com.google.android.material:material:1.0.0". J'utilise également AndroidX dans le projet.
hsson
21
Lorsque vous utilisez AndroidX, la ressource peut être trouvée àcom.google.android.material.R.id.design_bottom_sheet
urgentx
45

La réponse d'efeturi est excellente, cependant, si vous souhaitez utiliser onCreateView () pour créer votre BottomSheet, par opposition à onCreateDialog () , voici le code que vous devrez ajouter sous votre méthode onCreateView () :

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    getDialog().setOnShowListener(new DialogInterface.OnShowListener() {
        @Override
        public void onShow(DialogInterface dialog) {
            BottomSheetDialog d = (BottomSheetDialog) dialog;
            View bottomSheetInternal = d.findViewById(android.support.design.R.id.design_bottom_sheet);
            BottomSheetBehavior.from(bottomSheetInternal).setState(BottomSheetBehavior.STATE_EXPANDED);
        }
    });
    return inflater.inflate(R.layout.your_bottomsheet_content_layout, container, false);
}
goodKode
la source
3
Sinon, vous n'avez pas du tout besoin d'appeler getDialog. Je trouve que le moyen le plus propre de le faire est de remplacer à la fois onCreateView et onCreateDialog. Créez votre vue dans onCreateView (comme vous le feriez avec n'importe quel fragment) et faites le code spécifique à la boîte de dialogue dans onCreateDialog (appelez super.onCreateDialog pour obtenir l'instance)
Stimsoni
2
Cela me sauve la journée. Merci.
AndroidRuntimeException
@Stimsoni onCreateView n'est pas appelé si onCreateDialog est utilisé. developer.android.com/reference/android/support/v4/app/…
Vincent_Paing
1
@Vincent_Paing, oui c'est ça. Dans votre lien ci-joint, il est dit «onCreateView n'a pas besoin d'être implémenté». Il ne dit pas qu'il ne sera pas appelé. Jetez un œil au code source ici github.com/material-components/material-components-android/blob/… . L'implémentation par défaut appelle onCreateDialog pour créer la feuille du bas et chaque solution ci-dessus utilise toujours onCreateView, ce qui signifie qu'elles sont toutes les deux toujours appelées. Assurez-vous simplement de toujours appeler super.onCreateDialog () si vous le remplacez.
Stimsoni
dans BottomSheetDialogFragment ça me plante dans onCreateView () je l'ai mis dans onViewCreated () et c'est parfait! merci
avisper le
22

Une solution simpliste et élégante:

BottomSheetDialogFragment pourrait être sous-classée pour résoudre ce problème:

class NonCollapsableBottomSheetDialogFragment extends BottomSheetDialogFragment {

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        final BottomSheetDialog bottomSheetDialog = (BottomSheetDialog) super.onCreateDialog(savedInstanceState);

        bottomSheetDialog.setOnShowListener(new DialogInterface.OnShowListener() {
            @Override
            public void onShow(DialogInterface dialog) {
                FrameLayout bottomSheet = bottomSheetDialog.findViewById(com.google.android.material.R.id.design_bottom_sheet);

                BottomSheetBehavior behavior = BottomSheetBehavior.from(bottomSheet);
                behavior.setSkipCollapsed(true);
                behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
            }
        });
        return bottomSheetDialog;
    }
}

Alors étendez cette classe au lieu de BottomSheetDialogFragmentcréer votre propre feuille de fond.

Remarque

Remplacez com.google.android.material.R.id.design_bottom_sheetpar android.support.design.R.id.design_bottom_sheetsi votre projet utilise d'anciennes bibliothèques de support Android.

DYS
la source
1
Semble être com.google.android.material.Rmaintenant au lieu de android.support.design.R.
EpicPandaForce
@EpicPandaForce Bien sûr. L'équipe Android de Google a récemment reconditionné l'ancienne bibliothèque d'assistance.
DYS
4

Je pense que ceux ci-dessus sont meilleurs. Malheureusement, je n'ai pas trouvé cette solution avant de l'avoir résolue. Mais écrivez ma solution. assez similaire à tous.

=================================================== =================================

Je suis confronté au même problème. C'est ce que j'ai résolu. Le comportement est masqué dans BottomSheetDialog, qui est disponible pour obtenir le comportement. Si vous ne souhaitez pas changer votre mise en page parente en CooridateLayout, vous pouvez essayer ceci.

ÉTAPE 1: personnalisez le BottomSheetDialogFragment

open class CBottomSheetDialogFragment : BottomSheetDialogFragment() {
   //wanna get the bottomSheetDialog
   protected lateinit var dialog : BottomSheetDialog 
   override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
      dialog = super.onCreateDialog(savedInstanceState) as BottomSheetDialog
      return dialog
   }

   //set the behavior here
   fun setFullScreen(){
      dialog.behavior.state = STATE_EXPANDED
   }
}

ÉTAPE 2: faites étendre votre fragment à ce fragment personnalisé

class YourBottomSheetFragment : CBottomSheetDialogFragment(){

   //make sure invoke this method after view is built
   //such as after OnActivityCreated(savedInstanceState: Bundle?)
   override fun onStart() {
      super.onStart()
      setFullScreen()//initiated at onActivityCreated(), onStart()
   }
}
盧 誼 玲
la source
3
dialog.setOnShowListener(new DialogInterface.OnShowListener() {
            @Override
            public void onShow(DialogInterface dialog) {
                BottomSheetDialog d = (BottomSheetDialog) dialog;

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

J'ai rencontré NullPointException BottomSheetBehavior.from(bottomSheet)car d.findViewById(android.support.design.R.id.design_bottom_sheet)renvoie null.

C'est étrange. J'ajoute cette ligne de code à Watches dans Android Monitor en mode DEBUG et je l'ai trouvée renvoyer Framelayout normalement.

Voici le code de wrapInBottomSheetBottomSheetDialog:

private View wrapInBottomSheet(int layoutResId, View view, ViewGroup.LayoutParams params) {
        final CoordinatorLayout coordinator = (CoordinatorLayout) View.inflate(getContext(),
                R.layout.design_bottom_sheet_dialog, null);
        if (layoutResId != 0 && view == null) {
            view = getLayoutInflater().inflate(layoutResId, coordinator, false);
        }
        FrameLayout bottomSheet = (FrameLayout) coordinator.findViewById(R.id.design_bottom_sheet);
        BottomSheetBehavior.from(bottomSheet).setBottomSheetCallback(mBottomSheetCallback);
        if (params == null) {
            bottomSheet.addView(view);
        } else {
            bottomSheet.addView(view, params);
        }
        // We treat the CoordinatorLayout as outside the dialog though it is technically inside
        if (shouldWindowCloseOnTouchOutside()) {
            coordinator.findViewById(R.id.touch_outside).setOnClickListener(
                    new View.OnClickListener() {
                        @Override
                        public void onClick(View view) {
                            if (isShowing()) {
                                cancel();
                            }
                        }
                    });
        }
        return coordinator;
    }

Parfois, j'ai trouvé que ce R.id.design_bottom_sheetn'est pas égal à android.support.design.R.id.design_bottom_sheet. Ils ont une valeur différente dans différents R.java.

Alors je change android.support.design.R.id.design_bottom_sheetpour R.id.design_bottom_sheet.

dialog.setOnShowListener(new DialogInterface.OnShowListener() {
            @Override
            public void onShow(DialogInterface dialog) {
                BottomSheetDialog d = (BottomSheetDialog) dialog;

                FrameLayout bottomSheet = (FrameLayout) d.findViewById(R.id.design_bottom_sheet); // use R.java of current project
                BottomSheetBehavior.from(bottomSheet).setState(BottomSheetBehavior.STATE_EXPANDED);
            }
        });

Plus de NullPointException maintenant.

légendemohe
la source
3

Appliquer l' BottomsheetDialogFragmentétat dans onResumerésoudra ce problème

@Override
public void onResume() {
    super.onResume();
    if(mBehavior!=null)
       mBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
}

onShow(DialogInterface dialog)et postDelayedpeut causer un problème d'animation

John Ruban Singh
la source
2

Tous les résultats avec l'utilisation de onShow () provoquent un bogue de rendu aléatoire lorsque le clavier virtuel est affiché. Voir la capture d'écran ci-dessous - La boîte de dialogue BottomSheet n'est pas au bas de l'écran mais est placée comme le clavier était affiché. Ce problème ne se produit pas toujours mais assez souvent.

entrez la description de l'image ici

MISE À JOUR

Ma solution avec reflet de membre privé est inutile. Utiliser postDelayed (avec environ 100 ms) pour créer et afficher une boîte de dialogue après masquer le clavier logiciel est une meilleure solution. Alors les solutions ci-dessus avec onShow () sont correctes.

Utils.hideSoftKeyboard(this);
mView.postDelayed(new Runnable() {
    @Override
    public void run() {
        MyBottomSheetDialog dialog = new MyBottomSheetDialog();
        dialog.setListener(MyActivity.this);
        dialog.show(getSupportFragmentManager(), TAG_BOTTOM_SHEET_DLG);
    }
}, 100);

J'implémente donc une autre solution, mais cela nécessite l'utilisation de la réflexion, car BottomSheetDialog a tous les membres comme privés. Mais cela résout le bogue de rendu. La classe BottomSheetDialogFragment est uniquement AppCompatDialogFragment avec la méthode onCreateDialog qui crée BottomSheetDialog. Je crée mon propre enfant d'AppCompatDialogFragment qui crée ma classe étend BottomSheetDialog et qui résout l'accès au membre de comportement privé et le met dans la méthode onStart à l'état STATE_EXPANDED.

public class ExpandedBottomSheetDialog extends BottomSheetDialog {

    protected BottomSheetBehavior<FrameLayout> mBehavior;

    public ExpandedBottomSheetDialog(@NonNull Context context, @StyleRes int theme) {
        super(context, theme);
    }

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

        try {
            Field privateField = BottomSheetDialog.class.getDeclaredField("mBehavior");
            privateField.setAccessible(true);
            mBehavior = (BottomSheetBehavior<FrameLayout>) privateField.get(this);
        } catch (NoSuchFieldException e) {
            // do nothing
        } catch (IllegalAccessException e) {
            // do nothing
        }
    }

    @Override
    protected void onStart() {
        super.onStart();
        if (mBehavior != null) {
            mBehavior.setSkipCollapsed(true);
            mBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
        }
    }
}


public class AddAttachmentBottomSheetDialog extends AppCompatDialogFragment {

    ....

    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        return new ExpandedBottomSheetDialog(getContext(), getTheme());
    }

    ....
}
Petr Daňa
la source
1

La manière la plus simple que j'ai implémentée est la suivante: Ici, nous trouvons android.support.design.R.id.design_bottom_sheet et définissons l'état de la feuille du bas comme EXPANDED .

Sans cela, ma feuille du bas était toujours bloquée dans l'état COLLAPSED si la hauteur de la vue est supérieure à 0,5 de la hauteur de l'écran et que je dois faire défiler manuellement pour afficher la feuille du bas complète.

class BottomSheetDialogExpanded(context: Context) : BottomSheetDialog(context) {

    private lateinit var mBehavior: BottomSheetBehavior<FrameLayout>

    override fun setContentView(view: View) {
        super.setContentView(view)
        val bottomSheet = window.decorView.findViewById<View>(android.support.design.R.id.design_bottom_sheet) as FrameLayout
        mBehavior = BottomSheetBehavior.from(bottomSheet)
        mBehavior.state = BottomSheetBehavior.STATE_EXPANDED
    }

    override fun onStart() {
        super.onStart()
        mBehavior.state = BottomSheetBehavior.STATE_EXPANDED
    }
}
Akhil
la source
1

Semblable à uregentx answer, dans kotlin , vous pouvez déclarer votre classe de fragment qui s'étend de BottomSheetDialogFragment, et lorsque la vue est créée, vous pouvez définir l'état par défaut de l'écouteur de boîte de dialogue après l'affichage de la boîte de dialogue.

STATE_COLLAPSED: la feuille du bas est visible mais n'affiche que sa hauteur d'aperçu.

STATE_EXPANDED: La feuille du bas est visible et sa hauteur maximale.

STATE_HALF_EXPANDED: La feuille du bas est visible mais n'affiche que sa demi-hauteur.

class FragmentCreateGroup : BottomSheetDialogFragment() {
      ...

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,savedInstanceState: Bundle?): View? {
        // Set dialog initial state when shown
        dialog?.setOnShowListener {
            val bottomSheetDialog = it as BottomSheetDialog
            val sheetInternal: View = bottomSheetDialog.findViewById(com.google.android.material.R.id.design_bottom_sheet)!!
            BottomSheetBehavior.from(sheetInternal).state = BottomSheetBehavior.STATE_COLLAPSED
        }

        val view = inflater.inflate(R.layout.fragment_create_group, container, false)
        ...

        return view
    }
}

N'oubliez pas d'utiliser l'implémentation de la conception matérielle dans gradle.

implementation "com.google.android.material:material:$version"

Jetez également un œil aux feuilles de fond de référence de conception de matériaux

FJCG
la source
D'où vient la dialog?variable dans onCreateView?
Eric Smith
dialogest une propriété de la classe DialogFragment, est en fait un Getter. Dans cet exemple, j'ai utilisé ce getter pour obtenir l'instance DialogFragment actuelle et y accéder setOnShowListener. Peut-être avez-vous déjà utilisé ce type d'instructions dans votre projet, par exemple dans une activité, pour accéder à la barre d'action actionBargetter est utilisé, vous pouvez donc modifier ce composant, par exempleactionBar?.subtitle = "abcd"
FJCG
1

Ma réponse est plus ou moins la même que la plupart des réponses ci-dessus avec une légère modification. Au lieu d'utiliser findViewById pour trouver d'abord la vue de bas de page, j'ai préféré ne pas coder en dur les identifiants de ressources de vue du cadre, car ils pourraient changer à l'avenir.

setOnShowListener(dialog -> {
            BottomSheetBehavior bottomSheetBehavior = ((BottomSheetDialog)dialog).getBehavior();
            bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
        });
Prateek
la source
1
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
    return super.onCreateDialog(savedInstanceState).apply {
        setOnShowListener {
            (this@TipsBottomDialogFragment.dialog as BottomSheetDialog).behavior.setState(
                BottomSheetBehavior.STATE_EXPANDED
            )
        }
    }
}
leigo
la source
1

Postez ceci ici aux futurs lecteurs, car je pense que nous pouvons utiliser une autre solution.

J'essayais de résoudre le même problème que vous avez décrit avec un BottomSheetDialog.

Je n'aime pas utiliser les identifiants Android internes et je viens de BottomSheetDialog getBehaviordécouvrir qu'il existe une méthode à l'intérieur que vous pouvez utiliser:

Vous pouvez l'utiliser dans votre BottomSheetDialog:

behavior.state = BottomSheetBehavior.STATE_EXPANDED

En utilisant, BottomSheetDialogFragmentvous pouvez faire la même conversion de la boîte de dialogue de ce DialogFragment vers BottomSheetDialog.

culebrins
la source
1

BottomSheetDialogFragment :

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    (dialog as? BottomSheetDialog)?.behavior?.state = STATE_EXPANDED
}

ou lorsque vous êtes prêt à montrer:

private fun onContentLoaded(items: List<Any>) {
    adapter.submitList(items)
    (dialog as? BottomSheetDialog)?.behavior?.state = STATE_EXPANDED
}
Yeldar.N
la source
0

Dans votre classe Kotlin BottomSheetDialogFragment, remplacez onCreateDialog comme ci-dessous

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        val bottomSheetDialog = super.onCreateDialog(savedInstanceState) as BottomSheetDialog
        bottomSheetDialog.setOnShowListener {
            val bottomSheet =
                bottomSheetDialog.findViewById<FrameLayout>(
                    com.google.android.material.R.id.design_bottom_sheet
                )
            val behavior = BottomSheetBehavior.from(bottomSheet!!)
            behavior.skipCollapsed = true
            behavior.state = BottomSheetBehavior.STATE_EXPANDED
        }
        return bottomSheetDialog
    }
Emi Raz
la source