從零開始--系統深入學習android(實踐-讓我們開始寫代碼-Android框架學習-6.對話框)


第6章 對話框

一個對話框是一個小窗口,提示用戶做出決定或輸入額外的信息,一個對話框不填充屏幕並且通常用於在程序運行時中斷,然后彈出通知提示用戶,從而直接影響到正在運行的程序。圖6-1就是對話框的外觀。

 

圖6-1

Dialog類是所有具體對話框的基類,但你應該避免直接實例化Dialog。因為可能有些現成的已經給你提供好了,比如以下幾種:

◆AlertDialog

一個對話框,可以顯示一個標題(可選),三個按鈕(可選),內容的列表(可選),或一個自定義布局。

◆DatePickerDialog或TimePickerDialog

一個對話框,其中有一個預定義的用戶界面,允許用戶選擇一個日期或時間。

◆ProgressDialog

這個我們應該盡量避免使用,它用於顯示進度條。但是,如果你如果需要顯示這種加載或不確定的進度情況,更好的方法是Activity結合Progress,明確的在布局中使用ProgressBar來更新進度,以讓用戶明確的看到進度情況。

以上這些類定義了你對話框的風格和結構,但你也可以使用DialogFragment作為你對話框的容器。DialogFragment類提供所有創建和管理外觀的控制力,它比直接調用Dialog對象中的方法更好。使用DialogFragment管理對話框可以確保它正確地處理生命周期事件,比如當用戶按下后退按鈕或旋轉屏幕時。這個DialogFragment類還允許您重用對話框的用戶界面,就像一個傳統的Fragment (比如當你希望對話框在大型和小型屏幕中的UI不一樣)。注意DialogFragment類在android3.0或以上版本中才有,如果你需要在低版本中使用它可以使用Support Library,添加這個庫到你的應用中。一般比較新的ADT+Eclipse套裝會自動把這個庫添加到你的項目中。

6.1 創建一個對話框Fragment

你能完成各種各樣的對話框設計:包括自定義布局和那些描述。通過DialogFragment創建一個AlertDialog並在onCreateDialog()回調方法中設置一些屬性。如代碼清單6-1所示:

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) {
                       //相當於確定
                   }
               })
               .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
                   public void onClick(DialogInterface dialog, int id) {
                       // 相當於取消
                   }
               });
        // 創建AlertDialog對象並返回
        return builder.create();
    }
}

 

代碼清單6-1

現在當你創建完后調用show()方法,對話框出現圖6-2的效果。

 

圖6-2 一個對話框中帶有一個消息和兩個動作按鈕

6.2 構建一個Alert Dialog

AlertDialog類允許您構建各種對話框設計。如圖6-3所示, 一個警告對話框有三個區域

 

圖6-3 對話框布局

1. 標題(Title)

這是可選的,如果你需要一個簡單的信息或問題的狀態,你可以不需要一個標題。

2. 內容區域

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

3. 動作按鈕

在一個對話框最多不應該超過3個按鈕。

AlertDialog.Builder類系統了API允許你創建各種各樣的內容,包括布局等,現在在返回看下代碼清單6-1可能你會更清晰了。清單中還有兩個按鈕,如果你看到這樣的格式方法如set..Button(),就是設置動作按鈕了,下面讓我們來看下3種按鈕的不同之處:

◆Positive

這個按鈕表示接受或着繼續,相當於“確定”或者“OK”

◆Negative

這個按鈕相當於“取消”

◆Neutral

這是個中立按鈕,比如一個“以后提醒我”的功能。

每種按鈕只能出現一次,不能同時出現N次。

6.2.1添加一個List

有三種可用的AlertDialog API列表:

一個傳統的單選列表

一個持久化的單選列表(單選按鈕)

一個持久化的多選列表(復選框)

創建一個單選列表類似於圖6-4,你可以使用setItems()方法,如代碼清單6-2所示:

 

 

圖6-4 這個對話框里有標題和列表

@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' 參數包含被選中item的索引位置
           }
    });
    return builder.create();
}

 

代碼清單6-2

因為列表出現在對話框的內容區域里,對話框不能既顯示列表又顯示一個消息,所以你可以使用setTitle()設置標題以代替消息(message)。為了指定列表中的item,你可以通過數組來調用setItems()方法。或者你能使用setAdapter()來指定一個列表。它允許你使用一個ListAdapter來返回列表和動態數據。如果你選擇使用ListAdapter返回一個列表,那么請一直使用Loader以便內容異步加載。關於這方面的內容在“用戶界面和布局”和“裝載機”章節我們已講述過。注意:默認情況下,觸摸一個list item會dissmiss掉一個對話框,除非你使用下面我們要講述的持久化選擇列表。

