Search code examples
androidandroid-alertdialogtext-size

How to stop dialog title text from shrinking when positive button text gets shorter?


I want to create an AlertDialog whose title text and message changes each time the user clicks on the positive button as they work through a series of steps.

It all works ok except that when the user gets to the third and final screen the size of the title text shrinks! This appears to be because the string for the positive button's text is shorter for the final screen. If I make it longer the title text no longer shrinks on the final screen.

Here's a stripped down version of my code. dialog.xml layout file:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:paddingStart="16dp"
    android:paddingEnd="16dp"
    android:orientation="vertical">

    <TextView
        android:id="@+id/text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceMedium"/>

</LinearLayout>

This is the code for the dialog fragment:

package com.world.test.fixshrinkingdialogtitletext;

import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;

public class ChangeTitleDialogFragment
        extends DialogFragment {

    private static final String DIALOG_TITLE = "DIALOG_TITLE";
    private static final String TEXT_PROMPT = "TEXT_PROMPT";
    private static final String POSITIVE_BUTTON_TEXT = "POSITIVE_BUTTON_TEXT";
    private static final String SCREEN_COUNT = "SCREEN_COUNT";

    private String dialogTitle = null;
    private String textPrompt = null;
    private String positiveButtonText = null;
    private int screenCount;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (savedInstanceState == null) {
            screenCount = 1;
            dialogTitle = "First Screen";
            textPrompt = "Click continue";
            positiveButtonText = "Continue";
        } else {
            dialogTitle = savedInstanceState.getString(DIALOG_TITLE);
            textPrompt = savedInstanceState.getString(TEXT_PROMPT);
            positiveButtonText = savedInstanceState.getString(POSITIVE_BUTTON_TEXT);
            screenCount = savedInstanceState.getInt(SCREEN_COUNT);
        }
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putString(DIALOG_TITLE, dialogTitle);
        outState.putString(TEXT_PROMPT, textPrompt);
        outState.putString(POSITIVE_BUTTON_TEXT, positiveButtonText);
        outState.putInt(SCREEN_COUNT, screenCount);
    }

    @Override
    @NonNull
    public Dialog onCreateDialog(Bundle savedInstanceState) {

        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());

        LayoutInflater inflater = getActivity().getLayoutInflater();
        @SuppressLint("InflateParams")
        final View view = inflater.inflate(R.layout.dialog, null);
        builder.setView(view);
        builder.setTitle(dialogTitle);
        builder.setMessage(textPrompt);
        builder.setPositiveButton(positiveButtonText, null);
        builder.setNegativeButton("Cancel", null);

        AlertDialog dialog = builder.create();

        dialog.setOnShowListener(new DialogInterface.OnShowListener() {

            @Override
            public void onShow(final DialogInterface dialog) {
                final AlertDialog alertDialog = (AlertDialog) dialog;

                final Button okButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
                okButton.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {

                        if (screenCount == 3) {
                            dismiss();
                        } else {
                            screenCount++;

                            dialogTitle = "Screen Number " + screenCount;
                            alertDialog.setTitle(dialogTitle);

                            if (screenCount == 3) {
                                // "OKOKOKOKO" won't cause the dialog title text to shrink!
                                positiveButtonText = "OKOKOKOK";
                                okButton.setText(positiveButtonText);
                            }
                        }
                    }
                });
            }
        });

        return dialog;
    }
}

And here are the screen shots of the 3 screens (the last 2 are cropped):

screen 1

screen 2 (cropped)

screen 3 (cropped)

I notice the dialog shrunk slightly in width on the final screen, presumably because the layout is set to wrap its contents and the text on the positive button is shorter. So maybe the title text size is being shrunk automatically by Android as well to ensure it will still fit. But that's a guess.

Does anyone know how to update the title text but keep it the same size throughout all the screens?

I find it unintuitive that the the title text size changes at all. Can anyone explain why it does change size? Is this intended behaviour or an Android bug?

The best solution won't involve setting the text size explicitly because then, if I ever change the app theme later on, I'll have to remember to change the title text size to match the theme in all of the multi-screen dialogs like this one.


