Establecer el estado de BottomSheetDialogFragment en expandido


92

¿Cómo se configura el estado de un fragmento que se extiende BottomSheetDialogFragmenta expandido BottomSheetBehavior#setState(STATE_EXPANDED)usando la Biblioteca de diseño de soporte de Android (v23.2.1)?

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

Las hojas inferiores se establecen en STATE_COLLAPSED al principio. Llame a BottomSheetBehavior # setState (STATE_EXPANDED) si desea expandirlo. Tenga en cuenta que no puede llamar al método antes de ver diseños.

La práctica sugerida requiere que se infle una vista primero, pero no estoy seguro de cómo estableceré BottomSheetBehaviour en un fragment ( BottomSheetDialogFragment).

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

Respuestas:


221

"Tenga en cuenta que no puede llamar al método antes de ver diseños".

El texto anterior es la clave.

Los diálogos tienen un oyente que se activa una vez que se muestra el diálogo . El cuadro de diálogo no se puede mostrar si no está diseñado.

Entonces, en la onCreateDialog()parte inferior de su hoja modal ( BottomSheetFragment), justo antes de devolver el diálogo (o en cualquier lugar, una vez que tenga una referencia al diálogo), llame a:

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

En mi caso, mi costumbre BottomSheetresultó ser:

@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;
    }
}

Déjeme saber si esto ayuda.

ACTUALIZAR

Tenga en cuenta que también puede anular BottomSheetDialogFragmentcomo:

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;
    }
}

Pero realmente no veo por qué alguien querría hacer eso ya que la base BottomSheetFragmentno hace nada más que devolver un BottomSheetDialog.

ACTUALIZAR PARA ANDROIDX

Al usar AndroidX, el recurso que se encontraba anteriormente en android.support.design.R.id.design_bottom_sheetahora se puede encontrar en com.google.android.material.R.id.design_bottom_sheet.


Gracias, probé este método. Hace que BottomSheetDialogFragmentparezca inestable (parece saltar fotogramas en la animación de apertura) a medida que pasa del comportamiento contraído al expandido. Editar: probado esto en dispositivos Android Marshmallow y KitKat
user2560886

1
Funciona perfectamente para mí. No saltar. ¿Está haciendo algo más además de devolver un diálogo? Agradecería si actualiza su publicación con su código para que pueda tener una mejor idea.
efemoney

5
¿Es solo que no puedo encontrar el paquete android.support.design.Rdespués de actualizar las bibliotecas de soporte?
natario

2
También tengo problemas con la resolución android.support.design.R, como @natario. Estoy usando implementation "com.google.android.material:material:1.0.0". También estoy usando AndroidX en el proyecto.
hsson

21
Al usar AndroidX, el recurso se puede encontrar encom.google.android.material.R.id.design_bottom_sheet
urgentex

45

La respuesta de efeturi es excelente, sin embargo, si desea usar onCreateView () para crear su BottomSheet, en lugar de ir con onCreateDialog () , aquí está el código que deberá agregar en su método 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);
}

3
Alternativamente, no necesita llamar a getDialog en absoluto. Encuentro que la forma más limpia de hacerlo es anular tanto onCreateView como onCreateDialog. Construya su vista en onCreateView (como lo haría con cualquier fragmento) y haga el código específico del diálogo en onCreateDialog (llame a super.onCreateDialog para obtener la instancia)
Stimsoni

2
Esto me salva el día. Gracias.
AndroidRuntimeException

@Stimsoni onCreateView no se llama si se usa onCreateDialog. developer.android.com/reference/android/support/v4/app/…
Vincent_Paing

1
@Vincent_Paing, sí lo es. En su enlace adjunto dice 'onCreateView no necesita ser implementado'. No dice que no se llamará. Eche un vistazo al código fuente aquí github.com/material-components/material-components-android/blob/… . La implementación predeterminada llama a onCreateDialog para crear la hoja inferior y cada solución anterior todavía usa onCreateView, lo que significa que ambas siempre se llaman. Solo asegúrese de llamar a super.onCreateDialog () si lo anula.
Stimsoni

en BottomSheetDialogFragment me bloqueó en onCreateView () Lo puse en onViewCreated () y ¡es perfecto! gracias
avisper

22

Una solución simple y elegante:

BottomSheetDialogFragment podría subclasificarse para abordar esto:

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;
    }
}

Así que amplíe esta clase en lugar de BottomSheetDialogFragmentcrear su propia hoja inferior.

Nota

Cambie com.google.android.material.R.id.design_bottom_sheeta android.support.design.R.id.design_bottom_sheetsi su proyecto usa bibliotecas de soporte de Android antiguas.


1
Parece ser com.google.android.material.Rahora en lugar de android.support.design.R.
EpicPandaForce

@EpicPandaForce Seguro. El equipo de Android de Google reempaquetó recientemente la biblioteca de soporte anterior.
DYS

4

Creo que los de arriba son mejores. Lamentablemente, no encontré esas soluciones antes de resolverlas. Pero escribe mi solución. bastante similar a todos.

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

Me enfrento al mismo problema. Esto es lo que resolví. El comportamiento está oculto en BottomSheetDialog, que está disponible para obtener el comportamiento. Si no desea cambiar su diseño principal para que sea CooridateLayout, puede intentar esto.

PASO 1: personalice el 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
   }
}

