読者です 読者をやめる 読者になる 読者になる

Life is Really Short, Have Your Life!!

ござ先輩の主に技術的なメモ

DialogFragmentの汎用化を目指した話

Android

furudate.hatenablog.com

上記を大変参考にさせて頂いて実装を開始した。メッセージとOK/NGのコールバックだけあれば、ダイアログは生きていける。

ただ、場合によってメッセージだけじゃなくてEditTextを表示する必要がある場合があった。もちろんsetItemsしたい時もあった。こうなるとOK/NGだけでは生きていけない。

とりえあずsetViewを上記のコードに含み入れたものがこちら。無理やり感あるけど、まぁ別Fragmentにする理由もなかった。

public class ShowDialogFragment extends DialogFragment{

    private DialogListener listener = null;
    /**
     * ファクトリーメソッド
     * @param type ダイアログタイプ 0:OKボタンのみ 1:OK, NGボタン
     */
    public static ShowDialogFragment newInstance(String title, String message,int type,int viewId){
        ShowDialogFragment instance = new ShowDialogFragment();

        // ダイアログに渡すパラメータはBundleにまとめる
        Bundle arguments = new Bundle();
        arguments.putString("title", title);
        arguments.putString("message", message);
        arguments.putInt("type", type);
        arguments.putInt("viewId", viewId);

        instance.setArguments(arguments);

        return instance;
    }

    /**
     * AlertDialog作成
     */
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState){
        String title = getArguments().getString("title");
        String message = getArguments().getString("message");
        int type = getArguments().getInt("type");

        //Viewを初期化する
        int viewId =  getArguments().getInt("viewId");   
        LayoutInflater inflater = getActivity().getLayoutInflater();
        final View view = inflater.inflate(viewId, null, false);

        AlertDialog.Builder alert = new AlertDialog.Builder(getActivity())
                .setTitle(title)
                .setMessage(message)
                .setPositiveButton("OK", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        listener.doPositiveClick(view);
                        dismiss();
                    }
                });
        if (type == 1){
            alert.setNegativeButton("キャンセル", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    listener.doNegativeClick(view);
                    dismiss();
                }
            });
        }
        //超強引
        if(viewId != R.layout.empty) alert.setView(view);

        return alert.create();
    }

    /**
     * リスナーを追加
     */
    public void setDialogListener(DialogListener listener){
        this.listener = listener;
    }

    @Override
    public void onDetach() {
        super.onDetach();
        if(listener !=null) listener = null;
    }
}

//Listener

import android.view.View;
import java.util.EventListener;

public interface DialogListener extends EventListener {

    /**
     * OKボタンが押されたイベントを通知
     */
    void doPositiveClick(View view);

    /**
     * Cancelボタンが押されたイベントを通知
     */
    void doNegativeClick(View view);
}

inflateする時は必ずXMLファイルが必要になる

viewのオブジェクトを外から与えるには、layoutInflatorを使ってres/layoutのxmlのIDを与えるしか方法がない。で、それをsetView()。適当な数字を入れてinflateするとResourceNotFoundExceptionが発生するので、setViewする必要がないダイアログでも適当なlayoutのxml指定が必要になった。空レイアウトしかないempty.xmlを作った。

空レイアウトをそのままsetViewするとマージンが取られてしまい、ダイアログのメッセージとボタンの間に変な間が開いてしまう。ソースを追うのもめんどいので、argument経由で渡したviewIdがemptyのものじゃなければsetViewするというやり方を取った。

emptyだったらviewを初期化しなくても良いじゃないと一瞬思ったけど、クロージャのコールバックに渡す時はfinal修飾子をつけねばならない制約があるため、常にinflateするようにした。ちょっとオーバーヘッド。

使い方は下記の通り。上記のコードと大差ない。引数にviewのIdが増え、リスナーのコールバックにsetViewしたレイアウトがそのまま引数として渡る。setViewしたviewの状態をコールバックで拾えるので、ほとんどこれで行けるやろ。

            ShowDialogFragment f = ShowDialogFragment.newInstance(
                    "カートの保存",
                    "現在のカートの状態を名前をつけて保存します。",
                    1,R.layout.dialog_save_cart
            );
            f.setDialogListener(new DialogListener() {
                @Override
                public void doPositiveClick(View view) {
                    EditText t =(EditText)view.findViewById(R.id.dialog_save_cart_name);
                    Log.d("DEBUG",t.getText().toString());
                }
                @Override
                public void doNegativeClick(View view) {
                    //do nothing.
                }
            });
            f.show(getFragmentManager(), "dialog");


が、setItemsの場合は、OK/NGだけじゃなくて、ListのItemClickリスナーが別途必要になる。ActionSheet的に使うこともあれば、複数チェックしたものを画面に返したい時もあるだろう。こうなるとわけがわからなくなりそうなので、setItemsしたいDialogFragmentは別のクラスに起こそうと思っています。

追記 2015-05-01

qiita.com

基本的な考え方は一緒。地道にArgumentsにぶち込んで地道にisset()で初期化してる。