DialogFragment源碼分析-創(chuàng)新互聯(lián)

目錄介紹
  • 1.最簡單的使用方法
    • 1.1 官方建議
    • 1.2 最簡單的使用方法
    • 1.3 DialogFragment做屏幕適配
  • 2.源碼分析
    • 2.1 DialogFragment繼承Fragment
    • 2.2 onCreate(@Nullable Bundle savedInstanceState)源碼分析
    • 2.3 setStyle(@DialogStyle int style, @StyleRes int theme)
    • 2.4 onActivityCreated(Bundle savedInstanceState)源碼分析
    • 2.5 onCreateDialog(Bundle savedInstanceState)源碼分析
    • 2.6 重點分析彈窗展示和銷毀源碼
  • 3.經(jīng)典總結(jié)
  • 4.DialogFragment封裝庫介紹
  • 5.常見問題總結(jié)
    • 5.1 使用中show()方法遇到的IllegalStateException

好消息

  • 博客筆記大匯總【16年3月到至今】,包括Java基礎(chǔ)及深入知識點,Android技術(shù)博客,Python學(xué)習(xí)筆記等等,還包括平時開發(fā)中遇到的bug匯總,當然也在工作之余收集了大量的面試題,長期更新維護并且修正,持續(xù)完善……開源的文件是markdown格式的!同時也開源了生活博客,從12年起,積累共計47篇[近20萬字],轉(zhuǎn)載請注明出處,謝謝!
  • 鏈接地址:https://github.com/yangchong211/YCBlogs
  • 如果覺得好,可以star一下,謝謝!當然也歡迎提出建議,萬事起于忽微,量變引起質(zhì)變!
  • DialogFragment封裝庫項目地址:https://github.com/yangchong211/YCDialog
  • 02.Toast源碼深度分析
    • 最簡單的創(chuàng)建,簡單改造避免重復(fù)創(chuàng)建,show()方法源碼分析,scheduleTimeoutLocked吐司如何自動銷毀的,TN類中的消息機制是如何執(zhí)行的,普通應(yīng)用的Toast顯示數(shù)量是有限制的,用代碼解釋為何Activity銷毀后Toast仍會顯示,Toast偶爾報錯Unable to add window是如何產(chǎn)生的,Toast運行在子線程問題,Toast如何添加系統(tǒng)窗口的權(quán)限等等
  • 03.DialogFragment源碼分析
    • 最簡單的使用方法,onCreate(@Nullable Bundle savedInstanceState)源碼分析,重點分析彈窗展示和銷毀源碼,使用中show()方法遇到的IllegalStateException分析
  • 05.PopupWindow源碼分析
    • 顯示PopupWindow,注意問題寬和高屬性,showAsDropDown()源碼,dismiss()源碼分析,PopupWindow和Dialog有什么區(qū)別?為何彈窗點擊一下就dismiss呢?
  • 06.Snackbar源碼分析
    • 最簡單的創(chuàng)建,Snackbar的make方法源碼分析,Snackbar的show顯示與點擊消失源碼分析,顯示和隱藏中動畫源碼分析,Snackbar的設(shè)計思路,為什么Snackbar總是顯示在最下面
  • 07.彈窗常見問題
    • DialogFragment使用中show()方法遇到的IllegalStateException,什么常見產(chǎn)生的?Toast偶爾報錯Unable to add window,Toast運行在子線程導(dǎo)致崩潰如何解決?

1.最簡單的使用方法

1.1 官方建議
  • Android比較推薦采用DialogFragment實現(xiàn)對話框,它完全能夠?qū)崿F(xiàn)Dialog的所有需求,并且還能復(fù)用Fragment的生命周期管理,被后臺殺死后,可以恢復(fù)重建。
  • 在手機配置變化導(dǎo)致 Activity 需要重新創(chuàng)建時,例如旋轉(zhuǎn)屏幕,基于 DialogFragment 的對話框?qū)?FragmentManager 自動重建,然而基于 Dialog 實現(xiàn)的對話框卻沒有這樣的能力
