Android應用開發:Dialog使用123

引言


在Android開發中,我們在很多情況下都會使用到Dialog,即彈出框。如彈出一個登錄框,又如有操作需要用戶二次確認等情況。本片文章就來闡述一下如何在Android開發過程中,正確的使用Dialog。


Dialog的設計哲學


Dialog是一個彈出框,小的窗口,用來提示用戶確認下一步的操作(在確認前這個操作並不執行)或展示額外信息(如下一步的必然操作中確實需要用戶知道的信息)。既然是彈出框,就不應該充滿全屏(一般情況下),並且要儘可能簡短精悍的表達想要展示給用戶的意思,剔除一切冗餘信息。下圖展示了一個標準的Dialog:


1. 標題欄。

用來介紹這個Dialog用來幹什麼,比如形容自己是在請求用戶做什麼,或是應用所請求的是什麼內容。標題的展現形式可以是單一的文字,也可以是圖標+文字。


官方的建議是Dialog如果就是用來警示用戶,那麼完全可以不需要標題,直接顯示內容告訴用戶即將發生重要的事情。而有些對於用戶來說特別重要的事項,比如丟失數據、產生運營商流量、消費動作等則最好是用最直接的話做標題,然後在內容中做清晰表述。打個比方,如果產生了消費動作,標題就可以起“結賬”,內容用來詳細表明結的什麼賬,明細是什麼。最好不要在起“警告”這樣的標題,因爲完全可以用代表警告的圖標來表達。


2. 內容。

Dialog主要展示的就是內容了,可以展示多種類型的內容,比如一段重要的描述性話語,或如圖中展示的一樣是一些單選,同時也可以是多選列表,甚至可以是開發者自定義的任何視圖。最需要注意的就是對內容詳細或簡略的程度進行把控,如果內容過少,那麼可以考慮通過交互方式的修改來使用Toast進行展示,畢竟Dialog會阻擋用戶的操作,內容較少的Dialog可能會讓用戶感覺不疼不癢,甚至認爲這個提示並不重要,從而降低應用本身的用戶體驗或者是誤操作。

順便提一下題外話,前一陣子網上爆出的Android最可怕病毒(自動發送短信,傳播自身)其實就是濫用Android權限的結果。可見,如果在一些重要動作上不對用戶進行必要的提醒、提示,那麼某種角度上講這個應用與“病毒”差不多。


3. 動作按鈕


最多兩個按鈕,一個確定、一個取消,像上圖中內容爲單選列表,那麼就可以理解爲點擊內容即爲“確定”,所以“取消”按鈕就不需要了,甚至當Dialog允許通過返回按鍵或點擊Dialog外的區域進行取消時,“取消”按鈕也不需要。也就是說,動作按鈕這裏,可以是2,可以是1,也可以是無。具體的設計邏輯根據Dialog的具體作用決定,如果只是單單的提示,叫用戶知道某些信息而已,那麼完全可以只放一個“我知道了”按鈕,其實這個按鈕是“取消”的動作。

對於動作按鈕區域並沒有什麼需要格外注意的,唯一能想到的也就是Android在4.0後將“確定”、“取消”的左右位置換爲“確定“在右,”取消“在左。如果一意孤行非要跟主流不一樣的話,我想也只能用”呵呵“進行迴應了。畢竟是有”用戶習慣“這麼個名詞存在的,當大家都已經習慣了一套規則的時候,所謂”悖論創新“可能也只是”頑皮任性“的擋箭牌而已。


Toast

Dialog算是一個重度的彈出框,在描述”內容“的設計哲學時也說過,如果說內容過少,又只是想提示用戶而非叫用戶做選擇、決定時完全可以考慮使用更加輕量級、對用戶體驗影響偏小的Toast進行提示。在用戶操作上,Toast相比Dialog更加友好,因爲Toast在一段時間(Android默認長則3.5秒,短則2秒)後會自動消失,而Dialog必須需要用戶進行干涉纔可以消失。同時,Toast並不會影響用戶在界面上的下一步操作,而Dialog屬於一種打斷行爲,不進行處理就甭想進行下一步操作。信不信由你,如果一個應用能夠做到連續彈出3個以上Dialog,用戶不是摔手機就一定是卸載應用了。


