While optimizing an app for material theme on lollipop, I\'m encountering this annoying problem:
Whenever there is long text on dialog buttons, that doesn\'t fit the but
To summarize this topic for anyone interested:
Android Material theme seems to have a bug with automatic button-width-span in alert dialogs.
When you have for example 3 buttons, one of which having more than one word, the positive button will likely be squeezed out of the dialog to the right, instead of the button with more than one word being wrapped in multiple lines, so that all buttons fit in the button bar like the basic theme / holo theme are doing.
There don't seem to be solutions solely by applying changes to buttonBarStyle and/or buttonBarButtonStyle, since their styles are not restricting button texts being wrapped in multiple lines by default.
Sure, Material theme dialog buttons require more space than in other themes in general, due to caps, boldness and generous padding and background, but that is not the source of the problem, it just makes it appear sooner than if the styling was less space requiring.
The only way to solve this problem seems to be giving your buttons shorter titles, if you don't want to style away from Material's look and feel (like making the button text size smaller and / or setting allCaps to false).
use following code, let buttons on the right and vertical arrangement
alertDialog.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
try {
LinearLayout linearLayout = (LinearLayout) alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).getParent();
if (linearLayout != null) {
linearLayout.setOrientation(LinearLayout.VERTICAL);
linearLayout.setGravity(Gravity.RIGHT);
}
} catch (Exception ignored) {
}
}
});
This could be fixed with using stacked buttons instead of row buttons. Here my workaround how it could be achieved with using AppCompat lib :
Code import android.support.v7.app.AlertDialog;
AlertDialog.Builder builder;
builder = new AlertDialog.Builder(context, R.style.StackedAlertDialogStyle);
builder.setTitle("Title");
builder.setMessage("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc dignissim purus eget gravida mollis. Integer in auctor turpis. Morbi auctor, diam eget vestibulum congue, quam arcu pulvinar dui, blandit egestas erat enim non ligula." +
" Nunc quis laoreet libero. Aliquam consectetur nibh eu arcu eleifend efficitur.");
builder.setPositiveButton("Positive Button", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
builder.setNeutralButton("Neutral Button", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
builder.setNegativeButton("Cancel Button", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
AlertDialog alertDialog = builder.create();
alertDialog.show();
try{
final Button button = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
LinearLayout linearLayout = (LinearLayout) button.getParent();
linearLayout.setOrientation(LinearLayout.VERTICAL);
} catch(Exception ex){
//ignore it
}
Style
<style name="StackedAlertDialogStyle" parent="Theme.AppCompat.Light.Dialog.Alert">
<item name="buttonBarButtonStyle">@style/StackedButtonBarButtonStyle</item>
</style>
<style name="StackedButtonBarButtonStyle" parent="Widget.AppCompat.Button.ButtonBar.AlertDialog">
<item name="android:layout_gravity">right</item>
</style>
Result
A quick soln that is not dynamic is to add \n to break the long string up
Here is a workaround I came up with, with the help of the linked answer from Andrey T (https://stackoverflow.com/a/29662638/1317564):
First, you create an utility method which will wrap the buttons only if they need to be wrapped:
public static void applyWorkaroundForButtonWidthsTooWide(Button dialogButton) {
if (dialogButton == null)
return;
if (!(dialogButton.getParent() instanceof LinearLayout))
return;
// Workaround for buttons too large in alternate languages.
final LinearLayout linearLayout = (LinearLayout) dialogButton.getParent();
linearLayout.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop,
int oldRight, int oldBottom) {
if (right - left > 0) {
final int parentWidth = linearLayout.getWidth();
int childrenWidth = 0;
for (int i = 0; i < linearLayout.getChildCount(); ++i)
childrenWidth += linearLayout.getChildAt(i).getWidth();
if (childrenWidth > parentWidth) {
// Apply stacked buttons
linearLayout.setOrientation(LinearLayout.VERTICAL);
linearLayout.setPadding(linearLayout.getPaddingLeft(), 0, linearLayout.getPaddingRight(),
linearLayout.getPaddingBottom());
for (int i = 0; i < linearLayout.getChildCount(); ++i) {
if (linearLayout.getChildAt(i) instanceof Button) {
final Button child = (Button) linearLayout.getChildAt(i);
child.setGravity(Gravity.END | Gravity.CENTER_VERTICAL);
final LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) child.getLayoutParams();
params.width = LinearLayout.LayoutParams.MATCH_PARENT;
params.gravity = Gravity.END;
child.setLayoutParams(params);
} else if (linearLayout.getChildAt(i) instanceof Space) {
linearLayout.removeViewAt(i--);
}
}
}
linearLayout.removeOnLayoutChangeListener(this);
}
}
});
}
You can add additional error handing (i.e. try/catches) and customize this further as appropriate.
Now, you call this utility method when the dialog is shown:
dialog.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(DialogInterface dialogInterface) {
MaterialAlertDialogUtils.applyWorkaroundForButtonWidthsTooWide(dialog.getButton(AlertDialog.BUTTON_POSITIVE));
}
});
This does the trick and will only wrap the buttons if needed. I've been using it everywhere, as even two-button dialogs may need wrapping in German and three-button dialogs surely need it in many languages.
Following up - Since I'm not able to post more than two links due to my beginners reputation, I have to post an answer to my question instead of editing it.
Below is how I tried to style the buttons using buttonBarStyle and buttonBarButtonStyle to achieve any improvement - see the results here:
unfortunately those are obviously not desirable solutions.
<resources>
<style name="AppBaseTheme" parent="android:Theme.Material.Light">
<!-- AlertDialog Style override in order to try to fix non line breaking buttons -->
<item name="android:alertDialogTheme">@style/CustomAlertDialogStyle</item>
</style>
<style name="CustomAlertDialogStyle" parent="android:Theme.Material.Light.Dialog.Alert">
<item name="android:buttonBarButtonStyle">@style/CustomButtonBarButtonStyle</item>
<item name="android:buttonBarStyle">@style/CustomButtonBarStyle</item>
</style>
<style name="CustomButtonBarStyle" parent="@android:style/Widget.Material.Light.ButtonBar.AlertDialog">
<!-- Making sure, the button bar uses parent width and is not restricted in height -->
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:height">@null</item>
<item name="android:minHeight">@null</item>
</style>
<style name="CustomButtonBarButtonStyle" parent="@android:style/Widget.Material.Light.Button.Borderless.Colored">
<!-- Setting the weight as follows should result in equally wide buttons filling the alert dialog width,
but instead they span further out of the dialog, breaking in multiple lines though -->
<item name="android:layout_width">0dp</item>
<item name="android:layout_weight">1</item>
<!-- setting a fixed width as follows results in narrow buttons with line breaks, but of course this is not a solution -->
<!-- <item name="android:width">100dp</item> -->
</style>
</resources>