Skip to content

BottomDialog&BottomMenu_en

Kongzue edited this page Dec 12, 2024 · 10 revisions

🌐 View 简体中文文档 | 繁體中文文檔

🧾 BottomDialog and BottomMenu

BottomDialog and BottomMenu

BottomDialog provides a dialog style that pops up from the bottom. It can be set with a title, message text, and custom layout. When using the Material theme, it also offers features like swiping down to close and swiping up to expand.

BottomMenu is an extension component of BottomDialog, adding menu functionality on top of the basic BottomDialog. The menu can be set with menu content/icons/single-choice feature. In different themes, a "Cancel" close button may be provided (Note: As Material can be closed by swiping down, no additional "Cancel" button is provided for the Material theme).

Displaying a Simple Bottom Dialog

Use the following code to display a dialog:

BottomDialog.show("Title", "This is the dialog content.");

Please note, the BottomDialog in the Material theme is closed by default through a swipe-down action, and different themes might have different logic. For example, the iOS theme provides a "Cancel" button for closing.

In other themes, you can set the cancel button text and event using the .setCancelButton(...) method:

BottomDialog.show("Title", "This is the dialog content.")
    .setCancelButton("Cancel", new OnDialogButtonClickListener<BottomDialog>() {
        @Override
        public boolean onClick(BottomDialog baseDialog, View v) {
            //...
            return false;
        }
    });

Displaying a Bottom Menu

The BottomMenu is a quick-selection menu dialog box implemented based on the BottomDialog. You can quickly display a menu by passing List<String>, String[] menuList, List<CharSequence> menuList, or CharSequence[] menuList:

BottomMenu.show("Open in new tab", "Read later", "Copy link address")
        .setMessage("This is the title")
        .setOnMenuItemClickListener(new OnMenuItemClickListener<BottomMenu>() {
            @Override
            public boolean onClick(BottomMenu dialog, CharSequence text, int index) {
                toast(text);
                return false;
            }
        });

In the code above, OnMenuItemClickListener is the callback for menu item clicks, where text is the menu text, and index is the menu's index.

Radio menu can also easily set the menu icon, we try to save as much as possible to write the Adapter adapter trouble, to set the menu icon can be specified directly, for a menu does not need the icon can be passed directly into the 0:

.setIconResIds(R.mipmap.img_dialogx_demo_add, R.mipmap.img_dialogx_demo_edit...)

Whether the icon is colored according to the light/dark color theme can be adjusted with the following method:

.setAutoTintIconInLightOrDarkMode(boolean)

Pre-selection Effect

The bottom menu also provides a "selected" feature, allowing you to set a selected index. When the BottomMenu is displayed, the menu item at the corresponding position will be highlighted as selected. The selection effect may vary slightly depending on the theme.

private int selectMenuIndex;

BottomMenu.show("Deny", "Ask", "Always Allow", "Allow Only While Using")
        .setMessage("This is the text description for permission confirmation, it's a demo of a single-choice menu.")
        .setTitle("Permission Title")
        .setOnMenuItemClickListener(new OnMenuItemClickListener<BottomMenu>() {
            @Override
            public boolean onClick(BottomMenu dialog, CharSequence text, int index) {
                // Record the selected value
                selectMenuIndex = index;
                toast(text);
                return false;
            }
        })
        .setSelection(selectMenuIndex);  // Specify the selected position

Similarly, when you set the return value to return true in the OnMenuItemClickListener, the menu will not automatically close after an item is clicked.

(Optional) Setting Icons Using Callbacks:

Alternatively, you can use a callback by implementing setOnIconChangeCallBack(...):

