Как реализовать собственное представление AlertDialog

В документах Android по AlertDialog приведены следующие инструкции и примеры для настройка пользовательского представления в AlertDialog:

Если вы хотите отобразить более сложное представление, найдите FrameLayout под названием "body" и добавьте в него свое представление:

FrameLayout fl = (FrameLayout) findViewById(R.id.body);
fl.add(myView, new LayoutParams(FILL_PARENT, WRAP_CONTENT));

Во-первых, совершенно очевидно, что add() — это опечатка и должно быть addView().

Меня смущает первая строка, использующая R.id.body. Кажется, это элемент тела AlertDialog ... но я не могу просто ввести это в свой код, потому что это дает ошибку компиляции. Где определяется или назначается R.id.body или что-то еще?

Вот мой код. Я пытался использовать setView(findViewById(R.layout.whatever) в билдере, но это не сработало. Я предполагаю, потому что я не надувал его вручную?

AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Title")
    .setCancelable(false)
    .setPositiveButton("Go", new DialogInterface.OnClickListener() {

    @Override
    public void onClick(DialogInterface dialog, int id) {
        EditText textBox = (EditText) findViewById(R.id.textbox);
        doStuff();
    }
});

FrameLayout f1 = (FrameLayout)findViewById(R.id.body /*CURRENTLY an ERROR*/);
f1.addView(findViewById(R.layout.dialog_view));

AlertDialog alert = builder.create();
alert.show();

person stormin986    schedule 08.05.2010    source источник
comment
Чтобы найти и использовать свои объекты в диалоговом окне, выполните следующие четыре шага: stackoverflow.com/a/18773261/1699586   -  person Sara    schedule 13.09.2013
comment
Однострочный ответ: добавьте .setView(getLayoutInflater().inflate(R.layout.dialog_view, null)) в билдер. Кредит Серхио Виудес, ниже.   -  person 1''    schedule 19.01.2014


Ответы (11)


Вы правы, это потому, что вы не надували его вручную. Похоже, вы пытаетесь «извлечь» идентификатор «тела» из макета своей деятельности, и это не сработает.

Вы, вероятно, хотите что-то вроде этого:

LayoutInflater inflater = getLayoutInflater();
FrameLayout f1 = (FrameLayout)alert.findViewById(android.R.id.body);
f1.addView(inflater.inflate(R.layout.dialog_view, f1, false));
person synic    schedule 08.05.2010
comment
Интересно, что body не определено как константа в android.R.id. Я до сих пор не понимаю, как получить доступ к элементу «тело» созданного AlertDialog. Я все еще хотел бы знать, как это сделать, но сейчас я просто попытаюсь раздуть представление и использовать setView в конструкторе. - person stormin986; 08.05.2010
comment
На самом деле это все еще оставляет меня с вопросом (я новичок в раздувании взглядов). Используя builder.setView(inflater.inflate(R.id.dialog, ROOT_VIEWGROUP[, ATTACH_TO_ROOT])), документы говорят, что корневая группа просмотра не является обязательной. Следует ли это использовать в данном случае? Если так... еще нужно выяснить, как получить ссылку на AlertDialog... - person stormin986; 08.05.2010
comment
Это необязательно, но тогда у вас не будет ссылки на родителя внутри макета, который вы надуваете. Такие вещи, как android:layout_gravity, не будут работать в представлении верхнего уровня... и, возможно, они вам не нужны. Когда вы вызываете AlertDialog alert = builder.create(), у вас есть ссылка на ваш AlertDialog. Длинный ответ короткий, это необязательно. Попробуйте, в зависимости от того, что вы делаете в своем пользовательском макете, это, вероятно, сработает. - person synic; 08.05.2010
comment
Я не понимаю, как ссылаться на view в AlertDialog. Что бы вы порекомендовали сделать в этом случае, если бы я действительно хотел сослаться на родителя? Единственное, что я вижу в alertDialog, которое возвращает представление, это getCurrentFocus(). - person stormin986; 08.05.2010
comment
Интересный. Я получил его, чтобы отображать просто прекрасно представление, используя null для корневого представления. В моем пользовательском макете есть элемент EditText. Однако следующее возвращает null! EditText textBox = (EditText) findViewById(R.id.chat_username); Есть ли причина, по которой Activity findViewById здесь не работает? Никаких нюансов в этом методе я не знал, считал его универсальным. Я не знаю, как вызвать alert.findViewById, когда я все еще настраиваю конструктор. - person stormin986; 09.05.2010
comment
Держись за View, который ты надуваешь. Позвоните findViewById() этому View, когда вам понадобится материал из его содержимого. См.: github.com/commonsguy/cw-android/tree/master. /База данных/Константы - person CommonsWare; 09.05.2010
comment
Здорово. Спасибо! Работал как шарм. Происходит одна странная вещь: я раздуваю представление на верхнем уровне моего кода выше, и когда я пытаюсь сослаться на него в анонимном внутреннем классе, используемом для onClick, я получаю эту ошибку: Невозможно ссылаться на не конечную переменную v внутри внутренний класс, определенный другим методом. Создание финального объекта с раздутым видом исправляет это, но зачем это нужно? - person stormin986; 09.05.2010
comment
Вот ответ на ваш внутренний/последний вопрос: java.sun.com/docs/books/jls/second_edition/html/ Любая локальная переменная, формальный параметр метода или параметр обработчика исключений, используемые, но не объявленные во внутреннем классе, должны быть объявлены окончательными и должны быть однозначно назначены (§16) перед телом внутреннего класса - person berlindev; 19.07.2011
comment
на самом деле должно быть android.R.id.custom вместо android.R.id.body - person Bobby; 04.09.2015
comment
Настройка диалогового окна предупреждения делает ваш код грязным и нечитаемым. Простое пошаговое руководство см. на learnzone.info/. для настройки диалогового окна предупреждений. - person Madhav Bhattarai; 29.03.2017
comment
View v = inflater.inflate(R.layout.activity_main, null); генерирует предупреждение. Использование View.inflate(..., null, false) просто скрывает предупреждение, но не решает проблему. Правильный метод — использовать builder.setView(R.layout.edit_account_dialog); dialog = builder.create();, но findViewById можно будет использовать только после того, как активность вызовет dialog.show(); возникает еще одна проблема: findViewById диалогового окна нельзя использовать до show() и нелегко вызвать после show(). Кстати, вызов findViewById в действии сразу после dialog.show() не является хорошей практикой. - person JarsOfJam-Scheduler; 16.03.2019

Вы можете создать свое представление непосредственно из Layout Inflater, вам нужно только использовать имя XML-файла вашего макета и идентификатор макета в файле.

Ваш файл XML должен иметь такой идентификатор:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:id="@+id/dialog_layout_root"
              android:orientation="vertical"
              android:layout_width="fill_parent"
              android:layout_height="wrap_content"
              android:padding="10dp"
              />

И затем вы можете установить свой макет в конструкторе с помощью следующего кода:

LayoutInflater inflater = getLayoutInflater();
View dialoglayout = inflater.inflate(R.layout.dialog_layout, null);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setView(dialoglayout);
builder.show();
person Andrewx2    schedule 23.04.2011
comment
А где в этом примере R.id.dialog_layout_root? Разве это не представление в текущем действии? - person Alex Pretzlav; 28.12.2011
comment
@AlexPretzlav: в этом примере dialog_layout_root не нужен. Все, что вам нужно, это имя вашего xml-файла для R.layout.[name_of_xml_file]. - person IgorGanapolsky; 29.03.2012
comment
Я только что проверил код выше, и он не работает. Не знаю, почему люди продолжают голосовать за него. - person Temperage; 15.05.2012
comment
наоборот, он работает, пока android.R.id.custom по-прежнему возвращает null - person lorenzoff; 09.06.2012
comment
@Temperage Вы добавили builder.show в конце. Я попробовал этот код, и он сработал. Также я передал null в качестве второго параметра для infalter.inflate. - person Vinoth; 18.06.2012
comment
Ты самый лучший!! это действительно помогло мне - person Hossam Alaa; 19.08.2012
comment
Это должно было быть выбрано как лучший ответ. - person Salman Khakwani; 31.12.2013
comment
Это дает ClassCastException, когда пользовательский макет содержит EditText, потому что getCurrentFocus() вернет EditText, а EditText не может быть приведен к ViewGroup. Использование null в качестве второго аргумента исправляет это. - person 1''; 19.01.2014
comment
как теперь закрыть диалоговое окно с предупреждением? - person Mr.Ghamkhar; 19.12.2014
comment
Я получаю сообщение об ошибке, когда помещаю этот код в метод кнопки onClick, и я думаю, что это потому, что я добавляю другие вещи к своему Dialog Box, например заголовок и 2 кнопки. Есть ли другой способ обойти это? Ошибка в основном потому, что он думает, что 2 Views пытаются раздуть (один - EditText, другой - для других упомянутых вещей), но не уверен, как собрать все это в один View. Любые предложения приветствуются, спасибо. - person Azurespot; 30.12.2014
comment
View v = inflater.inflate(R.layout.activity_main, null); генерирует предупреждение. Использование View.inflate(..., null, false) просто скрывает предупреждение, но не решает проблему. Правильный метод — использовать builder.setView(R.layout.edit_account_dialog); dialog = builder.create();, но findViewById можно будет использовать только после того, как активность вызовет dialog.show(); возникает еще одна проблема: findViewById диалогового окна нельзя использовать до show() и нелегко вызвать после show(). Кстати, вызов findViewById в действии сразу после dialog.show() не является хорошей практикой. - person JarsOfJam-Scheduler; 16.03.2019

android.R.id.custom возвращал для меня значение null. Мне удалось заставить это работать, если кто-то столкнется с той же проблемой,

AlertDialog.Builder builder = new AlertDialog.Builder(context)
            .setTitle("My title")
            .setMessage("Enter password");
final FrameLayout frameView = new FrameLayout(context);
builder.setView(frameView);

final AlertDialog alertDialog = builder.create();
LayoutInflater inflater = alertDialog.getLayoutInflater();
View dialoglayout = inflater.inflate(R.layout.simple_password, frameView);
alertDialog.show();

Для справки, R.layout.simple_password:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
          android:orientation="vertical"
          android:layout_width="match_parent"
          android:layout_height="match_parent">

<EditText
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:id="@+id/password_edit_view"
        android:inputType="textPassword"/>
<CheckBox
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/show_password"
        android:id="@+id/show_password_checkbox"
        android:layout_gravity="left|center_vertical"
        android:checked="false"/>

</LinearLayout>
person John Rellis    schedule 27.03.2013
comment
Джон Реллис. Ваше решение работает нормально. Просто есть что-то в этом. Мы должны надуть перед builder.create и установить frameView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); или что-то первое, что предотвратит некоторые ошибки, которые не ожидаются - person MOBYTELAB; 31.07.2013
comment
спасибо за отзыв .. как вы надуваете перед builder.create()? inflate вызывается для надувания диалога, и у меня есть диалог только потому, что я вызвал builder.create() - person John Rellis; 31.07.2013
comment
мы можем использовать инфлятор активности, а не привязываться к FrameLayout, поэтому мы можем уменьшить небольшой код, подобный этому. (название) .setIcon(R.drawable.sample_icon); Просмотр TroubleView = inflater.inflate(R.layout.sample_layout, null, false); builder.setView(просмотр проблем); оповещение = строитель.создать(); Извините, я не знаю, как написать код четко в комментарии - person MOBYTELAB; 31.07.2013
comment
Это просто ошибка дизайна, если мы соберем все в layout xml и забудем Framelayout как корень диалога, нам может быть любопытно, не является ли layout в xml родительским или чем-то еще, просто случай. - person MOBYTELAB; 31.07.2013
comment
Работал для меня, спасибо! Это позволяет мне добавить заголовок и кнопку «Отмена» и «ОК», включая EditText без ошибок. :) Вопрос только в том, как это работает? Является ли FrameLayout своего рода фрагментом? Когда я попробовал ответ Andrewx2 выше, я получил ошибку, потому что подумал, что я раздуваю два макета (это мое предположение). - person Azurespot; 30.12.2014