Solution

  • This was tough to solve so I want to share it here so others don't have to tear their hair out like me!

    First, why does the title text change size in the first place? I don't know for sure but I'm guessing the app's theme specifies the text size in 'scaled pixel' units which means that the actual text size might change if the screen configuration changes. So I think this is intended behaviour.

    The example layout I used was a bit silly because it had an unused TextView, as @Ruan_Lopes pointed out. This is completely unnecessary because I could've used alertDialog.setMessage(textPrompt) to change the dialog's message anyway. In fact, using the Layout Hierarchy Viewer in Android Device Monitor showed me that the user's layout is inserted below the message anyway.

    The Hierarchy Viewer also showed me that the height and width of the alert dialog's title actually did not change even when the text did shrink! So setting the height or width of the TextView for the title did not cure the text shrinkage issue.

    In the end I recorded the text size of the title (in pixels) when the dialog was first shown and then used that same value to set the text size whenever I updated the dialog title's text. As I also wanted to preserve the text size across device rotations I chose to save the text size in onSaveInstanceState(Bundle outState). But you don't have to do that if you don't mind the title text size being different between screen rotations.

    Note that TextView.getTextSize() returns a value in pixels so you have to use the correct units when calling TextView.setTextSize(int, float).

    Here's the final code. I didn't change the layout xml other than delete the TextView and replace it with an EditText although that makes no difference here.

    package com.world.test.fixshrinkingdialogtitletext;
    
    import android.annotation.SuppressLint;
    import android.app.AlertDialog;
    import android.app.Dialog;
    import android.app.DialogFragment;
    import android.content.DialogInterface;
    import android.os.Bundle;
    import android.support.annotation.NonNull;
    import android.util.TypedValue;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.widget.Button;
    import android.widget.TextView;
    
    public class ChangeTitleDialogFragment
            extends DialogFragment {
    
        private static final String DIALOG_TITLE = "DIALOG_TITLE";
        private static final String TEXT_PROMPT = "TEXT_PROMPT";
        private static final String POSITIVE_BUTTON_TEXT = "POSITIVE_BUTTON_TEXT";
        private static final String SCREEN_COUNT = "SCREEN_COUNT";
        private static final String TITLE_TEXT_SIZE = "TITLE_TEXT_SIZE";
    
        private String dialogTitle = null;
        private String textPrompt = null;
        private String positiveButtonText = null;
        private int screenCount;
        private float titleTextSize;
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            if (savedInstanceState == null) {
                screenCount = 1;
                dialogTitle = "First screen";
                textPrompt = "Click continue";
                positiveButtonText = "Continue";
                titleTextSize = -1;
            } else {
                dialogTitle = savedInstanceState.getString(DIALOG_TITLE);
                textPrompt = savedInstanceState.getString(TEXT_PROMPT);
                positiveButtonText = savedInstanceState.getString(POSITIVE_BUTTON_TEXT);
                screenCount = savedInstanceState.getInt(SCREEN_COUNT);
                titleTextSize = savedInstanceState.getFloat(TITLE_TEXT_SIZE);
            }
        }
    
        @Override
        public void onSaveInstanceState(Bundle outState) {
            super.onSaveInstanceState(outState);
    
            outState.putString(DIALOG_TITLE, dialogTitle);
            outState.putString(TEXT_PROMPT, textPrompt);
            outState.putString(POSITIVE_BUTTON_TEXT, positiveButtonText);
            outState.putInt(SCREEN_COUNT, screenCount);
            outState.putFloat(TITLE_TEXT_SIZE, titleTextSize);
        }
    
        @Override
        @NonNull
        public Dialog onCreateDialog(Bundle savedInstanceState) {
    
            AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
    
            LayoutInflater inflater = getActivity().getLayoutInflater();
            @SuppressLint("InflateParams")
            final View view = inflater.inflate(R.layout.dialog, null);
            builder.setView(view);
            builder.setTitle(dialogTitle);
            builder.setMessage(textPrompt);
            builder.setPositiveButton(positiveButtonText, null);
            builder.setNegativeButton("Cancel", null);
    
            AlertDialog dialog = builder.create();
    
            dialog.setOnShowListener(new DialogInterface.OnShowListener() {
    
                @Override
                public void onShow(final DialogInterface dialog) {
                    final AlertDialog alertDialog = (AlertDialog) dialog;
    
                    final int alertTitleId = getActivity().getResources().getIdentifier( "alertTitle", "id", "android" );
                    final TextView titleTV = (TextView)alertDialog.findViewById(alertTitleId);
                    if (titleTextSize < 0) {
                        // Then user has launched this dialog directly from the calling activity.
                        titleTextSize = titleTV.getTextSize(); // Units = pixels!
                    } else {
                        // Use the existing value we saved when the configuration changed:
                        titleTV.setTextSize(TypedValue.COMPLEX_UNIT_PX, titleTextSize);
                    }
    
                    final Button okButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
                    okButton.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
    
                            if (screenCount == 3) {
                                dismiss();
                            } else {
                                screenCount++;
    
                                textPrompt = "Blah blah blah blah blah blah blah blah";
                                alertDialog.setMessage(textPrompt);
    
                                dialogTitle = "Screen Number " + screenCount;
                                alertDialog.setTitle(dialogTitle);
    
                                titleTV.setTextSize(TypedValue.COMPLEX_UNIT_PX, titleTextSize);
    
                                if (screenCount == 3) {
                                    positiveButtonText = "OK";
                                    okButton.setText(positiveButtonText);
                                }
                            }
                        }
                    });
                }
            });
    
            return dialog;
        }
    }