BottomMenu.show("Add", "View", "Edit", "Delete")
        .setOnIconChangeCallBack(new OnIconChangeCallBack(true) { // The parameter indicates whether to tint icons based on light/dark mode
            @Override
            public int getIcon(BottomMenu bottomMenu, int index, String menuText) {
                switch (menuText) {
                    case "Add":
                        return R.mipmap.img_dialogx_demo_add;
                    case "View":
                        return R.mipmap.img_dialogx_demo_view;
                    case "Edit":
                        return R.mipmap.img_dialogx_demo_edit;
                    case "Delete":
                        return R.mipmap.img_dialogx_demo_delete;
                }
                return 0;  // Return 0 means no icon will be shown
            }
        })
        .setOnMenuItemClickListener(new OnMenuItemClickListener<BottomMenu>() {
            @Override
            public boolean onClick(BottomMenu dialog, CharSequence text, int index) {
                toast(text);
                return false;
            }
        });

In the above code, OnIconChangeCallBack has a parameter (boolean)autoTintIconInLightOrDarkMode that determines whether the icons should be tinted to match the light or dark mode. When set to true, menu icons will automatically adjust their color according to the text color in light or dark mode. It's recommended to enable this feature when using linear or outline icons that are not multi-colored.

Asynchronously Loading Menu Icons

Since version 0.0.50.beta27, you can also asynchronously load menu icons from the network. This feature requires the use of the new MenuIconAdapter. Here's how you can do it:

.setOnIconChangeCallBack(new MenuIconAdapter<BottomMenu>(false) {
    String[] urls = {
            "http://www.kongzue.com/test/res/dialogx/ic_menu_add.png",
            "http://www.kongzue.com/test/res/dialogx/ic_menu_read_later.png",
            "http://www.kongzue.com/test/res/dialogx/ic_menu_link.png"
    };
    @Override
    public boolean applyIcon(BottomMenu dialog, int index, String menuText, ImageView iconImageView) {
        Glide.with(MainActivity.this).load(urls[index]).into(iconImageView); // Example of loading network resources into menu icons using Glide
        return true;
    }
});

In the applyIcon callback method, the ImageView iconImageView of the menu icon is exposed, and you can use any asynchronous framework to load the icon resource. Returning true indicates that the icon for this menu item should be displayed, while returning false will hide the icon for that menu item.

Single-Choice Menu

If you need to select a menu item but not immediately close the dialog, maintaining the selected state for user confirmation or cancellation, you can use the single-choice state callback OnMenuItemSelectListener#onOneItemSelect to monitor the menu's real-time selected state:

.setOnMenuItemClickListener(new OnMenuItemSelectListener<BottomMenu>() {
    @Override
    public void onOneItemSelect(BottomMenu dialog, CharSequence text, int index, boolean select) {
        // index is the menu item index clicked by the user
    }
})
// Enable single-choice mode, choose one of the following:
.setSelection(selectMenuIndex)	// Enable single-choice mode and specify a selected item
.setSingleSelection()		// Enable single-choice mode without specifying a selected item

In this case, the dialog does not immediately disappear after the user clicks on a menu item, but waits for possible further changes or selections by the user.

To get the menu item selected by the user when the dialog closes or later, you can use the .getSelectionIndex() method to get the current selected menu index, and .getMenuList() to get all menu texts.

Multi-Selection Menu

To enable a multi-selection menu, you can implement the following code, using multi-selection menu state callbacks to get real-time selected items:

.setOnMenuItemClickListener(new OnMenuItemSelectListener<BottomMenu>() {
    @Override
    public void onMultiItemSelect(BottomMenu dialog, CharSequence[] text, int[] index) {
        // index[] array is the collection of all selected menu item indexes, text[] is the collection of all selected menu item texts
    }
})
// Enable multi-selection mode, choose one of the following:
.setSelection(selectMenuIndexArray);	// Enable multi-selection mode and specify a selected index array
.setMultiSelection()	// Enable multi-selection mode without specifying selected items

Here, the dialog does not immediately disappear after the user clicks on a menu item, but waits for possible further changes or additional selections by the user.

