lightbulb_outline Help shape the future of the Google Play Console, Android Studio, and Firebase. Start survey

對話方塊

對話方塊是一種小型視窗,可提示使用者做出決定或輸入額外資訊。 對話視窗並不會佔滿整個螢幕,而且通常是供強制回應事件使用,這種事件會要求使用者必須先完成特定動作,才能繼續下一步。

設計對話方塊

如要瞭解如何設計對話方塊,包括建議使用的設計語言,請參閱對話方塊設計指南。

Dialog 類別是對話方塊的基礎類別,但請避免直接啟動 Dialog。建議您改用下列其中一個子類別:

AlertDialog
這種對話方塊可以顯示一個標題、最多三個按鈕、一系列可選取項目或一個自訂版面配置。
DatePickerDialogTimePickerDialog
這種對話方塊會提供預先定義的 UI,可讓使用者選取日期或時間。

上述類別可定義對話方塊的樣式和結構,但建議您使用 DialogFragment 做為對話方塊的容器。 DialogFragment 類別可提供您所需的所有控制項,方便您建立對話方塊及管理對話方塊的外觀,如此您不必對 Dialog 物件呼叫方法。

使用 DialogFragment 管理對話方塊可確保對話方塊能正確控制生命週期事件,例如使用者點擊 [返回] 按鈕或旋轉螢幕。 此外,DialogFragment 類別還能像傳統的 Fragment 一樣讓您重複使用對話方塊的 UI,做為大型 UI 中的可嵌入元件 (例如當您想讓對話方塊使用者介面在大型和小型螢幕上呈現不同外觀時)。

本指南的以下各節說明如何搭配 AlertDialog 物件使用 DialogFragment。 如果您是想建立日期或時間挑選器,請改為參閱挑選器指南。

注意:DialogFragment 類別最初是在 Android 3.0 (API 級別 11) 中導入,因此本文說明如何使用支援程式庫提供的 DialogFragment 類別。 只要將這個程式庫加到您的應用程式,即可在搭載 Android 1.6 以上版本的裝置上使用 DialogFragment 以及多種其他 API。 如果您應用程式支援的最低版本為 API 級別 11 以上版本,您就可以使用架構版本的 DialogFragment,但請注意,本文中提供的連結是用於取得支援程式庫 API。 使用支援程式庫時,請務必匯入 android.support.v4.app.DialogFragment 類別,而「不是」android.app.DialogFragment

建立對話方塊片段

如果想建立多種對話方塊設計 — 包括自訂版面配置和對話方塊設計指南中所述的對話方塊設計 — 只要延伸 DialogFragment 然後利用 onCreateDialog() 回呼方法建立 AlertDialog 即可。

例如,假設您利用 DialogFragment 管理以下的基本 AlertDialog

public class FireMissilesDialogFragment extends DialogFragment {
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        // Use the Builder class for convenient dialog construction
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        builder.setMessage(R.string.dialog_fire_missiles)
               .setPositiveButton(R.string.fire, new DialogInterface.OnClickListener() {
                   public void onClick(DialogInterface dialog, int id) {
                       // FIRE ZE MISSILES!
                   }
               })
               .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
                   public void onClick(DialogInterface dialog, int id) {
                       // User cancelled the dialog
                   }
               });
        // Create the AlertDialog object and return it
        return builder.create();
    }
}

圖 1. 包含一段訊息和兩個動作按鈕的對話方塊。

在這種情況下,如果您建立這個類別執行個體,然後對該物件呼叫 show(),就會顯示如圖 1 所示的對話方塊。

如要進一步瞭解如何使用 AlertDialog.Builder API 建立對話方塊,請參閱下一節。

視對話方塊的複雜程度而定,您可以在 DialogFragment 中實作多種其他回呼方法,包括所有的基本片段生命週期方法

建置快訊對話方塊

AlertDialog 通常是您需要使用的唯一對話方塊類別,可讓您建置多種對話方塊設計。如圖 2 所示,快訊對話方塊是由 3 個區塊組合而成:

圖 2.對話方塊的版面配置。

  1. 標題

    此為選用區塊;只有在內容區域提供詳細訊息、一份清單或自訂版面配置時,您才需要使用標題。 如果您想提供一段簡短訊息或一個簡易問題 (如圖 1 所示的對話方塊),您就不必使用標題。

  2. 內容區塊

    這個區塊可以顯示一段訊息、一份清單或其他自訂版面配置。

  3. 動作按鈕

    單一對話方塊最多只能包含 3 個動作按鈕。

AlertDialog.Builder 類別提供的 API 可讓您建立包含這些內容類型 (包括自訂版面配置) 的 AlertDialog

如何建置 AlertDialog

// 1. Instantiate an AlertDialog.Builder with its constructor
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());

// 2. Chain together various setter methods to set the dialog characteristics
builder.setMessage(R.string.dialog_message)
       .setTitle(R.string.dialog_title);

// 3. Get the AlertDialog from create()
AlertDialog dialog = builder.create();

以下主題說明如何定義多種採用 AlertDialog.Builder 類別的對話方塊屬性。

加入按鈕

想要加入如圖 2 所示的動作按鈕,請呼叫 setPositiveButton()setNegativeButton() 方法:

AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
// Add the buttons
builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
           public void onClick(DialogInterface dialog, int id) {
               // User clicked OK button
           }
       });
builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
           public void onClick(DialogInterface dialog, int id) {
               // User cancelled the dialog
           }
       });
// Set other dialog properties
...

// Create the AlertDialog
AlertDialog dialog = builder.create();

set...Button() 方法會要求字串資源提供的按鈕標題,以及可定義使用者點擊按鈕後需要完成的動作的DialogInterface.OnClickListener

您可以加入的動作按鈕分為 3 種:

正面
這種按鈕的用途是接受及繼續進行特定動作 (「確定」按鈕)。
負面
這種按鈕的用途是取消動作。
中立
如果使用者不想繼續進行特定動作,但並非要取消動作,請使用這種按鈕。 這種按鈕會顯示在正面和負面按鈕之間。 範例:[稍後提醒我] 按鈕。

您可以將以上其中一種按鈕加入 AlertDialog;換句話說,您最多只能加入一個「正面」按鈕。

圖 3. 包含一個標題和一份清單的對話方塊。

加入清單

AlertDialog API 可提供以下 3 種清單類型:

  • 傳統的單一選項清單
  • 永續性的單一選項清單 (圓形按鈕)
  • 永續性的多重選項清單 (核取方塊)

想要建立如圖 3 所示的單一選項清單,請使用 setItems() 方法:

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
    builder.setTitle(R.string.pick_color)
           .setItems(R.array.colors_array, new DialogInterface.OnClickListener() {
               public void onClick(DialogInterface dialog, int which) {
               // The 'which' argument contains the index position
               // of the selected item
           }
    });
    return builder.create();
}

由於清單會出現在對話方塊的內容區塊中,因此對話方塊無法同時顯示訊息和清單,而且您必須使用 setTitle() 為對話方塊設定標題。 如要指定清單列出的項目,請呼叫 setItems() 來傳送陣列。或者,您也可以使用 setAdapter() 指定清單。 如此一來,您就可以使用 ListAdapter 為清單加入動態資料 (例如資料庫中的動態資料)。

如果您選擇讓清單採用 ListAdapter,請一律使用 Loader 以非同步方式載入內容。 詳情請參閱使用配接器建置版面配置載入器指南。

注意:在預設情況下,輕觸某個清單項目會關閉對話方塊,除非您使用以下的永續性選項清單。

圖 4. 多重選項清單。

加入永續性的多重選項或單一選項清單

如要加入多重選項 (核取方塊) 或單一選項 (圓形按鈕),請使用 setMultiChoiceItems()setSingleChoiceItems() 方法。

例如,以下說明如何建立如圖 4 所示能夠在 ArrayList 中儲存所選項目的多重選項清單:

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
    mSelectedItems = new ArrayList();  // Where we track the selected items
    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
    // Set the dialog title
    builder.setTitle(R.string.pick_toppings)
    // Specify the list array, the items to be selected by default (null for none),
    // and the listener through which to receive callbacks when items are selected
           .setMultiChoiceItems(R.array.toppings, null,
                      new DialogInterface.OnMultiChoiceClickListener() {
               @Override
               public void onClick(DialogInterface dialog, int which,
                       boolean isChecked) {
                   if (isChecked) {
                       // If the user checked the item, add it to the selected items
                       mSelectedItems.add(which);
                   } else if (mSelectedItems.contains(which)) {
                       // Else, if the item is already in the array, remove it
                       mSelectedItems.remove(Integer.valueOf(which));
                   }
               }
           })
    // Set the action buttons
           .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
               @Override
               public void onClick(DialogInterface dialog, int id) {
                   // User clicked OK, so save the mSelectedItems results somewhere
                   // or return them to the component that opened the dialog
                   ...
               }
           })
           .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
               @Override
               public void onClick(DialogInterface dialog, int id) {
                   ...
               }
           });

    return builder.create();
}

雖然傳統清單和包含圓形按鈕的清單都可提供「單選」動作,但如果您想保留使用者的選擇,請使用 setSingleChoiceItems()。也就是說,如果您想讓對話方塊再次開啟時顯示使用者目前所選的選項,請建立包含圓形按鈕的清單。

建立自訂版面配置

圖 5.自訂的對話方塊版面配置。

如果您想自訂對話方塊的版面配置,請建立所需的版面配置,然後對 AlertDialog.Builder 物件呼叫 setView(),將新建的版面配置加到 AlertDialog

自訂版面配置預設會佔滿整個對話方塊視窗,但您仍可使用 AlertDialog.Builder 方法在其中加入按鈕和標題。

例如,以下是圖 5 所示的對話方塊版面配置檔案:

res/layout/dialog_signin.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">
    <ImageView
        android:src="@drawable/header_logo"
        android:layout_width="match_parent"
        android:layout_height="64dp"
        android:scaleType="center"
        android:background="#FFFFBB33"
        android:contentDescription="@string/app_name" />
    <EditText
        android:id="@+id/username"
        android:inputType="textEmailAddress"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:layout_marginLeft="4dp"
        android:layout_marginRight="4dp"
        android:layout_marginBottom="4dp"
        android:hint="@string/username" />
    <EditText
        android:id="@+id/password"
        android:inputType="textPassword"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="4dp"
        android:layout_marginLeft="4dp"
        android:layout_marginRight="4dp"
        android:layout_marginBottom="16dp"
        android:fontFamily="sans-serif"
        android:hint="@string/password"/>
</LinearLayout>

提示:在預設情況下,如果您設定 EditText 元素使用 "textPassword" 輸入類型,字型系列就會設為等寬字型,因此您必須將該元素的字型系列變更為 "sans-serif",讓文字欄位採用對應的字型。

如要擴大 DialogFragment 中的版面配置,請透過 getLayoutInflater() 取得 LayoutInflater,然後呼叫inflate() (其中的第一個參數為版面配置資源 ID,第二個參數則是版面配置的上層檢視)。接著,您可以呼叫 setView() 來取代對話方塊中的版面配置。

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
    // Get the layout inflater
    LayoutInflater inflater = getActivity().getLayoutInflater();

    // Inflate and set the layout for the dialog
    // Pass null as the parent view because its going in the dialog layout
    builder.setView(inflater.inflate(R.layout.dialog_signin, null))
    // Add action buttons
           .setPositiveButton(R.string.signin, new DialogInterface.OnClickListener() {
               @Override
               public void onClick(DialogInterface dialog, int id) {
                   // sign in the user ...
               }
           })
           .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
               public void onClick(DialogInterface dialog, int id) {
                   LoginDialogFragment.this.getDialog().cancel();
               }
           });
    return builder.create();
}

提示:如果您想自訂對話方塊,請改為將 Activity 顯示為對話方塊,而不是使用 Dialog API。 方法很簡單,只要建立 Activity 然後在 &lt;activity&gt; 宣示說明元素中將其主題設為 Theme.Holo.Dialog 即可:

<activity android:theme="@android:style/Theme.Holo.Dialog" >

這樣一來,Activity 就會顯示在對話方塊視窗,而不是以全螢幕模式顯示。

將事件傳回對話方塊的主控件

使用者在對話方塊中輕觸任一動作按鈕或從清單中選取一個項目後,您的 DialogFragment 可能會自行執行必要動作,不過,您通常會想將事件傳送到 Activity 或對話方塊開啟的片段。 如要這麼做,請透過每個點擊事件類型適用的方法定義介面,然後在會接收對話方塊的動作事件的主機元件中實作該介面。

例如,以下是定義用於將事件傳回主機 Activity 的介面的 DialogFragment

public class NoticeDialogFragment extends DialogFragment {

    /* The activity that creates an instance of this dialog fragment must
     * implement this interface in order to receive event callbacks.
     * Each method passes the DialogFragment in case the host needs to query it. */
    public interface NoticeDialogListener {
        public void onDialogPositiveClick(DialogFragment dialog);
        public void onDialogNegativeClick(DialogFragment dialog);
    }

