This article will be updated from time to time, and the project under Watch is recommended.

Please call star if you like it, submit issue if you think there are flaws, and submit pull Request if you have better ideas.

The sample code for this article is based on the EasyDialog library, so if you have other tips and methods to help improve this article.

This article fixed link: github.com/tianzhijiex…


background





image_1bk93uogp1qhvr2911u1p3f10ms6c.png-124kB

Whether it is a large project or a small project, the dialog box style presented by the design is very variable, and it is difficult to form a unified modular style. After long-term analysis, it is found that the following problems generally exist in various projects:

  • No native Android Dialog style, all custom
  • Dialog has no uniform style and at least three or more styles
  • There are so many custom dialogs without a unified design that it is difficult to expand and associate
  • Most dialogs are strongly bound to the business and are extremely independent

We want to leverage native apis to implement highly extensible custom dialogs. After a long period of exploration, I found a more lightweight integration solution.

demand

  • Modular encapsulation of dialog, managed by dialogFragment
  • Use native apis to configure dialogs, reducing learning costs
  • Let dialog builder support inheritance, implement composition + inheritance form
  • A configuration item changes a custom dialog to pop up from the bottom
  • Allows dialog backgrounds to be set, supporting transparent backgrounds
  • You can change a native Dialog to a custom style by directly changing the style
  • Data in the Dialog should not be lost after the screen is rotated
  • Can listen for dialog to disappear, click on a blank to close, etc
  • Dialogs can be associated with events between activities
  • Implement a dialog style that pulls out from the bottom

implementation

Modular encapsulated Dialog

In v7, alertDialog also provides a theme and a variety of capabilities (single and multiple). It is commonly used in activities as follows:

New Alertdialow.builder (this).setTitle("title").seticon (r.drawer.ic_launcher).setpositiveButton (" good ", New positiveListener()).setNeutralButton(" middle ", new NeutralListener()).setnegativeButton (" bad ", new NegativeListener()) .creat() .show();Copy the code

But there is an obvious problem — dialog is very independent!

Because alertDialog is new as a Builder, it deprives dialog of its inheritable nature. If the dialogs in a project have some common code, we should definitely tidy them up. If you still want dialogs to be managed uniformly, then by all means create a wrapper class:

