1. 程式人生 > >還在用Dialog嗎——DialogFragment帶你體驗完美高效率

還在用Dialog嗎——DialogFragment帶你體驗完美高效率

DialogFragment帶你體驗完美高效率

最近研究了DialogFragment形式的dialog,發現有很多優勢,與普通的dialog一起做了一些比較和總結。

效果如下:

Markdown

最近學習彈框時發現有三種類型的可供使用,
PopupWindow、dialog,DialogFragment。

比如說需求:

  • 只攔截自身所佔空間部分的事件,其餘空間的點選事件不處理

  • 可以根據改變View的佈局排列方式,View是否設定底部背景及居中方式

雖然在功能上 PopupWindow 更符合需要,dialog也能做到,但是使用 DialogFragment 程式碼更簡潔、更方便封裝功能模組。

DialogFragment

基於Fragment的DialogFragment。

  • 從程式碼的編寫角度看,Dialog使用起來要更為簡單

  • Android 官方推薦使用 DialogFragment 來代替 Dialog ,可以讓它具有更高的可複用性(降低耦合)和更好的便利性(很好的處理螢幕翻轉的情況)。

  • DialogFragment果然有一個非常好的特性(在手機配置變化,導致Activity需要重新建立時

  • 例如旋屏,基於DialogFragment的對話方塊將會由FragmentManager自動重建,然而基於Dialog實現的對話方塊則沒有這樣的能力)。

下面是兩段例項程式碼:

他們使用的介面都一樣:(dialog.xml)

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

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_launcher" />

</LinearLayout>

1.基於Dialog實現的對話方塊

public class MainActivity extends Activity {
    private Button clk;
    private Dialog dialog;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        clk = (Button) findViewById(R.id.clk);
        dialog = new Dialog(this);
        dialog.setContentView(R.layout.dialog);
        clk.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                dialog.show();
            }
        });
    }
}

當我們點選按鈕時,會彈出對話方塊(內容為android logo),當我們旋轉屏幕後,Activity重新建立,整個Activity的介面沒有問題,而對話方塊消失了。

除此之外,其實還有一個問題,就是在logcat中會看到異常資訊:Android..leaked .. window,這是因為在Activity結束之前,Android要求所有的Dialog必須要關閉。

我們旋屏後,Activity會被重建,而上面的程式碼邏輯並沒有考慮到對話方塊的狀態以及是否已關閉。

優化dialog程式碼修改為:

public class MainActivity extends Activity {
    private Button clk;
    private Dialog dialog;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        clk = (Button) findViewById(R.id.clk);
        dialog = new Dialog(this);
        dialog.setContentView(R.layout.dialog);
        clk.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                dialog.show();
            }
        });

        //使用者恢復對話方塊的狀態
        if(savedInstanceState != null && savedInstanceState.getBoolean("dialog_show"))
            clk.performClick();
    }

    /**
     * 用於儲存對話方塊的狀態以便恢復
     */
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        if(dialog != null && dialog.isShowing())
            outState.putBoolean("dialog_show", true);
        else
            outState.putBoolean("dialog_show", false);
    }

    /**
     * 在Activity銷燬之前,確保對話方塊以關閉
     */
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if(dialog != null && dialog.isShowing())
            dialog.dismiss();
    }
}

2. 基於DialogFragment的對話方塊

與上面的對話方塊使用同樣的介面佈局,此處僅僅展現一個簡單對話方塊,因此只重寫了onCreateView方法

public class MyDialogFragment extends DialogFragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.dialog, container, false);
        return v;
    }
}


public class MainActivity extends FragmentActivity {
    private Button clk;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        clk = (Button) findViewById(R.id.clk);
        clk.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                MyDialogFragment mdf = new MyDialogFragment();
                FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
                ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
                mdf.show(ft, "df");
            }
        });
    }
}

這兩段程式碼可以實現第一種方式的同樣功能,此處我們並沒有去關心對話方塊的重建,以及Activity銷燬前對話方塊是否已關閉,這一切都是由FragmentManager來管理。

其實DialogFragment還擁有fragment的優點,即可以在一個Activity內部實現回退(因為FragmentManager會管理一個回退棧)

建立:

建立 DialogFragment 有兩種方式:

  • 覆寫其 onCreateDialog 方法

    • *應用場景*:一般用於建立替代傳統的 Dialog 對話方塊的場景,UI 簡單,功能單一。
      方法
  • 覆寫其 onCreateView 方法

    • *應用場景*
      一般用於建立複雜內容彈窗或全屏展示效果的場景,UI 複雜,功能複雜,一般有網路請求等非同步操作。

覆寫其 onCreateDialog

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
    //為了樣式統一和相容性,可以使用 V7 包下的 AlertDialog.Builder
    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
    // 設定主題的構造方法
    // AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), R.style.CustomDialog);
    builder.setTitle("注意:")
           .setMessage("是否退出應用?")
           .setPositiveButton("確定", null)
           .setNegativeButton("取消", null)
           .setCancelable(false);
           //builder.show(); // 不能在這裡使用 show() 方法
    return builder.create();
}  

自定義 View 來建立

  @Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
    // 設定主題的構造方法
    // AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), R.style.CustomDialog);
    LayoutInflater inflater = getActivity().getLayoutInflater();  
    View view = inflater.inflate(R.layout.fragment_dialog, null);  
    builder.setView(view) 
    // Do Someting,eg: TextView tv = view.findViewById(R.id.tv);
    return builder.create();
}