6.2.2添加一個持久化的單選或多選列表

為了添加一個列表的多選item或單選item,你可以使用setMultiChoiceItems()或setSingleChoiceItems()方法。例如圖6-5是在一個ArrayList中使用多選item的例子。

 

圖6-5 一個多選列表的items

@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) {
                       // 如果選擇一個item,就添加一個索引
                       mSelectedItems.add(which);
                   } else if (mSelectedItems.contains(which)) {
                       // 否則,如果item已經存在就移除它 
                       mSelectedItems.remove(Integer.valueOf(which));
                   }
               }
           })
    // 設置動作按鈕
           .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
               @Override
               public void onClick(DialogInterface dialog, int id) {
                   // 用戶點擊OK,保存已選中的item結果到某處或者返回結果到已打開的對話框中
                   ...
               }
           })
           .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
               @Override
               public void onClick(DialogInterface dialog, int id) {
                   ...
               }
           });
 
    return builder.create();
}

 

代碼清單6-3

雖然一個傳統的列表和一個單選列表都提供一個“單選”動作。但如果你想要保存用戶的選擇,你應該使用setSingleChoiceItems()。就是說如果你稍后再次打開對話框應該表明用戶當前的選擇是什么,然后你使用單選按鈕創建一個列表。

6.2.3創建以自定義的布局

如果你想要一個自定義的布局,那么你可以使用AlertDialog.Builder對象中的setView()方法來添加一個你創建的布局。默認情況下,自定義布局會填充對話框窗口,但你仍然能使用AlertDialog.Builder中的方法來添加動作按鈕和標題。例如,圖6-6和代碼清單6-4所示:

 

圖6-6 一個自定義的對話框布局

代碼文件在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>

 

代碼清單6-4

注意:默認情況下,當你設置一個EditText元素為“textPassword”輸入類型時,字體會設置為等寬字體,因此你應該使用字體為“sans-serif”來統一匹配文本字體風格。為了在DialogFragment 中inflate布局,你可以使用getLayoutInflater()獲得一個LayoutInflater並調用inflate(),然后setView()。如代碼清單6-5所示:

@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))
    //設置action響應
           .setPositiveButton(R.string.signin, new DialogInterface.OnClickListener() {
               @Override
               public void onClick(DialogInterface dialog, int id) {
                   // todo...
               }
           })
           .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
               public void onClick(DialogInterface dialog, int id) {
                   LoginDialogFragment.this.getDialog().cancel();
               }
           });      
    return builder.create();
}

 

代碼清單6-5

注意:如果你想要自定義對話框,你其實可以使用Activity來作為對話框。簡單的創建一個activity然后在manifest中的<activity>節點下設置它的主題(theme)為Theme.Holo.Dialog即可。如代碼清單6-6所示:

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

 

代碼清單6-6

就是這樣。這個activity現在作為一個對話框窗口顯示而不是全屏顯示。

6.3 通過事件返回到對話框的Host

當用戶觸摸一個對話框的動作按鈕或選擇一個item時,你的DialogFragment可能執行必要的操作,但常常你想要傳遞這個事件到已經打開的對話框中的上層activity或fragment中。為了做到這些,定義一個點擊事件的接口類型。然后實現接口中的方法。如代碼清單6-7所示:

public class NoticeDialogFragment extends DialogFragment {
    public interface NoticeDialogListener {
        public void onDialogPositiveClick(DialogFragment dialog);
        public void onDialogNegativeClick(DialogFragment dialog);
    }
    
    NoticeDialogListener mListener;
    
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            mListener = (NoticeDialogListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString()
                    + " must implement NoticeDialogListener");
        }
    }
    @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) {
                       // 發送positive按鈕事件回調到host activity
                       mListener.onDialogPositiveClick(NoticeDialogFragment.this);
                   }
               })
               .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
                   public void onClick(DialogInterface dialog, int id) {
                       // 發送negative按鈕事件回調到host activity
                       mListener.onDialogPositiveClick(NoticeDialogFragment.this);
                   }
               });
        return builder.create();
    }
 
}

 

代碼清單6-7

代碼清單6-7只是定義接口,當然我們還需要實現這個接口,下面代碼清單6-8就是如何實現並運用的:

