Android 對話框詳解

對話框是提示用戶作出決定或輸入額外信息的小窗口。 對話框不會填充屏幕,通常用於需要用戶採取行動才能繼續執行的模式事件。

Dialog 類是對話框的基類,但您應該避免直接實例化 Dialog,而是使用下列子類之一:

AlertDialog
此對話框可顯示標題、最多三個按鈕、可選擇項列表或自定義佈局。
DatePickerDialog 或 TimePickerDialog
此對話框帶有允許用戶選擇日期或時間的預定義 UI。

這些類定義您的對話框的樣式和結構,但您應該將 DialogFragment 用作對話框的容器。DialogFragment 類提供您創建對話框和管理其外觀所需的所有控件,而不是調用 Dialog 對象上的方法。

使用 DialogFragment 管理對話框可確保它能正確處理生命週期事件,如用戶按“返回”按鈕或旋轉屏幕時。 此外,DialogFragment 類還允許您將對話框的 UI 作爲嵌入式組件在較大 UI 中重複使用,就像傳統 Fragment 一樣(例如,當您想讓對話框 UI 在大屏幕和小屏幕上具有不同外觀時)。

本指南的後文將描述如何將 DialogFragment 與 AlertDialog 對象結合使用。 如果您想創建一個日期或時間選取器,應改爲閱讀選取器指南。

注:由於 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 所示,提醒對話框有三個區域:

圖 2. 對話框的佈局。

  1. 標題

    這是可選項,只應在內容區域被詳細消息、列表或自定義佈局佔據時使用。 如需陳述的是一條簡單消息或問題(如圖 1 中的對話框),則不需要標題。

  2. 內容區域

    它可以顯示消息、列表或其他自定義佈局。

  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());
// 添加按鈕
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
           }
       });
// 設置其他屬性
...

// 創建 AlertDialog
AlertDialog dialog = builder.create();

set...Button() 方法需要一個按鈕標題(由字符串資源提供)和一個 DialogInterface.OnClickListener,後者用於定義用戶按下該按鈕時執行的操作。

您可以添加三種不同的操作按鈕:

肯定
您應該使用此按鈕來接受並繼續執行操作(“確定”操作)。
否定
您應該使用此按鈕來取消操作。
中性
您應該在用戶可能不想繼續執行操作,但也不一定想要取消操作時使用此按鈕。 它出現在肯定按鈕和否定按鈕之間。 例如,實際操作可能是“稍後提醒我”。

對於每種按鈕類型,您只能爲 AlertDialog 添加一個該類型的按鈕。也就是說,您不能添加多個“肯定”按鈕。

圖 3. 一個包含標題和列表的對話框。

添加列表

可通過 AlertDialog API 提供三種列表:

  • 傳統單選列表
  • 永久性單選列表(單選按鈕)
  • 永久性多選列表(複選框)

要想創建如圖 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());
    // 設置 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));
                   }
               }
           })
    //設置 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();

  
    //設置佈局
    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,並在 <activity> 清單文件元素中將其主題設置爲 Theme.Holo.Dialog

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

就這麼簡單。Activity 現在會顯示在一個對話框窗口中,而非全屏顯示。

將事件傳遞迴對話框的宿主


當用戶觸摸對話框的某個操作按鈕或從列表中選擇某一項時,您的 DialogFragment 可能會自行執行必要的操作,但通常您想將事件傳遞給打開該對話框的 Activity 或片段。 爲此,請定義一個界面,爲每種點擊事件定義一種方法。然後在從該對話框接收操作事件的宿主組件中實現該界面。

例如,以下 DialogFragment 定義了一個界面,通過該界面將事件傳回給宿主 Activity:

public class NoticeDialogFragment extends DialogFragment {

    /*設置回調監聽
     * 在那個activity使用,就實現此接口
     */
    public interface NoticeDialogListener {
        public void onDialogPositiveClick(DialogFragment dialog);
        public void onDialogNegativeClick(DialogFragment dialog);
    }

  
    NoticeDialogListener mListener;

    // 重寫onAttach方法,初始化mListener
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        // Verify that the host activity implements the callback interface
        try {
           
            mListener = (NoticeDialogListener) activity;
        } catch (ClassCastException e) {
            
            throw new ClassCastException(activity.toString()
                    + " must implement NoticeDialogListener");
        }
    }
    ...
}

對話框的宿主 Activity 會通過對話框片段的構造函數創建一個對話框實例,並通過實現的 NoticeDialogListener 界面接收對話框的事件:

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

    public void showNoticeDialog() {
        // 創建dialog對象
        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) {
        // 點擊確定按鈕
        ...
    }

    @Override
    public void onDialogNegativeClick(DialogFragment dialog) {
        // 返回按鈕
        ...
    }
}

由於宿主 Activity 會實現 NoticeDialogListener—由以上顯示的 onAttach() 回調方法強制執行 — 因此對話框片段可以使用界面回調方法向 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 和標記名稱。

您可以通過從 FragmentActivity 調用 getSupportFragmentManager() 或從 Fragment 調用 getFragmentManager() 來獲取 FragmentManager。例如:

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 {
    /** 通過onCreateView加載佈局 */
    @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 布爾值指定當前設備是否應該使用應用的大布局設計(進而將此片段顯示爲對話框,而不是全屏顯示)。 設置這種布爾值的最佳方法是聲明一個布爾資源值,其中包含適用於不同屏幕尺寸的備用資源值。 例如,以下兩個版本的布爾資源適用於不同的屏幕尺寸:

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 顯示爲大屏幕上的對話框

相對於在小屏幕上將對話框顯示爲全屏 UI,您可以通過在大屏幕上將 Activity 顯示爲對話框來達到相同的效果。您選擇的方法取決於應用設計,但當應用已經針對小屏幕進行設計,而您想要通過將短生存期 Activity 顯示爲對話框來改善平板電腦體驗時,將 Activity 顯示爲對話框往往很有幫助。

要想僅在大屏幕上將 Activity 顯示爲對話框,請將 Theme.Holo.DialogWhenLarge 主題應用於 <activity> 清單文件元素:

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


發佈了66 篇原創文章 · 獲贊 101 · 訪問量 46萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章