    // Use this instance of the interface to deliver action events
    NoticeDialogListener mListener;

    // Override the Fragment.onAttach() method to instantiate the NoticeDialogListener
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        // Verify that the host activity implements the callback interface
        try {
            // Instantiate the NoticeDialogListener so we can send events to the host
            mListener = (NoticeDialogListener) activity;
        } catch (ClassCastException e) {
            // The activity doesn't implement the interface, throw exception
            throw new ClassCastException(activity.toString()
                    + " must implement NoticeDialogListener");
        }
    }
    ...
}

代管對話方塊的 Activity 會建立包含對話方塊片段的建構函式的對話方塊執行個體,以及透過您實作的 NoticeDialogListener 介面接收對話方塊的事件:

public class MainActivity extends FragmentActivity
                          implements NoticeDialogFragment.NoticeDialogListener{
    ...

    public void showNoticeDialog() {
        // Create an instance of the dialog fragment and show it
        DialogFragment dialog = new NoticeDialogFragment();
        dialog.show(getSupportFragmentManager(), "NoticeDialogFragment");
    }

    // The dialog fragment receives a reference to this Activity through the
    // Fragment.onAttach() callback, which it uses to call the following methods
    // defined by the NoticeDialogFragment.NoticeDialogListener interface
    @Override
    public void onDialogPositiveClick(DialogFragment dialog) {
        // User touched the dialog's positive button
        ...
    }

    @Override
    public void onDialogNegativeClick(DialogFragment dialog) {
        // User touched the dialog's negative button
        ...
    }
}

由於主機 Activity 會實作 NoticeDialogListener — 如上所述的 onAttach() 回呼方法會強制執行這個 Activity — 因此對話方塊片段可以使用介面回呼方法將點擊事件傳送到 Activity:

public class NoticeDialogFragment extends DialogFragment {
    ...

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        // Build the dialog and set up the button click handlers
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        builder.setMessage(R.string.dialog_fire_missiles)
               .setPositiveButton(R.string.fire, new DialogInterface.OnClickListener() {
                   public void onClick(DialogInterface dialog, int id) {
                       // Send the positive button event back to the host activity
                       mListener.onDialogPositiveClick(NoticeDialogFragment.this);
                   }
               })
               .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
                   public void onClick(DialogInterface dialog, int id) {
                       // Send the negative button event back to the host activity
                       mListener.onDialogNegativeClick(NoticeDialogFragment.this);
                   }
               });
        return builder.create();
    }
}

顯示對話方塊

如果想顯示對話方塊,請建立 DialogFragment 執行個體並呼叫 show() 來傳送對話方塊片段的 FragmentManager 和標籤名稱。

如要取得 FragmentManager,請呼叫 FragmentActivity 中的 getSupportFragmentManager()Fragment 中的 getFragmentManager()。例如:

public void confirmFireMissiles() {
    DialogFragment newFragment = new FireMissilesDialogFragment();
    newFragment.show(getSupportFragmentManager(), "missiles");
}

第二個引數 "missiles" 是不重複的標籤名稱,可供系統視需要用於儲存及還原片段狀態。 此外,該標籤還可讓您呼叫 findFragmentByTag() 來取得片段的控點。

顯示全螢幕對話方塊或內嵌片段

您可能會想建立 UI 設計,讓 UI 片段在某些情況下於該設計中顯示為對話方塊,而不是其他設計中的全螢幕或內嵌片段 (例如視裝置採用螢幕大小而定)。 DialogFragment 類別能為您提供這樣的彈性,這是因為該類別仍可做為可嵌入的 Fragment 使用。

不過,您無法使用 AlertDialog.Builder 或其他 Dialog 物件來建置這種對話方塊。 如果您想建立可嵌入的 DialogFragment,請務必在版面配置中定義對話方塊的 UI,然後透過 onCreateView() 回呼載入版面配置。

以下的 DialogFragment 範例可顯示為對話方塊或可嵌入片段 (使用名為 purchase_items.xml 的版面配置):

