Mostrar diálogo do fragmento?

119

Tenho alguns fragmentos que precisam mostrar um diálogo regular. Nessas caixas de diálogo, o usuário pode escolher uma resposta sim / não e, em seguida, o fragmento deve se comportar de acordo.

Agora, a Fragmentclasse não tem um onCreateDialog()método para sobrescrever, então acho que tenho que implementar os diálogos fora, no contêiner Activity. Está tudo bem, mas então a Activitynecessidade de relatar a resposta escolhida de alguma forma para o fragmento. Eu poderia, é claro, usar um padrão de retorno de chamada aqui, de modo que o fragmento se registrasse Activitycom uma classe de ouvinte e a Activity relataria a resposta por meio disso, ou algo parecido.

Mas isso parece ser uma grande bagunça para uma tarefa simples como exibir um diálogo sim-não "simples" em um fragmento. Além disso, dessa forma meu Fragmentseria menos independente.

Existe alguma maneira mais limpa de fazer isso?

Editar:

A resposta a esta pergunta não explica em detalhes como se deve usar DialogFragments para exibir caixas de diálogo de Fragments. Então, AFAIK, o caminho a seguir é:

  1. Exibir um fragmento.
  2. Quando necessário, instancie um DialogFragment.
  3. Defina o Fragment original como o destino deste DialogFragment, com .setTargetFragment().
  4. Mostre o DialogFragment com .show () do Fragment original.
  5. Quando o usuário escolhe alguma opção neste DialogFragment, notifique o Fragment original sobre esta seleção (por exemplo, o usuário clicou em 'sim'), você pode obter a referência do Fragment original com .getTarget ().
  6. Dispense o DialogFragment.
Zsombor Erdődy-Nagy
fonte
1
Sua técnica funciona, exceto quando ocorre uma rotação da tela. Eu consigo uma força perto então. Alguma ideia?
Weston
@Weston confira a primeira resposta de Zsombor: stackoverflow.com/questions/8235080/…
mayimaus

Respostas:

37

Você deve usar um DialogFragment em vez disso.

mgv
fonte
9
Infelizmente, essa abordagem é um pouco mais detalhada do que a abordagem clássica de diálogos gerenciados das revisões anteriores do Android, mas agora é o método preferido. Você pode evitar fazer referência a Activityinteiramente usando os métodos putFragmente getFragmentde FragmentManager, permitindo que o DialogFragmentreporte diretamente ao fragmento de chamada (mesmo após mudanças de orientação).
Dave
E se você tiver um ListFragment que precisa exibir Dialogs, mas não pode estender os dois
marchinram
16
A subclasse ListFragment usaria DialogFragments instanciando novos, não criando uma subclasse DialogFragment. (Um DialogFragment é um diálogo implementado como um Fragment, não um Fragment que pode exibir Dialogs.)
nmr
4
Por favor, adicione algum trecho para que possamos entender eaisly
Arpit Patel
35

Devo duvidar cautelosamente da resposta aceita anteriormente de que usar um DialogFragment é a melhor opção. O propósito (principal) pretendido do DialogFragment parece ser exibir fragmentos que são diálogos em si, não exibir fragmentos que têm diálogos para exibir.

Acredito que usar a atividade do fragmento para mediar entre o diálogo e o fragmento é a opção preferível.

Mark D
fonte
10
Uma vez que a onCreateDialogabordagem de dialogs gerenciados ( ) logo será descontinuada, eu discordo e digo que esse DialogFragmenté realmente o caminho a seguir.
Dave
4
Resposta aceita significa usar um DialogFragment em vez de um Dialog, não em vez de um ListFragment, AFAICS.
nmr
Na verdade, um fragmento de caixa de diálogo pode ser usado como uma caixa de diálogo e um fragmento normal - consulte a incorporação de developer.android.com/reference/android/app/DialogFragment.html
Clive Jefferies
@CliveJefferies Pode, mas ainda não deve mostrar outros diálogos de dentro de si mesmo. Acho que os caras aqui estão entendendo a pergunta errada.
Marcel Bro
@anoniim depende de como o fragmento de diálogo está sendo usado. Se for usado como um fragmento regular, não há problema. Se estiver sendo usado como um diálogo, então sim, você não deveria mostrar outro diálogo.
Clive Jefferies
24

Aqui está um exemplo completo de um DialogFragment sim / não:

A classe:

public class SomeDialog extends DialogFragment {

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        return new AlertDialog.Builder(getActivity())
            .setTitle("Title")
            .setMessage("Sure you wanna do this!")
            .setNegativeButton(android.R.string.no, new OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    // do nothing (will close dialog)
                }
            })
            .setPositiveButton(android.R.string.yes,  new OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    // do something
                }
            })
            .create();
    }
}

Para iniciar o diálogo:

        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        // Create and show the dialog.
        SomeDialog newFragment = new SomeDialog ();
        newFragment.show(ft, "dialog");

Você também pode permitir que a classe implemente onClickListener e use-o em vez de ouvintes incorporados.

Retorno de chamada para atividade

Se você deseja implementar o retorno de chamada, é assim que é feito em sua atividade:

YourActivity extends Activity implements OnFragmentClickListener

e

@Override
public void onFragmentClick(int action, Object object) {
    switch(action) {
        case SOME_ACTION:
        //Do your action here
        break;
    }
}

A classe de retorno de chamada:

public interface OnFragmentClickListener {
    public void onFragmentClick(int action, Object object);
}

Então, para realizar um retorno de chamada de um fragmento, você precisa se certificar de que o listener está conectado assim:

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    try {
        mListener = (OnFragmentClickListener) activity;
    } catch (ClassCastException e) {
        throw new ClassCastException(activity.toString() + " must implement listeners!");
    }
}

E um retorno de chamada é executado assim:

mListener.onFragmentClick(SOME_ACTION, null); // null or some important object as second parameter.
Warpzit
fonte
4
Isso não explica como iniciá-lo a partir de um Fragmento
akohout
@raveN você simplesmente faria um retorno de chamada para sua atividade que, então, iniciaria o fragmento.
Warpzit
@ Warpzit Obrigado pela resposta abrangente. Tenho medo de tocar no FragmentManager, com o qual já estou fazendo coisas horríveis ... Como posso passar eventos onClick de volta para o Fragment (não-Dialog) que precisava da entrada do usuário? BTW, a transação não deveria ser adicionada ao backstack?
kaay
@kaay da atividade, você pode chamar qualquer método público no fragmento fornecido que precise da nova entrada. A partir daí, deve ser muito fácil atualizar com o novo conteúdo.
Warpzit de
1
@RichardLeMesurier De fato, fragmentos são altos e baixos.
Warpzit
13

Para mim, foi o seguinte

MyFragment:

public class MyFragment extends Fragment implements MyDialog.Callback
{
    ShowDialog activity_showDialog;

    @Override
    public void onAttach(Activity activity)
    {
        super.onAttach(activity);
        try
        {
            activity_showDialog = (ShowDialog)activity;
        }
        catch(ClassCastException e)
        {
            Log.e(this.getClass().getSimpleName(), "ShowDialog interface needs to be     implemented by Activity.", e);
            throw e;
        }
    }

    @Override
    public void onClick(View view) 
    {
        ...
        MyDialog dialog = new MyDialog();
        dialog.setTargetFragment(this, 1); //request code
        activity_showDialog.showDialog(dialog);
        ...
    }

    @Override
    public void accept()
    {
        //accept
    }

    @Override
    public void decline()
    {
        //decline
    }

    @Override
    public void cancel()
    {
        //cancel
    }

}

MyDialog:

public class MyDialog extends DialogFragment implements View.OnClickListener
{
    private EditText mEditText;
    private Button acceptButton;
    private Button rejectButton;
    private Button cancelButton;

    public static interface Callback
    {
        public void accept();
        public void decline();
        public void cancel();
    }

    public MyDialog()
    {
        // Empty constructor required for DialogFragment
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    {
        View view = inflater.inflate(R.layout.dialogfragment, container);
        acceptButton = (Button) view.findViewById(R.id.dialogfragment_acceptbtn);
        rejectButton = (Button) view.findViewById(R.id.dialogfragment_rejectbtn);
        cancelButton = (Button) view.findViewById(R.id.dialogfragment_cancelbtn);
        acceptButton.setOnClickListener(this);
        rejectButton.setOnClickListener(this);
        cancelButton.setOnClickListener(this);
        getDialog().setTitle(R.string.dialog_title);
        return view;
    }

    @Override
    public void onClick(View v)
    {
        Callback callback = null;
        try
        {
            callback = (Callback) getTargetFragment();
        }
        catch (ClassCastException e)
        {
            Log.e(this.getClass().getSimpleName(), "Callback of this class must be implemented by target fragment!", e);
            throw e;
        }

        if (callback != null)
        {
            if (v == acceptButton)
            {   
                callback.accept();
                this.dismiss();
            }
            else if (v == rejectButton)
            {
                callback.decline();
                this.dismiss();
            }
            else if (v == cancelButton)
            {
                callback.cancel();
                this.dismiss();
            }
        }
    }
}

Atividade:

public class MyActivity extends ActionBarActivity implements ShowDialog
{
    ..

    @Override
    public void showDialog(DialogFragment dialogFragment)
    {
        FragmentManager fragmentManager = getSupportFragmentManager();
        dialogFragment.show(fragmentManager, "dialog");
    }
}

Layout DialogFragment:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/dialogfragment_textview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="10dp"
        android:text="@string/example"/>

