android之Dialog自定义引发的血案

来源:互联网 发布:app数据采集工具 编辑:程序博客网 时间:2024/04/30 09:57

      我仍然从实际工作中出发!最近需要在照相机里面添加声控拍照功能(语音拍照),在设置当中需要实现如下图的效果:

其设置的"语音拍照"菜单功能描述如下:

(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_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" 如下代码所示:

<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;

这就可以了.

0 0