首页 > 代码库 > android之Dialog自定义引发的血案
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是一个主题设置的参数, 这里需要设置背景透明等,如下代码所示:
<style name="transparent_dialog_them" parent="@style/Theme.HWDroid.Ali.NoActionBar"> <item name="android:windowBackground">@android:color/transparent</item> <item name="android:colorBackgroundCacheHint">@null</item> <item name="android:windowIsTranslucent">true</item> <!-- Note that we use the base animation style here (that is no animations) because we really have no idea how this kind of activity will be used. --> <item name="android:windowAnimationStyle">@android:style/Animation</item> </style>这里我把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_preference String key = getString(R.string.camera_setting_item_pref_voice_key); boolean value = http://www.mamicode.com/getPreferenceManager().getSharedPreferences().getBoolean(key, false);>从上面代码可以清楚知道,实际上我采用的就是平常我们使用的SwitchPreference,只是自己去定义了他的布局吧了!SwitchPreference的方法setLayoutResource就可以配置自己的布局! 所以解决这个问题的重点就在这个布局的使用上面!其实这个布局跟默认的 SwitchPreference的布局没有什么区别!只是在这个布局的父view上面加了一个属性android:onClick="VoiceCommandClickListener" 如下代码所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="?android:attr/listPreferredItemHeight" android:gravity="center_vertical" android:paddingEnd="?android:attr/scrollbarSize" 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:改为如下:<style name="transparent_dialog_them" parent="@style/Theme.HWDroid.Ali.Dialog.NoActionBar"> <item name="android:windowBackground">@android:color/transparent</item> <item name="android:colorBackgroundCacheHint">@null</item> <item name="android:windowIsTranslucent">true</item> <!-- Note that we use the base animation style here (that is no animations) because we really have no idea how this kind of activity will be used. --> <item name="android:windowAnimationStyle">@android:style/Animation</item> </style>原来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:VoiceCommandDialogFragmentprivate 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;
这就可以了.
android之Dialog自定义引发的血案