處理螢幕翻轉

  • 如果使用傳統的 Dialog ,需要我們手動處理螢幕翻轉的情況

  • 但使用 DialogFragment 的話,則不需要我們進行任何處理, FragmentManager 會自動管理 DialogFragment 的生命週期。

無標題欄

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
    LayoutInflater inflater = getActivity().getLayoutInflater();
    View view = inflater.inflate(R.layout.fragment_dialog, null);
    Dialog dialog = new Dialog(getActivity(), R.style.CustomDialog);
    // 關閉標題欄,setContentView() 之前呼叫
    dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
    dialog.setContentView(view);
    dialog.setCanceledOnTouchOutside(true);
    return dialog;
}

對於在onCreateView裡

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    /**
     * setStyle() 的第一個引數有四個可選值:
     * STYLE_NORMAL|STYLE_NO_TITLE|STYLE_NO_FRAME|STYLE_NO_INPUT
     * 其中 STYLE_NO_TITLE 和 STYLE_NO_FRAME 可以關閉標題欄
     * 每一個引數的詳細用途可以直接看 Android 原始碼的說明
     */
    setStyle(DialogFragment.STYLE_NO_TITLE, R.style.CustomDialog);
}

實現全屏(寬/高度全屏)

// 設定寬度為屏寬、位置靠近螢幕底部
Window window = dialog.getWindow();
window.setBackgroundDrawableResource(R.color.transparent);
WindowManager.LayoutParams wlp = window.getAttributes();
wlp.gravity = Gravity.BOTTOM;
wlp.width = WindowManager.LayoutParams.MATCH_PARENT;
wlp.height = WindowManager.LayoutParams.WRAP_CONTENT;
window.setAttributes(wlp);  

部分程式碼如下:

/**
 * 類功能描述:</br>
 *  DialogFragment主測試類
 * @author 於亞豪
 *  部落格地址: http://blog.csdn.net/androidstarjack
 * 公眾號: 終端研發部
 * @version 1.0 </p> 修改時間:</br> 修改備註:</br>
 */
public class MainActivity extends AppCompatActivity {
    private Dialog dialog;
    @Bind(R.id.btn01)
    Button btn01;
    FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
    MyShouDialogFramgment mdf;
    MyShouDialogFramgment2 mdf2 = new MyShouDialogFramgment2();
    FragmentTransaction ft2 = getSupportFragmentManager().beginTransaction();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        dialog = new Dialog(this);
        dialog.setContentView(R.layout.fragment_dialog);
        ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
        ft2.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
        mdf = new MyShouDialogFramgment();
        //使用者恢復對話方塊的狀態
        if(savedInstanceState != null && savedInstanceState.getBoolean("dialog_show"))
            btn01.performClick();
    }

    /**
     * 用於儲存對話方塊的狀態以便恢復
     */
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        if(dialog != null && dialog.isShowing())
            outState.putBoolean("dialog_show", true);
        else
            outState.putBoolean("dialog_show", false);
    }

    /**
     * 在Activity銷燬之前,確保對話方塊以關閉
     */
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if(dialog != null && dialog.isShowing())
            dialog.dismiss();
    }
    @OnClick({R.id.btn01,R.id.btn02,R.id.btn03})
    public void onClick(View view){
        switch (view.getId()){
            case R.id.btn01:
                dialog.show();
                break;
            case R.id.btn02:
                mdf.show(ft, "df");
               //可以拓展
                break;
            case R.id.btn03:
                mdf2.show(ft2, "df");
                //可以拓展
                break;
        }
    }

}

*MyShouDialogFramgment2.Java如下*

public class MyShouDialogFramgment2 extends DialogFragment {
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return super.onCreateView(inflater, container, savedInstanceState);
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        //為了樣式統一和相容性,可以使用 V7 包下的 AlertDialog.Builder
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        LayoutInflater inflater = getActivity().getLayoutInflater();
        View view = inflater.inflate(R.layout.fragment_dialog, null);
        Button    btn_cancle = (Button) view.findViewById(R.id.btn_cancle);
        Button     btn_ok = (Button) view.findViewById(R.id.btn_ok);
        final Dialog dialog = new Dialog(getActivity(), R.style.activity_DialogTransparent);
        // 關閉標題欄,setContentView() 之前呼叫
        dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
        dialog.setContentView(view);
        dialog.setCanceledOnTouchOutside(true);
        btn_cancle.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                dialog.dismiss();
            }
        });
        btn_ok.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                GetToast.useString(getActivity(),"今晚開始搞事情了...");
            }
        });
        return dialog;
    }
}

總結

  • 可以讓DialogFragment的使用像Dialog一樣的簡單、靈活,同時也保持了DialogFragment的優點,可以在任何的類中使用

    • 很簡單的新增新型別的Dialog
  • 利用Fragment的特性,為不同螢幕做

下載連線

部落格地址

#### 相信自己,沒有做不到的,只有想不到的
如果你覺得此文對您有所幫助,歡迎入群 QQ交流群 :232203809
微信公眾號:終端研發部

Markdown

(這裡 學到的不僅僅是技術)