首页 > 代码库 > 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: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;

这就可以了.

android之Dialog自定义引发的血案