public class MainActivity extends FragmentActivity
                          implements NoticeDialogFragment.NoticeDialogListener{
    ...
    
    public void showNoticeDialog() {
        DialogFragment dialog = new NoticeDialogFragment();
        dialog.show(getSupportFragmentManager(), "NoticeDialogFragment");
    }
 
    @Override
    public void onDialogPositiveClick(DialogFragment dialog) {
    }
 
    @Override
    public void onDialogNegativeClick(DialogFragment dialog) {
    }
}

 

代碼清單6-8

6.4 顯示一個對話框

當你想要顯示你的對話框時,創建一個DialogFragment的實例並調用show()方法,並傳遞一個FragmentManager和一個標簽名字。你可以通過getSupportFragmentManager()來獲得一個FragmentManager。如代碼清單6-9所示: 

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

 

代碼清單6-9

第二個參數,"missiles"是一個唯一的標簽名,因為某些必要情況下系統用來保存和恢復fragment的狀態。當然你可以通過findFragmentByTag()來獲得一個fragment。

6.5 顯示一個全屏對話框或作為一個嵌入式

你可能有一個UI設計,你想要一個區域用來放置這個UI,這里你用到了一個對話框,但有時候你又需要全屏顯示(比如在大屏幕下可能作為標准的對話框,在小屏幕下你可能需要全屏)。DialogFragment類提供了這種靈活性。但是,你不能使用AlertDialog.Builder或其他Dialog對象來處理這種情況。如果你想要DialogFragment是可嵌入的,你必須在布局中定義對話框的UI,然后在onCreateView()回調方法中載入布局。如代碼清單6-10所示(purchase_items.xml):

public class CustomDialogFragment extends 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) {
        Dialog dialog = super.onCreateDialog(savedInstanceState);
        dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
        return dialog;
    }
}

 

代碼清單6-10

下面是一些代碼,根據屏幕大小決定是否顯示Fragment作為一個對話框或全屏UI,如代碼清單6-11所示:

public void showDialog() {
    FragmentManager fragmentManager = getSupportFragmentManager();
    CustomDialogFragment newFragment = new CustomDialogFragment();
    
    if (mIsLargeLayout) {
        // 如果是大型布局,作為對話框顯示即可
        newFragment.show(fragmentManager, "dialog");
    } else {
        // 如果是小設備,作為fragment全屏顯示
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        //指定一個過渡動畫
        transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
        // 使用全屏就把對話框newFragment作為根view
        transaction.add(android.R.id.content, newFragment)
                   .addToBackStack(null).commit();
    }
}

 

代碼清單6-11

在這個例子中,mIsLargeLayout這個boolean值是我們代碼檢測出來的結果,其實更好的方法是在不同的文件夾下指定資源值,如下所示:

res/values/bools.xml

<!—默認boolean值 -->
<resources>
    <bool name="large_layout">false</bool>
</resources>

 

res/values-large/bools.xml

<!—Large屏幕下的boolean值-->
<resources>
    <bool name="large_layout">true</bool>
</resources>

 

然后再代碼中這樣獲取如代碼清單6-12所示:

boolean mIsLargeLayout;
 
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
 
    mIsLargeLayout = getResources().getBoolean(R.bool.large_layout);
}

 

代碼清單6-12

6.5.1 在大屏幕下顯示一個以對話框形式的activity

有個很簡單的方法就是,如代碼清單6-13所示:

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

 

代碼清單6-13

6.6 Dissmiss對話框

當用戶觸摸任何一個由AlertDialog.Builder創建動作按鈕時,系統會dismiss掉對話框。當用戶觸摸一個對話框的itme時系統會dismiss對話框,除單選按鈕或復選框之外。當然你也能手動調用DialogFragment的dismiss()的方法。如果你需要當對話框消失時,執行某些操作,你可以在DialogFragment中實現onDismiss()方法。當然有些時候你想取消一個對話框。如果用戶按下返回按鈕,觸摸對話框外面區域或調用Dialog的cancel()方法都會執行取消操作,或者你可以在DialogFragment中實現onCancel()方法來做一些取消之前的操作。注意:系統會優先調用onDismiss()。如果你主動調用了Dialog.dismiss()或者DialogFragment.dismiss()那么系統會調用onDismiss()而不會調用onCancel()。所以通常情況下我們都使用dismiss()來關閉對話框。

 FAQ QQ群213821767


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM