Android添加拍照功能,Android相机开发(二): 给相机加上偏好设置

Android Camera Develop: add settings to camera app

概述

继上一篇实现了一个最简单的相机APP后,本篇主要介绍实现相机的各种偏好设置,比如分辨率、闪光灯、对焦等。添加这些设置看起来很容易,但实际实现时还是有很多需要注意的。

添加设置菜单

我们使用Android推荐的PreferenceFragment作为相机偏好设置的菜单。PreferenceFragment继承自Fragment,而Fragment从简单来说可以理解成Activity里的Activity,但可以让我们不再非常关心其UI布局,因为Android已经帮我们搞定了;PreferenceFragment则可以理解为专门为设置量身定做的Fragment,我们只用向其添加设置的内容,而不用关心具体设置参数用户交互设计等繁琐的内容。

添加SettingsFragment类

就像上篇介绍的CameraPreview继承自SurfaceView实现了一个自定义的View,现在我们需要创建SettingsFragment继承自PreferenceFragment实现一个自定义的Fragment。

新建SettingsFragment类

451cb47824d2a4afa0f8c0918a7d6d0a.png

内容如下:

addPreferencesFromResource(R.xml.preferences);是加载来自preferences.xml文件中的菜单条目,对于PreferenceFragment,其菜单条目都是存储在xml文件中,实例化时从xml文件加载的,别着急,我们马上创建preferences.xml文件。

添加菜单条目

对于PreferenceFragment来说,需要从xml文件加载菜单条目等内容的。在res下新建xml文件夹,在其内新建preferences.xml文件

3041e9efa9382aaa98a586ec52ba9bbc.png

内容如下:

菜单的每一个条目都是一个Preference。这里的ListPreference继承自Preference,就是条目选项是列表的菜单条目。其名字是照片品质,对应的key(条目以key-value对形式存储)是jpeg_quality;其对用户可见的列表选项由entries定义,而对代码而言是entryValues,提供可供选择的value,其内容相同,都是一个叫做pref_jpegQuality的array,这个稍后解释;最后为这个条目设定一个默认值即defaultValue,设为100。

添加array值

在res->values下新建arrays.xml文件

4e20b88a968aa0efc2b43ac589846141.png

内容如下:

这样就创建了一个名字为pref_jpegQuality的array,供上一步的jpeg_quality使用,现在回去看preferences.xml已经没有红色错误提示啦。

主窗口中加入按钮,调用菜单

刚才创建了一个非常简单的设置菜单,接下来让这个菜单通过点击主窗口中的“设置”按钮就能显示出来。

修改activity_main.xml

修改activity_main.xml,在FrameLayout后加入Button,修改后内容如下:

修改MainActivity

修改MainActivity类,在onCreate()方法最后添加

即为设置按钮绑定点击监听,当点击设置按钮时,就显示设置菜单。

是调用Fragment的比较通用的写法,关于具体细节还请自行查找。

修改后的MainActivity内容如下:

运行一下看看

现在就可以运行你的APP啦,现在APP的最右边会有“设置”按钮,点击此按钮后就会在相机预览上显示“照片品质”这个菜单条目,而点击这个条目,就会弹出从50到100共6个选项,其中100已经默认选中。效果如下:

2f20559f2c535b6bf422d98a9259c938.png

但是现在这个设置菜单很简单,而且没有实际功能,我们接下来进行完善。

动态添加设置条目

像相机支持的分辨率,支持的对焦方式等都因设备而异,我们不大可能在preferences.xml就写死相机支持的分辨率的值;而为了能够让我们的APP能够运行在不同的设备上(如果只需要运行在特定的设备上,会容易许多),我们尝试动态加载设置条目的列表选项。

完善菜单条目

菜单条目名称我们是能够直接写在文件中的,因为不同相机基本都支持这些设置,只是value不同而已。

修改preferences.xml,修改后内容如下:

上面的这些选项基本覆盖到了所有常用的选项(在你读完这篇文章后你还可以自己添加想要的选项)。注意到除了“照片品质”外的所有ListPreference都没有entries和entryValues,这些值就是我们需要在代码中动态添加的;上面的“地理位置”是一个SwitchPreference,就是开关那种样式的,只有两个值。注意到有些条目有defaultValue默认值,是因为这些默认值对于不同相机都是通用的。

这样修改后,由于条目内容不完整,APP暂时就不能运行了

动态添加

获取相机

添加条目的列表选项当然是在SettingsFragment中了。想要知道相机支持的参数,首先需要获得一个打开的相机,这个先不追究,在SettingsFragment中添加

passCamera()用来将相机传输给SettingsFragment,SettingsFragment将相机保存到静态成员变量mCamera中,getParameters()则用来获取相机参数,将相机参数保存到静态成员变量mParameters中。

动态加载预览分辨率

首先以动态加载预览分辨率为例,介绍动态加载过程。预览分辨率即相机预览时屏幕显示的分辨率大小。在SettingsFragment中添加

静态成员变量KEY_PREF_PREV_SIZE存储预览分辨率的key(在preferences.xml定义)。

先看stringListToListPreference(),其首先将List转换为CharSequence[];由getPreferenceScreen().findPreference()获取由key指定的菜单条目;由setEntries()和setEntryValues向这个菜单条目指定用户可见的所有value和代码可见的所有value,就完成了对这个key条目的列表内容的动态加载。

mParameters.getSupportedPreviewSizes()获取相机支持的所有预览分辨率,保存在List中,再由cameraSizeListToListPreference()将List转换为List。

整个过程逻辑挺清晰的,理解起来应该没太大问题。

动态加载曝光补偿

再举个例子,在SettingsFragment中添加

由mParameters.getMinExposureCompensation()获取相机支持的最低曝光补偿,mParameters.getMaxExposureCompensation()获取相机支持的最高曝光补偿,由最低到最高形成一个List,指定key后交给stringListToListPreference()就好了。

全部的动态加载项

下面贴上全部的load,嫌乱可以到DEMO中去看完整代码,注意这里的load一个都不能少。

设置菜单创建时即加载

在onCreate()触发时就调用这些load进行动态加载,在onCreate()尾部添加

向SettingsFragment传入相机

所有上面的代码要执行必须首先获取到一个打开的相机,而CameraPreview正好有一个打开的相机,我们就可以通过MainActivity进行相机的传递。

首先需要修改一下CameraPreview的getCameraInstance()方法,使其返回正在使用的相机,修改getCameraInstance()为

然后在MainActivity中按钮监听之前添加

向SettingsFragment传递来自mPreview的相机。

声明GPS权限

注意到菜单中有个GPS的选项,想要拍到的照片中包含GPS信息,就要在AndroidManifest.xml中声明需要GPS权限。在AndroidManifest.xml中添加

运行一下看看

现在就可以运行你的APP啦,点击“设置”按钮,就会出现很多的设置选项,而点击这些选项就是显示出动态加载得到的列表项目。效果如下:

63c741476cab30ecfabe8e5001039066.png

为菜单条目设定默认值

现在我们来谈谈偏好设置,即Preference的问题。通常来说,在Android中,每个APP的每个设置条目都是一个Preference,这些Preference以键值对(key-value)的形式,存储在每个APP的指定文件中。当APP首次运行时,则只有key没有value;不过可以通过Android提供的方法,加载xml文件中的默认值(defaultValue)到对应的value,Android推荐给每个key都指定默认值,否则默认值为空。记住这些key-value都是写在文件中的,一旦value发生变化,对应文件中value值也发生变化,而且以后启动APP这些值都不会被重置(不过可以通过代码重置)。所以通常的做法就是在APP首次启动时给所有的key都生成默认值即value,然后加载默认值实现不同的偏好;在以后APP启动时,就会读取这些key-value实现不同偏好,即初始化。

给xml中有默认值的添加默认值

虽然在xml文件中指定了默认值,但还是要用代码让其加载。在MainActivity的onCreate()适当位置添加

其中false代表在执行这个方法时,如果key已经有value则不进行任何操作(即不覆盖),否则设置value为xml文件中的指定值。因为APP每次运行都会执行onCreate(),设为false很有必要。

动态添加默认值

仔细点看你会发现,如果没有进行手动设置,设置菜单的“相机预览分辨率”、“照片分辨率”、“对焦模式”是没有默认值的,现在我们就为其添加默认值。因为从“正规”来说,Android只提供了从xml文件添加默认值的方法(就像其他有默认值的条目一样),想要用代码实现默认值就得花点功夫了。

修改SettingsFragment

Update 20160504: 本块内容分割线以下为旧方法,不再采用

我们把目光放到SettingsFragment上,可以创建一个方法setDefault(),就像上面添加静态默认值那样,通过调用setDefault(),来添加动态默认值。但setDefault()只在最初“相机预览分辨率”等没有默认值时才为其指定默认值,否则不进行任何操作。所以setDefault()首先找到“相机预览分辨率”的value值,如果值为空则指定默认值,否则返回。我们还应该注意到,相机本身是有默认值的,只是我们的偏好设置中还没有设置这个默认值,因此我们可以获取到相机的默认值,然后构造成为偏好设置中的格式,将这个值指定为value就可以了。

这样setDefault()代码就出来了

SharedPreferences由MainActivity提供,是操作Preference的接口,既可以读取也可以写入。SharedPreferences.Editor就是编辑Preference,其putString()将key-value对写入到APP中,注意最后需要apply()保存这些更改(也可以用commit(),但效率会低一些)。具体怎么找到value代码很简单,就是通过mParameters获取此时相机预览的参数,然后转换为特定形式的String,就作为value返回了。

这样就完成动态添加默认值,可以发现我们只需要用到SharedPreferences和mParameters,所以只需要在MainActivity中传递了相机之后调用就好了;因为方法是静态方法,调用时甚至都不需要实例化。

我们把目光放到SettingsFragment上,可以创建一个方法setDefault(),在onCreate()中调用setDefault()。但setDefault()只在最初“相机预览分辨率”等没有默认值时才为其指定默认值,否则不进行任何操作。所以setDefault()首先找到“相机预览分辨率”的value值,如果值为空则指定默认值,否则返回;而指定默认值可以直接从其动态加载的value列表中选择第一个就好了。

这样setDefault()代码就出来了

getPreferenceScreen().findPreference()获取菜单条目,setValueIndex(0)则将其value设置为value列表中的第一个。还记得之前APP相机预览一片模糊吗?现在就解决这个问题啦!首先查找对焦方式中是否存在continuous-picture,若存在则设置,否则设置为continuous-video(设备一般都支持这两种对焦模式)。这两种对焦模式都会在镜头移动时重新对焦,就像其他的相机APP那样。

注意setDefault()需要在条目已经动态生成后运行,所以在onCreate()中应当在所有load之后调用这个方法

修改MainActivity

Update 20160504: 本块内容分割线以下为旧方法,不再采用

上面的setDefault()每次调用时都会判断“相机预览分辨率”有没有默认值,因此在MainActivity中就可以放心大胆调用了,我们可以把这个方法的调用和onCreate中的setDefaultValues()放在一起;虽然onCreate()每次都会调用这个方法,但只有在APP第一次运行时才会真正进行动态添加默认值。

onCreate()中涉及到SettingsFragment的代码就是

很简单不必过多解释。

这里要注意一个问题,动态添加默认值会在SettingsFragment的onCreate()方法执行时执行,而SettingsFragment的onCreate()会在主窗口点击了“设置”后才会触发。那么如果APP从来没有点击“设置”就永远不会动态加载默认值了?APP每次运行都会读取设置菜单条目并进行偏好设置,所以在APP第一次运行时必须在不点击“设置”时就触发SettingsFragment的onCreate()。

我想到的一个很“笨”的方法是,在MainActivity的onCreate()中,也对“相机预览分辨率”是否有value进行判断,如果没有value则强行调用SettingsFragment,触发其onCreate();如果有value则不进行任何操作。这样,在MainActivity的onCreate()中,passCamera()后加入如下代码:

