Android對話框Dialog深度剖析

對話框

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

對話框設計

這裏寫圖片描述

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

AlertDialog

此對話框可顯示標題、最多三個按鈕、可選擇項列表或自定義佈局。 DatePickerDialog 或 TimePickerDialog 此對話框帶有容許用戶選擇日期或時間的預約義 UI。 避免使用 ProgressDialog Android 包括另外一種名爲 ProgressDialog 的對話框類,可顯示具備進度條的對話框。不過,如需指示加載進度或不肯定的進度,則應改成遵循進度和 Activity 的設計指南,並在您的佈局中使用 ProgressBar。 這些類定義您的對話框的樣式和結構,但您應該將 DialogFragment 用做對話框的容器。DialogFragment 類提供您建立對話框和管理其外觀所需的全部控件,而不是調用 Dialog 對象上的方法。數組

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

注:

因爲 DialogFragment 類最初是經過 Android 3.0(API 級別 11)添加的,所以本文描述的是如何使用支持庫附帶的 DialogFragment 類。 經過將該庫添加到您的應用,您能夠在運行 Android 1.6 或更高版本的設備上使用 DialogFragment 以及各類其餘 API。若是您的應用支持的最低版本是 API 級別 11 或更高版本,則可以使用 DialogFragment 的框架版本,但請注意,本文中的連接適用於支持庫 API。 使用支持庫時,請確保您導入的是 android.support.v4.app.DialogFragment 類,而不是 android.app.DialogFragment。app

建立對話框片斷

您能夠完成各類對話框設計—包括自定義佈局以及對話框設計指南中描述的佈局—經過擴展 DialogFragment 並在 onCreateDialog() 回調方法中建立 AlertDialog。框架

例如,如下是一個在 DialogFragment 內管理的基礎 AlertDialog:less

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 中實現各類其餘回調方法,包括全部基礎 片斷生命週期方法。ide

構建提醒對話框

您能夠經過 AlertDialog 類構建各類對話框設計,而且該類一般是您須要的惟一對話框類。如圖 2 所示,提醒對話框有三個區域: 函數

這裏寫圖片描述

圖 2. 對話框的佈局。

標題

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

內容區域

它能夠顯示消息、列表或其餘自定義佈局。

操做按鈕

對話框中的操做按鈕不該超過三個。 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,後者用於定義用戶按下該按鈕時執行的操做。

您能夠添加三種不一樣的操做按鈕:

確定 您應該使用此按鈕來接受並繼續執行操做(「肯定」操做)。 否認 您應該使用此按鈕來取消操做。 中性 您應該在用戶可能不想繼續執行操做,但也不必定想要取消操做時使用此按鈕。 它出如今確定按鈕和否認按鈕之間。 例如,實際操做多是「稍後提醒我」。 對於每種按鈕類型,您只能爲 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());
    // 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();
}
複製代碼

儘管傳統列表和具備單選按鈕的列表都能提供「單選」操做,但若是您想持久保存用戶的選擇,則應使用 {@linkandroid.app.AlertDialog.Builder#setSingleChoiceItems(int,int,DialogInterface.OnClickListener) 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,並在 清單文件元素中將其主題設置爲 Theme.Holo.Dialog:

<activity android:theme="@android:style/Theme.Holo.Dialog" >
複製代碼

就這麼簡單。Activity 如今會顯示在一個對話框窗口中,而非全屏顯示。 將事件傳遞迴對話框的宿主 當用戶觸摸對話框的某個操做按鈕或從列表中選擇某一項時,您的 DialogFragment 可能會自行執行必要的操做,但一般您想將事件傳遞給打開該對話框的 Activity 或片斷。 爲此,請定義一個界面,爲每種點擊事件定義一種方法。而後在從該對話框接收操做事件的宿主組件中實現該界面。

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

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 傳遞點擊事件:

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

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

相關文章
相關標籤/搜索