Defina o estado de BottomSheetDialogFragment como expandido


98

Como você define o estado de um fragmento que BottomSheetDialogFragmentse estende a expandido BottomSheetBehavior#setState(STATE_EXPANDED)usando a Android Support Design Library (v23.2.1)?

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

As folhas inferiores são definidas como STATE_COLLAPSED no início. Chame BottomSheetBehavior # setState (STATE_EXPANDED) se quiser expandi-lo. Observe que você não pode chamar o método antes de visualizar os layouts.

A prática sugerida requer que uma visão seja aumentada primeiro, mas não tenho certeza de como configurarei BottomSheetBehaviour em um fragmento ( BottomSheetDialogFragment).

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

Respostas:


234

"Observe que você não pode chamar o método antes de visualizar os layouts."

O texto acima é a pista.

Os diálogos têm um ouvinte que é disparado assim que o diálogo é mostrado . A caixa de diálogo não pode ser exibida se não estiver disposta.

Portanto, no onCreateDialog()de sua folha de fundo modal ( BottomSheetFragment), antes de retornar o diálogo (ou em qualquer lugar, uma vez que você tenha uma referência para o diálogo), chame:

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

No meu caso, meu costume BottomSheetacabou sendo:

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

Avise-me se isso ajudar.

ATUALIZAR

Observe que você também pode substituir 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;
    }
}

Mas eu realmente não vejo por que alguém iria querer fazer isso já que a base BottomSheetFragmentnão faz nada além de retornar um BottomSheetDialog.

ATUALIZAÇÃO PARA ANDROIDX

Ao usar o AndroidX, o recurso anteriormente encontrado em android.support.design.R.id.design_bottom_sheetagora pode ser encontrado em com.google.android.material.R.id.design_bottom_sheet.


Obrigado, tentei este método. Isso faz com que BottomSheetDialogFragmentpareça instável (parece pular quadros na animação de abertura) à medida que vai do comportamento recolhido ao expandido. Editar: Testado em dispositivos Android Marshmallow e KitKat
user2560886

1
Funciona perfeitamente para mim. Sem pular. Você está fazendo alguma coisa além de apenas retornar um diálogo? Agradecemos se você atualizar sua postagem com seu código para que eu possa ter uma ideia melhor.
efemoney

5
É só eu não consigo encontrar o pacote android.support.design.Rapós atualizar as bibliotecas de suporte?
natario

2
Também estou tendo problemas para resolver android.support.design.R, assim como @natario. Estou usando implementation "com.google.android.material:material:1.0.0". Também estou usando o AndroidX no projeto.
hsson

24
Ao usar o AndroidX, o recurso pode ser encontrado emcom.google.android.material.R.id.design_bottom_sheet
urgente de

46

A resposta de efeturi é ótima, no entanto, se você quiser usar onCreateView () para criar sua BottomSheet, em vez de usar onCreateDialog ( ) , aqui está o código que você precisará adicionar em seu 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, você não precisa chamar getDialog. Acho que a maneira mais limpa de fazer isso é substituir onCreateView e onCreateDialog. Construa sua visualização em onCreateView (como faria com qualquer fragmento) e faça o código específico do diálogo em onCreateDialog (chame super.onCreateDialog para obter a instância)
Stimsoni

2
Isso salva meu dia. Obrigado.
AndroidRuntimeException

@Stimsoni onCreateView não é chamado se onCreateDialog for usado. developer.android.com/reference/android/support/v4/app/…
Vincent_Paing

1
@Vincent_Paing, sim, é. Em seu link anexado diz 'onCreateView não precisa ser implementado'. Não diz que não será chamado. Dê uma olhada no código-fonte aqui github.com/material-components/material-components-android/blob/… . A implementação padrão chama onCreateDialog para criar a folha inferior e cada solução acima ainda está usando onCreateView, o que significa que ambas são sempre chamadas. Apenas certifique-se de ainda chamar super.onCreateDialog () se você substituí-lo.
Stimsoni

em BottomSheetDialogFragment ele travou em onCreateView () coloquei em onViewCreated () e está perfeito! obrigado
avisper

21

Uma solução simplista e elegante:

BottomSheetDialogFragment poderia ser uma subclasse para resolver isso:

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

Portanto, estenda esta classe em vez de BottomSheetDialogFragmentcriar sua própria planilha inferior.

Nota

Altere com.google.android.material.R.id.design_bottom_sheetpara android.support.design.R.id.design_bottom_sheetse o seu projeto usa bibliotecas de suporte Android antigas.


1
Parece ser com.google.android.material.Ragora, em vez de android.support.design.R.
EpicPandaForce

@EpicPandaForce Certo. A equipe do Android no Google recentemente reformulou a antiga biblioteca de suporte.
DYS

4

Eu acho que aqueles acima são melhores. Infelizmente, não encontrei essas soluções antes de resolver. Mas escreva minha solução. bastante semelhante a todos.

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