Dialog的初級開發使用


Android官方建議不要直接使用Dialog這個類來構造應用的彈出框,在開發實踐中也很容易發覺確實使用Dialog直接構造彈出框並不方便,基本的視圖框架都需要自己來進行設計規劃,對於一個簡簡單單的彈出框來說,這樣的實現複雜度明顯不符合開發預期。而遵循官方的建議,我們通常使用AlertDialog進行彈出框的構造,同時對於開發過程中可能出現的日期、時間的選擇窗口,Android官方也早有準備,分別是DatePickerDialog和TimePickerDialog。(推薦一個開源項目,界面更加友好美觀https://github.com/flavienlaurent/datetimepicker)


另,不得不在此提及Android所提供的另一個Dialog,即ProgressDialog。這個Dialog類型是Android官方不建議使用的,原話是:避免使用ProgressDialog。從開發角度上講,ProgressDialog的使用極其簡單,並沒有很高的門檻。Android官方這樣建議更多是出於設計和用戶體驗方面的考慮。ProgressDialog的彈出在屏幕上佔用了非常小的空間,其他區域罩以灰色蒙板。並且通常情況下ProgressDialog還需要阻擋用戶下一步操作,直到其所等待的事件完成爲止。即使不考慮可能發生的設備旋轉情況,ProgressDialog的單一、阻擋性強、視圖不友好這幾方面就足以導致用戶的不舒適感。所以,在設計、開發過程中,遇到需要用戶等待的情況,建議以視圖佈局中嵌入ProgressBar代替,這樣的原因有:

一、整個屏幕不會罩以灰色蒙板,相信很多用戶並不喜歡這樣的東西;

二、ProgressBar並不會阻擋用戶在屏幕上的全部操作。即使ProgressBar在顯示中,也不會影響例如NavigationDrawer控件的使用。

三、在開發中,ProgressDialog對於旋轉的處理要難於ProgressBar。因此容易出現疏忽,造成用戶的混淆或誤操作。


下邊直接介紹關於AlertDialog的使用明細。

AlertDialog使用示例:

AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(),
                AlertDialog.THEME_DEVICE_DEFAULT_DARK);
        builder.setTitle("Dialog title")
                .setIcon(android.R.drawable.stat_sys_warning)
                .setMessage("Dialog content")
                .setPositiveButton("OK", null)
                .setNegativeButton("Cancel", null);
        builder.create().show();

效果圖:


使用AlertDialog構造彈出框都是通過Builder構造器進行構造,除了設置三大視覺結構相關設置外,還可以設置是否可以取消(通過返回按鈕)。兩種方法:

第一種是在AlertDialog.Builder構造時調用

setCancelable(boolean cancelable)
第二種通過調用AlertDialog.Builder create生成的Dialog同樣的接口。

還可以設置在用戶點擊彈出框以外的視圖區域時是否讓彈出框消失,通過調用生成的Dialog的接口:

setCanceledOnTouchOutside(boolean cancel)

以上兩個默認都是false。


1.  需要顯示列表

調用AlertDialog.Builder的接口。

setAdapter(ListAdapter adapter, DialogInterface.OnClickListener listener)

adapter參數即爲ListView的Adapter, 用來確定列表的視圖。

listener參數爲列表項的點擊監聽器。


視覺效果如:

這時並不需要”確認“與”取消“按鈕,直接點擊列表項即視爲選擇。


2. 需要顯示單選列表

形如:


實現代碼爲:

AlertDialog.Builder builder = new AlertDialog.Builder(this, AlertDialog.THEME_HOLO_DARK);
        builder.setTitle("Choose ringtone")
                .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        //Handle ok event
                    }
                })
                .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        //Handle cancel event
                    }
                })
                .setSingleChoiceItems(ringtones, 0, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        //Handle item click event
                    }
                });
        builder.create().show();

AlertDialog.Builder的setSingleChoiceItems接口,形式共有四種:

第一種:

setSingleChoiceItems(CharSequence[] items, int checkedItem, DialogInterface.OnClickListener listener)
items參數爲列表中顯示的字符;

