android之Dialog自訂引發的血案
我仍然從實際工作中出發!最近需要在照相機裡面添加聲控拍照功能(語音拍照),在設定當中需要實現如的效果:
其設定的"語音拍照"菜單功能描述如下:
(1)當點擊""語音拍照"菜單時候就會彈出如所示的Dialog, 點擊Dialog裡面的"拍照"/"茄子"就會自動播放聲音.
(2)Dialog出現時候,只要點擊Dialog以外的地區,Dialog就會自動消失.
(3)當點擊"語音拍照"菜單最右邊的綠色switch按鈕時候,就會開啟/關閉語音拍照功能.
上面的實現其實是一個PreferenceActivity, 所以我們新加的"語音拍照"菜單就是在這個PreferenceActivity(CameraSettingActivity)裡面添加.
首先,我們要解決的是如何建立一個自己定義的Dialog: 在Activity裡面有一個方法public Dialog onCreateDialog(int dialogId),用他就可以建立屬於自己的Dialog,然後調用Activity的public final void showDialog(int id)就可以顯示我們建立的Dialog. 這裡Activity是根據不同的dialogId來建立和顯示不同的Dialog,而 dialogId就是你自己定義的!在我們的CameraSettingActivity如下定義
private static final int DIALOG_ID_VOICE_COMMAND_SHOW_TONES = 111;
然後Override父activity的onCreateDialog方法來定義自己的Dialog,如下代碼:
@Override public Dialog onCreateDialog(int dialogId) { if(dialogId == DIALOG_ID_VOICE_COMMAND_SHOW_TONES){ Dialog dialog = new Dialog(this, R.style.transparent_dialog_them); dialog.setContentView(R.layout.setting_switch_sublist_layout);VoiceManager voice_manager = ((CameraApp)CameraSettingActivity.this.getApplication()).getVoiceManager();SettingSwitchSublistLayout mVoiceSettingLayout =(SettingSwitchSublistLayout) dialog.findViewById(R.id.SettingSwitchSublistLayout_ID);mVoiceSettingLayout.initialize(voice_manager.getVoiceEntryValues());mVoiceSettingLayout.setSettingChangedListener(this);//dialog.getWindow().setCloseOnTouchOutside(true);View content =(View) dialog.getWindow().getDecorView().findViewById(com.android.internal.R.id.content);content.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) {dismissDialog(DIALOG_ID_VOICE_COMMAND_SHOW_TONES); } });return dialog;}return super.onCreateDialog(dialogId); }這裡一步步解釋一下上面的代碼:
(1)
Dialog dialog = new Dialog(this, R.style.transparent_dialog_them);
第二這個參數:R.style.transparent_dialog_them是一個主題設定的參數, 這裡需要設定背景透明等,如下代碼所示:
這裡我把windowBackground設定為透明,並且還設定為NoActionBar和NoTitle的模式! 當然這裡windowBackground實際上可以設定成辦透明的背景!從這裡你是否看出什麼奇怪的呢? 為什麼建立一個Dialog又和window/action等有什麼關係呢? 如果你看了dialog實現的class實作類別,你就會發現,其實建立一個dialog就等於建立一個window,而我們知道一個window就有action,title等屬性! 通過學習activity我們也知道建立一個activity其實也就建立了一個window, 實際上一個介面的顯示都是起源於一個window的! 一個window除了管理介面的顯示,其實所有裝置輸入事件都是從這裡出發的!
(2)
dialog.setContentView(R.layout.setting_switch_sublist_layout);
這一行代碼其實就是我們這個顯示的dialog布局的配置! 裡面的詳細就很簡單,不以多說!
(3)
View content =(View) dialog.getWindow().getDecorView().findViewById(com.android.internal.R.id.content);content.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) {dismissDialog(DIALOG_ID_VOICE_COMMAND_SHOW_TONES); } });這幾行代碼, 就可以實現,當使用者點擊dialog 以外的地區時候,dialog自動消失!
上面是關於dialog的建立! 你覺得有問題嗎?
然後,我們來看看如何?點擊:當點擊""語音拍照"菜單時候就會彈出如所示的Dialog,而當點擊"語音拍照"菜單最右邊的綠色switch按鈕時候,就會開啟/關閉語音拍照功能.新看看如下代碼:
SwitchPreference voicePref =(SwitchPreference) findPreference(getString(R.string.camera_setting_item_pref_voice_key));
voicePref.setLayoutResource(R.layout.xunhu_voice_preference);//xunhu_ali_preferenceString key = getString(R.string.camera_setting_item_pref_voice_key);boolean value = getPreferenceManager().getSharedPreferences().getBoolean(key, false);//Log.d("pre_carmera", "CameraSettingActivity : initDefaultSavePath mValue="+value);VoiceManager voice_manager = ((CameraApp)this.getApplication()).getVoiceManager();String mValue = voice_manager.getVoiceValue();從上面代碼可以清楚知道,實際上我採用的就是平常我們使用的SwitchPreference,只是自己去定義了他的布局吧了!SwitchPreference的方法setLayoutResource就可以配置自己的布局! 所以解決這個問題的重點就在這個布局的使用上面!
其實這個布局跟預設的 SwitchPreference的布局沒有什麼區別!只是在這個布局的父view上面加了一個屬性android:onClick="VoiceCommandClickListener" 如下代碼所示:
最後看看VoiceCommandClickListener的定義:public void VoiceCommandClickListener(View v) { showDialog(DIALOG_ID_VOICE_COMMAND_SHOW_TONES);}其實就是調用showDialog來顯示我自己的Dialog.
到了這裡後,會很快發現有兩個疑問:(1)我們實現的當點擊dialog 以外的地區時候,dialog自動消失,是否有簡單的辦法!(2)可以用DialogFragment來替換我們這裡的Dialog嗎?
先解決地一個疑問:
通過查看dialog.java可以發現,其實一個dialog的建立其實就會為這個dialog建立window,通過工具hierarchyviewer來查看時候,你會發現這個dialog其實佔領了整個螢幕,而不是僅僅只是只有顯示地區的那麼多! 為什麼這樣呢! 這是你會很快發現,當要是一個activity成dialog模式的時候,你就需要給這個activity的主題配置為dialog模式:Theme.Dialog. 剛才說了,一個Dialog和activity的顯示都是起源與一個window,那麼Dialog建立時候是否制定了主題為Theme.Dialog就可以解決問題了呢! 實際結果的確和我推斷一樣!
把上面的代碼修改如下:
@Override public Dialog onCreateDialog(int dialogId) { if(dialogId == DIALOG_ID_VOICE_COMMAND_SHOW_TONES){ Dialog dialog = new Dialog(this, R.style.transparent_dialog_them); dialog.setContentView(R.layout.setting_switch_sublist_layout);VoiceManager voice_manager = ((CameraApp)CameraSettingActivity.this.getApplication()).getVoiceManager();SettingSwitchSublistLayout mVoiceSettingLayout =(SettingSwitchSublistLayout) dialog.findViewById(R.id.SettingSwitchSublistLayout_ID);mVoiceSettingLayout.initialize(voice_manager.getVoiceEntryValues());mVoiceSettingLayout.setSettingChangedListener(this);dialog.getWindow().setCloseOnTouchOutside(true);/*View content =(View) dialog.getWindow().getDecorView().findViewById(com.android.internal.R.id.content);content.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) {dismissDialog(DIALOG_ID_VOICE_COMMAND_SHOW_TONES); } }); */dialog.setCanceledOnTouchOutside(true);return dialog;}return super.onCreateDialog(dialogId); }上面的代碼我注釋了content.setOnClickListener這段的內容,新加了下面這行代碼:dialog.getWindow().setCloseOnTouchOutside(true);
你會發現,當點擊dialog以外的地區的時候,dialog根本無法消失!難道是上面這行代碼沒有作用! 其實不然, 剛才我們說了,這個dialog其實是佔據了整個螢幕的!只是我們把其背景設定為全透明的了! 所以此時你根本無法點擊到dialog地區 以外的地區! 要證明上面這行代碼是可用的! 你只需要修改一下主題的設定!把上面的R.style.transparent_dialog_them配置為:Theme.Dialog:改為如下:
原來transparent_dialog_them的父是:Theme.HWDroid.Ali.NoActionBar,現在改成:Theme.HWDroid.Ali.Dialog.NoActionBar. 這樣就解決來疑問.
來看看下一個疑問:
先開啟Activity.java來看看與Dialog有關的源碼, 其中有關於Dialog的說明如下: * @deprecated Use the new {@link DialogFragment} class with * {@link FragmentManager} instead; this is also * available on older platforms through the Android compatibility package.上面說的很明白, 現在已經不推薦使用Dialog, 而是推薦大家使用DialogFragment. 看一看DialogFragment源碼就知道實際上DialogFragment就是一個Fragment, 也就是說建議搭建用Fragment來解決這個問題!所以大家會Fragment的,也就會了DialogFragment,其實DialogFragment就是封裝了Dialog的Fragment. 下面直接上代碼,看看採用DialogFragment如何?:
public void VoiceCommandClickListener(View v) { //showDialog(DIALOG_ID_VOICE_COMMAND_SHOW_TONES); FragmentTransaction ft = getFragmentManager().beginTransaction(); VoiceCommandDialogFragment prev =(VoiceCommandDialogFragment) getFragmentManager().findFragmentByTag("voice_dialog"); if (prev != null) { ft.show(prev); }else{prev = VoiceCommandDialogFragment.newInstance();prev.show(ft, "voice_dialog"); }}上面代碼時顯示DialogFragment時候調用.下面來看看自訂的DialogFragment:VoiceCommandDialogFragment private static final class VoiceCommandDialogFragment extends DialogFragment {//private static VoiceCommandDialogFragment f;//private static final Object mLock = new Object();private static class SingletonHolder {private static VoiceCommandDialogFragment f = new VoiceCommandDialogFragment();}/* static VoiceCommandDialogFragment newInstance() { if(null == f){synchronized (mLock){if(null == f){f = new VoiceCommandDialogFragment();}} } return f; } */ public static VoiceCommandDialogFragment newInstance() { /* if(null == f){f = new VoiceCommandDialogFragment(); } return f; */ return SingletonHolder.f; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Pick a style based on the num. int style = DialogFragment.STYLE_NO_TITLE, theme = R.style.transparent_dialog_them; setStyle(style, theme); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View v = inflater.inflate(R.layout.setting_switch_sublist_layout, container, false);VoiceManager voice_manager = ((CameraApp)getActivity().getApplication()).getVoiceManager();SettingSwitchSublistLayout mVoiceSettingLayout =(SettingSwitchSublistLayout) v.findViewById(R.id.SettingSwitchSublistLayout_ID);mVoiceSettingLayout.initialize(voice_manager.getVoiceEntryValues());mVoiceSettingLayout.setSettingChangedListener((CameraSettingActivity)getActivity()); return v; } }上面的代碼需要注意以下三點:(1)需要重複建立DialogFragment問題, 這裡使用單例模式.這裡我採用內部類來解決;
(2)顯示的時候,需要檢測當前FragmentManager裡面是否有存在的我需要顯示的DialogFragment;
(3)跟上面一樣,器主題設定一定要設定為dialog_them;
這就可以了.