    <Button
        android:id="@+id/dialogfragment_acceptbtn"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:layout_centerHorizontal="true"
        android:layout_below="@+id/dialogfragment_textview"
        android:text="@string/accept"
        />

    <Button
        android:id="@+id/dialogfragment_rejectbtn"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:layout_alignLeft="@+id/dialogfragment_acceptbtn"
        android:layout_below="@+id/dialogfragment_acceptbtn"
        android:text="@string/decline" />

     <Button
        android:id="@+id/dialogfragment_cancelbtn"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:layout_marginBottom="20dp"
        android:layout_alignLeft="@+id/dialogfragment_rejectbtn"
        android:layout_below="@+id/dialogfragment_rejectbtn"
        android:text="@string/cancel" />

     <Button
        android:id="@+id/dialogfragment_heightfixhiddenbtn"
        android:layout_width="200dp"
        android:layout_height="20dp"
        android:layout_marginTop="10dp"
        android:layout_marginBottom="20dp"
        android:layout_alignLeft="@+id/dialogfragment_cancelbtn"
        android:layout_below="@+id/dialogfragment_cancelbtn"
        android:background="@android:color/transparent"
        android:enabled="false"
        android:text=" " />
</RelativeLayout>

Como o nome dialogfragment_heightfixhiddenbtnmostra, eu simplesmente não consegui descobrir uma maneira de consertar que a altura do botão inferior foi cortada pela metade, apesar de dizer wrap_content, então adicionei um botão oculto para ser "cortado" ao meio. Desculpe pelo hack.

EpicPandaForce
fonte
1
+1, o conjunto de referência setTargetFragment()é recriado corretamente pelo sistema quando ele reinicia o conjunto de Atividade / Fragmento após a rotação. Portanto, a referência apontará para o novo alvo automaticamente.
Richard Le Mesurier
Eu me socaria no nariz agora por não usar ButterKnife naquela época, no entanto. Use ButterKnife, torna o seu tratamento de visualização muito mais bonito.
EpicPandaForce
E você pode usar Otto para enviar eventos de Fragment to Activity, de forma que não precise da mágica de anexação de interface. Exemplo de Otto aqui: stackoverflow.com/a/28480952/2413303
EpicPandaForce
@EpicPandaForce Por favor, você pode adicionar a interface / classe "ShowDialog"? É a única coisa que falta em seu exemplo.
ntrch
@ntrch erapublic interface ShowDialog { void showDialog(DialogFragment dialogFragment); }
EpicPandaForce
3
 public void showAlert(){


     AlertDialog.Builder alertDialog = new AlertDialog.Builder(getActivity());
     LayoutInflater inflater = getActivity().getLayoutInflater();
     View alertDialogView = inflater.inflate(R.layout.test_dialog, null);
     alertDialog.setView(alertDialogView);

     TextView textDialog = (TextView) alertDialogView.findViewById(R.id.text_testDialogMsg);
     textDialog.setText(questionMissing);

     alertDialog.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
         public void onClick(DialogInterface dialog, int which) {
             dialog.cancel();
         }
     });
     alertDialog.show();

}

onde .test_dialog é um xml personalizado

Alex Zaraos
fonte
2

Eu também sou um iniciante e honestamente não consegui encontrar uma resposta satisfatória que pudesse entender ou implementar.

Então aqui está um link externo que realmente me ajudou a conseguir o que queria. É muito simples e fácil de seguir.

http://www.helloandroid.com/tutorials/how-display-custom-dialog-your-android-application

ISSO QUE TENTEI OBTER COM O CÓDIGO:

Eu tenho uma MainActivity que hospeda um Fragment. Eu queria que uma caixa de diálogo aparecesse no topo do layout para solicitar a entrada do usuário e, em seguida, processar a entrada de acordo. Veja uma captura de tela

Aqui está a aparência do onCreateView do meu fragmento

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

    View rootView = inflater.inflate(R.layout.fragment_home_activity, container, false);

    Button addTransactionBtn = rootView.findViewById(R.id.addTransactionBtn);

    addTransactionBtn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            Dialog dialog = new Dialog(getActivity());
            dialog.setContentView(R.layout.dialog_trans);
            dialog.setTitle("Add an Expense");
            dialog.setCancelable(true);

            dialog.show();

        }
    });

Espero que te ajude

Avise-me se houver alguma confusão. :)

Junaid Aziz
fonte
0
    public static void OpenDialog (Activity activity, DialogFragment fragment){

    final FragmentManager fm = ((FragmentActivity)activity).getSupportFragmentManager();

    fragment.show(fm, "tag");
}
Elmar Fazlagic
fonte
3
por favor, acrescente alguma explicação à sua resposta
RealCheeseLord