checkedItem 因爲列表是單選,一般是有一個默認就選中的項目,這個參數就用來指定第幾個項目是默認被選中的,從0起。若爲-1則代表沒有默認選擇;

listener參數就是列表項點擊監聽器。

這種方式適合選項只是簡單的字符。


第二種:

setSingleChoiceItems(int itemsId, int checkedItem, DialogInterface.OnClickListener listener)
itemsId 參數爲指定xml中事先定義好的String array。其他參數意義同第一種。


第三種:

setSingleChoiceItems(ListAdapter adapter, int checkedItem, DialogInterface.OnClickListener listener)

adapter參數爲列表的Adapter,用來決定每個列表項的視覺效果

checkedItem和listener的意義同第一種方式中一樣。

這種方式適合選項的視圖複雜的需求。


第四種:

setSingleChoiceItems (Cursor cursor, int checkedItem, String labelColumn, DialogInterface.OnClickListener listener)

cursor爲列表中項目數據所在的數據庫Cursor

labelColumn參數爲需要展示的數據在數據庫中的列名是什麼

其他參數與另三種方法無異。


再次提一下在單選項彈出框中動作按鈕是否需要展現的問題。建議同時展現”確定“和”取消“按鈕。

展現”確認“按鈕的原因是在實際使用過程中,用戶極有可能輪流點擊不同的選項,若開發者視爲點擊選項就是確定,那麼這樣用戶需要做的重複動作就比較多。用戶可能有這樣的行爲是因爲:

一、手指的習慣動作;

二、想要通過選擇不同選項直接看到結果,如更換主題(想要看到更換後的直接效果,哪一個符合就選擇確定)。


而展現“取消”按鈕的原因是:

一、因爲彈出框可以對本身是否能夠通過點擊外部區域進行取消做設置,假設用戶使用了多款對各自彈出框進行了不同設置的應用,那麼極有可能已經混淆了究竟是否可以通過點擊外部區域進行彈出框的取消,所以這個時候直接提供一個“取消”按鈕就能夠解決用戶的疑慮,正如Android設計規範中所說,不要讓用戶去想,而是提供給用戶最直接的選項讓他選擇,這樣做纔是較好的用戶體驗。

二、即使手機的返回按鍵可以取消彈出框,大多數情況下用戶手指從彈出框移動到返回鍵的距離還是不小的,而且如果是單手操作,這樣也可能產生手機滑落的情況發生,最糟的用戶體驗也莫過於此了。所以,提供一個“取消”按鈕,大多數情況下,用戶直接點擊“取消”來取消彈出框顯然更方便、更安全。


3. 需要顯示多選列表

形如:


以下代碼實現了上圖中的Dialog:

AlertDialog.Builder builder = new AlertDialog.Builder(this, AlertDialog.THEME_HOLO_LIGHT);
        builder.setTitle("Pick your toppings")
                .setMultiChoiceItems(new String[]{"Onion", "Lettuce", "Tomato"},
                new boolean[]{false, true, true},
                new DialogInterface.OnMultiChoiceClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which, boolean isChecked) {
                        //Handle item click event
                    }
                })
                .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        // Handle ok event
                    }
                })
                .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        // Handle cancel event
                    }
                });
        builder.create().show();

AlertDIalog.Builder的setMultiChoiceItems方法,有三種形式:


第一種:

setMultiChoiceItems(CharSequence[] items, boolean[] checkedItems, DialogInterface.OnMultiChoiceClickListener listener)

items參數與單選中的意義一樣,是所有項目的字符集合;

checkedItems參數爲哪些選項是默認選中的。需要注意的是,如果是null就代表沒有默認選中的,而如果是非null,那麼長度必須要和items一樣。

listener參數爲表內項目點擊監聽器。


第二種:

setMultiChoiceItems(int itemsId, boolean[] checkedItems, DialogInterface.OnMultiChoiceClickListener listener)

itemsId參數將需要顯示的所有項目指定到事先定義好的String array上。其他參數與第一種無異。


第三種:

setMultiChoiceItems(Cursor cursor, String isCheckedColumn, String labelColumn, DialogInterface.OnMultiChoiceClickListener listener)

