如何实现自定义AlertDialog视图

115

Android AlertDialog文档中,它提供了以下设置AlertDialog自定义视图的指示和示例:

如果您想显示更复杂的视图,请查找名为“body”的FrameLayout并将其添加到其中:

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

首先,很明显add()是一个笔误,应该是addView()

对于第一行使用的R.id.body,我感到困惑。它似乎是AlertDialog的body元素...但我不能在我的代码中输入它,因为它会导致编译错误。R.id.body在哪里定义或分配了呢?

这是我的代码。我尝试在builder上使用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();

要在对话框上查找和使用对象,请按照以下四个步骤进行:https://dev59.com/n2kv5IYBdhLWcg3w1kSr#18773261 - Sara
6
一句话回答:在构建器中添加.setView(getLayoutInflater().inflate(R.layout.dialog_view, null))。感谢下面的Sergio Viudes。 - 1''
12个回答

161

你可以直接使用Layout Inflater从XML布局文件创建视图,只需要使用布局文件的名称和文件中布局的ID。

你的XML文件应该像这样有一个ID:

<?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在哪里?它不是当前Activity中的一个视图吗? - Alex Pretzlav
5
在这个例子中,dialog_layout_root不是必需的。你只需要使用xml文件的名称作为R.layout.[xml文件名]即可。 - IgorGanapolsky
7
@Temperage,你最后是否添加了 builder.show?我尝试了这段代码,它可以工作。此外,我将null作为第二个参数传递给inflater.inflate。 - Vinoth
2
这应该被选为最佳答案。 - Salman Khakwani
2
当自定义布局包含EditText时,这会导致ClassCastException,因为getCurrentFocus()将返回EditText,而EditText无法转换为ViewGroup。使用null作为第二个参数可以解决此问题。 - 1''
显示剩余5条评论

51

你说得对,这是因为你没有手动膨胀它。看起来你试图从Activity的布局中“提取”“body”id,但那样做是行不通的。

你可能想要像这样:

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

18
有趣的是,在android.R.id中,body并未被定义为常量。我仍然不清楚如何访问创建的AlertDialog的“body”元素。我仍然想知道如何做到这一点,但现在我将尝试填充视图并在构建器中使用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中引用view。如果我确实想引用父级,您会推荐我做什么?我在alertDialog中看到的唯一返回视图的是getCurrentFocus()。 - stormin986
5
在使用 View 的时候要牢牢把握它,需要从其内容中获取东西时,可以在该 View 上调用 findViewById() 方法。详情请参见:http://github.com/commonsguy/cw-android/tree/master/Database/Constants/ - CommonsWare
显示剩余6条评论

21

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>

John Rellis,你的解决方案很好。只是其中有一些问题。我们应该在builder.create之前进行inflate,并设置frameView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); 或者先进行一些其他操作,这将防止一些意外的错误。 - MOBYTELAB
感谢您的反馈。在builder.create()之前如何膨胀?膨胀是在对话框的inflater上调用的,而我只有一个对话框,因为我已经调用了builder.create()。 - John Rellis
我们可以使用Activity Inflater而不是附加到FrameLayout,这样我们就可以减少一些代码,例如:AlertDialog.Builder builder = new AlertDialog.Builder(new ContextThemeWrapper(getActivity(), android.R.style.Theme_Holo)) .setTitle("标题") .setIcon(R.drawable.sample_icon); View troubleView = inflater.inflate(R.layout.sample_layout, null, false); builder.setView(troubleView); alert = builder.create(); 抱歉,我不知道如何在评论中清晰地编写代码。 - MOBYTELAB
这只是关于设计错误的问题,如果我们在布局XML中将所有内容聚集在一起并忘记将Framelayout作为对话框的根元素,那么当XML布局不是填充父级或其他情况时,我们可能会感到好奇。 - MOBYTELAB
对我有用,谢谢!这个让我能够添加一个标题和我的取消和确定按钮,包括EditText没有错误。 :)唯一的问题是,这是如何工作的?FrameLayout是否充当某种片段?当我尝试上面Andrewx2的答案时,我得到了一个错误,因为它认为我正在填充两个布局(这是我的猜测)。 - Azurespot

17

15

自定义AlertDialog

这个完整的示例包括将数据传递回Activity。

enter image description here

创建自定义布局

这个简单的示例使用了带有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>

在代码中使用对话框

关键部分包括:

  • 使用 setViewAlertDialog.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); should be EditText editText = (EditText) customLayout.findViewById(R.id.editText); - philcruz
5
如果你升级到Android Studio 3.0,你就不再需要显式地转换视图类型了。集成开发环境可以推断出类型。这是一个非常棒的功能。它可以帮助大大简化代码。 - Suragch
非常好的答案,真的很有帮助。 - Noor Hossain

12

这个对我起作用:

dialog.setView(dialog.getLayoutInflater().inflate(R.layout.custom_dialog_layout, null));

4
我最常用的几行代码如下:

以下是我使用的最简单的代码:

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

无论采用何种布局(LinearLayout、FrameLayout、RelativeLayout),都可以通过setView方法进行设置,它们之间的区别仅在于外观和行为上的差异。

很棒,简单易学 - Frank Eno

3

最简单的方法是使用android.support.v7.app.AlertDialog而不是android.app.AlertDialog,在API 21以下可以使用public AlertDialog.Builder setView (int layoutResId)

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 帧布局中。以下是 Android 源代码中 AlertController.setupView() 的片段,它最终处理此操作(mView 是传递给 AlertDialog.setView 方法的视图)。
...
FrameLayout custom = (FrameLayout) mWindow.findViewById(R.id.**custom**);
custom.addView(**mView**, new LayoutParams(FILL_PARENT, FILL_PARENT));
...

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

如果您想设置更多的内容,请在Android Studio中输入.set


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接