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


107

В документации 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 ... но я не могу просто ввести это в свой код b / c, это дает ошибку компиляции. Где 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();

Чтобы найти и использовать свои объекты в диалоге, выполните следующие четыре шага: stackoverflow.com/a/18773261/1699586
— Сара,

3
Однострочный ответ: добавляем .setView(getLayoutInflater().inflate(R.layout.dialog_view, null))в конструктор. Благодарим Серхио Виудеса, ниже.
— 1 '19

Ответы:


49

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

Вероятно, вам нужно что-то вроде этого:

LayoutInflater inflater = getLayoutInflater();
FrameLayout f1 = (FrameLayout)alert.findViewById(android.R.id.body);
f1.addView(inflater.inflate(R.layout.dialog_view, f1, false));

17
Интересно, что body не определяется как константа в android.R.id. Я все еще не понимаю, как получить доступ к элементу body созданного AlertDialog. Я все еще хотел бы знать, как это сделать, но пока я просто попробую раздуть представление и использовать setView в конструкторе.
— stormin986

2
На самом деле это все еще оставляет меня с вопросом (я новичок в раздувании просмотров). Используя builder.setView(inflater.inflate(R.id.dialog, ROOT_VIEWGROUP[, ATTACH_TO_ROOT])), в документах говорится, что корневая группа просмотра является необязательной. Следует ли это использовать в этом случае? Если так ... еще предстоит выяснить, как получить ссылку на AlertDialog ...
— stormin986

2
Это необязательно, но тогда у вас не будет ссылки на родительский элемент внутри макета, который вы раздуваете. Такие вещи, как android: layout_gravity, не будут работать в представлении верхнего уровня ... и, возможно, они вам не нужны. Когда вы вызываете AlertDialog alert = builder.create (), у вас есть ссылка на ваш AlertDialog. Длинный ответ Короче говоря, это является обязательным. Попробуйте, в зависимости от того, что вы делаете в настраиваемом макете, он, вероятно, сработает.
— synic

2
Я не понимаю, как ссылаться на представление в AlertDialog. Что бы вы порекомендовали сделать в этом случае, если бы я действительно хотел сослаться на родителя? Единственное, что я вижу в alertDialog, который возвращает представление, это getCurrentFocus ()
— stormin986,

5
Держитесь за то, Viewчто надуваете. Вызовите его findViewById(), Viewкогда вам понадобится что-то из его содержимого. См .: github.com/commonsguy/cw-android/tree/master/Database/Constants
— CommonsWare

159

Вы можете создать свое представление непосредственно из 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();

4
А где в этом примере R.id.dialog_layout_root? Разве это не представление в текущем действии?
— Alex Pretzlav

5
@AlexPretzlav: dialog_layout_root в этом примере не нужен. Все, что вам нужно, это имя вашего xml файла для R.layout. [Name_of_xml_file].
— IgorGanapolsky

7
@Temperage Вы добавили builder.show в конце. Я попробовал этот код, и он сработал. Также я передал null в качестве второго параметра для infalter.inflate
— Винот

2
Это должно было быть выбрано как лучший ответ.
— Салман Хаквани

2
Это дает ClassCastException, когда настраиваемый макет содержит EditText, потому getCurrentFocus()что возвращает EditText, а EditText не может быть приведен к ViewGroup. Использование nullв качестве второго аргумента исправляет это.
— 1 '19

20

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>

Джон Реллис. Ваше решение отлично работает. Просто есть что-нибудь в этом. Мы должны надуть перед builder.create и установить frameView.setLayoutParams (new LayoutParams (LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); или что-то в первую очередь, что предотвратит некоторые
— непредвиденные

спасибо за отзыв .. как надуть перед builder.create ()? inflate вызывается в инфляторе диалогового окна, и у меня есть диалог только потому, что я вызвал builder.create ()
— Джон Реллис

мы можем использовать надуватель активности, а не присоединяться к FrameLayout, поэтому мы можем сократить небольшой код вроде этого: AlertDialog.Builder builder = new AlertDialog.Builder (new ContextThemeWrapper (getActivity (), android.R.style.Theme_Holo)) .setTitle ("заголовок") .setIcon (R.drawable.sample_icon); Просмотр TroubleView = inflater.inflate (R.layout.sample_layout, null, false); builder.setView (TroubleView); alert = builder.create (); Извините, я не знаю, как четко писать код в комментариях
— MOBYTELAB

Это просто ошибка дизайна, если мы соберем все в макете xml и забудем Framelayout как корень диалога, нам может быть любопытно, если макет в xml не заполнит родительский элемент или что-то в этом роде, просто случай.
— MOBYTELAB

У меня сработало, спасибо! Это позволяет мне без ошибок добавлять заголовок и кнопку «Отменить» и «ОК», включая EditText. :) Вопрос только в том, как это работает? Действует FrameLayoutкак своего рода фрагмент? Когда я попробовал ответить Andrewx2 выше, у меня возникла ошибка, потому что он подумал, что я раздуваю два макета (это мое предположение).
— Azurespot



8

Пользовательский AlertDialog

Этот полный пример включает передачу данных обратно в 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подкласса, как описано в документации .

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


1
EditText editText = customLayout.findViewById(R.id.editText); должно быть EditText editText = (EditText) customLayout.findViewById(R.id.editText);
— philcruz

4
@philcruz, Если вы обновитесь до Android Studio 3.0, вам больше не нужно явно приводить представление. IDE может определить тип. Это очень приятная особенность. Это сильно помогает очистить код.
— Suragch

отличный ответ, действительно полезно
— Нур Хоссейн

4

Вот самые простые строки кода, которые мне подходят:

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

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


потрясающе, просто и легко
— Фрэнк Ино

2

Самый простой способ сделать это - использовать 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();

Любой, кто читает это после того, как был представлен API 21, должен использовать этот метод. Как упоминается в ответе, если ваше приложение minSDKversion меньше 21, используйте AlerDialog из пакета поддержки. Вуаля !!!
— Dibzmania

2

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));
...

1

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

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

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

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

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


1

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

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.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.