Dialogs(對話框)

對話框

對話框是一種提示用戶去做出選擇或輸入其他信息的小窗口。 對話框不填充屏幕並且通常被用於在執行前需要用戶做出決定的模態事件。

對話框設計

閱讀 Dialogs 設計指南,獲取包括語言規範等關於如何設計對話框的更多信息。

雖然 Dialog 是對話框的基類,但是你不應該直接去實例化 Dialog 類,而是實例化它下列子類中的一種。

AlertDialog
這種對話框可以顯示一個標題,最多三個按鈕,一個可選項列表或用戶自定義佈局。
DatePickerDialog or TimePickerDialog
這種使用預定義界面的對話框可以允許用戶選擇日期或時間。

避免使用 ProgressDialog

Android 擁有另外一種叫做 ProgressDialog 的對話框類,它能顯示一個帶進度條的對話框的對話框。然而如果你需要表明正在加載或有不確定的進度,你應該遵循 Progress & Activity 的設計指南並在你的佈局中使用 ProgressBar

雖然這些類已經定義了對話框的樣式和結構,但是你應該使用 DialogFragment 作爲對話框的容器。你不用調用 Dialog 對象的方法,因爲 DialogFragment 類能提供所有的控制手段,你只需創建你的對話框並控制它的外觀。

使用 DialogFragment 來管理對話框,可以確保對話框能正確的處理生命週期內的事件(例如:當用戶按下回退鍵或旋轉屏幕)。就像傳統的 Fragment 一樣,在一個大的用戶界面中,DialogFragment 同樣允許你把對話框界面當做嵌入式組件來重用(比如你想在大小不同的界面裏顯示不同的對話框界面的時候)。

這篇指南的以下章節介紹瞭如何把 DialogFragment 與 AlertDialog 結合使用。如果你想要創建日期或時間的選擇器,你應該去閱讀 Pickers 指南。

註解: 因爲 DialogFragment 類直到Android 3.0(API等級11)才被引入,所以這篇文檔介紹的是在擁有 Support Library 時使用 DialogFragment 類。通過把該類庫添加到你的應用中,你能在運行Android 1.6或更高版本的設備上使用 DialogFragment 或其他種種API。 如果你的應用支持的最低版本是API等級11或更高,那麼你可以直接使用框架版本的DialogFragment,但是請注意這篇文檔裏的鏈接使用的都是支持庫的API。使用支持庫前,請確保你引入了 android.support.v4.app.DialogFragment 類而不是android.app.DialogFragment

創建對話框碎片


通過繼承 DialogFragment 並且在它的 onCreateDialog() 回調方法裏創建一個 AlertDialog ,你能完成包含自定義佈局和那些在Dialogs 設計指南中描述過的的各種各樣的對話框設計。

例如,這裏有一個在 DialogFragment 內部被管理的基本的 AlertDialog

public class FireMissilesDialogFragment extends DialogFragment {
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        // 使用Builder類方便的構建對話框
        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) {
                       // 用戶取消對話框
                   }
               });
        // 創建 AlertDialog 對象並返回
        return builder.create();
    }
}

圖1. 一個帶有一條消息和兩個操作按鈕的對話框。

現在,當你創建這個類的實例並且調用了這個對象的 show() 方法,像圖1中展示的對話框就會顯示出來。

下一節介紹了更多關於使用 AlertDialog.Builder API去創建對話框的知識。

根據你的對話框的複雜程度,你可以繼承 DialogFragment 裏其他的多種多樣的回調方法,比如所有基礎的 fragment lifecycle methods

構建警告對話框


通常你僅僅需要使用 AlertDialog 類就能構建多種多樣的對話框設計。如圖2所示,一個警告對話框有三個區域:

圖2. 對話框的佈局。

  1. 標題

    這是可選的並且應該只有當內容區被一條詳細的消息、一個列表或自定義佈局填充時才被使用。如果你只需要聲明一條簡單的消息或問題(如圖1的對話框),那麼你不需要使用標題。

  2. 內容區

    這裏可以顯示一條消息,一個列表或其他自定義佈局。

  3. 操作按鈕

    在對話框裏最多隻能顯示3個操作按鈕。

使用 AlertDialog.Builder 類提供的API,你可以構建一個包含這些內容甚至自定義佈局的警告對話框。

構建警告對話框的步驟:

// 1. 用構造方法初始化一個 AlertDialog.Builder 實例
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());

// 2. 鏈接上各種各種的設置方法來設置對話框的特徵
builder.setMessage(R.string.dialog_message)
       .setTitle(R.string.dialog_title);

// 3. 通過 create() 方法獲取 AlertDialog 實例
AlertDialog dialog = builder.create();