cursor參數爲需要顯示的項目所在數據庫Cursor

labalColumn爲項目數據所在數據庫中的列名

其他參數與其他方法無異。


到這裏可以發現,包含多選列表的彈出框中,並沒有通過指定Adapter構造的方法,看似是一種缺失。而聯繫實際使用,不難發現,複雜視圖的多選項彈出框鮮有。我本人是想不到究竟有什麼場景需要複雜視圖的、多選項的——彈出框。當然,如果一定有這種需求要實現,可完全可以通過自定義視圖的彈出框實現(稍後做介紹)。


4. 需要自定視圖


自定義視圖,指的是自定義Dialog的“內容”區域視圖,通過API setView(View view)實現。實現過程也非常簡單,可以實現設計好一套佈局,然後通過LayoutInflater產出一個View,將這個View設置進去。如果在這個View(“內容”自定義視圖)中,需要做必要的監聽,同樣通過findViewById方法得到需要監聽的對象做相應處理即可。


PS:不對AlertDialog.Builder設置Title,最終的Dialog就不會有Title視圖。


Dialog開發進階


通過以上的介紹,相信在對Dialog的使用上起碼視圖的需求都應該沒有問題了。接下來還有一些實際開發過程中的問題需要處理。如:設備發生旋轉,上面所做的這些Dialog會發生什麼?答案是他們會消失。爲什麼消失?以開發過程中最熟悉的Activity爲例,若設備發生旋轉,Activity會先銷燬onDestroy,再恢復onCreate,同時相關必要數據的恢復可以通過Bundle進行。而上邊所做的這些Dialog並不能處理自己的生命週期,一旦設備發生旋轉,會立即由系統dismiss掉。通過引入DialogFragment,使其作爲AlertDialog的載體可以完美解決這個問題。


DialogFragment繼承於Fragment,專門用來做Dialog的承載體,類似設備旋轉這樣的事件DialogFragment自然就有了能力去處理,其中的Dialog也不至於被系統直接dismiss掉。


實現一個DialogFragment非常簡單,只要實現onCreateDialog方法即可,其原型爲:

public Dialog onCreateDialog(Bundle savedInstanceState) {
        return new Dialog(getActivity(), getTheme());
    }

通過直接返回一個Dialog對象即可實現,所以上邊實現的第一個AlertDialog就可以轉換爲:

public Dialog onCreateDialog(Bundle savedInstanceState) {
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(),
                AlertDialog.THEME_DEVICE_DEFAULT_DARK);
        builder.setTitle("Dialog title")
                .setIcon(android.R.drawable.stat_sys_warning)
                .setMessage("Dialog content")
                .setPositiveButton("OK", null)
                .setNegativeButton("Cancel", null);
        return builder.create()
    }
光是這樣Dialog還沒有顯示出來,像普通的Dialog一樣,需要一個show方法的觸發,不過通過DialogFragment實現的Dialog的show方法有些不同:

public void show(FragmentManager manager, String tag)
public int show(FragmentTransaction transaction, String tag)

不要忘了DialogFragment是繼承於Fragment的,要顯示他必然需要用到FragmentManager或FragmentTransaction。這裏提醒一下,如果是Fragment中調用DialogFragment,在show的時候需要注意使用的是getChildFragmentManager,這樣能保證正確的Fragment層級關係。

通過上邊對DialogFragment的引入,達到了旋轉設備Dialog不消失的效果。但是如果Dialog內容中有狀態變動,旋轉後會被複位,其實這個問題就是要解決Fragment的旋轉屏幕問題。通過onSaveInstanceState與onCreateDialog的配合很好做到這一點。這裏不再贅述。

最後,強烈提示!!!實現自己的DialogFragment一定要保留默認構造函數,即空的、public的構造函數,否則會產生程序異常。


總結


本篇文章完整、詳盡的從設計到開發介紹了Android系統中關於Dialog的使用詳情。本質上,Dialog的使用非常簡單,更多的則是關於應用、產品質量與用戶體驗的追求。無論是移動互聯網狂潮也好,還是傳統企業也好,質量、用戶體驗都是需要與時俱進的。·

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