Eu enfrento o mesmo problema. Isso é o que eu resolvi. O comportamento está oculto em BottomSheetDialog, que está disponível para obter o comportamento. Se você não quiser alterar o layout pai para CooridateLayout, pode tentar isso.

ETAPA 1: personalize o 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
   }
}

PASSO 2: faça seu fragmento estender 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);
            }
        });

Conheci NullPointException em BottomSheetBehavior.from(bottomSheet)porque d.findViewById(android.support.design.R.id.design_bottom_sheet)retorna null.

É estranho. Eu adicionei esta linha de código a Relógios no Android Monitor no modo DEBUG e descobri que ela retorna o Framelayout normalmente.

Aqui está o código 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;
    }

Ocasionalmente, descobri que R.id.design_bottom_sheetnão é igual a android.support.design.R.id.design_bottom_sheet. Eles têm valores diferentes em diferentes R.java.

Então eu mudo android.support.design.R.id.design_bottom_sheetpara 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);
            }
        });

Não há mais NullPointException agora.


3

Aplicar BottomsheetDialogFragmentestado em onResumeresolverá este problema

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

onShow(DialogInterface dialog)e postDelayedpode causar falha de animação


2

Todos os resultados com o uso de onShow () causam bug de renderização aleatória quando o teclado virtual é exibido. Veja a captura de tela abaixo - a caixa de diálogo BottomSheet não está na parte inferior da tela, mas está posicionada como o teclado foi exibido. Este problema não ocorre sempre, mas com bastante frequência.

insira a descrição da imagem aqui

ATUALIZAR

Minha solução com reflexão de membro privado é desnecessária. Usar postDelayed (com cerca de 100 ms) para criar e mostrar o diálogo após ocultar o teclado virtual é a melhor solução. Então, as soluções acima com onShow () estão ok.

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

Portanto, implemento outra solução, mas exige o uso de reflexão, porque BottomSheetDialog tem todos os membros como privados. Mas resolve bug de renderização. A classe BottomSheetDialogFragment é apenas AppCompatDialogFragment com o método onCreateDialog que cria BottomSheetDialog. Eu crio o próprio filho de AppCompatDialogFragment que cria minha classe extends BottomSheetDialog e que resolve o acesso ao membro de comportamento privado e o define no método onStart para o 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());
    }

    ....
}

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

2

Postando isso aqui para futuros leitores, pois acho que agora podemos usar outra solução.

Eu estava tentando resolver o mesmo problema que você descreveu com a BottomSheetDialog.

Não gosto de usar IDs internos do Android e acabei de descobrir que há um método interno BottomSheetDialog getBehaviorque você pode usar:

Você pode usar isso dentro de BottomSheetDialog:

behavior.state = BottomSheetBehavior.STATE_EXPANDED

Usando, BottomSheetDialogFragmentvocê pode fazer o mesmo convertendo a caixa de diálogo desse DialogFragment para BottomSheetDialog.


1

A maneira mais fácil que implementei é a abaixo, aqui estamos encontrando android.support.design.R.id.design_bottom_sheet e definindo o estado da folha inferior como EXPANDIDO .

Sem isso, minha folha inferior sempre ficava presa no estado RECOLHIDO se a altura da visualização for maior que 0,5 da altura da tela e eu tiver que rolar manualmente para visualizar a folha inferior inteira.

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

Semelhante à resposta uregentx , no kotlin , você pode declarar sua classe de fragmento que se estende de BottomSheetDialogFragment, e quando a visualização é criada, você pode definir o estado padrão do ouvinte de diálogo após a caixa de diálogo ser exibida.

STATE_COLLAPSED: a folha inferior está visível, mas mostrando apenas a altura da peek.

STATE_EXPANDED: A folha inferior é visível e sua altura máxima.

STATE_HALF_EXPANDED: A folha inferior é visível, mas mostrando apenas sua meia 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
    }
}

Lembre-se de usar a implementação de design de material no gradle.

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

Também dê uma olhada nas folhas inferiores de referência do material design


De onde vem a dialog?variável em onCreateView?
Eric Smith

dialogé uma propriedade da classe DialogFragment, na verdade é um Getter. Neste exemplo, usei esse getter para obter a instância DialogFragment atual e setOnShowListenerpara ela. Pode ser que você já tenha usado esse tipo de instruções em seu projeto, por exemplo em uma atividade, para acessar o actionBargetter da barra de ação , então você pode modificar esse componente, por exemploactionBar?.subtitle = "abcd"
FJCG

1

Minha resposta é mais ou menos igual à maioria das respostas acima, com uma ligeira modificação. Em vez de usar findViewById para encontrar primeiro a visualização da folha inferior, preferi não codificar permanentemente nenhum ID de recurso de visualização de estrutura, pois eles podem mudar no futuro.

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

1

BottomSheetDialogFragment :

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

ou quando estiver pronto para mostrar:

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

1

Em sua classe Kotlin BottomSheetDialogFragment, substitua onCreateDialog como abaixo

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
    }
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.