Как реализовать настраиваемое представление 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();
шторм986
источник
Чтобы найти и использовать свои объекты в диалоге, выполните следующие четыре шага: 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();
Андрейx2
источник
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
12

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

dialog.setView(dialog.getLayoutInflater().inflate(R.layout.custom_dialog_layout, null));
Серджио Виудес
источник
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подкласса, как описано в документации .

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

Suragch
источник
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();
Flanaras
источник
Любой, кто читает это после того, как был представлен 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.

StackAttack
источник