即可像静态加载默认值那样,动态添加默认值了。效果如下:

f9581c059143a611424f9f352e91805b.png

需要注意的地方

Update 20160504: 本块内容分割线以下为旧内容

之前的方法在APP第一次运行时会出现设置菜单需要手动退出,虽然对用户体验影响不太大,但终究是不符合常理。更新后的代码不再存在这个问题,且逻辑更为清晰。

这样实现动态添加默认值后,造成的负面影响就是APP在首次运行时,会自动出现设置菜单,需要手动退出。但这种情况只会在APP第一次运行时产生,以后都不会再出现,所以还是可以忍受的。其实有更复杂一些的方法解决这个问题,但为了这个介绍的简介,就不考虑那些方法了。

APP启动时加载偏好

我们自然希望相机APP每次启动时都能自动加载好之前偏好设置,思路也很简单,在SettingsFragment中创建init()方法,负责设置相机;在MainActivity的onCreate()中,在设置完静态和动态默认值后调用init()方法,完成相机设置。

修改SettingsFragment

在SettingsFragment中添加

SharedPreferences即APP存储的key-value对;getString()即获取指定key的value值,对于ListPreference是getString(),对于SwitchPreference是getBoolean()。这些set就是分别修改相机参数mParameters的不同属性,很简单不详细解释了。最后,首先停止相机预览,然后应用修改后的mParameters到相机,最后再开始相机预览。这样就完成了相机的偏好设置。

修改MainActivity

在MainActivity的onCreate()设置完静态和动态默认值后添加

其中PreferenceManager.getDefaultSharedPreferences(this)就是得到此APP的SharedPreferences。

运行一下看看

打开APP,修改偏好设置后关闭APP(需要退出后台,下一篇文章会介绍一个不需要这么麻烦的方法),再次打开APP就能看到效果了。

条目value变化时立即应用设置

我们肯定不希望修改设置后,需要重启APP才能看到效果。现在来实现修改设置后,相机立即应用新的设置。

我们只需要监听条目value变化就好了,一旦出现变化,就根据其key立即应用新的value到相机。

给SettingsFragment添加监听接口

修改

OnSharedPreferenceChangeListener为监听Preference变化的接口

给SettingsFragment添加监听事件

在SettingsFragment中添加

onSharedPreferenceChanged()为监听事件回调,干的事情就像之前说的,代码也很简单。onResume()和onPause()是Android推荐写法,防止由于Fragment的不断调用导致事件监听失效。

运行一下看看

现在在APP中修改设置马上就能看到效果了!

设置菜单中显示ListPreference的当前值

目前功能已经全部实现了,现在做一点美化。设置菜单中的ListPreference只显示了其条目的标题,而当前用户可见的value只有在点击条目后才会显示,现在就来实现让当前值直接显示在条目上。

添加方法

让ListPreference显示当前值,就是为其summary赋值,即将当前值赋给其summary。

在SettingsFragment中添加

initSummary()处理全部的Preference,主要是处理含有PreferenceGroup的情况,这个APP目前没有这个情况,但还是保留这个功能。updatePrefSummary()则处理具体的ListPreference,将其用户可见的值getEntry()通过setSummary()赋给summary。

调用方法

有两个调用上述方法的地方。

首先是SettingsFragment的onCreate()中,给所有的Preference都设置summary。在onCreate最后添加

其次是在onSharedPreferenceChanged()中,每次条目value发生变化时,summary也随机变化。在onSharedPreferenceChanged()最头上添加

运行一下看看

现在设置菜单的ListPreference都显示其当前值了。效果如下:

449f8dec0ed0bc7801fb0e7bb6192eec.png

一点唠叨

现在看来实现偏好设置还是有些麻烦的,其实主要难点在动态加载和默认值上,为了能够自适应不同设备就是要这么折腾。像系统自带的相机可能就可以根据设备本身直接将分辨率等都写到xml文件中,这样工作量小了不少。本篇还是没有实现基本的拍照功能,不过已经是一步之遥了呢。

DEMO

参考

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值