1.2 最簡單的使用方法
  • 如下所示:

    專注于為中小企業(yè)提供網(wǎng)站制作、網(wǎng)站建設(shè)服務(wù),電腦端+手機端+微信端的三站合一,更高效的管理,為中小企業(yè)慈溪免費做網(wǎng)站提供優(yōu)質(zhì)的服務(wù)。我們立足成都,凝聚了一批互聯(lián)網(wǎng)行業(yè)人才,有力地推動了上千余家企業(yè)的穩(wěn)健成長,幫助中小企業(yè)通過網(wǎng)站建設(shè)實現(xiàn)規(guī)模擴充和轉(zhuǎn)變。
    public class CustomDialogFragment extends DialogFragment {
    
        @Override
        public void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            //設(shè)置樣式
            setStyle(DialogFragment.STYLE_NO_TITLE, R.style.CenterDialog);
        }
    
        @Nullable
        @Override
        public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
                                 @Nullable Bundle savedInstanceState) {
            return inflater.inflate(R.layout.view_fragment_dialog, container, false);
        }
    
        public static void showDialog(FragmentActivity activity){
            CustomDialogFragment customDialogFragment = new CustomDialogFragment();
            customDialogFragment.show(activity.getSupportFragmentManager(),"yc");
        }
    }
    
    //然后一行代碼調(diào)用
    CustomDialogFragment.showDialog(this);
  • 1.2.1 創(chuàng)建theme主題樣式,并且進行設(shè)置
    • 設(shè)置樣式,以DialogFragment為例,只需要在onCreate中setStyle(DialogFragment.STYLE_NO_TITLE, R.style.CenterDialog)即可。
    • 注意,CenterDialog中可以設(shè)置彈窗的動畫效果。
    • 注意一下style常量,這里只是展示常用的。
      STYLE_NORMAL:會顯示一個普通的dialog
      STYLE_NO_TITLE:不帶標題的dialog
      STYLE_NO_FRAME:無框的dialog
      STYLE_NO_INPUT:無法輸入內(nèi)容的dialog,即不接收輸入的焦點,而且觸摸無效。
  • 1.2.2 重寫onCreateView方法創(chuàng)建彈窗
  • 1.2.3 創(chuàng)建類的對象,然后調(diào)用show(FragmentManager manager, String tag)方法即可創(chuàng)建出彈窗
  • 1.2.4 如何去掉標題欄,也許你會問,為什么第二種要在super.onActivityCreated(savedInstanceState)之前設(shè)置呢。這個是因為,看了源碼之后才知道onActivityCreated這個方法中,有mDialog.setContentView(view)這一步,說到setContentView是不是很熟悉。沒錯,后面再深度解析這塊源碼思路……

    //第一種
    //設(shè)置樣式時,使用STYLE_NO_TITLE
    setStyle(DialogFragment.STYLE_NO_TITLE, R.style.CenterDialog);
    
    //第二種
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
       window window = getDialog().getWindow();
        if(window!=null){
           window.requestFeature(Window.FEATURE_NO_TITLE);
        }
        super.onActivityCreated(savedInstanceState);
    }

2.源碼分析

2.1 DialogFragment繼承Fragment
  • DialogFragment是繼承Fragment,具有Fragment的生命周期,本質(zhì)上說就是Fragment,只是其內(nèi)部還有一個dialog而已。你既可以當它是Dialog使用,也可以把它作為Fragment使用。
2.2 onCreate(@Nullable Bundle savedInstanceState)源碼分析
  • onCreate這個方法主要是保存一些屬性狀態(tài),比如style樣式,theme注意,是否可以取消,后退棧的ID等等。

    • 重點看一下mShowsDialog這個參數(shù),這個參數(shù)是Boolean值,mShowsDialog = mContainerId == 0;所以,默認情況下,mContainerId就是0,所以mShowsDialog就是true;而當你在把它當成Fragment使用時,會為其指定xml布局中位置,那么mContainerId也會不為0,所以mShowsDialog就是false。

      @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);
      }
      }
  • mShowsDialog這個參數(shù)的作用
    • 然后直接搜索,可以看到這個參數(shù),可以看到mShowsDialog是false,如果不是Dialog,則調(diào)用Fragment自身的方法;否則,就先創(chuàng)建一個dialog,然后,根據(jù)之前設(shè)置的style,通過setupDialog(mDialog, mStyle),對dialog賦值。所以,setStyle這個方法調(diào)用,一定要在onCreateView之前。一般來講,都會放到onCreate中調(diào)用。
    • DialogFragment源碼分析