Документы Android были отредактированы, чтобы исправить ошибки.

Представление внутри AlertDialog называется android.R.id.custom

http://developer.android.com/reference/android/app/AlertDialog.html

person Dapp    schedule 17.05.2012

Это сработало для меня:

dialog.setView(dialog.getLayoutInflater().inflate(R.layout.custom_dialog_layout, null));
person Sergio Viudes    schedule 07.08.2013

Пользовательский диалог предупреждений

Этот полный пример включает передачу данных обратно в Activity.

введите описание изображения здесь

Создать индивидуальный макет

В этом простом примере используется макет с EditText, но вы можете заменить его чем угодно.

custom_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:paddingLeft="20dp"
              android:paddingRight="20dp"
              android:layout_width="match_parent"
              android:layout_height="match_parent">

    <EditText
        android:id="@+id/editText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</LinearLayout>

Используйте диалог в коде

Ключевые части

  • используя setView для назначения пользовательского макета AlertDialog.Builder
  • отправка любых данных обратно в активность при нажатии кнопки диалога.

Это полный код примера проекта, показанного на изображении выше:

MainActivity.java

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void showAlertDialogButtonClicked(View view) {

        // create an alert builder
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle("Name");

        // set the custom layout
        final View customLayout = getLayoutInflater().inflate(R.layout.custom_layout, null);
        builder.setView(customLayout);

        // add a button
        builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                // send data from the AlertDialog to the Activity
                EditText editText = customLayout.findViewById(R.id.editText);
                sendDialogDataToActivity(editText.getText().toString());
            }
        });

        // create and show the alert dialog
        AlertDialog dialog = builder.create();
        dialog.show();
    }

    // do something with the data coming from the AlertDialog
    private void sendDialogDataToActivity(String data) {
        Toast.makeText(this, data, Toast.LENGTH_SHORT).show();
    }
}

