Android高手進階之兩幅圖搞定DilogFragment

前言

關於DilogFragment以前寫過幾篇博客,可是並非很深刻,有些問題也沒有解決。 例如:java

一、DialogFragment和Activity的關係。android

二、DilogFragment生命週期和和設置佈局大小無效問題。web

爲了解決上面的2個問題,須要從兩個方面來入手,一、DialogFragment和Activity的關係。二、DialogFragment和Dialog的關係。理清楚了這些關係後上面的三點問題也就解決了。數據庫

一、DialogFragment和Activity的關係。

DialogFragment繼承Fragment生命週期和所在的Activity生命週期相關,由FragmentManager管理。Activity的生命週期由Framework層中的ActivityManagerService來管理的,而Fragment只對Activity可見,對Framework層並不可見,也就是Framework並不知道Fragment的存在,Fragment的生命週期徹底由Activity中的FragmentManager來管理。Activity和Fragment關係以下圖所示: ide

在這裏插入圖片描述

Activity經過FragmentManager來管理Fragment。FragmentManager能夠經過FragmentTransaction把Fragment加入到Back Stack中,FragmentTransaction和數據庫操做的方法同樣,有add(),remove(),commit()等方法來操做Fragment。已經知道了Fragment是依賴於Activity的,下面給出Fragment和Activity生命週期的關係。 函數

在這裏插入圖片描述
Fragment和Activity的生命週期的對應關係後面我會專門寫一篇博客。其實原理並不複雜,就是在Activity的各個生命週期中去分別調用其包含的Fragment對應的生命週期的函數。在Activtiy中使用Fragment很是簡單以下:

public class DemoActivity extends FragmentActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_crime);
        FragmentManager fm = getSupportFragmentManager();
        Fragment fragment = fm.findFragmentById(R.id.fragmentContainer);
        if (fragment == null) {
            fragment = new CrimeFragment();
            fm.beginTransaction()
                    .add(R.id.fragmentContainer, fragment)
                    .commit();
        }
    }
}

複製代碼

上面的例子是在Activity的onCreate方法中調用FragmentManager 來將Fragment加入到Activity中。佈局

若是在Activity的其餘生命週期中將Fragment加入到Activity中呢?好比說在Activity處於stopped,paused,或者running狀態時,加入Fragment的生命週期是怎樣的?學習

這個時候FragmentManager會當即執行Fragment須要的生命狀態直到和activity相匹配的狀態。好比說在Activity處於running狀態時加入Fragment,此時Fragment會執行生命 onAttach(Activity), onCreate(Bundle), onCreateView(…), onActivityCreated(Bundle), onStart(),最後執行 onResume(). 以下圖:在Activity處於onResume的時候addFragment,此時Fragment執行的生命週期。 ui

在這裏插入圖片描述

二、DilogFragment生命週期和和設置佈局大小無效問題。

下面看看在Activity中使用DialogFragment的例子,而後分析兩點:this

一、爲何DialogFragment會以彈框的形式顯示。

二、爲何在要在onStart()方法中動態改變DialogFragment才能動態改變佈局。

new MyDialogFragment().show(getFragmentManager(),"id");
複製代碼

DialogFragment的使用十分簡單,直接在Activity中調用DialogFragment的show()方法同時傳入當前Activity中的FragmentManager,傳入的FragmentManager確定是用來管理DialogFragment的。

public void show(FragmentManager manager, String tag) {
        mDismissed = false;
        mShownByMe = true;
        FragmentTransaction ft = manager.beginTransaction();
        ft.add(this, tag);
        ft.commit();
    }
複製代碼

DialogFragment中的show()方法調用FragmentManager把本身加入到back stack中,生命週期由FragmentManager管理。在Activity中調用DialogFragment的show()方法此時並不會當即顯示,這個時候關聯了DialogFragment和activity的生命週期,那麼DialogFragment在何時顯示的呢?

@Override
    public void onStart() {
        super.onStart();
        if (mDialog != null) {
            mViewDestroyed = false;
            mDialog.show();
        }
    }
複製代碼

只有DialogFragment的生命週期執行到onStart()的時候纔會調用mDialog.show();顯示彈框,在DialogFragment源碼解析中已經說過,DialogFragment的佈局會被加到mDialog中,因此DialogFragment纔會顯示成彈框形式。這解決了爲何DialogFragment會以彈框的形式顯示的問題。

在onCreateView中定義的佈局最後會被加入到Dialog中,因此要改變彈出框的大小實際上是要改變Dialog的大小。Dialog的大小要在Dialog.show()方法以後才能動態改變。下面分析一下爲何Dialog的大小在Dialog.show()方法以後才能改變。看看Dialog.show()的源碼:

public void show() {
        if (mShowing) {
            return;
        }
        if (!mCreated) {
            dispatchOnCreate(null);
        } else {
        }
    mWindowManager.addView(mDecor, l);    
}
  
複製代碼

上面代碼是簡化以後的,若是Dialog尚未建立就會調用dispatchOnCreate(null);方法來建立Dialog的佈局,下面看看dispatchOnCreate(null)方法,簡化後以下:

void dispatchOnCreate(Bundle savedInstanceState) {
        if (!mCreated) {
            mDialog.setContentView(selectContentView());
            mCreated = true;
        }
    }
//獲取相應的佈局樣式
    private int selectContentView() {
        if (mButtonPanelSideLayout == 0) {
            return mAlertDialogLayout;
        }
        if (mButtonPanelLayoutHint == AlertDialog.LAYOUT_HINT_SIDE) {
            return mButtonPanelSideLayout;
        }
        return mAlertDialogLayout;
    }
複製代碼

由上面能夠看出,使用Dialog的子類AlertDialog時,使用的contentView是Android自帶樣式和大小的Layout,用戶自定義的view被加到mDecor上,因此在show()以前設置xml的大小是無效的,最後仍是會在show中被覆蓋成系統自帶的格式,只有在show後面改變佈局屬性纔會生效。

這也就解釋了爲何DialogFragment在onCreate()和onCreateView()中設置佈局大小無效,由於onCreate()和onCreateView()生命週期在onStart()生命週期以前,此時還未調用Dialog.show()方法,設置大小無效。能夠總結出Activity和DialogFragment的關係以下圖:

在這裏插入圖片描述

DialogFragment自定義佈局大小坑分析

坑自定義彈框的大小 在自定義佈局中設置的大小是不起做用的,要設置自定義佈局的大小隻有在代碼中動態設置,在onStart中重寫佈局大小,在onCreat或者onCreateView中無效

/** * 修改佈局的大小 */
    @Override
    public void onStart() {
        super.onStart();
        XLLog.d(TAG, "onStart");
        resizeDialogFragment();

    }

    private void resizeDialogFragment() {
        Dialog dialog = getDialog();
        if (null != dialog) {
            Window window = dialog.getWindow();
            WindowManager.LayoutParams lp = getDialog().getWindow().getAttributes();
            lp.height = (25 * ScreenUtil.getScreenHeight(getContext()) / 32);//獲取屏幕的寬度,定義本身的寬度
            lp.width = (8 * ScreenUtil.getScreenWidth(getContext()) / 9);
            if (window != null) {
                window.setLayout(lp.width, lp.height);
            }
        }
    }
複製代碼

這裏不得不提到的坑是,看起來已經動態設置了本身想要的佈局大小。但實際運行出來的大小和定義的尺寸有誤差。上面代碼中設置的寬度是屏幕的8/9,運行代碼是獲得的 lp.width=960,但我用Layout Inspector檢測出來自定義的佈局寬度僅僅是876,這中間差了84。因此確定是系統在自定義的佈局外面又包了一層其餘的東西,致使設置出來的寬度和實際顯示的不同。 經過

Dialog dialog = getDialog();
        if (null != dialog) {
            Window window = dialog.getWindow();
            Log.d(TAG, "padding.................." + window.getDecorView().getPaddingLeft() + "............................." + window.getDecorView().getPaddingTop());
//結果:padding..................42.............................42
複製代碼

由上面結果可知,在自定義佈局外面還有一個padding的距離,這個padding 距離四周的距離都是42,876+42*2=960,正好和設置的寬度相同。

檢測佈局

用Android studio 中的Layout Inspector檢測佈局

在這裏插入圖片描述
由上圖能夠看到佈局結構:
在這裏插入圖片描述
整個彈出框的根佈局是DecorView,DecorView裏面包含了一個FragmentLayout,FragmentLayout裏面包含兩個佈局一個是content,就是咱們自定義的佈局,Action_mode_bar_stub這個是actionBar,咱們這裏的actionBar佈局爲null什麼都沒有。 其中DecorView的定義能夠看一段英文:

The DecorView is the view that actually holds the window’s background drawable. Calling getWindow().setBackgroundDrawable() from your Activity changes the background of the window by changing the DecorView‘s background drawable. As mentioned before, this setup is very specific to the current implementation of Android and can change in a future version or even on another device.

其中的padding=42就是DecorView與其餘佈局的間距,因此獲取到DecorView再設置它的padding就行了。

解決方案:設置透明背景

要在onCreateView中設置背景爲透明,原來dialogFragment在自定義的佈局外加了一個背景,設置爲透明背景後,尺寸就和設置的尺寸同樣了。加上

getDialog().getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
複製代碼
@Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        getDialog().getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
        View view = inflater.inflate(R.layout.message_share_websit_dialog, container);
        return view;

    }
複製代碼

搞定

總結

本文主要從DialogFragment和Activity的關係以及DialogFragment的生命週期特色來分析爲何DialogFragment會顯示彈框形式,以及動態設置佈局時爲何要在onStart()方法中生效,在onCreate()和onCreateView()中設置不生效問題。

參考文獻

一、Android Programming_ The Big Nerd Ranch Guide

二、developer.android.com/courses/fun…

備註:2連接中有google官方提供的初級和高級教程,並且配有ppt講解十分詳細,是很好的學習資源。

相關文章
相關標籤/搜索