2.3 setStyle(@DialogStyle int style, @StyleRes int theme)源碼分析
  • 這個方法很重要呢,注意是設(shè)置對話框的基本外觀和設(shè)置主題等等。通過手動設(shè)置Dialog和Window可以實現(xiàn)相同的效果,如果是在對話框創(chuàng)建之后調(diào)用它將會失去作用……
    • 通過這個方法,可以看到,在不設(shè)置theme,即為0的情況下,theme會被設(shè)置為android.R.style.Theme_Panel。
      public void setStyle(@DialogStyle int style, @StyleRes int theme) {
      mStyle = style;
      if (mStyle == STYLE_NO_FRAME || mStyle == STYLE_NO_INPUT) {
          mTheme = android.R.style.Theme_Panel;
      }
      if (theme != 0) {
          mTheme = theme;
      }
      }
2.4 onActivityCreated(Bundle savedInstanceState)源碼分析
  • 該方法的作用主要是:當DialogFragment依附的Activity被創(chuàng)建的時候調(diào)用,此時fragment的活動窗體被初始化
    • 可以看到這個方法,如果是彈窗已經(jīng)show出來的話,則直接return。然后通過setContentView方法將view創(chuàng)建出來。同時還設(shè)置了彈窗是否可以被取消,以及點擊事件等等。
    • DialogFragment源碼分析
2.5 onCreateDialog(Bundle savedInstanceState)源碼分析
  • onCreateDialog方法,你可以重寫這個方法,創(chuàng)建一個自己定義好的dialog。默認情況下,會自己創(chuàng)建一個Dialog。
    @NonNull
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        return new Dialog(getActivity(), getTheme());
    }
2.6 重點分析彈窗展示和銷毀源碼
2.6.1 show方法
  • 第一種:顯示對話框,將片段添加到給定的FragmentManager中。這對于顯式創(chuàng)建事務(wù)、使用給定的標記將片段添加到事務(wù)并提交它是很方便的。這樣做可以將事務(wù)添加到后臺堆棧。當片段被取消時,將執(zhí)行一個新的事務(wù)來從活動中刪除它。
  • 第二種:顯示對話框,使用現(xiàn)有事務(wù)添加片段,然后提交事務(wù)。
  • 共同點:這兩種顯示方式都是通過tag的方式將DialogFragment以事務(wù)的形式提交,不同的是第二種方式是采用已經(jīng)創(chuàng)建過的transaction,并且他返回了一個int類型的數(shù)值mBackStackId,mBackStackId是干什么用的呢?

    • mBackStackId:是做為將DialogFragment壓入回退棧的編號,初始值是-1,如果DialogFragment是用第二種方式show的話,他將被transaction默認壓入回退棧,mBackStackId=transaction.commit(),此時她的回退棧編號大于0,她的具體使用在dismissInternal方法中后面會具體介紹
      public void show(FragmentManager manager, String tag) {
      mDismissed = false;
      mShownByMe = true;
      FragmentTransaction ft = manager.beginTransaction();
      ft.add(this, tag);
      ft.commit();
      }

    public int show(FragmentTransaction transaction, String tag) {
    mDismissed = false;
    mShownByMe = true;
    transaction.add(this, tag);
    mViewDestroyed = false;
    mBackStackId = transaction.commit();
    return mBackStackId;
    }

2.6.2 dismiss()銷毀方法
  • 在源碼中可以看到這兩個方法都調(diào)用了dismissInternal(boolean)方法,不同的是傳入的boolean值一個為false一個為true,那么究竟這個boolean起到什么作用呢?
    • 在dismissInternal這個方法中,主要操作了:如果對話框已經(jīng)不可見就跳出方法體;設(shè)置對話框消失,然后將對話框?qū)傩栽O(shè)置不可見;如果DialogFragment中的Dialog對象不為空,就讓其內(nèi)的對話框消失;然后銷毀View;對于回退棧編號mBackStackId,在前面show方法源碼分析時提到這個呢!主要是用show(FragmentTransaction transaction, String tag)這個方法來壓棧的,所以要取消對話框需要在這里面判斷,已壓棧的要彈出回退棧,這個回退棧是由Activity來管理的,如果show(FragmentManager manager, String tag)方式的話則不需要彈棧,只需要在FragmentTransaction中將其remove掉即可。
    • 簡單總結(jié)就是:調(diào)用dialog的dismiss方法后,如果自己在后退棧中,就將自己從后退棧中移除掉;如果自己不在后退棧中,就將自己從FragmentManager中移除掉。
    • DialogFragment源碼分析
    • DialogFragment源碼分析