public class DialogHelper { private String title, msg; Public void setTitle(String title) {this.title = title; Public void setMsg(String MSG) {this. MSG = MSG; } public void show(Context Context) {// Create a dialog AlertDialog dialog = new Alertdialog.builder (Context) .setTitle(title) .setMessage(msg) .create(); / /... // Universal Settings Window Window = dialog.getwindow (); window.setBackgroundDrawable(new ColorDrawable(0xffffffff)); // white background dialog.show(); }}Copy the code

The wrapper class solves the problem of repetitive code, but it still doesn’t solve the problems of Dialog data preservation and lifecycle management. Later, Google introduced a new class called dialogFragment in android3.0. Now, we can use dialogFragment as a control to manage alertDialog.

public class MyDialogFragment extends DialogFragment{ @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {// Undesirable return inflater.inflate(r.layout.dialog, null); }}Copy the code

Attention, attention! If you make a Dialog layout in onCreateView, then all our previous work may be meaningless and break modularity. I strongly recommend creating a dialog with onCreateDialog!





An AlertDialog is managed by a DialogFragment, and a DialogFragment is managed by the FragmentManager. This is true object-oriented encapsulation, and the code is much cleaner.

@Override public Dialog onCreateDialog(Bundle savedInstanceState) { Builder builder = new AlertDialog.Builder(getActivity()); Builder.settitle (" I am the title ").setMessage(getResources().getString(r.sing.hello_world)).setpositiveButton (" I agree ", This).setnegativeButton (" Disagree ", this).setCancelable(false); //.show(); // show cann't be use here return builder.create(); }Copy the code

If you want to make a custom dialog, you can do so directly with setView:

Builder.setview (view) // Set custom viewCopy the code

So their responsibilities are clear:

  1. FragmentManager Manages the binding relationship between the fragment life cycle and the activity
  2. DialogFragment to handle various events (onDismiss, etc.) and receive external arguments (bundles)
  3. AlertDialog is responsible for displaying the content and style of the Dialog
public class MyDialog extends DialogFragment{ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Bundle bundle = getArguments(); / /... } @override public Dialog onCreateDialog(Bundle savedInstanceState) { Return new alertdialog.Builder (getActivity()).setMessage("message").create(); } @Override public void onDismiss(DialogInterface dialog) { super.onDismiss(dialog); // Handle response events}}Copy the code

The Dialog trilogy is now complete:

  1. Get the external parameters in onCreate
  2. Build an alertDialog object in onCreateDialog
  3. Through DialogFragmentshow()To display the dialog box

Understand DialogFragment method calls

Since the Fragment itself is a complex manager, and many developers have a different understanding of the various callback methods in dialogFragment, I made the following diagram:





Image_1bk895qo21fb71qkn14bb1vk3175p9. PNG – 36.3 kB

public class MyDialog extends android.support.v4.app.DialogFragment { private static final String TAG = "MyDialog"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @override public View onCreateView(LayoutInflater inflater, @nullable ViewGroup container, Bundle savedInstanceState) {// return null to fragment as a control return null; } @nonnull @override public Dialog onCreateDialog(Bundle savedInstanceState) AlertDialog.Builder(getActivity()) .setMessage("msg") .setTitle("title") .create(); } @Override public void setupDialog(Dialog dialog, int style) { super.setupDialog(dialog, style); } @override public void onStart() {super.onstart (); // The view is from onCreateView, so null view view = getView(); / /... }} findViewById ();Copy the code

The call flow is as follows:





Image_1bk8b5uh6ikp3the4k1iv8ote13. PNG – 49.9 kB

Note that the findView operation should only be performed in onStart because the window has not been configured and Windows Manager has not mounted the entire window.

Use native Builder for parameter transfer

You can pass parameters to a fragment with an intent, but the actual number of parameters can be very large. In a typical project, we would create a simple Serializable object for one-time assembly and then stuff it into the fragment.

public class BuildParams implements Serializable {

    public int mIconId = 0;

    public int themeResId;

    public CharSequence title;

    public CharSequence message;

    public CharSequence positiveText;

    public CharSequence neutralText;

    public CharSequence negativeText;

    public CharSequence[] items;

    public boolean[] checkedItems;

    public boolean isMultiChoice;

    public boolean isSingleChoice;

    public int checkedItem;

}Copy the code

Once we have our data objects, we naturally want to encapsulate them in build mode:

public class BuildParamsBuilder { private int mIconId; private int mThemeResId; / /... Public BuildParamsBuilder setIconId(int iconId) {mIconId = iconId; return this; } public BuildParamsBuilder setThemeResId(int themeResId) { mThemeResId = themeResId; return this; } / /... Public BuildParams build() {return new BuildParams(mIconId, mThemeResId, mTitle, mMessage, mPositiveText, mNeutralText, mNegativeText, mItems, mCheckedItems, mIsMultiChoice, mIsSingleChoice, mCheckedItem); }}Copy the code

At this point, we can obviously find that the Builder here is very similar to the Builder of Alert. So can we just use it? By reading the source code we found AlertController AlertParams is native API provides for various parameters of the object, we can be, and custom BuildParams mapping, so that you can save for since the creation of the builder.

Mapping process:

public BuildParams getBuildParams(AlertController.AlertParams p) {
    BuildParams data = new BuildParamsBuilder().createBuildParams();
    data.themeResId = themeResId;

    data.mIconId = p.mIconId;
    data.title = p.mTitle;
    data.message = p.mMessage;
    data.positiveText = p.mPositiveButtonText;
    data.neutralText = p.mNeutralButtonText;
    data.negativeText = p.mNegativeButtonText;
    data.items = p.mItems;
    data.isMultiChoice = p.mIsMultiChoice;
    data.checkedItems = p.mCheckedItems;
    data.isSingleChoice = p.mIsSingleChoice;
    data.checkedItem = p.mCheckedItem;

    return data;
}Copy the code

The build process:

public <D extends EasyDialog> D build() {
    EasyDialog dialog = createDialog();
    AlertController.AlertParams p = getParams();

    Bundle bundle = new Bundle();
    bundle.putSerializable(KEY_BUILD_PARAMS, getBuildParams(p));
    bundle.putBoolean(KEY_IS_BOTTOM_DIALOG, isBottomDialog);
    dialog.setArguments(bundle);

    dialog.setOnCancelListener(p.mOnCancelListener);
    dialog.setOnDismissListener(p.mOnDismissListener);

    dialog.setPositiveListener(p.mPositiveButtonListener);
    dialog.setNeutralListener(p.mNeutralButtonListener);
    dialog.setNegativeListener(p.mNegativeButtonListener);
    dialog.setOnClickListener(p.mOnClickListener);
    dialog.setOnMultiChoiceClickListener(p.mOnCheckboxClickListener);

    dialog.setCancelable(p.mCancelable);
    return (D) dialog;
}Copy the code

This way we can directly throw the assembled parameters to the Fragment.

Let native Builder support inheritance

Typically, our Builder does not support inheritance, but for dialog we want to have parent-child relationships.

Dialog 1:





Image_1bk8e07oi1rjqmg01rcs1me94ha1g. PNG – 23.9 kB

Dialog 2:





image_1bk8e7psmimlrac6dcbfq93s1t.png-25kB

These two dialogs are very similar, and we want to do something interesting. My second dialog has no icon. If the title field is passed in with a value of “title”, I change it to the New value, that is, “New title”.

Public class MyEasyDialog extends EasyDialog{/** * public static Class Builder extends EasyDialog EasyDialog.Builder { public Builder(@NonNull Context context) { super(context); } protected EasyDialog createDialog() { return new MyEasyDialog(); } } @Override protected void modifyOriginBuilder(EasyDialog.Builder builder) { super.modifyOriginBuilder(builder); builder.setIcon(0); // Remove icon if (textutils.equals (getBuildParams().title, "title ")) {builder.setTitle("New title "); }}}Copy the code

There are two important methods:

  • ModifyOriginBuilder () : Used to modify the Builder object of the original parent class
  • GetBuildParams () : Gets the parameters that were originally set in the Builder in the parent class

All we need to do now is inherit the Builder from the parent class and duplicate the createDialog method, and the rest is in modifyOriginBuilder.

Imagine if we didn’t have to inherit. To complete this work, it is necessary to add some conditional judgments to the original dialogFragment, which is not flexible enough.

Sample code using the native Builder:

EasyDialog.Builder builder = new EasyDialog.Builder(); builder.setTitle("Title") .setMessage(R.string.hello_world) .setOnCancelListener(new OnCancelListener() { public void onCancel(DialogInterface dialog) { // onCancel - > onDismiss } }) .setOnDismissListener(new OnDismissListener() { public  void onDismiss(DialogInterface dialog) { } }) .setNeutralButton("no", null) .setPositiveButton("ok", new OnClickListener() { public void onClick(DialogInterface dialog, int which) { } }) .setNegativeButton("cancel", new OnClickListener() { public void onClick(DialogInterface dialog, int which) { } }); EasyDialog dialog = builder.build(); dialog.setCancelable(true); / / click blank whether can cancel the dialog. The show (getSupportFragmentManager (), the TAG);Copy the code

Create custom dialog templates

1. Customize a Builder

To customize a Dialog, you must first define a Builder derived from BaseeasyDialog. Builder, which also supports bundle parameters.

public static class Builder extends BaseEasyDialog.Builder<Builder> { private Bundle bundle = new Bundle(); Public Builder setImageBitmap(Bitmap Bitmap) {bundle.putbytearray (KEY_IMAGE_BITMAP, bitmap2ByteArr(bitmap)); return this; } public Builder setInputText(CharSequence text, CharSequence hint) { bundle.putCharSequence(KEY_INPUT_TEXT, text); bundle.putCharSequence(KEY_INPUT_HINT, hint); return this; } protected DemoSimpleDialog createDialog() { DemoSimpleDialog dialog = new DemoSimpleDialog(); dialog.setArguments(bundle); return dialog; }}Copy the code

Note: The generic above takes the current Builder class as an argument

2. Create a Dialog that inherits from BaseCustomDialog

There is also a flow to the way dialog is written:

  1. To get the data
  2. Setting the layout file
  3. Binding the view
  4. Set the View and its related events
  5. Destruction of the view
public class DemoSimpleDialog extends BaseCustomDialog { @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Get the Bundle arguments = getArguments(); if (arguments ! = null) { mInputText = arguments.getCharSequence(KEY_INPUT_TEXT); }} @override protected int getLayoutResId() {return r.layout.demo_dialog_layout; } @override protected void bindViews(View root) {// bindView mInputTextEt = findView(r.i.nput_et); } @override public void setViews() {// Set view if (mInputText! = null) { mInputTextEt.setVisibility(View.VISIBLE); if (! IsRestored ()) {// if the fragment isRestored from a rotation screen or other state minputtextet.settext (mInputText); } } } @Override public void onDestroyView() { super.onDestroyView(); // Destroy the associated view mInputTextEt = null; }}Copy the code

Customize the dialog that pops up from the bottom

The effect





Image_1bk8m111gth5p41ijr1rt4jkq2a. PNG – 47.4 kB

Dialogs popping up from the bottom are not uncommon, but Android doesn’t provide them natively, so customize them. The custom method is also simple and inherits from BaseCustomDialog:

public class CustomBottomSheetDialog extends BaseCustomDialog { public static class Builder extends BaseEasyDialog.Builder<Builder> { public Builder(@NonNull Context context) { super(context); } protected EasyDialog createDialog() { return new CustomBottomSheetDialog(); } } @Override protected int getLayoutResId() { return R.layout.custom_dialog_layout; } @Override protected void bindViews(View root) { // findView... } @Override protected void setViews() { ((TextView) findView(R.id.message_tv)).setText(getBuildParams().message); }}Copy the code

The only difference is that we need to add a flag bit to the build:

CustomBottomSheetDialog.Builder builder = new CustomBottomSheetDialog.Builder(this); builder.setIsBottomDialog(true); // indicate that this is a CustomBottomSheetDialog popup from the bottom dialog = Builder.build (); dialog.show(getSupportFragmentManager(), "dialog");Copy the code

The principle of

The idea here is to use the BottomSheetDialog provided in the Support package. BottomSheetBehavior is already configured inside the BottomSheetDialog, which also defines a container:

<? The XML version = "1.0" encoding = "utf-8"? > <! -- ~ Copyright (C) 2015 The Android Open Source Project ~ ~ Licensed under the Apache License, Version 2.0 (the "License"); ~ you may not use this file except in compliance with the License. ~ You may obtain a copy of the License at ~ ~ http://www.apache.org/licenses/LICENSE-2.0 ~ ~ Unless required by applicable law or agreed to in writing, software ~ distributed under the License is distributed on an "AS IS" BASIS, ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ~ See the License for the specific language governing permissions and ~ limitations under the  License. --> <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <View android:id="@+id/touch_outside" android:layout_width="match_parent" android:layout_height="match_parent" android:importantForAccessibility="no" Android: soundEffectsEnabled = "false" / > < FrameLayout / / your custom layout will ultimately be the add here android: id = "@ + id/design_bottom_sheet" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center_horizontal|top" app:layout_behavior="@string/bottom_sheet_behavior" style="?attr/bottomSheetStyle"/> </android.support.design.widget.CoordinatorLayout>Copy the code

We all know that BottomSheetBehavior is looking for the “bottom layout” through app:layout_behavior=”@string/bottom_sheet_behavior”, Our custom layout is in design_bottom_sheet, so naturally we have the bottom pop-up.

By the way, since the container layout is dead in the source code, your custom layout is inside the container, so you write it in the custom layout

app:behavior_hideable="true"
app:behavior_peekHeight="40dp"
app:layout_behavior="@string/bottom_sheet_behavior"Copy the code

Style =”? Style =”? Attr/bottomSheetStyle “.

In addition to this method, you can also implement this effect in setViews yourself:

@override protected void setViews() {final DisplayMetrics dm = new DisplayMetrics(); getActivity().getWindowManager().getDefaultDisplay().getMetrics(dm); / / build layoutParams final WindowManager. LayoutParams layoutParams = getDialog () getWindow (). The getAttributes (); int padding = getResources().getDimensionPixelOffset(R.dimen.kale_dialog_padding); layoutParams.width = dm.widthPixels - (padding * 2); layoutParams.gravity = Gravity.BOTTOM; GetDialog ().getwindow ().setAttributes(layoutParams); // getDialog().getwindow ().setLayout(dm.widthPixels, getDialog().getWindow().getAttributes().height); }Copy the code

This is the standard way to implement a bottom pop-up dialog.

Dialog style that is pulled from the bottom





652417-6 c205a491048768c. GIF – 164.8 kB

Android has a perfectly good BottomSheet for the bottom popover effect.

<android.support.design.widget.CoordinatorLayout android:id="@+id/coordinatorlayout"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    >

    <include layout="@layout/content_bottom_sheet" />

</android.support.design.widget.CoordinatorLayout>Copy the code
<? The XML version = "1.0" encoding = "utf-8"? > <LinearLayout android:id="@+id/ll_sheet_root" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="200dp" Android: Orientation ="vertical" app:behavior_hideable="true" app:behavior_peekHeight="40dp" // The bottom exit distance App :layout_behavior="@string/bottom_sheet_behavior" > <TextView // Android :layout_height=" 40DP "Android :gravity="center" Android :text=" textSize=" 30DP" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="bottom|center" android:layout_marginTop="20dp" android:orientation="horizontal" > // ... Hidden parts </LinearLayout> </LinearLayout>Copy the code
// Get the BottomSheetBehavior object behavior = corresponding to the view object of the BottomSheet BottomSheetBehavior.from(findViewById(R.id.ll_sheet_root)); if (behavior.getState() == BottomSheetBehavior.STATE_EXPANDED) { behavior.setState(BottomSheetBehavior.STATE_COLLAPSED);  } else { behavior.setState(BottomSheetBehavior.STATE_EXPANDED); }Copy the code

It looks like a Dialog, but is essentially a layout file that has little to do with a Dialog.

Set dialog background correctly

There are two ways to set the dialog background:

1. Set setBackgroundDrawable for window

At dialogFragment#onStart:

getDialog().getWindow().setBackgroundDrawable(new ColorDrawable()); GetDialog ().getwindow ().setBackgroundDrawable(new ColorDrawable(0xffFFFFFF)); / / white background getDialog (.) getWindow () setBackgroundDrawableResource (R.d rawable. Dialog_bg_custom_red); // Resource filesCopy the code

2. Set it in style

<! -- Dialog box background (important), default = abc_dialog_material_background--> <item name="android:windowBackground">@drawable/dialog_bg_custom</item>Copy the code

In practice, our design usually gives us a rounded corner + margin style:





Image_1bk8ntkeanu61ic2f1l151l10u42n. PNG – 123.4 kB

Our goal is to create rounded corners and margins, so shape and inset come naturally to mind:

<? The XML version = "1.0" encoding = "utf-8"? > <! Copyright (C) 2016 The Android Open Source Project Licensed under The Apache License, Version 2.0 (The "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> <inset xmlns:android="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="@android:color/white" /> </shape> </inset>Copy the code




Image_1bk8o2fkg1nhf1j2c51m1ar9olc34. PNG – 66.7 kB

A Drawable that insets another Drawable by a specified distance or fraction of the content bounds. This is used when a View needs a background that is smaller than the View’s actual bounds.

The inset tag, if you haven’t used it yet. When you set this resource to background for the view, it will keep some distance from the view’s margin and become a smaller background image than the view.





Image_1bk8okdprcea1eg34ra11lpvml3h. PNG – 305.7 kB

If your dialog is transparent at the top and neat at the bottom, you might consider using layer-list and inset:

<inset xmlns:android="http://schemas.android.com/apk/res/android" android:insetBottom="16dp" android:insetLeft="26dp" <layer-list> <item> <color Android :color="#00E4007F" /> // transparent area </item> <item android:top="80dp"> <shape android:shape="rectangle"> <corners android:radius="10dp" /> <solid android:color="#d19a70" /> </shape> </item> </layer-list> </inset>Copy the code




Image_1bk8oonia8431su6cjiojr5r3u. PNG – 88.6 kB

Change the style by changing the style

If the dialogs in your project are very simple and just want to slightly customize the native style, you might consider changing the style of your dialogs. This is done by setting the alertDialogTheme property in the theme of the project.

<style name="AppTheme.CustomDialogStyle"> <! <item name="alertDialogTheme">@style/ theme.dialog. Alert</item> </style>Copy the code
<! -- parent="@style/Theme.AppCompat.Light.Dialog.Alert" --> <style name="Theme.Dialog.Alert"> <item name="windowMinWidthMajor">@dimen/abc_dialog_min_width_major</item> <item name="windowMinWidthMinor">@dimen/abc_dialog_min_width_minor</item> </style>Copy the code

The key is the various properties in the Theme.Dialog:

<style name="Theme.Dialog" parent="Theme.AppCompat.Light.Dialog"> <item name="windowActionBar">false</item> <! <item name="windowNoTitle">true</item> <! - frame - > < item name = "android: windowFrame" > @ null < / item > <! <item name=" Android :windowIsFloating">true</item> <! - whether transparent - > < item name = "android: windowIsTranslucent" > true < / item > <! <item name=" Android :windowNoTitle">true</item> <! - whether the dialog has cover -- > < item name = "android: windowContentOverlay" > @ null < / item > <! - dialog appears when the background is dark - - > < item name = "android: backgroundDimEnabled" > true < / item > <! -- Background color, because the background in windowBackground is already written down, <item name=" Android :colorBackground">@color/ background_FLOATing_material_light </item> <! - coloring cache (generally don't have to) -- - > < item name = "android: colorBackgroundCacheHint" > @ null < item > <! - the title font style - > < item name = "android: windowTitleStyle" > @ style/RtlOverlay DialogWindowTitle. AppCompat < / item > < item name="android:windowTitleBackgroundStyle">@style/Base.DialogWindowTitleBackground.AppCompat</item> <! -- Dialog box background (important), default = abc_dialog_material_background--> <item name="android:windowBackground">@drawable/dialog_bg_custom</item> <! - Animation - > < item name = "android: windowAnimationStyle" > @ style/Animation. AppCompat. Dialog < / item > <! - when the ime pop-up adaptive - > < item name = "android: windowSoftInputMode" > stateUnspecified | adjustPan < / item > < item name="windowActionModeOverlay">true</item> <! -- The inner margin of the list section, On the radio, multi-select list - > < item name = "listPreferredItemPaddingLeft" > 20 dip < / item > < item name="listPreferredItemPaddingRight">24dip</item> <item name="android:listDivider">@null</item> <! -- The default color of the text in the radio and multi-select dialog boxes is: @color/abc_primary_text_material_light --> <item name="textColorAlertDialogListItem">#00ff00</item> <! -- Separate lines for radio and multiple selection dialogs --> <! -- Divider in the Dialog listView divider default: @null--> <item name="listDividerAlertDialog">@drawable/ Divider </item> <! - the radio button in the dialog ICONS (the default is not null) - > < item name = "android: listChoiceIndicatorSingle" > @ android: drawable/btn_radio < / item > <! -- The entire inner margin of the dialog box, but not the list part default: @dimen/abc_dialog_padding_material--> <item name="dialogPreferredPadding">20dp</item> <item name="alertDialogCenterButtons">true</item> <! <item name="alertDialogStyle">@style/ alertDialogStyle </item> </style>Copy the code

The attributes here have been explained in detail without further explanation, but the key is:

<! <item name="alertDialogStyle">@style/ alertDialogStyle </item>Copy the code
<! - here are all custom attributes, such as modified will change the color of the dialog style - > < style name = "AlertDialogStyle" parent "=" Base. AlertDialog. AppCompat "> <! -- AlertController.class - line:168 --> <! <item name=" Android :layout">@layout/custom_dialog_alert_material</item> <! <item name="listLayout">@layout/custom_dialog_list_material</item> <! <item name="listItemLayout">@layout/custom_dialog_select_item_material</item> <! <item name="multiChoiceItemLayout">@layout/custom_dialog_select_multichoice_material</item> <! <item name="singleChoiceItemLayout">@layout/custom_dialog_select_singlechoice_material</item> </style>Copy the code

If you want to modify the native layout slightly, you can just copy the native layout and put the new layout in here.

Before modifying the layout:





Image_1bk8pj19b1qdsa9431k10dadj74b. PNG – 23.8 kB

After modifying the layout:





Image_1bk8pjjr45kj1slr1ens1ibs1t964o. PNG – 40.7 kB

The style changes completely, but the code doesn’t change a line, and the effect is still amazing.

Note: The native Layout code changes from version to version of Support, so you need to check this every time you update your support package to prevent unforeseen crashes.

Hold the data in the dialog after the screen rotates

1. Save the view status

As we know, when an Activity calls onSaveInstanceState(), it saves its View Tree and further calls onSaveInstanceState() for each child View to save the state. If your Dialog doesn’t have any asynchronous or special data and is just an editText, then android’s view autosave mechanism already does that for you.

Landscape:





Image_1bk8r00as1ng01rok1dgolk3tmg55. PNG – 31.3 kB

Vertical screen:





image_1bk8r0eig1ka81u66128k1qbm6o5i.png-26kB

If your Dialog has a custom view in which you do not handle the view’s onSaveInstanceState(), then the data in the rotated dialog probably will not survive as you expect.

For more information on how to handle the state of a custom view, see Saving The State of a View correctly in Android.

2. Save the data in the intent

OnCreate refires after each rotation of the screen, and the data in the bundle retrieved from onCreate remains the same, so you don’t have to worry about manually saving the bundle retrieved from getArgument().

You can use isRestored() to determine if the current dialog is a rebuild. In this way, setting a new title will flush out eidtText’s automatically saved input values.

@Override
protected void setViews() {
    // ...
    if (!isRestored()) {
        editText.setText("default value");
    } 
}Copy the code

3. Save logical data





Image_1bk8t7mb71trfd07128k1cql1e595v. PNG – 175.3 kB

One of the great benefits of managing a Fragment dialog is that you can use its own data preservation scheme:

public class MyEasyDialog extends EasyDialog { private static final String TAG = "MyEasyDialog"; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); } @Override protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); }}Copy the code

When the operations in our Dialog have asynchronous network operations, a simple view save scheme is no longer sufficient. Consider saving the status and results of network requests in onSaveInstanceState and restoring them in onRestoreInstanceState.

Dialog related event handling

For simplicity, I still use Builder mode to set dialog listening events:

EasyDialog.Builder builder = new MyEasyDialog.Builder(this); builder.setTitle("Title") .setIcon(R.mipmap.ic_launcher) .setMessage(R.string.hello_world) .setOnCancelListener(new DialogInterface. OnCancelListener () {@ Override public void onCancel (DialogInterface dialog) {/ / disappears will trigger point margin!!!! } }) .setOnDismissListener(new DialogInterface.OnDismissListener() { @Override public void onDismiss(DialogInterface }}). SetPositiveButton ("ok", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { } }) .setNegativeButton("cancel", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); // cancel -> dismiss } }) // ...Copy the code

The advantage of this is simplicity, but the disadvantage is that after the screen is rotated, the dialog’s various listeners are null. So, if you want to keep dialog events alive after a screen turn, you still need to use the activity interface to do so.

Special attention should be paid to:

  1. Dialog appears and disappears without triggering the activity’s onPause() and onResume().
  2. OnCancelListener simply listens for the event when the dialog disappears after clicking on the blank

conclusion

Dialog is one of the most commonly used controls, but it doesn’t have a lot of knowledge. If we think about it from scratch, you’ll see that it covers encapsulation techniques, life cycles, Windows Manager mounts, Fragment&Activity communication, and more. I believe that if you can simplify your existing dialog design through the simplest API, you can use native or off-the-shelf solutions to meet the needs of your project, and you don’t have to define dialog boxes all over the place.





Weibo: @Sky Boundary 2010

Reference article:

  • Read DialogFragment in detail — developer_Kale
  • DialogFragment Creates a dialog box
  • BottomSheet, BottomSheetDialog use detailed solution
  • How does Android save and restore the state of a custom View? Jane the book –
  • Save view status correctly in Android – surfing days
  • Android: Bottom Sheet – Days spent on the Internet