To get the menu items selected by the user when the dialog closes or later, you can use the .getSelectionIndexArray() method to get the current selected menu index collection, .getSelectTextArray() to get the collection of selected menu texts, and .getMenuList() to get all menu texts.

Button Click Callbacks

Both BottomDialog and BottomMenu, like MessageDialog, support setting display for OK, Cancel, and Other buttons. However, please note that some themes may not support additional Cancel and Other buttons.

To set buttons and callbacks, similar to MessageDialog, use the following code:

.setOkButton("OK", new OnDialogButtonClickListener<BottomDialog>() {
    @Override
    public boolean onClick(BottomDialog dialog, View v) {
        //...
        return false;
    }
})
.setCancelButton("Cancel", new OnDialogButtonClickListener<BottomDialog>() {
    @Override
    public boolean onClick(BottomDialog dialog, View v) {
        //...
        return false;
    }
})
.setOtherButton("Other", new OnDialogButtonClickListener<BottomDialog>() {
    @Override
    public boolean onClick(BottomDialog dialog, View v) {
        //...
        return false;
    }
})

The callback has a return value; if return true, the dialog will not close automatically after a click.

Additionally, DialogX offers various methods to set callback and button text:

// Set only the button text
.setOkButton("OK")

// Set only the button click callback
.setOkButton(new OnDialogButtonClickListener<BottomDialog>() {
    @Override
    public boolean onClick(BottomDialog dialog, View v) {
        toast("Clicked OK button");
        return false;
    }
});

// Set button text and callback
.setOkButton("OK", new OnDialogButtonClickListener<BottomDialog>() {
    @Override
    public boolean onClick(BottomDialog dialog, View v) {
        toast("Clicked OK button");
        return false;
    }
});

// Hide the button
.setOkButton(null)

Please use as you prefer.

Getting User Selection Status

If your business process does not need to immediately handle user operations, but needs to know which button the user selected, you can hold a dialog handle and later obtain the selection status, for example:

private BottomDialog dialog;

// Business process simulation, creating and displaying a dialog but not immediately processing the click events
dialog = BottomDialog.show(...);

// When needed, you can get which button option the user clicked using the getButtonSelectResult() method
BUTTON_SELECT_RESULT result = dialog.getButtonSelectResult();

BUTTON_SELECT_RESULT is an enumeration that includes the following types:

NONE,           // No selection made
BUTTON_OK,      // OK button selected
BUTTON_CANCEL,  // Cancel button selected
BUTTON_OTHER    // Other button selected

You can determine which button the user clicked based on its status.

Based on this feature, if in your business process, the same part of the code needs to be executed regardless of the user's choice, developers can also handle the user's choice uniformly after the selection, for example, in the DialogLifecycle#onDismiss dialog close event, to reduce redundant code;

Lifecycle Callbacks

To monitor the lifecycle of the dialog, you can implement its .setDialogLifecycleCallback(...) interface. It is recommended to use the build() method to construct the dialog:

BottomMenu.build()
        .setDialogLifecycleCallback(new DialogLifecycleCallback<BottomMenu>() {
            @Override
            public void onShow(BottomMenu dialog) {
                // Callback when the dialog starts
            }
            @Override
            public void onDismiss(BottomMenu dialog) {
                // Callback when the dialog closes
            }
        })
        .show();

BottomDialog/BottomMenu also supports Lifecycle, and you can use .getLifecycle() to get the Lifecycle object.

For scrollable BottomDialog/BottomMenu (limited by theme), you can use the enhanced DialogLifecycleCallback to implement BottomDialogSlideEventLifecycleCallback, which provides extended event handling and callbacks for the sliding process and slide-to-close:

.setDialogLifecycleCallback(new BottomDialogSlideEventLifecycleCallback<BottomDialog>() {
    @Override
    public boolean onSlideClose(BottomDialog dialog) {
        log("Sliding close dialog triggered");
        return false;	// Default return false, return true to intercept, dialog will not continue to close;
    }
    @Override
    public boolean onSlideTouchEvent(BottomDialog dialog, View v, MotionEvent event) {
        log("#Sliding process triggered: action="+ event.getAction() + " y="+event.getY());
        return false;	// Default return false, return true to intercept, dialog will not handle sliding process, can implement touch event takeover;
    }
})

You can also handle lifecycle events by overriding lifecycle events when constructing an instance using new, for example:

// Override event demonstration
new BottomDialog() {
    @Override
    public void onShow(BottomDialog dialog) {
        //...
        tip("onShow");
    }
    @Override
    public void onDismiss(BottomDialog dialog) {
        //...
        tip("onDismiss");
    }
}

You can also use the methods .onShow(DialogXRunnable) and .onDismiss(DialogXRunnable) to handle lifecycle transactions, for example:

BottomDialog.show(...) 
        .onShow(new DialogXRunnable<BottomDialog>() {
            @Override
            public void run(BottomDialog dialog) {
                //BottomDialog show!
            }
        })
        .onDismiss(new DialogXRunnable<BottomDialog>() {
            @Override
            public void run(BottomDialog dialog) {
                //BottomDialog dismiss!
            }
        });

Custom Layout

BottomDialog natively supports custom layouts. You can customize the layout of the bottom dialog using the following code:

BottomDialog.show("Title", "This is the dialog content.\nBottom dialog also supports custom layout extension usage.",
        new OnBindView<BottomDialog>(R.layout.layout_custom_view) {
            @Override
            public void onBind(BottomDialog dialog, View v) {
                //v.findViewById...
            }
        });

For BottomMenu menus, use the following code to add a custom layout:

BottomMenu.show(new String[]{"Open in new tab", "Read later", "Copy link address"})
        .setMessage("Menu title")
        .setOnMenuItemClickListener(new OnMenuItemClickListener<BottomMenu>() {
            @Override
            public boolean onClick(BottomMenu dialog, CharSequence text, int index) {
                toast(text);
                return true;
            }
        })
        .setCustomView(new OnBindView<BottomDialog>(R.layout.layout_custom_view) {
            @Override
            public void onBind(BottomDialog dialog, View v) {
                //v.findViewById...
            }
        });

In the callback parameters, v is the instantiated component of your given layout file. You can instantiate other sub-layout components through v.findViewById(resId) and set their functionality and event callbacks in the onBind method.

If you are using ViewBinding, you can also replace it with OnBindingView to get the layout instance directly through binding:

BottomDialog.show("Title", "Here are the contents of the dialog box. The \n bottom dialog also supports custom layout extension usage.",
        new OnBindingView<BottomDialog, LayoutCustomViewBinding>() {
            @Override
            public void onBind(BottomDialog dialog, View view, LayoutCustomViewBinding binding) {
                //View childView = binding.childView
            }
        });

Additional Methods

// set menu items
.setMenus("Add", "Edit", "Delete", "Share"...);

// set icons
.setIconResIds(R.mipmap.img_dialogx_demo_add, R.mipmap.img_dialogx_demo_edit...);
  
//Set the menu items (list or collection)
.setMenuList(list);

// Force a refresh of the interface
.refreshUI();

// Close the dialog
.dismiss();

// Set whether clicking outside the area or the back button is allowed to close the dialog
.setCancelable(boolean);

// Set the title text style
.setTitleTextInfo(TextInfo

);

// Set the message text style
.setMessageTextInfo(TextInfo);

// Set the button text style
.setCancelTextInfo(TextInfo);
    
// Set the back button callback
.setOnBackPressedListener(OnBackPressedListener);

// Get the dialog instance object, you can customize the Dialog features more deeply through this method
.getDialogImpl()

// Get the custom layout instance
.getCustomView()
    
// Set the background color, forcibly dye the dialog background. Note that the parameter is an int type color value, not an R.color index
.setBackgroundColor(ColorInt);

// Set the menu text style
.setMenuTextInfo(TextInfo)

// Set dialog corner radius (will crop content display, this setting in BottomDialog and BottomMenu only affects the top left and top right corners)
.setRadius(float px)

// Hide the dialog (no animation), to show again, execute the non-static method .show()
.hide();

// Hide the dialog (simulate the close dialog animation), to show again, execute the non-static method .show()
.hideWithExitAnim();

// Allow slide-to-close (only effective for Material theme)
.setAllowInterceptTouch(boolean)

// Check if it is currently displayed
.isShow()

// Front dialog box display hierarchy
.bringToFront()

// Specify the level of dialog display
.setThisOrderIndex(int)

Background Mask

BottomDialog supports modifying the background mask for added flexibility. To set the background mask, you can use the following code:

bottomDialog.setMaskColor(colorInt);

Please note, the parameter is a ColorInt value. You can use Color.parseColor("#4D000000") to set a HEX color value, or getResources().getColor(R.color.black30) to set a color resource value.

Additional Components

TextInfo

TextInfo is used to store basic text style settings, including a series of properties and their respective get/set methods, explained as follows:

Property Explanation Default Value
fontSize Font size, -1 for default style, unit: dp -1
gravity Alignment, -1 for default style, can use values like Gravity.CENTER -1
fontColor Text color, 1 for default style, can use Color.rgb(r,g,b) to get value 1
bold Whether it's bold false

Note that fontColor is a ColorInt value. You can use Color.parseColor("#4D000000") to set a HEX color value, or a resource getResources().getColor(R.color.black30) to set a color resource value. Do not directly pass in a resource ID as it might not work.

About "Slide Indicator"

In the Material theme style with BottomDialog by default, it is scrollable to close. When the cancelable value is true, a "slide indicator" will be displayed at the top of the dialog. If you need to hide it according to your needs, you can close it as follows:

Method 1, Disable Swipe Down to Close

Disabling the swipe down dialog to close the BottomDialog hides the sliding prompt bar.

.setAllowInterceptTouch(false)

Method 2, effective for a single instance

Leverage DialogX's feature of exposing internal elements to delete the tab layout.

BottomDialog.show("Title", "This is the dialog content.\nBottom dialog also supports custom layout extension usage.",
        new OnBindView<BottomDialog>(R.layout.layout_custom_view) {
            @Override
            public void onBind(BottomDialog dialog, View v) {
                if (dialog.getDialogImpl().imgTab != null) {
                    ((ViewGroup) dialog.getDialogImpl().imgTab.getParent()).removeView(dialog.getDialogImpl().imgTab);
                }
                //...
            }
        });

Method 3, Override Layout

Manually create a layout_dialogx_bottom_material layout (dark mode corresponds to layout_dialogx_bottom_material_dark), contents refer to layout_dialogx_bottom_material.xml (dark mode corresponds to layout_dialogx_bottom_material_dark.xml)

Modify and delete the <ImageView id="@+id/img_tab"> layout:

<ImageView
    android:id="@+id/img_tab"
    android:layout_width="30dp"
    android:layout_height="4dp"
    android:layout_centerHorizontal="true"
    android:layout_marginTop="10dp"
    android:src="@drawable/rect_dialogx_material_dialogtap" />

This method utilizes resource override to make changes. No need to worry about null pointer, as DialogX has corresponding internal handling.

Specifying Style Individually

If your App incorporates multiple themes and you need a dialog to display in a specific non-global theme style in certain scenarios, you can use .build() to construct the dialog. Then, apply .setStyle(style) to specify the theme style, and finally execute .show() to display the dialog. For example:

BottomDialog.build()
        // or directly use .build(IOSStyle.style())
        .setStyle(IOSStyle.style())
        .setTitle("Title")
        .setMessage("Message content.")
        .setOkButton("OK")
        .show();
Clone this wiki locally