2.6.3 dialog顯示與隱藏
  • 具體看下面代碼

    • 在OnStart的時候,將dialog進行show出來;在生命周期方法onStop()時,則是將其先隱藏;最后在onDestroyView方法,它會將dialog銷毀并置null。
      @Override
      public void onStart() {
      super.onStart();
      if (mDialog != null) {
          mViewDestroyed = false;
          mDialog.show();
      }
      }

    @Override
    public void onStop() {
    super.onStop();
    if (mDialog != null) {
    mDialog.hide();
    }
    }

    @Override
    public void onDestroyView() {
    super.onDestroyView();
    if (mDialog != null) {
    // Set removed here because this dismissal is just to hide
    // the dialog -- we don't want this to cause the fragment to
    // actually be removed.
    mViewDestroyed = true;
    mDialog.dismiss();
    mDialog = null;
    }
    }

3.經(jīng)典總結(jié)

  • DialogFragment是繼承Fragment,具有Fragment的生命周期,本質(zhì)上說就是Fragment,只是其內(nèi)部還有一個dialog而已。你既可以當它是Dialog使用,也可以把它作為Fragment使用。
  • onCreateView可以加載客戶化更高的對話框,onCreateDialog加載系統(tǒng)AlertDialog類型對話框比較合適。
  • DialogFragmnet對話框橫屏?xí)r對話框不會關(guān)閉,因為DailogFragment有Fragment屬性,會在屏幕發(fā)生變化時重新創(chuàng)建DialogFragment。
  • setStyle的調(diào)用點,要放在onCreateView前,一般是放在onCreat方法中執(zhí)行,否則,設(shè)置的style和theme將不起作用!setStyle中,style的參數(shù)是不可以相互一起使用的,只能用一個,如果還不滿足你使用,可以通過設(shè)置theme來滿足。

4.DialogFragment封裝庫介紹

項目地址:https://github.com/yangchong211/YCDialog
  • 自定義對話框,其中包括:自定義Toast,采用builder模式,支持設(shè)置吐司多個屬性;自定義dialog控件,仿IOS底部彈窗;自定義DialogFragment彈窗,支持自定義布局,也支持填充recyclerView布局;自定義PopupWindow彈窗,輕量級,還有自定義Snackbar等等;還有自定義loading加載窗,簡單便用。這里只是展示dialogFragment用法!
  • 第一種:鏈式編程,如下所示

    BottomDialogFragment.create(getSupportFragmentManager())
        .setViewListener(new BottomDialogFragment.ViewListener() {
            @Override
            public void bindView(View v) {
    
            }
        })
        .setLayoutRes(R.layout.dialog_bottom_layout_list)
        .setDimAmount(0.5f)
        .setTag("BottomDialog")
        .setCancelOutside(true)
        .setHeight(getScreenHeight() / 2)
        .show();
  • 第二種:直接繼承,可以高度定制自己想要的彈窗

    public class ADialog extends BaseDialogFragment {
        @Override
        protected boolean isCancel() {
            return false;
        }
    
        @Override
        public int getLayoutRes() {
            return 0;
        }
    
        @Override
        public void bindView(View v) {
    
        }
    }

5.常見問題總結(jié)