接下來的專題將會展示通過使用 AlertDialog.Builder 類如何定義各種各樣的對話框屬性。

添加按鈕

調用 setPositiveButton() 和 setNegativeButton() 方法來添加如圖2裏的操作按鈕。

AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
// 添加按鈕
builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
           public void onClick(DialogInterface dialog, int id) {
               // 用戶點擊OK按鈕
           }
       });
builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
           public void onClick(DialogInterface dialog, int id) {
               // 用戶點擊取消按鈕
           }
       });
// 這是對話框的其他屬性
...

// 創建警告對話框
AlertDialog dialog = builder.create();

這些 set...Button() 方法需要爲按鈕設置一個標題(由字符串資源提供),還需要實現 DialogInterface.OnClickListener 接口以便在用戶按下按鈕時響應。

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

積極性質的
你應該使用這種按鈕來接收和繼續執行操作("OK"操作)。
消極性質的
你應該使用這種按鈕來取消操作。
中立性質的
當用戶可能不想繼續這個操作但是又不想取消它時你應該使用這種按鈕。它顯示在積極按鈕和消極按鈕之間。例如:這個操作可能叫做“稍後提醒”。

對於每種類型的按鈕,你只能添加一個到警告對話框中。也就是說,警告對話框中不能出現多餘一個的“積極”按鈕。

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

添加列表

AlertDialog API提供了三種可用的列表:

  • 傳統的單選列表
  • 持久的單選列表(單選按鈕)
  • 持久的多選列表(複選框)

使用 setItems() 方法來創造如圖3所示的單選列表:@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) {
               // ‘which’參數包含被選中項目的索引
           }
    });
    return builder.create();
}




由於列表也是在對話框的內容區裏呈現的,所以對話框不能同時顯示消息和列表,你只能使用 setTitle() 方法設置個標題。調用 setItems() 方法,傳遞一個數組參數,就能指明列表的項目了。或者,你也可以使用 setAdapter() 方法來指定列表。使用 ListAdapter 你能用動態數據(例如來自數據庫)來填充列表。

如果你選擇使用 ListAdapter 來填充你的列表,爲了能異步加載內容請使用 Loader。在 Building Layouts with an Adapter 和 Loaders 指南中有更詳細的描述。

註解: 默認情況下,觸摸一個列表選項後就會關閉對話框,除非你使用的是下面持久的選項列表中的一種。

圖4. 多選列表。

添加持久的多選或單選列表

可以分別使用 setMultiChoiceItems() 和 setSingleChoiceItems() 方法來添加多選列表(複選框)和單選列表(單選按鈕)。

例如,下面的例子展示瞭如何創建一個如圖4的多選列表並把選中的項目保存在一個 ArrayList中:

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
    mSelectedItems = new ArrayList();  // 追蹤選中項目的數組
    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
    // 設置對話框標題
    builder.setTitle(R.string.pick_toppings)
    // 指定數組列表,默認所有項目都未選中,當項目被選中時通過監聽器可以接收到回調
           .setMultiChoiceItems(R.array.toppings, null,
                      new DialogInterface.OnMultiChoiceClickListener() {
               @Override
               public void onClick(DialogInterface dialog, int which,
                       boolean isChecked) {
                   if (isChecked) {
                       // 如果用戶選中,把選中項添加到選中項目的數組中
                       mSelectedItems.add(which);
                   } else if (mSelectedItems.contains(which)) {
                       // 否則,如果項目已經在數組中了,那麼移除它
                       mSelectedItems.remove(Integer.valueOf(which));
                   }
               }
           })
    // 設置操作按鈕
           .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
               @Override
               public void onClick(DialogInterface dialog, int id) {
                   // 用戶點擊OK後,在某處保存選中項目的結果或返回給打開這個對話框的組件
                   ...
               }
           })
           .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
               @Override
               public void onClick(DialogInterface dialog, int id) {
                   ...
               }
           });

    return builder.create();
}

雖然傳統的列表和帶單選按鈕的列表都提供了單選操作,但是如果你想持久的保存用戶的選擇你應該使用 setSingleChoiceItems() 方法。也就是說,如果以後再打開這個對話框,應該指明用戶的當前選中是什麼,然後你才能創建單選按鈕的列表。

創建自定義佈局

圖5. 自定義佈局的對話框。

如果你想在對話框中使用自定義佈局,創建佈局並調用 AlertDialog.Builder 對象的 setView() 方法把自定義佈局添加到警告對話框中。

