Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android為啥推薦用DialogFragment創建Dialog?

Android為啥推薦用DialogFragment創建Dialog?

編輯:關於Android編程

前言:這段時候有點忙,因為在趕項目,說忙也都是在敷衍,時間擠擠還是有的,從開始寫博客的那一刻起就應該一直堅持下來,不要三天打魚兩天曬網,上次寫過一個Android進階之(dialog詳解一),今天繼續上次的內容探究一下,從Android3.0後Android引入的DailogFragment,我們一起從源碼的角度去看看為啥谷歌推薦這樣創建Dialog.
DialogFragment對話框出現的意義

為什麼android系統有AlertDialog,PopupWindow對話框,基本滿足客戶需求,為啥還要跑出一個DialogFragment對話框呢?
這就要從DialogFragment的優點說起了:

有和Fragment基本一致的生命周期,因此便於Activity更好的控制管理DialogFragment。 隨屏幕旋轉(橫豎屏幕切換)DialogFragment對話框隨之自動調整對話框大小。而AlertDialog和PopupWindow隨屏幕切換而消失。 DialogFragment的出現解決 橫豎屏幕切換Dialog消失的問題。

既然是探究性的話題,我們就從DialogFragment源碼探究一下。
在此次之前先貼一張Fragment的生命周期圖:
這裡寫圖片描述

DialogFragment的全部源碼我就不貼了,要研究的童鞋就多按按Ctrl+左鍵吧(^__^) 嘻嘻……我們按照Fragment的生命周期一個一個往下看,揭開它的神秘面紗。
首先看看onAttach()方法

 @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (!mShownByMe) {
            // 如果不是通過調用DialogFragment自己的show方法彈出Dialog的話
            // 標志著這個Dialog不再被消失
            mDismissed = false;
        }
    }

這個方法也沒什麼重要代碼。
接下來看看onCreate()方法

@Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mShowsDialog = mContainerId == 0;

        if (savedInstanceState != null) {
            mStyle = savedInstanceState.getInt(SAVED_STYLE, STYLE_NORMAL);
            mTheme = savedInstanceState.getInt(SAVED_THEME, 0);
            mCancelable = savedInstanceState.getBoolean(SAVED_CANCELABLE, true);
            mShowsDialog = savedInstanceState.getBoolean(SAVED_SHOWS_DIALOG, mShowsDialog);
            mBackStackId = savedInstanceState.getInt(SAVED_BACK_STACK_ID, -1);
        }
    }

當Activity生命周期發生變換的時候,也就是比如切換橫豎屏的時候,對Dialog的一些屬性進行保存,當再次創建Fragment的時候回到上一次Dialog的狀態,既然有取的過程,那必定有存的過程。

 @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        if (mDialog != null) {
            Bundle dialogState = mDialog.onSaveInstanceState();
            if (dialogState != null) {
                outState.putBundle(SAVED_DIALOG_STATE_TAG, dialogState);
            }
        }
        if (mStyle != STYLE_NORMAL) {
            outState.putInt(SAVED_STYLE, mStyle);
        }
        if (mTheme != 0) {
            outState.putInt(SAVED_THEME, mTheme);
        }
        if (!mCancelable) {
            outState.putBoolean(SAVED_CANCELABLE, mCancelable);
        }
        if (!mShowsDialog) {
            outState.putBoolean(SAVED_SHOWS_DIALOG, mShowsDialog);
        }
        if (mBackStackId != -1) {
            outState.putInt(SAVED_BACK_STACK_ID, mBackStackId);
        }
    }

緊接著我們看到這麼一個方法:

 /** @hide */
    @Override
    public LayoutInflater getLayoutInflater(Bundle savedInstanceState) {
        if (!mShowsDialog) {
            return super.getLayoutInflater(savedInstanceState);
        }

        mDialog = onCreateDialog(savedInstanceState);

        if (mDialog != null) {
            setupDialog(mDialog, mStyle);

            return (LayoutInflater) mDialog.getContext().getSystemService(
                    Context.LAYOUT_INFLATER_SERVICE);
        }
        return (LayoutInflater) mHost.getContext().getSystemService(
                Context.LAYOUT_INFLATER_SERVICE);
    }

這個方法雖然隱藏了,但是我們看到,在方法裡面new了一個Dialog,onCreateDialog,所以我們知道,其實DialogFragment裡面也是悄悄的創建了一個Dialog,創建了一個Dialog,那系統怎麼知道我們創建的Dialog的樣式呢?總要一些Dialog的初始化吧?

帶著疑問,接著我們看看onActivityCreated這個方法:

@Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        if (!mShowsDialog) {
            return;
        }

        View view = getView();
        if (view != null) {
            if (view.getParent() != null) {
                throw new IllegalStateException(
                        "DialogFragment can not be attached to a container view");
            }
            mDialog.setContentView(view);
        }
        final Activity activity = getActivity();
        if (activity != null) {
            mDialog.setOwnerActivity(activity);
        }
        mDialog.setCancelable(mCancelable);
        mDialog.setOnCancelListener(this);
        mDialog.setOnDismissListener(this);
        if (savedInstanceState != null) {
            Bundle dialogState = savedInstanceState.getBundle(SAVED_DIALOG_STATE_TAG);
            if (dialogState != null) {
                mDialog.onRestoreInstanceState(dialogState);
            }
        }
    }

我們看到有這麼一段代碼:

View view = getView();
        if (view != null) {
            if (view.getParent() != null) {
                throw new IllegalStateException(
                        "DialogFragment can not be attached to a container view");
            }
            mDialog.setContentView(view);
        }

看到這個我們很熟悉,就是給Dialog設置了一個布局文件,那麼這個布局又是哪來的呢?View view = getView();,這裡的getView獲取到的值,正是我們在onCreateView中return的那個view。那麼我們在onCreateView中返回了什麼,我們Dialog就會顯示啥樣。

看到這裡我們一定有點思路了,如果要用DialogFragment來彈出一個Dialog,有兩種方法。
一:重寫createDialog方法,給一個自己的Dialog

@NonNull
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        return new Dialog(getActivity(), getTheme());
    }

二:重寫Fragment的onCreateView方法,返回一個自定義個布局

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

好了,現在Dialog創建好了,也初始化過了,那麼怎麼顯示Dialog呢?
機智的你肯定有思路了,
一:調用DialogFragment的getDialog方法然後show

 public Dialog getDialog() {
        return mDialog;
    }

二:調用DialogFragment的show方法

public void show(FragmentManager manager, String tag)

public int show(FragmentTransaction transaction, String tag) 

但是這只是把一個沒有UI的Fragment添加到了Activity的Fragment棧中,Dialog又是哪彈出來的呢?
我們接著Fragment的生命周期看看onStart方法,這個方法也就是當Activity可見的時候調用。

@Override
    public void onStart() {
        super.onStart();

        if (mDialog != null) {
            mViewDestroyed = false;
            mDialog.show();
        }
    }

我們很清晰的看到了mDialog.show();此時Dialog對用戶可見。
再延伸一下,我們可以看到這裡有一個 if (mDialog != null)的判斷,那麼什麼時候mDialog==null?我們看看它在哪創建的:

/** @hide */
    @Override
    public LayoutInflater getLayoutInflater(Bundle savedInstanceState) {
        if (!mShowsDialog) {
            return super.getLayoutInflater(savedInstanceState);
        }

        mDialog = onCreateDialog(savedInstanceState);

        if (mDialog != null) {
            setupDialog(mDialog, mStyle);

            return (LayoutInflater) mDialog.getContext().getSystemService(
                    Context.LAYOUT_INFLATER_SERVICE);
        }
        return (LayoutInflater) mHost.getContext().getSystemService(
                Context.LAYOUT_INFLATER_SERVICE);
    }

也就是當mShowsDialog為false的時候,我們的dialog就是null了。
我們再onCreate方法中看到這麼一行代碼:

mShowsDialog = mContainerId == 0;

那麼這個mContainerId 又是什麼呢?

// When a fragment is being dynamically added to the view hierarchy, this
    // is the identifier of the parent container it is being added to.

英語不是很好,大致翻譯一下,此id就是你需要將Fragment添加的那個容器,也就是跟我們平時使用的普通的Fragment一樣,當需要把一個Fragment添加到Activity的時候,調用

  FragmentTransaction ft = manager.beginTransaction();
        ft.add(R.id.container, tag);

到這,我們終於把DialogFragment的源碼跟著Fragment的生命周期跑了一遍,總結一下:
DialogFragment也就是在內部new了一個Dialog然後顯示在Activity上,但是有了Fragment的附屬後,我們有了一套完整的生命周期了,Dialog依附Fragment從而當Activty生命周期發生變換的時候,會重新把Fragment添加進Activity中,從而不會使Dialog消失。

說了這麼多原理性的東西,我們都有點疲憊了,接下來讓我們來實戰一下~~~~

為了展示效果,我們還是模仿一下微信的Dialog吧,先看看效果
這裡寫圖片描述
普通的創建Dialog的方法,我在Android進階之(dialog詳解一),已經有實現了,如果有需要的朋友可以直接去拖代碼,(^__^) 嘻嘻……

方式一:重寫DialogFragment的onCreateView

wechat_dialog.xml:



    
        
        
    

布局很簡單,我們定義一個Fragment叫WechatFragment

package com.cisetech.dialogdemo.dialog;

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.DialogFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.cisetech.dialogdemo.R;

/**
 * author:yinqingy
 * date:2016-11-01 22:20
 * blog:http://blog.csdn.net/vv_bug
 * desc:
 */