PASO 2: haz que tu fragmento extienda este fragmento personalizado

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()
   }
}

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

Me encontré con NullPointException BottomSheetBehavior.from(bottomSheet)porqued.findViewById(android.support.design.R.id.design_bottom_sheet) devuelve nulo.

Es extraño. Agregué esta línea de código a Watches en Android Monitor en modo DEBUG y encontré que devuelve Framelayout normalmente.

Aquí está el código de wrapInBottomSheeten BottomSheetDialog:

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;
    }

De vez en cuando, descubrí que R.id.design_bottom_sheetno es igual a android.support.design.R.id.design_bottom_sheet. Tienen un valor diferente en diferentes R.java.

Entonces me cambio android.support.design.R.id.design_bottom_sheeta 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);
            }
        });

No más NullPointException ahora.


3

Aplicar BottomsheetDialogFragmentestado en onResumeresolverá este problema

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

onShow(DialogInterface dialog)y postDelayedpuede causar problemas de animación


2

Todos los resultados con el uso de onShow () causan un error de renderizado aleatorio cuando se muestra el teclado virtual. Vea la captura de pantalla a continuación: el cuadro de diálogo Hoja inferior no está en la parte inferior de la pantalla, pero se coloca como se mostró el teclado. Este problema no ocurre siempre, pero sí con bastante frecuencia.

ingrese la descripción de la imagen aquí

ACTUALIZAR

Mi solución con la reflexión de un miembro privado es innecesaria. Usar postDelayed (con aproximadamente 100 ms) para crear y mostrar el diálogo después de ocultar el teclado virtual es una mejor solución. Entonces las soluciones anteriores con onShow () están bien.

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);

Entonces implemento otra solución, pero requiere el uso de la reflexión, porque BottomSheetDialog tiene todos los miembros como privados. Pero resuelve el error de renderizado. La clase BottomSheetDialogFragment es solo AppCompatDialogFragment con el método onCreateDialog que crea BottomSheetDialog. Creo el propio hijo de AppCompatDialogFragment que crea mi clase extiende BottomSheetDialog y que resuelve el acceso al miembro de comportamiento privado y lo configuro en el método onStart al estado 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());
    }

    ....
}

1

La forma más fácil que implementé es la siguiente: Aquí encontramos android.support.design.R.id.design_bottom_sheet y configuramos el estado de la hoja inferior como EXPANDED .

Sin esto, mi hoja inferior siempre estaba atascada en el estado COLLAPSED si la altura de la vista es más de 0.5 de la altura de la pantalla y tengo que desplazarme manualmente para ver la hoja inferior completa.

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
    }
}

1

Similar a la respuesta de uregentx , en kotlin , puede declarar su clase de fragmento que se extiende desde BottomSheetDialogFragment, y cuando se crea la vista, puede establecer el estado predeterminado del oyente de diálogo después de que se muestre el diálogo.

STATE_COLLAPSED: La hoja inferior es visible pero solo muestra su altura de vista.

STATE_EXPANDED: La hoja inferior es visible y su altura máxima.

STATE_HALF_EXPANDED: La hoja inferior es visible pero solo muestra su mitad de altura.

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
    }
}

Recuerde usar la implementación de material design en gradle.

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

También eche un vistazo a las hojas inferiores de referencia de material design


¿De dónde dialog?viene la variable en onCreateView?
Eric Smith

dialoges una propiedad de la clase DialogFragment, en realidad es un Getter. En este ejemplo utilicé ese captador para obtener la instancia actual de DialogFragment y setOnShowListenera ella. Puede ser que ya haya usado ese tipo de instrucciones en su proyecto, por ejemplo, en una actividad, para acceder a la barra de acción actionBarse usa el getter, por lo que puede modificar ese componente, por ejemploactionBar?.subtitle = "abcd"
FJCG

1

Mi respuesta es más o menos la misma que la mayoría de las respuestas anteriores con una ligera modificación. En lugar de usar findViewById para buscar primero la vista de la hoja inferior, he preferido no codificar ningún ID de recurso de vista de marco, ya que podrían cambiar en el futuro.

setOnShowListener(dialog -> {
            BottomSheetBehavior bottomSheetBehavior = ((BottomSheetDialog)dialog).getBehavior();
            bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
        });

1
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
    return super.onCreateDialog(savedInstanceState).apply {
        setOnShowListener {
            (this@TipsBottomDialogFragment.dialog as BottomSheetDialog).behavior.setState(
                BottomSheetBehavior.STATE_EXPANDED
            )
        }
    }
}

1

Publicando esto aquí para futuros lectores, ya que creo que podemos usar otra solución.

Estaba tratando de resolver el mismo problema que describiste con un BottomSheetDialog.

No me gusta usar ID internos de Android y acabo de descubrir que hay un método dentro BottomSheetDialog getBehaviorque puedes usar:

Puedes usar esto dentro de tu BottomSheetDialog:

behavior.state = BottomSheetBehavior.STATE_EXPANDED

Utilizando BottomSheetDialogFragmentpuede hacer lo mismo para convertir el diálogo desde ese DialogFragment a BottomSheetDialog.


1

BottomSheetDialogFragment :

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

o cuando esté listo para mostrar:

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

0

En su clase Kotlin BottomSheetDialogFragment, anule onCreateDialog como se muestra a continuación

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
    }
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.