5.1 使用中show()方法遇到的IllegalStateException
  • 報錯日志如下:
    lang.IllegalStateException: Can not perform this action after onSaveInstanceState at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1493)
  • 出現(xiàn)該問題的原因
    • Activity 調(diào)用了onSaveInstanceState()以后有觸發(fā)了dialog的顯示,dialog.show()方法里邊用的是commit()而不是commitAllowingStateLoss()
  • 追蹤報錯日志的來源

    • 于是,我挺好奇,show方法中只有兩個參數(shù),決定從getSupportFragmentManager()方法分析.FragmentManager是抽象類,我這里主要是看FragmentManagerImpl實現(xiàn)類代碼
      //第一步:
      public FragmentManager getSupportFragmentManager() {
      return mFragments.getSupportFragmentManager();
      }

    //第二步:
    public FragmentManager getSupportFragmentManager() {
    return mHost.getFragmentManagerImpl();
    }

    //第三步:
    FragmentManagerImpl getFragmentManagerImpl() {
    return mFragmentManager;
    }

    //第四步:看beginTransaction()方法@Override
    br/>@Override
    return new BackStackRecord(this);
    }

    //第五步:看BackStackRecord類中看commit方法@Override
    br/>@Override
    return commitInternal(false);
    }

    @Override
    public int commitAllowingStateLoss() {
    return commitInternal(true);
    }

    //第六步:可以看到這倆函數(shù)的區(qū)別就是commitInternal()方法中參數(shù)一個為true,一個為false
    int commitInternal(boolean allowStateLoss) {
    if (mCommitted) throw new IllegalStateException("commit already called");
    if (FragmentManagerImpl.DEBUG) {
    Log.v(TAG, "Commit: " + this);
    LogWriter logw = new LogWriter(TAG);
    PrintWriter pw = new PrintWriter(logw);
    dump(" ", null, pw, null);
    pw.close();
    }
    mCommitted = true;
    if (mAddToBackStack) {
    mIndex = mManager.allocBackStackIndex(this);
    } else {
    mIndex = -1;
    }
    mManager.enqueueAction(this, allowStateLoss);
    return mIndex;
    }

    //第七步:再追蹤到enqueueAction(this,allowStateLoss)
    public void enqueueAction(OpGenerator action, boolean allowStateLoss) {
    if (!allowStateLoss) {
    checkStateLoss();
    }
    synchronized (this) {
    if (mDestroyed || mHost == null) {
    throw new IllegalStateException("Activity has been destroyed");
    }
    if (mPendingActions == null) {
    mPendingActions = new ArrayList<>();
    }
    mPendingActions.add(action);
    scheduleCommit();
    }
    }

    //第八步:checkStateLoss()方法,這里可以看到拋出的錯誤日志呢
    private void checkStateLoss() {
    if (mStateSaved) {
    throw new IllegalStateException(
    "Can not perform this action after onSaveInstanceState");
    }
    if (mNoTransactionsBecause != null) {
    throw new IllegalStateException(
    "Can not perform this action inside of " + mNoTransactionsBecause);
    }
    }

關(guān)于其他內(nèi)容介紹

01.關(guān)于博客匯總鏈接
  • 1.技術(shù)博客匯總
  • 2.開源項目匯總
  • 3.生活博客匯總
  • 4.喜馬拉雅音頻匯總
  • 5.其他匯總
02.關(guān)于我的博客
  • 我的個人站點:www.yczbj.org,www.ycbjie.cn
  • github:https://github.com/yangchong211
  • 知乎:https://www.zhihu.com/people/yang-chong-69-24/pins/posts
  • 簡書:http://www.jianshu.com/u/b7b2c6ed9284
  • csdn:http://my.csdn.net/m0_37700275
  • 喜馬拉雅聽書:http://www.ximalaya.com/zhubo/71989305/
  • 開源中國:https://my.oschina.net/zbj1618/blog
  • 泡在網(wǎng)上的日子:http://www.jcodecraeer.com/member/content_list.php?channelid=1
  • 郵箱:yangchong211@163.com
  • 阿里云博客:https://yq.aliyun.com/users/article?spm=5176.100- 239.headeruserinfo.3.dT4bcV
  • segmentfault頭條:https://segmentfault.com/u/xiangjianyu/articles

另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無理由+7*72小時售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國服務(wù)器、虛擬主機、免備案服務(wù)器”等云主機租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務(wù)可用性高、性價比高”等特點與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場景需求。

網(wǎng)頁題目:DialogFragment源碼分析-創(chuàng)新互聯(lián)
文章出自:http://bm7419.com/article4/dihdie.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供App設(shè)計、網(wǎng)站設(shè)計外貿(mào)建站、網(wǎng)站營銷軟件開發(fā)、網(wǎng)站設(shè)計公司

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

微信小程序開發(fā)