Примечания

  • Если вы обнаружите, что используете это в нескольких местах, рассмотрите возможность создания подкласса DialogFragment, как описано в документация.

Смотрите также

person Suragch    schedule 14.10.2017
comment
EditText editText = customLayout.findViewById(R.id.editText); должно быть EditText editText = (EditText) customLayout.findViewById(R.id.editText); - person philcruz; 18.11.2017
comment
@philcruz, если вы обновитесь до Android Studio 3.0, вам больше не нужно явно приводить представление. IDE может определить тип. Это очень хорошая функция. Это очень помогает очистить код. - person Suragch; 18.11.2017
comment
отличный ответ, очень помог - person Noor Hossain; 05.07.2020

Самые простые строки кода, которые работают для меня, следующие:

AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setView(R.layout.layout_resource_id);
builder.show();

Независимо от типа макета (LinearLayout, FrameLayout, RelativeLayout) он будет работать с setView и будет отличаться только внешним видом и поведением.

person kenix    schedule 07.11.2016
comment
классно, просто и легко - person Frank Eno; 01.03.2017

Самый простой способ сделать это — использовать android.support.v7.app.AlertDialog вместо android.app.AlertDialog, где public AlertDialog.Builder setView (int layoutResId) можно использовать ниже API 21.

new AlertDialog.Builder(getActivity())
    .setTitle(title)
    .setView(R.layout.dialog_basic)
    .setPositiveButton(android.R.string.ok,
        new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int whichButton) {
                //Do something
            }
        }
    )
    .setNegativeButton(android.R.string.cancel,
        new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int whichButton) {
                //Do something
            }
        }
    )
    .create();
person flanaras    schedule 25.05.2016
comment
Любой, кто читает это после того, как был представлен API 21, должен использовать этот метод. Как упоминается в ответе, если версия minSDK вашего приложения меньше 21, используйте AlerDialog из пакета поддержки. Вуаля !!! - person Dibzmania; 24.02.2017

AlertDialog.setView(View view) действительно добавляет данное представление в R.id.custom FrameLayout. Ниже приведен фрагмент исходного кода Android из AlertController.setupView(), который, наконец, обрабатывает это (mView — это представление, данное методу AlertDialog.setView).

...
FrameLayout custom = (FrameLayout) mWindow.findViewById(R.id.**custom**);
custom.addView(**mView**, new LayoutParams(FILL_PARENT, FILL_PARENT));
...
person Jung Soo Kim    schedule 02.03.2015

После изменения идентификатора android.R.id.custom мне нужно было добавить следующее, чтобы отобразить представление:

((View) f1.getParent()).setVisibility(View.VISIBLE);

Однако это привело к тому, что новое представление отображалось в большом родительском представлении без фона, разбивая диалоговое окно на две части (текст и кнопки, а между ними — новое представление). Наконец-то я получил желаемый эффект, вставив свое представление рядом с сообщением:

LinearLayout f1 = (LinearLayout)findViewById(android.R.id.message).getParent().getParent();

Я нашел это решение, изучив дерево представлений с помощью View.getParent() и View.getChildAt(int). Впрочем, ни то, ни другое не особо радует. Ничего из этого нет в документах Android, и если они когда-либо изменят структуру AlertDialog, это может сломаться.

person Black Mantha    schedule 15.09.2015

Было бы разумнее сделать это таким образом, с наименьшим количеством кода.

new AlertDialog.Builder(this).builder(this)
        .setTitle("Title")
        .setView(R.id.dialog_view)   //notice this setView was added
        .setCancelable(false)
        .setPositiveButton("Go", new DialogInterface.OnClickListener() {
            @Override 
            public void onClick(DialogInterface dialog, int id) {
                EditText textBox = (EditText) findViewById(R.id.textbox);
                doStuff();
            }
        }).show();

Чтобы получить расширенный список вещей, которые вы можете установить, начните вводить .set в Android Studio.

person StackAttack    schedule 27.04.2018