public class CustomDialogFragment extends DialogFragment {
    /** The system calls this to get the DialogFragment's layout, regardless
        of whether it's being displayed as a dialog or an embedded fragment. */
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        // Inflate the layout to use as dialog or embedded fragment
        return inflater.inflate(R.layout.purchase_items, container, false);
    }

    /** The system calls this only when creating the layout in a dialog. */
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        // The only reason you might override this method when using onCreateView() is
        // to modify any dialog characteristics. For example, the dialog includes a
        // title by default, but your custom layout might not need it. So here you can
        // remove the dialog title, but you must call the superclass to get the Dialog.
        Dialog dialog = super.onCreateDialog(savedInstanceState);
        dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
        return dialog;
    }
}

以下的程式碼可根據螢幕大小,決定要將片段顯示為對話方塊或全螢幕 UI:

public void showDialog() {
    FragmentManager fragmentManager = getSupportFragmentManager();
    CustomDialogFragment newFragment = new CustomDialogFragment();

    if (mIsLargeLayout) {
        // The device is using a large layout, so show the fragment as a dialog
        newFragment.show(fragmentManager, "dialog");
    } else {
        // The device is smaller, so show the fragment fullscreen
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        // For a little polish, specify a transition animation
        transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
        // To make it fullscreen, use the 'content' root view as the container
        // for the fragment, which is always the root view for the activity
        transaction.add(android.R.id.content, newFragment)
                   .addToBackStack(null).commit();
    }
}

如要進一步瞭解如何進行片段交易,請參閱片段指南。

在本範例中,mIsLargeLayout 布林值可指定是否要讓目前的裝置採用應用程式的大型版面配置設計 (藉此將這個片段顯示為對話方塊,而不是全螢幕)。 設定這種布林值的最佳做法,是使用替代資源值為不同裝置大小宣告布林資源值。 例如,以下是適用於不同螢幕大小的 2 種布林資源版本:

res/values/bools.xml

<!-- Default boolean values -->
<resources>
    <bool name="large_layout">false</bool>
</resources>

res/values-large/bools.xml

<!-- Large screen boolean values -->
<resources>
    <bool name="large_layout">true</bool>
</resources>

接著,您可以在呼叫 Activity 的 onCreate() 方法時初始化 mIsLargeLayout 值:

boolean mIsLargeLayout;

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

    mIsLargeLayout = getResources().getBoolean(R.bool.large_layout);
}

針對大型螢幕將 Activity 顯示為對話方塊

您可以針對大型螢幕將 Activity 顯示為對話方塊,而不是將對話方塊顯示為全螢幕 UI,藉此達到相同結果。 您要採用的方法取決於您的應用程式設計,但如果您的應用程式是針對小型螢幕進行設計,將 Activity 顯示為對話方塊通常就能獲得良好效果,而如果您想針對平板電腦改善使用者體驗,請將生命週期較短的 Activity 顯示為對話方塊。

如果只想針對大型螢幕將 Activity 顯示為對話方塊,請為 &lt;activity&gt; 宣示元素套用 Theme.Holo.DialogWhenLarge 主題:

<activity android:theme="@android:style/Theme.Holo.DialogWhenLarge" >

如要進一步瞭解如何運用主題設定 Activity 的樣式,請參閱樣式和主題指南。

關閉對話方塊

只要使用者輕觸任何用 AlertDialog.Builder 建立的動作按鈕,系統就會為您關閉對話方塊。

此外,系統也會在使用者輕觸對話方塊清單中的項目時,關閉對話方塊,但如果清單採用圓形按鈕或核取方塊,系統就不會關閉對話方塊。 不過,您可以對 DialogFragment 呼叫 dismiss(),藉此手動關閉對話方塊。

如果您需要在對話方塊關閉時執行特定動作,請在 DialogFragment 中實作 onDismiss() 方法。

此外,您還可以「取消」對話方塊。這種特殊事件可用於指明使用者尚未完成工作便關閉對話方塊。 如果使用者按下 [返回] 按鈕、輕觸對話方塊以外的螢幕畫面,或如果您對 Dialog 呼叫 cancel() (例如藉此回應對話方塊中的 [取消] 按鈕),就會發生這個事件。

如上方範例所示,您可以在 DialogFragment 類別中實作 onCancel() 來回應取消事件。

注意:系統會在發生會呼叫 onCancel() 回呼的事件時呼叫 onDismiss()。 不過,如果您呼叫 Dialog.dismiss()DialogFragment.dismiss(),則系統會呼叫 onDismiss()「而不是」onCancel()。 因此,我們通常建議您在使用者點擊您對話方塊中的「正面」按鈕以便將對話方塊從檢視移除時,呼叫 dismiss()