public class WechatFragment extends DialogFragment {
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.wechat_dialog,null);
    }
}

我們創建Fragment然後調用show代碼:

public void showWechatDialog(){
        WechatFragment wechatDialog=new WechatFragment();

        wechatDialog.show(getSupportFragmentManager(),"wechatDialog");
    }

然後我們運行看看效果:

這裡寫圖片描述

好吧,Dialog是顯示出來了,出來的是這吊樣,跟我們想要的結果還很遠啊,
1、去掉Dialog默認的Title位置,然後背景設成自己的透明色。

 public void showWechatDialog(){
        WechatFragment wechatDialog=new WechatFragment();
        wechatDialog.setStyle(DialogFragment.STYLE_NO_TITLE,R.style.loading_dialog);
        wechatDialog.show(getSupportFragmentManager(),"wechatDialog");
    }

Dialog的style文件:

 

shape文件:



    
    

然後我們再運行下代碼:
這裡寫圖片描述

好吧,經過我們的修改我們終於離我們的目標就差一點了,就是Dialog的寬度了,我們讓Dialog的寬度為屏幕寬的0.618,(^__^) 嘻嘻……
重寫onStart()方法,在dialog彈出之後進行修改寬度屬性

@Override
    public void onStart() {
        super.onStart();
        Dialog dialog =getDialog();//獲取Dialog
        WindowManager.LayoutParams attr = dialog.getWindow().getAttributes();//獲取Dialog屬性
        WindowManager wm= (WindowManager) dialog.getContext().getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics outMetric=new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(outMetric);
        attr.width= (int) (outMetric.widthPixels*0.618f);
        dialog.getWindow().setAttributes(attr);
    }

再次運行代碼:
這裡寫圖片描述

可以看到我們已經成功達到了我們的目標,還沒完。。。
我們轉換屏幕看看什麼效果:
這裡寫圖片描述

可以看到,效果是一致的。(^__^) 嘻嘻……

我們使用直接new Dialog的形式,然後切換橫屏試試:
效果還是一致,但是系統報了一個錯誤

android.view.WindowLeaked: Activity com.cisetech.dialogdemo.MainActivity has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView{230312c5 V.E..... R....... 0,0-791,140} that was originally added here
                                                                          at android.view.ViewRootImpl.(ViewRootImpl.java:363)
                                                                          at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:265)
                                                                          at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:69)
                                                                          at android.app.Dialog.show(Dialog.java:298)
                                                                          at com.cisetech.dialogdemo.MainActivity.onCreate(MainActivity.java:94)
                                                                          at android.app.Activity.performCreate(Activity.java:5941)
                                                                          at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1105)
                                                                          at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2256)
                                                                          at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2363)
                                                                          at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:3915)
                                                                          at android.app.ActivityThread.access$900(ActivityThread.java:147)
                                                                          at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1289)
                                                                          at android.os.Handler.dispatchMessage(Handler.java:102)
                                                                          at android.os.Looper.loop(Looper.java:135)
                                                                          at android.app.ActivityThread.main(ActivityThread.java:5235)
                                                                          at java.lang.reflect.Method.invoke(Native Method)
                                                                          at java.lang.reflect.Method.invoke(Method.java:372)
                                                                          at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:906)
                                                                          at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:701)

雖然效果一樣,但是如果我們Dialog是一個輸入用戶名密碼的Dialog,那麼當切換屏幕的時候,用直接new Dialog的話會被清空掉,如果是DialogFragment的話則不會被清空。

方式二,重寫onCreateDialog實現DialogFragment彈出Dialog

MainActivity.java

 private WechatFragment wechatDialog;
    public void showWechatDialog(){
        if(wechatDialog!=null&&wechatDialog.getDialog()!=null&&wechatDialog.getDialog().isShowing()){
            wechatDialog.dismiss();
            return;
        }
        wechatDialog=new WechatFragment();
       // wechatDialog.setStyle(DialogFragment.STYLE_NO_TITLE,R.style.loading_dialog);
        wechatDialog.show(getSupportFragmentManager(),"wechatDialog");
    }

WechatFragment.java:

 @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        LayoutInflater inflater = LayoutInflater.from(getActivity());
        View v = inflater.inflate(R.layout.wechat_dialog, null);// 得到加載view
        Dialog loadingDialog = new Dialog(getActivity(), R.style.loading_dialog);// 創建自定義樣式dialog
        loadingDialog.setCancelable(true);// 不可以用“返回鍵”取消
        loadingDialog.setContentView(v);// 設置布局
        return loadingDialog;
    }

運行效果:
這裡寫圖片描述

和我們上面那種重寫onCreateView的方式效果一樣。

到此~ DialogFragment的基本內容已經完畢了,以前看到一些大牛封裝的DialogUtil,接下來還會寫一篇關於這個工具類的博客,未完待續,(^__^) 嘻嘻……

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved