3つの文のコードは全画面DialogあるいはDialogFragmentを作成します:あなたを連れてソースコードの角度から実現します

16257 ワード

Dialogはアプリ開発でよく使われるコントロールで、Activityと似ていて、独立したWindowウィンドウを持っていますが、DialogとActivityには一定の違いがあります.最も明らかなのは、デフォルトではDialogは全画面ではありませんので、レイアウトはActivityほど快適ではありません.例えば、上部がそろっていて、下部が揃えられていて、マージン、幅、高さなどです.Dialogをフルスクリーンに定義すると多くの問題が省け、一般的なレイアウトで完全に処理できます.ネット上での実装方法は少なくありませんが、一般的には効果的ですが、なぜ一部のウィンドウ属性(タイトルを隠す)はsetContentViewの前に設定しなければならないのか、逆に後に設定しなければならないのかという疑問もあるかもしれません.ここではいくつかの簡単な実現方法を選んで、それから原因を言って、Androidのウィンドウ管理とViewの描画はとても大きいので、ここはあまり深くありません.まず、実装効果を見てみましょう.
全画面Dialog

フルスクリーンDialog実装方法


ここでオブジェクトは2つに分けられ,1つは従来のDialogに対して,もう1つはDialogFragment(推奨)に対して,方法も2つに分けられ,1つはコードによる実装,もう1つはテーマスタイルThemeによる実装である.
Dialogの実装方法
public class FullScrreenDialog extends Dialog {
    public FullScrreenDialog(Context context) {
        super(context);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        getWindow().requestFeature(Window.FEATURE_NO_TITLE);
        View view = LayoutInflater.from(getContext()).inflate(R.layout.fragment_full_screen, null);
        
        setContentView(view);
        
        getWindow().setBackgroundDrawable(new ColorDrawable(0x00000000));
        
        getWindow().setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT);
    }
}

ここでは4つのポイントに関連して、キー1はsetContentViewの前に設定され、主にいくつかの低バージョンの互換性のためにTitle部分が表示されないようにするため、キー2はよく使われるsetContentViewであり、キー3本4は全画面ダイアログボックスの修正のためであり、キー4はsetContentViewの後ろに置かなければならない.setContentViewの前に置くと、この属性はsetContentView関数によって無効になるため、原因は後で話します.統一されたフルスクリーンDialogをカプセル化したい場合は、キー1を構築方法に、キー3と4をonStartに、主にsetContentViewの実行順序を保証します.
public class FullScreenDialog extends Dialog {
    public FullScreenDialog(Context context) {
        super(context);
        getWindow().requestFeature(Window.FEATURE_NO_TITLE);
    }

    @Override
    protected void onStart() {
        getWindow().setBackgroundDrawable(new ColorDrawable(0x00000000));
        getWindow().setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT);
    }
}

あとでDialogFragmentのやり方を見てみましょう.
DialogFragmentの実装方法
AndroidはDialogFragment実装ダイアログボックスを推奨しており、Dialogのすべてのニーズを完全に実現し、Fragmentのライフサイクル管理を多重化し、バックグラウンドで殺された後、自動的に復元することができます.フルスクリーンを実現する原理はDialogと同じで、タイミングの把握にすぎない.
public class FullScreen DialogFragment extends DialogFragment {

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_full_screen, container, false);
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
    
        getDialog().getWindow().requestFeature(Window.FEATURE_NO_TITLE);
        super.onActivityCreated(savedInstanceState);
    
        getDialog().getWindow().setBackgroundDrawable(new ColorDrawable(0x00000000));
        getDialog().getWindow().setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT);
    }

}

まず、ここでonActivity Createdで処理する理由を見てみましょう.DialogFragmentの実装ソースコードを少しフォローすると、setContentViewのタイミングはonActivity Createdで、次のコードキー1を見てみましょう.
@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);
    }
    ...
}

もちろん、ベースクラスDialogの実装も参照できますが、setContentViewの呼び出しタイミングを把握することが重要です.その後2つ目の案を見て、Themeを利用して実現します.
Themeトピックを使用したフルスペルダイアログ
最初のステップstyleで全画面Dialogスタイルを定義する

ステップ2:DialogFragmentを例に、onCreateでsetStyle(STYLE_NORMAL,R.style.Dialog_FullScreen)をセットするだけでスタイルを設定します.(DialogFragmentを使用することをお勧めします.Fragmentの宣言サイクルを多重化し、殺された後、再構築を再開することができます)
public class FragmentFullScreen extends DialogFragment {

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setStyle(STYLE_NORMAL, R.style.Dialog_FullScreen);
    }
}

Dialogであれば、以下のコードを設定すればよい.
public class FullScreenDialog extends Dialog {
    public FullScreenDialog(Context context) {
        super(context);
        getWindow().requestFeature(Window.FEATURE_NO_TITLE);
      }
}

実は純粋なコードの効果はこの3つの属性に対応して、それではこの3つの属性はいったいどんな作用があって、設定のタイミングはどうしてまた制限があって、以下は簡単に原因を分析します.

フルスクリーンDialog実現原理


次の3つのプロパティについて一歩一歩分析します.
    false
    @color/transparent
    true

まず最初の属性を見て、android:windowIsFloating、この属性はActivityのデフォルトスタイルとDialogの最大の違いの一つかもしれません.デフォルトのDialogテーマとActivityテーマを比較すると、どちらもThemeを継承し、Themeでは
Theme
     

ただしDialogの一般的な上書きは行われていますが、ActivityのデフォルトではwindowIsFloatingプロパティは上書きされていません
Base.V7.Theme.AppCompat.Dialog

つまりActivityはデフォルトのfalseを採用していますが、Dialogの一般的なTrueはWindowを作成する際にどのような違いがありますか?PhoneWindowに入ります.JAvaでは、Windowが初めてDecorViewを作成するときにその属性に基づいて最上位レイアウトパラメータを作成する必要がある、つまりRootMeasureSpec、Windowが新規作成されたときにWindowManagement.LayoutParamsのデフォルトはMATCH_です.PARENT、ただしwindowIsFloatingがTrueに設定されている場合、WindowManager.LayoutParamsパラメータのサイズはWRAP_に設定されますCONTENT、具体的なソースコードは以下の通りです.
protected ViewGroup generateLayout(DecorView decor) {
    // Apply data from current theme.
    TypedArray a = getWindowStyle();
    mIsFloating = a.getBoolean(com.android.internal.R.styleable.Window_windowIsFloating, false);
    int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
            & (~getForcedWindowFlags());
     
    if (mIsFloating) {
        setLayout(WRAP_CONTENT, WRAP_CONTENT);
        setFlags(0, flagsToUpdate);
    } else {
        setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
    }
    ...       
}

キー1からwindowIsFloatingがtrueに設定されている場合、WindowのウィンドウプロパティWindowManagerはsetLayout(WRAP_CONTENT,WRAP_CONTENT)によってLayoutParamsをWRAPに設定CONTENT,この属性はルートレイアウトMeasureSpecパラメータの生成に重要な役割を果たす
public void setLayout(int width, int height) {
    final WindowManager.LayoutParams attrs = getAttributes();
    attrs.width = width;
    attrs.height = height;
    if (mCallback != null) {
        mCallback.onWindowAttributesChanged(attrs);
    }
}

なぜsetContentViewの後にパラメータを設定するのかというと、generateLayoutは一般的にsetContentViewによって呼び出されるので、事前に設定しても効果がないとしても、PhoneWindowIsFloatingに基づいてWindowManagerを設定する.LayoutParams.実はViewが本当に表示している点はActivity resumeの時、WMSにViewを追加させます.実はここでWindowManagerGlobalのaddViewを呼び出します.ここには重要なレイアウトパラメータparamsがあります.実はWindowManagerです.LayoutParams l = r.window.getAttributes();Dialogのデフォルトトピックの場合、このパラメータの幅はWRAP_です.CONTENTは、最初に定義されたパラメータ値を測定する起点であり、つまり、1つのWindowがどれだけ大きいのか、このパラメータには最終的な発言権があり、具体的なViewペイントの流れは詳しくはなく、ViewのmeasureHierarchyがwindowパラメータを利用してRootMeasureSpecを構築する方法を見てみましょう.
measureHierarchy(final View host, final WindowManager.LayoutParams lp,
        final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) { 
         ...
         
         childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
     childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
           ... 
     }  

desiredWindowWindowWidthとdesiredWindowHeightは一般的に画面の幅と高さであり、WindowManagerは一般的である.LayoutParams lpは上記のパラメータで、Activityの場合、デフォルトはView Groupです.LayoutParams.MATCH_PARENT、DialogならView Groupです.LayoutParams.WRAP_CONTENTでは、MeasureSpecのデフォルトの生成ルールに従って、次のようになります.
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
    int measureSpec;
    switch (rootDimension) {
    case ViewGroup.LayoutParams.MATCH_PARENT:
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
        break;
    case ViewGroup.LayoutParams.WRAP_CONTENT:
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
        break;
    default:
        measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
        break;
    }
    return measureSpec;
}

Dialogなら、後でMeasureSpecを利用します.makeMeasureSpec(windowSize,MeasureSpec.AT_MOST)はRootMeasureSpecを生成します.つまり、最大スクリーンサイズです.実際の効果は私たちがよく使うwrap_です.Contentは、このRootMeasureSpecを利用してDecorViewを測定描画します.
  private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
    try {
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

以上がデフォルトDialogでフルスクリーンできない要因の一つです.次に、第2のプロパティandroid:windowBackgroundを見てみましょう.このプロパティはデフォルト値を採用すると、黒い枠が設定されます.実は、ここは主にデフォルトの背景の問題です.デフォルトではpaddingのInsetDrawableが採用されています.いくつかのマージンが設定されています.上のステータスバー、下のナビゲーションバー、左右に一定のマージンがあります.
"http://schemas.android.com/apk/res/android"
       android:insetLeft="16dp"
       android:insetTop="16dp"
       android:insetRight="16dp"
       android:insetBottom="16dp">
    <shape android:shape="rectangle">
        <corners android:radius="2dp" />
        <solid android:color="@color/background_floating_material_dark" />
    shape>
inset>

DecorViewは、描画時にここの余白を考慮し、windowIsFloating=falseのWindowについては、ステータスバーおよび下部ナビゲーションバーを考慮します(ここでは分析しません).後で最後に残された問題を見てみましょうなぜ?FEATURE_NO_TITLEプロパティは、setContentViewが呼び出される前に必要です.

なぜsetContentViewの前にWindowを設定する必要があるのか.FEATURE_NO_TITLEプロパティ


このアトリビュートを設定しないと、次のような効果が得られます.
ウィンドウを設定しません.FEATURE_NO_TITLE
上記の分析ではsetContentViewがgenerateLayoutをさらに呼び出してルートレイアウトを作成することを知っています.Androidシステムはデフォルトで複数のスタイルのルートレイアウトを実現しています.異なるシーンに対応するために、選択のルールはユーザーが設定したテーマスタイル(Windowプロパティ)です.例えば、Titleを必要としないでください.レイアウトスタイルは選択後は変更できません(サイズは可能です).一部のプロパティはレイアウトファイルを選択する参考です.setContentViewの後に設定すると意味がなくなり、Androidもレイアウトを選択した後にレイアウト選択に影響を与える属性を設定することは許されず、例外が投げ出されます.原理は以下の通りです.
    protected ViewGroup generateLayout(DecorView decor) {
    TypedArray a = getWindowStyle();
          ...
    if (a.getBoolean(com.android.internal.R.styleable.Window_windowNoTitle, false)) {
        requestFeature(FEATURE_NO_TITLE);
    } else if (a.getBoolean(com.android.internal.R.styleable.Window_windowActionBar, false)) {
        requestFeature(FEATURE_ACTION_BAR);
    }

 @Override
 public boolean requestFeature(int featureId) {
    if (mContentParent != null) {
        throw new AndroidRuntimeException("requestFeature() must be called before adding content");
    }
    ...
    }

以上、フルスクリーンDialogに対してカスタマイズされたいくつかの処理とフルスクリーン原理の浅い分析(ここではステータスバーの処理は含まれていません.その部分はSystemUIに関連しています).
参考までに、転載をご指摘ください.出典を明記してください.

リファレンスドキュメント


Android公式推奨:DialogFragment作成ダイアログボックス幅Android Project Butter分析androidアプリケーションインタフェースの表示フローを制御する方法(4)表面を描くAndroidのウィンドウを作成する