默認情況下,自定義佈局文件填充整個對話框窗口,然而你還是可以使用  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"時,字體屬性將被更改成monospace,所以爲了讓所有的輸入框使用同樣的字體風格你應該把它的字體屬性更改爲"sans-serif"

使用  getLayoutInflater() 方法獲取一個 LayoutInflater 實例,然後可以調用它的 inflate() 方法在你的 DialogFragment 中擴充佈局,第一個參數是佈局的資源ID,第二個參數是佈局的父視圖。然後你就能調用 setView() 方法把佈局放置到對話框中。

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

    // 填充並設置對話框
    // 因爲它的行爲發生在對話框佈局中,所以把它的父視圖置爲空
    builder.setView(inflater.inflate(R.layout.dialog_signin, null))
    // 添加操作按鈕
           .setPositiveButton(R.string.signin, new DialogInterface.OnClickListener() {
               @Override
               public void onClick(DialogInterface dialog, int id) {
                   // 用戶登錄...
               }
           })
           .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或fragment。爲此,定義一個接口並且爲每種點擊事件定義各自的方法。然後在宿主組件中繼承這個接口,這樣宿主就能接收到來自對話框的動作事件。

例如,下面的 DialogFragment 定義了一個通過它可以把事件回傳給宿主activity的接口。

public class NoticeDialogFragment extends DialogFragment {
    
    /*爲了接收到事件的回調,創建對話框的activity必須繼承這個接口。
     * 以防宿主需要查詢對話框的屬性,每個方法都將傳遞一個DialogFragment實例。 */
    public interface NoticeDialogListener {
        public void onDialogPositiveClick(DialogFragment dialog);
        public void onDialogNegativeClick(DialogFragment dialog);
    }
    
    // 使用這個接口的實例來傳遞動作事件
    NoticeDialogListener mListener;
    
    // 重寫Fragment.onAttach()方法來實例化NoticeDialogListener
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        // 驗證宿主acrivity是否繼承回調接口
        try {
            // 實例化NoticeDialogListener以便我們能向宿主傳遞事件
            mListener = (NoticeDialogListener) activity;
        } catch (ClassCastException e) {
            // activity沒有繼承接口則拋出異常
            throw new ClassCastException(activity.toString()
                    + " must implement NoticeDialogListener");
        }
    }
    ...
}

託管對話框的activity使用DialogFragment的構造方法來實例化對話框,並且通過繼承NoticeDialogListener接口可以接收到對話框的事件:

public class MainActivity extends FragmentActivity
                          implements NoticeDialogFragment.NoticeDialogListener{
    ...
    
    public void showNoticeDialog() {
        // 創建DialogFragment的實例並顯示
        DialogFragment dialog = new NoticeDialogFragment();
        dialog.show(getSupportFragmentManager(), "NoticeDialogFragment");
    }

    // 通過Fragment.onAttach()的回調,DialogFragment的實例可以接收到那個被用來實現下面NoticeDialogFragment.NoticeDialogListener定義的方法的引用
    @Override
    public void onDialogPositiveClick(DialogFragment dialog) {
        // 用戶觸碰對話框的積極按鈕
        ...
    }

    @Override
    public void onDialogNegativeClick(DialogFragment dialog) {
        // 用戶觸碰對話框的消極按鈕
        ...
    }
}

因爲宿主activity繼承了NoticeDialogListener接口並且如上所示可以在 onAttach() 中被強制轉換,所以DialogFragment可以使用接口的回調方法把點擊事件傳遞給activity。

public class NoticeDialogFragment extends DialogFragment {
    ...

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        // 構建對話框並設置按鈕點擊事件處理器
        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) {
                       // 把積極按鈕點擊事件回傳給宿主activity
                       mListener.onDialogPositiveClick(NoticeDialogFragment.this);
                   }
               })
               .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
                   public void onClick(DialogInterface dialog, int id) {
                       // 把消極按鈕點擊事件回傳給宿主activity
                       mListener.onDialogNegativeClick(NoticeDialogFragment.this);
                   }
               });
        return builder.create();
    }
}

顯示對話框


當你想要顯示你的對話框時,實例化你的 DialogFragment 對象並傳遞 FragmentManager 和標籤名給它的 show() 方法,調用該方法即可。

你可以通過調用 FragmentActivity 的 getSupportFragmentManager() 或  Fragment 的 getFragmentManager() 方法獲取  FragmentManager 實例。例如:

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

第二個參數, ”missiles”是一個唯一的標籤名,它是爲了讓系統在必要的時候用來保存和恢復fragment狀態的。這個標籤同樣也允許你通過調用 findFragmentByTag() 來獲取fragment的句柄。

顯示全屏或嵌入式碎片化的對話框


你可能需要這樣一種界面設計,你希望界面的一部分在某些情況下顯示爲對話框,並且在某些情況(可能依賴於設備是大屏幕或小屏幕)下對話框會顯示爲全屏的或嵌入式碎片化的對話框。因爲DialogFragment 類始終相當於嵌入式的 Fragment,所以它能爲你的設計提供了靈活性。

然而,如果你想要 DialogFragment 成爲可嵌入的,那麼你不能使用 AlertDialog.Builder 或其他 Dialog 對象去構建對話框,你必須在佈局文件中定義對話框的用戶界面,然後在 onCreateView() 回調中加載。

下面例子的 DialogFragment 可以顯示爲一個對話框或嵌入式的碎片(使用名爲purchase_items.xml的佈局):

public class CustomDialogFragment extends DialogFragment {
    /** 不管DialogFragment顯示爲對話框或嵌入式的碎片,系統調用該方法去擴充它的佈局。 */
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        // 擴充佈局以使它作爲對話框或嵌入式的碎片使用
        return inflater.inflate(R.layout.purchase_items, container, false);
    }
  
    /** 系統僅在對話框中創建佈局時調用。 */
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        // 當使用onCreateView()後,你可能重寫該方法的唯一原因是修改對話框的特徵。
        // 例如,默認情況下對話框包含一個標題,但是你的自定義佈局可能不需要它。
        // 所以在調用超類方法獲取對話框對象後,你就可以移除對話框的標題了。
        Dialog dialog = super.onCreateDialog(savedInstanceState);
        dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
        return dialog;
    }
}

下面的代碼基於屏幕的尺寸就能決定什麼時候顯示碎片化的對話框,什麼時候顯示全屏的界面:

public void showDialog() {
    FragmentManager fragmentManager = getSupportFragmentManager();
    CustomDialogFragment newFragment = new CustomDialogFragment();
    
    if (mIsLargeLayout) {
        // 設備使用大型佈局,所以把它作爲對話框顯示
        newFragment.show(fragmentManager, "dialog");
    } else {
        // 設備屏幕很小,所以全屏顯示
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        // 爲了更加完美,指定過渡動畫
        transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
        // 爲了達到全屏效果,使用經常作爲activity根視圖的名爲‘content’的根視圖作爲fragment的容器
        transaction.add(android.R.id.content, newFragment)
                   .addToBackStack(null).commit();
    }
}

更多關於碎片表現效果的信息,請參考 Fragments 指南。

在這個例子裏,布爾變量mIsLargeLayout指明瞭當前設備是否應該使用應用的大型佈局的設計(這樣顯示碎片化的對話框,而不是全屏的) 。設置這種布爾變量的最好的方式是爲不同屏幕大小在 alternative resource 聲明不同的 bool resource value

res/values/bools.xml

<!-- 默認布爾值 -->
<resources>
    <bool name="large_layout">false</bool>
</resources>

res/values-large/bools.xml

<!-- 大屏布爾值 -->
<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作爲對話框顯示對於提升平板上用戶體驗是非常有用的。

在清單裏 <activity> 元素中應用 Theme.Holo.DialogWhenLarge 主題就能實現僅當在大型屏幕上把activity當做對話框顯示的效果。

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

更多關於activity使用主題樣式信息,請參考 Styles and Themes 指南。

解散對話框


當用戶觸碰到通過 AlertDialog.Builder 創建的任何一個操作按鈕時,系統將會爲你解散這個對話框。

當用戶觸碰到對話框列表中的項目時系統同樣會解散這個對話框,除非這是一個擁有單選按鈕或複選框列表的對話框。另外,你可以通過調用 DialogFragment 裏的 dismiss() 方法來手動解散你的對話框。

如果你需要在對話框消失時執行某些操作,你可以在你的 DialogFragment 類中繼承 onDismiss() 方法。

你也可以取消一個對話框。這是一種指明用戶在沒有完成任務時就要明確的要求離開的特別的事件。如果用戶點擊返回按鈕,觸碰屏幕上對話框區域之外的地方,或者你明確的調用  Dialog 的 cancel() 方法時(例如響應對話框的“取消”按鈕)。

就像上面列子裏展示的,你可以在你的 DialogFragment 類中繼承 onCancel() 方法來響應取消事件。

註解: 雖然系統在每次調用 onDismiss() 之前會先調用 onCancel() 回調,但是如果你直接調用的是 Dialog.dismiss() 或 DialogFragment.dismiss()方法的話系統僅觸發 onDismiss() 而不會觸發 onCancel()。所以一般而言當用戶按下你對話框裏的積極按鈕時,你應該調用 dismiss() 方法把對話框從視圖中移除。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章