轉(zhuǎn)載自:Penguin
Android Camera Develop: add settings to camera app
概述
繼上一篇實(shí)現(xiàn)了一個(gè)最簡單的相機(jī)APP后通贞,本篇主要介紹實(shí)現(xiàn)相機(jī)的各種偏好設(shè)置秉沼,比如分辨率钟哥、閃光燈、對焦等绢慢。添加這些設(shè)置看起來很容易,但實(shí)際實(shí)現(xiàn)時(shí)還是有很多需要注意的洛波。
添加設(shè)置菜單
我們使用Android推薦的PreferenceFragment
作為相機(jī)偏好設(shè)置的菜單胰舆。PreferenceFragment
繼承自Fragment
,而Fragment
從簡單來說可以理解成Activity
里的Activity
蹬挤,但可以讓我們不再非常關(guān)心其UI布局缚窿,因?yàn)锳ndroid已經(jīng)幫我們搞定了;PreferenceFragment
則可以理解為專門為設(shè)置量身定做的Fragment
焰扳,我們只用向其添加設(shè)置的內(nèi)容倦零,而不用關(guān)心具體設(shè)置參數(shù)用戶交互設(shè)計(jì)等繁瑣的內(nèi)容。
添加SettingsFragment類
就像上篇介紹的CameraPreview
繼承自SurfaceView
實(shí)現(xiàn)了一個(gè)自定義的View
吨悍,現(xiàn)在我們需要?jiǎng)?chuàng)建SettingsFragment
繼承自PreferenceFragment
實(shí)現(xiàn)一個(gè)自定義的Fragment
光绕。
新建SettingsFragment
類
內(nèi)容如下:
Java
import android.os.Bundle;
import android.preference.PreferenceFragment;
public class SettingsFragment extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
}
}
addPreferencesFromResource(R.xml.preferences);
是加載來自preferences.xml
文件中的菜單條目,對于PreferenceFragment
畜份,其菜單條目都是存儲(chǔ)在xml文件中诞帐,實(shí)例化時(shí)從xml文件加載的,別著急爆雹,我們馬上創(chuàng)建preferences.xml
文件停蕉。
添加菜單條目
對于PreferenceFragment
來說愕鼓,需要從xml文件加載菜單條目等內(nèi)容的。在res
下新建xml
文件夾慧起,在其內(nèi)新建preferences.xml
文件
內(nèi)容如下:
XML
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<ListPreference
android:defaultValue="100"
android:entries="@array/pref_jpegQuality"
android:entryValues="@array/pref_jpegQuality"
android:key="jpeg_quality"
android:title="照片品質(zhì)" />
</PreferenceScreen>
菜單的每一個(gè)條目都是一個(gè)Preference
菇晃。這里的ListPreference
繼承自Preference
,就是條目選項(xiàng)是列表的菜單條目蚓挤。其名字是照片品質(zhì)
,對應(yīng)的key
(條目以key-value對形式存儲(chǔ))是jpeg_quality
磺送;其對用戶可見的列表選項(xiàng)由entries
定義,而對代碼而言是entryValues
灿意,提供可供選擇的value估灿,其內(nèi)容相同,都是一個(gè)叫做pref_jpegQuality
的array
缤剧,這個(gè)稍后解釋馅袁;最后為這個(gè)條目設(shè)定一個(gè)默認(rèn)值即defaultValue
,設(shè)為100荒辕。
添加array值
在res
->values
下新建arrays.xml
文件
內(nèi)容如下:
XML
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="pref_jpegQuality">
<item>100</item>
<item>90</item>
<item>80</item>
<item>70</item>
<item>60</item>
<item>50</item>
</string-array>
</resources>
這樣就創(chuàng)建了一個(gè)名字為pref_jpegQuality
的array
汗销,供上一步的jpeg_quality
使用,現(xiàn)在回去看preferences.xml
已經(jīng)沒有紅色錯(cuò)誤提示啦抵窒。
主窗口中加入按鈕弛针,調(diào)用菜單
剛才創(chuàng)建了一個(gè)非常簡單的設(shè)置菜單,接下來讓這個(gè)菜單通過點(diǎn)擊主窗口中的“設(shè)置”按鈕就能顯示出來李皇。
修改activity_main.xml
修改activity_main.xml
削茁,在FrameLayout
后加入Button
,修改后內(nèi)容如下:
XML
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/camera_preview"
android:layout_width="0px"
android:layout_height="fill_parent"
android:layout_weight="1" />
<Button
android:id="@+id/button_settings"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:text="設(shè)置" />
</LinearLayout>
修改MainActivity
修改MainActivity
類疙赠,在onCreate()
方法最后添加
Java
Button buttonSettings = (Button) findViewById(R.id.button_settings);
buttonSettings.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
getFragmentManager().beginTransaction().replace(R.id.camera_preview,
new SettingsFragment()).addToBackStack(null).commit();
}
});
即為設(shè)置
按鈕綁定點(diǎn)擊監(jiān)聽付材,當(dāng)點(diǎn)擊設(shè)置按鈕時(shí),就顯示設(shè)置菜單圃阳。
Java
getFragmentManager().beginTransaction().replace(R.id.camera_preview,
new SettingsFragment()).addToBackStack(null).commit();
是調(diào)用Fragment
的比較通用的寫法厌衔,關(guān)于具體細(xì)節(jié)還請自行查找。
修改后的MainActivity
內(nèi)容如下:
Java
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.FrameLayout;
public class MainActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
CameraPreview mPreview = new CameraPreview(this);
FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview);
preview.addView(mPreview);
Button buttonSettings = (Button) findViewById(R.id.button_settings);
buttonSettings.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
getFragmentManager().beginTransaction().replace(R.id.camera_preview,
new SettingsFragment()).addToBackStack(null).commit();
}
});
}
}
運(yùn)行一下看看
現(xiàn)在就可以運(yùn)行你的APP啦捍岳,現(xiàn)在APP的最右邊會(huì)有“設(shè)置”按鈕富寿,點(diǎn)擊此按鈕后就會(huì)在相機(jī)預(yù)覽上顯示“照片品質(zhì)”這個(gè)菜單條目,而點(diǎn)擊這個(gè)條目锣夹,就會(huì)彈出從50到100共6個(gè)選項(xiàng)页徐,其中100已經(jīng)默認(rèn)選中。效果如下:
但是現(xiàn)在這個(gè)設(shè)置菜單很簡單银萍,而且沒有實(shí)際功能变勇,我們接下來進(jìn)行完善。
動(dòng)態(tài)添加設(shè)置條目
像相機(jī)支持的分辨率,支持的對焦方式等都因設(shè)備而異搀绣,我們不大可能在preferences.xml
就寫死相機(jī)支持的分辨率的值飞袋;而為了能夠讓我們的APP能夠運(yùn)行在不同的設(shè)備上(如果只需要運(yùn)行在特定的設(shè)備上,會(huì)容易許多)链患,我們嘗試動(dòng)態(tài)加載設(shè)置條目的列表選項(xiàng)巧鸭。
完善菜單條目
菜單條目名稱我們是能夠直接寫在文件中的,因?yàn)椴煌鄼C(jī)基本都支持這些設(shè)置麻捻,只是value不同而已纲仍。
修改preferences.xml
,修改后內(nèi)容如下:
XML
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<ListPreference
android:key="preview_size"
android:title="相機(jī)預(yù)覽分辨率" />
<ListPreference
android:key="picture_size"
android:title="照片分辨率" />
<ListPreference
android:key="video_size"
android:title="視頻分辨率" />
<SwitchPreference
android:defaultValue="true"
android:key="gps_data"
android:title="地理位置" />
<ListPreference
android:defaultValue="auto"
android:key="flash_mode"
android:title="閃光燈" />
<ListPreference
android:key="focus_mode"
android:title="對焦模式" />
<ListPreference
android:defaultValue="auto"
android:key="white_balance"
android:title="白平衡" />
<ListPreference
android:defaultValue="auto"
android:key="scene_mode"
android:title="場景" />
<ListPreference
android:defaultValue="0"
android:key="exposure_compensation"
android:title="曝光補(bǔ)償" />
<ListPreference
android:defaultValue="100"
android:entries="@array/pref_jpegQuality"
android:entryValues="@array/pref_jpegQuality"
android:key="jpeg_quality"
android:title="照片品質(zhì)" />
</PreferenceScreen>
上面的這些選項(xiàng)基本覆蓋到了所有常用的選項(xiàng)(在你讀完這篇文章后你還可以自己添加想要的選項(xiàng))贸毕。注意到除了“照片品質(zhì)”外的所有ListPreference
都沒有entries
和entryValues
郑叠,這些值就是我們需要在代碼中動(dòng)態(tài)添加的;上面的“地理位置”是一個(gè)SwitchPreference
崖咨,就是開關(guān)那種樣式的锻拘,只有兩個(gè)值油吭。注意到有些條目有defaultValue
默認(rèn)值击蹲,是因?yàn)檫@些默認(rèn)值對于不同相機(jī)都是通用的。
這樣修改后婉宰,由于條目內(nèi)容不完整歌豺,APP暫時(shí)就不能運(yùn)行了
動(dòng)態(tài)添加
獲取相機(jī)
添加條目的列表選項(xiàng)當(dāng)然是在SettingsFragment
中了。想要知道相機(jī)支持的參數(shù)心包,首先需要獲得一個(gè)打開的相機(jī)类咧,這個(gè)先不追究,在SettingsFragment
中添加
Java
import android.hardware.Camera;
static Camera mCamera;
static Camera.Parameters mParameters;
public static void passCamera(Camera camera) {
mCamera = camera;
mParameters = camera.getParameters();
}
passCamera()
用來將相機(jī)傳輸給SettingsFragment
蟹腾,SettingsFragment
將相機(jī)保存到靜態(tài)成員變量mCamera
中痕惋,getParameters()
則用來獲取相機(jī)參數(shù),將相機(jī)參數(shù)保存到靜態(tài)成員變量mParameters
中娃殖。
動(dòng)態(tài)加載預(yù)覽分辨率
首先以動(dòng)態(tài)加載預(yù)覽分辨率為例值戳,介紹動(dòng)態(tài)加載過程。預(yù)覽分辨率即相機(jī)預(yù)覽時(shí)屏幕顯示的分辨率大小炉爆。在SettingsFragment
中添加
Java
import android.preference.ListPreference;
import java.util.ArrayList;
import java.util.List;
public static final String KEY_PREF_PREV_SIZE = "preview_size";
private void loadSupportedPreviewSize() {
cameraSizeListToListPreference(mParameters.getSupportedPreviewSizes(), KEY_PREF_PREV_SIZE);
}
private void cameraSizeListToListPreference(List<Camera.Size> list, String key) {
List<String> stringList = new ArrayList<>();
for (Camera.Size size : list) {
String stringSize = size.width + "x" + size.height;
stringList.add(stringSize);
}
stringListToListPreference(stringList, key);
}
private void stringListToListPreference(List<String> list, String key) {
final CharSequence[] charSeq = list.toArray(new CharSequence[list.size()]);
ListPreference listPref = (ListPreference) getPreferenceScreen().findPreference(key);
listPref.setEntries(charSeq);
listPref.setEntryValues(charSeq);
}
靜態(tài)成員變量KEY_PREF_PREV_SIZE
存儲(chǔ)預(yù)覽分辨率的key
(在preferences.xml
定義)堕虹。
先看stringListToListPreference()
,其首先將List<String>
轉(zhuǎn)換為CharSequence[]
芬首;由getPreferenceScreen().findPreference()
獲取由key
指定的菜單條目赴捞;由setEntries()
和setEntryValues
向這個(gè)菜單條目指定用戶可見的所有value和代碼可見的所有value,就完成了對這個(gè)key
條目的列表內(nèi)容的動(dòng)態(tài)加載郁稍。
mParameters.getSupportedPreviewSizes()
獲取相機(jī)支持的所有預(yù)覽分辨率赦政,保存在List<Camera.Size>
中,再由cameraSizeListToListPreference()
將List<Camera.Size>
轉(zhuǎn)換為List<String>
耀怜。
整個(gè)過程邏輯挺清晰的恢着,理解起來應(yīng)該沒太大問題掸屡。
動(dòng)態(tài)加載曝光補(bǔ)償
再舉個(gè)例子,在SettingsFragment
中添加
Java
public static final String KEY_PREF_EXPOS_COMP = "exposure_compensation";
private void loadSupportedExposeCompensation() {
int minExposComp = mParameters.getMinExposureCompensation();
int maxExposComp = mParameters.getMaxExposureCompensation();
List<String> exposComp = new ArrayList<>();
for (int value = minExposComp; value <= maxExposComp; value++) {
exposComp.add(Integer.toString(value));
}
stringListToListPreference(exposComp, KEY_PREF_EXPOS_COMP);
}
由mParameters.getMinExposureCompensation()
獲取相機(jī)支持的最低曝光補(bǔ)償然评,mParameters.getMaxExposureCompensation()
獲取相機(jī)支持的最高曝光補(bǔ)償仅财,由最低到最高形成一個(gè)List<String>
,指定key
后交給stringListToListPreference()
就好了碗淌。
全部的動(dòng)態(tài)加載項(xiàng)
下面貼上全部的load盏求,嫌亂可以到DEMO中去看完整代碼,注意這里的load一個(gè)都不能少亿眠。
Java
public static final String KEY_PREF_PREV_SIZE = "preview_size";
public static final String KEY_PREF_PIC_SIZE = "picture_size";
public static final String KEY_PREF_VIDEO_SIZE = "video_size";
public static final String KEY_PREF_FLASH_MODE = "flash_mode";
public static final String KEY_PREF_FOCUS_MODE = "focus_mode";
public static final String KEY_PREF_WHITE_BALANCE = "white_balance";
public static final String KEY_PREF_SCENE_MODE = "scene_mode";
public static final String KEY_PREF_GPS_DATA = "gps_data";
public static final String KEY_PREF_EXPOS_COMP = "exposure_compensation";
public static final String KEY_PREF_JPEG_QUALITY = "jpeg_quality";
private void loadSupportedPreviewSize() {
cameraSizeListToListPreference(mParameters.getSupportedPreviewSizes(), KEY_PREF_PREV_SIZE);
}
private void loadSupportedPictureSize() {
cameraSizeListToListPreference(mParameters.getSupportedPictureSizes(), KEY_PREF_PIC_SIZE);
}
private void loadSupportedVideoeSize() {
cameraSizeListToListPreference(mParameters.getSupportedVideoSizes(), KEY_PREF_VIDEO_SIZE);
}
private void loadSupportedFlashMode() {
stringListToListPreference(mParameters.getSupportedFlashModes(), KEY_PREF_FLASH_MODE);
}
private void loadSupportedFocusMode() {
stringListToListPreference(mParameters.getSupportedFocusModes(), KEY_PREF_FOCUS_MODE);
}
private void loadSupportedWhiteBalance() {
stringListToListPreference(mParameters.getSupportedWhiteBalance(), KEY_PREF_WHITE_BALANCE);
}
private void loadSupportedSceneMode() {
stringListToListPreference(mParameters.getSupportedSceneModes(), KEY_PREF_SCENE_MODE);
}
private void loadSupportedExposeCompensation() {
int minExposComp = mParameters.getMinExposureCompensation();
int maxExposComp = mParameters.getMaxExposureCompensation();
List<String> exposComp = new ArrayList<>();
for (int value = minExposComp; value <= maxExposComp; value++) {
exposComp.add(Integer.toString(value));
}
stringListToListPreference(exposComp, KEY_PREF_EXPOS_COMP);
}
設(shè)置菜單創(chuàng)建時(shí)即加載
在onCreate()
觸發(fā)時(shí)就調(diào)用這些load進(jìn)行動(dòng)態(tài)加載碎罚,在onCreate()
尾部添加
Java
loadSupportedPreviewSize();
loadSupportedPictureSize();
loadSupportedVideoeSize();
loadSupportedFlashMode();
loadSupportedFocusMode();
loadSupportedWhiteBalance();
loadSupportedSceneMode();
loadSupportedExposeCompensation();
向SettingsFragment傳入相機(jī)
所有上面的代碼要執(zhí)行必須首先獲取到一個(gè)打開的相機(jī),而CameraPreview
正好有一個(gè)打開的相機(jī)纳像,我們就可以通過MainActivity
進(jìn)行相機(jī)的傳遞荆烈。
首先需要修改一下CameraPreview
的getCameraInstance()
方法,使其返回正在使用的相機(jī)竟趾,修改getCameraInstance()
為
Java
public Camera getCameraInstance() {
if (mCamera == null) {
try {
mCamera = Camera.open();
} catch (Exception e) {
Log.d(TAG, "camera is not available");
}
}
return mCamera;
}
然后在MainActivity
中按鈕監(jiān)聽之前添加
Java
SettingsFragment.passCamera(mPreview.getCameraInstance());
向SettingsFragment
傳遞來自mPreview
的相機(jī)憔购。
聲明GPS權(quán)限
注意到菜單中有個(gè)GPS的選項(xiàng),想要拍到的照片中包含GPS信息岔帽,就要在AndroidManifest.xml
中聲明需要GPS權(quán)限玫鸟。在AndroidManifest.xml
中添加
XML
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
運(yùn)行一下看看
現(xiàn)在就可以運(yùn)行你的APP啦,點(diǎn)擊“設(shè)置”按鈕犀勒,就會(huì)出現(xiàn)很多的設(shè)置選項(xiàng)屎飘,而點(diǎn)擊這些選項(xiàng)就是顯示出動(dòng)態(tài)加載得到的列表項(xiàng)目。效果如下:
為菜單條目設(shè)定默認(rèn)值
現(xiàn)在我們來談?wù)勂迷O(shè)置贾费,即Preference
的問題钦购。通常來說,在Android中褂萧,每個(gè)APP的每個(gè)設(shè)置條目都是一個(gè)Preference
押桃,這些Preference
以鍵值對(key-value)的形式,存儲(chǔ)在每個(gè)APP的指定文件中箱玷。當(dāng)APP首次運(yùn)行時(shí)怨规,則只有key沒有value;不過可以通過Android提供的方法锡足,加載xml文件中的默認(rèn)值(defaultValue
)到對應(yīng)的value波丰,Android推薦給每個(gè)key都指定默認(rèn)值,否則默認(rèn)值為空舶得。記住這些key-value都是寫在文件中的掰烟,一旦value發(fā)生變化,對應(yīng)文件中value值也發(fā)生變化,而且以后啟動(dòng)APP這些值都不會(huì)被重置(不過可以通過代碼重置)纫骑。所以通常的做法就是在APP首次啟動(dòng)時(shí)給所有的key都生成默認(rèn)值即value蝎亚,然后加載默認(rèn)值實(shí)現(xiàn)不同的偏好;在以后APP啟動(dòng)時(shí)先馆,就會(huì)讀取這些key-value實(shí)現(xiàn)不同偏好发框,即初始化。
給xml中有默認(rèn)值的添加默認(rèn)值
雖然在xml文件中指定了默認(rèn)值煤墙,但還是要用代碼讓其加載梅惯。在MainActivity
的onCreate()
適當(dāng)位置添加
Java
PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
其中false
代表在執(zhí)行這個(gè)方法時(shí),如果key已經(jīng)有value則不進(jìn)行任何操作(即不覆蓋)仿野,否則設(shè)置value為xml文件中的指定值铣减。因?yàn)锳PP每次運(yùn)行都會(huì)執(zhí)行onCreate()
,設(shè)為false
很有必要脚作。
動(dòng)態(tài)添加默認(rèn)值
仔細(xì)點(diǎn)看你會(huì)發(fā)現(xiàn)葫哗,如果沒有進(jìn)行手動(dòng)設(shè)置,設(shè)置菜單的“相機(jī)預(yù)覽分辨率”球涛、“照片分辨率”劣针、“對焦模式”是沒有默認(rèn)值的,現(xiàn)在我們就為其添加默認(rèn)值宾符。因?yàn)閺摹罢?guī)”來說酿秸,Android只提供了從xml文件添加默認(rèn)值的方法(就像其他有默認(rèn)值的條目一樣)灭翔,想要用代碼實(shí)現(xiàn)默認(rèn)值就得花點(diǎn)功夫了魏烫。
修改SettingsFragment
Update 20160504: 本塊內(nèi)容分割線以下為舊方法,不再采用
我們把目光放到SettingsFragment
上肝箱,可以創(chuàng)建一個(gè)方法setDefault()
哄褒,就像上面添加靜態(tài)默認(rèn)值那樣,通過調(diào)用setDefault()
煌张,來添加動(dòng)態(tài)默認(rèn)值呐赡。但setDefault()
只在最初“相機(jī)預(yù)覽分辨率”等沒有默認(rèn)值時(shí)才為其指定默認(rèn)值,否則不進(jìn)行任何操作骏融。所以setDefault()
首先找到“相機(jī)預(yù)覽分辨率”的value值链嘀,如果值為空則指定默認(rèn)值,否則返回档玻。我們還應(yīng)該注意到怀泊,相機(jī)本身是有默認(rèn)值的,只是我們的偏好設(shè)置中還沒有設(shè)置這個(gè)默認(rèn)值误趴,因此我們可以獲取到相機(jī)的默認(rèn)值霹琼,然后構(gòu)造成為偏好設(shè)置中的格式,將這個(gè)值指定為value就可以了。
這樣setDefault()
代碼就出來了
Java
public static void setDefault(SharedPreferences sharedPrefs) {
String valPreviewSize = sharedPrefs.getString(KEY_PREF_PREV_SIZE, null);
if (valPreviewSize == null) {
SharedPreferences.Editor editor = sharedPrefs.edit();
editor.putString(KEY_PREF_PREV_SIZE, getDefaultPreviewSize());
editor.putString(KEY_PREF_PIC_SIZE, getDefaultPictureSize());
editor.putString(KEY_PREF_VIDEO_SIZE, getDefaultVideoSize());
editor.putString(KEY_PREF_FOCUS_MODE, getDefaultFocusMode());
editor.apply();
}
}
private static String getDefaultPreviewSize() {
Camera.Size previewSize = mParameters.getPreviewSize();
return previewSize.width + "x" + previewSize.height;
}
private static String getDefaultPictureSize() {
Camera.Size pictureSize = mParameters.getPictureSize();
return pictureSize.width + "x" + pictureSize.height;
}
private static String getDefaultVideoSize() {
Camera.Size VideoSize = mParameters.getPreferredPreviewSizeForVideo();
return VideoSize.width + "x" + VideoSize.height;
}
private static String getDefaultFocusMode() {
List<String> supportedFocusModes = mParameters.getSupportedFocusModes();
if (supportedFocusModes.contains("continuous-picture")) {
return "continuous-picture";
}
return "continuous-video";
}
SharedPreferences
由MainActivity
提供枣申,是操作Preference
的接口售葡,既可以讀取也可以寫入。SharedPreferences.Editor
就是編輯Preference
忠藤,其putString()
將key-value對寫入到APP中挟伙,注意最后需要apply()
保存這些更改(也可以用commit()
,但效率會(huì)低一些)模孩。具體怎么找到value代碼很簡單像寒,就是通過mParameters
獲取此時(shí)相機(jī)預(yù)覽的參數(shù),然后轉(zhuǎn)換為特定形式的String瓜贾,就作為value返回了诺祸。
這樣就完成動(dòng)態(tài)添加默認(rèn)值,可以發(fā)現(xiàn)我們只需要用到SharedPreferences
和mParameters
祭芦,所以只需要在MainActivity
中傳遞了相機(jī)之后調(diào)用就好了筷笨;因?yàn)榉椒ㄊ庆o態(tài)方法,調(diào)用時(shí)甚至都不需要實(shí)例化龟劲。
我們把目光放到SettingsFragment
上胃夏,可以創(chuàng)建一個(gè)方法setDefault()
,在onCreate()
中調(diào)用setDefault()
昌跌。但setDefault()
只在最初“相機(jī)預(yù)覽分辨率”等沒有默認(rèn)值時(shí)才為其指定默認(rèn)值仰禀,否則不進(jìn)行任何操作。所以setDefault()
首先找到“相機(jī)預(yù)覽分辨率”的value值蚕愤,如果值為空則指定默認(rèn)值答恶,否則返回;而指定默認(rèn)值可以直接從其動(dòng)態(tài)加載的value列表中選擇第一個(gè)就好了萍诱。
這樣setDefault()
代碼就出來了
Java
public void setDefault() {
ListPreference prefPreviewSize = (ListPreference) getPreferenceScreen().findPreference(KEY_PREF_PREV_SIZE);
if (prefPreviewSize.getValue() == null) {
prefPreviewSize.setValueIndex(0);
ListPreference prefPictureSize = (ListPreference) getPreferenceScreen().findPreference(KEY_PREF_PIC_SIZE);
prefPictureSize.setValueIndex(0);
ListPreference prefVideoSize = (ListPreference) getPreferenceScreen().findPreference(KEY_PREF_VIDEO_SIZE);
prefVideoSize.setValueIndex(0);
ListPreference prefFocusMode = (ListPreference) getPreferenceScreen().findPreference(KEY_PREF_FOCUS_MODE);
if (prefFocusMode.findIndexOfValue("continuous-picture") != -1) {
prefFocusMode.setValue("continuous-picture");
} else {
prefFocusMode.setValue("continuous-video");
}
}
}
getPreferenceScreen().findPreference()
獲取菜單條目悬嗓,setValueIndex(0)
則將其value設(shè)置為value列表中的第一個(gè)。還記得之前APP相機(jī)預(yù)覽一片模糊嗎裕坊?現(xiàn)在就解決這個(gè)問題啦包竹!首先查找對焦方式中是否存在continuous-picture
,若存在則設(shè)置籍凝,否則設(shè)置為continuous-video
(設(shè)備一般都支持這兩種對焦模式)周瞎。這兩種對焦模式都會(huì)在鏡頭移動(dòng)時(shí)重新對焦,就像其他的相機(jī)APP那樣饵蒂。
注意setDefault()
需要在條目已經(jīng)動(dòng)態(tài)生成后運(yùn)行声诸,所以在onCreate()
中應(yīng)當(dāng)在所有l(wèi)oad之后調(diào)用這個(gè)方法
修改MainActivity
Update 20160504: 本塊內(nèi)容分割線以下為舊方法,不再采用
上面的setDefault()
每次調(diào)用時(shí)都會(huì)判斷“相機(jī)預(yù)覽分辨率”有沒有默認(rèn)值苹享,因此在MainActivity
中就可以放心大膽調(diào)用了双絮,我們可以把這個(gè)方法的調(diào)用和onCreate
中的setDefaultValues()
放在一起浴麻;雖然onCreate()
每次都會(huì)調(diào)用這個(gè)方法,但只有在APP第一次運(yùn)行時(shí)才會(huì)真正進(jìn)行動(dòng)態(tài)添加默認(rèn)值囤攀。
onCreate()
中涉及到SettingsFragment
的代碼就是
Java
SettingsFragment.passCamera(mPreview.getCameraInstance());
PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
SettingsFragment.setDefault(PreferenceManager.getDefaultSharedPreferences(this));
SettingsFragment.init(PreferenceManager.getDefaultSharedPreferences(this));
很簡單不必過多解釋软免。
這里要注意一個(gè)問題,動(dòng)態(tài)添加默認(rèn)值會(huì)在SettingsFragment
的onCreate()
方法執(zhí)行時(shí)執(zhí)行焚挠,而SettingsFragment
的onCreate()
會(huì)在主窗口點(diǎn)擊了“設(shè)置”后才會(huì)觸發(fā)膏萧。那么如果APP從來沒有點(diǎn)擊“設(shè)置”就永遠(yuǎn)不會(huì)動(dòng)態(tài)加載默認(rèn)值了?APP每次運(yùn)行都會(huì)讀取設(shè)置菜單條目并進(jìn)行偏好設(shè)置蝌衔,所以在APP第一次運(yùn)行時(shí)必須在不點(diǎn)擊“設(shè)置”時(shí)就觸發(fā)SettingsFragment
的onCreate()
榛泛。
我想到的一個(gè)很“笨”的方法是,在MainActivity
的onCreate()
中噩斟,也對“相機(jī)預(yù)覽分辨率”是否有value進(jìn)行判斷曹锨,如果沒有value則強(qiáng)行調(diào)用SettingsFragment
,觸發(fā)其onCreate()
剃允;如果有value則不進(jìn)行任何操作沛简。這樣,在MainActivity
的onCreate()
中斥废,passCamera()
后加入如下代碼:
Java
if (PreferenceManager.getDefaultSharedPreferences(this).getString(SettingsFragment.KEY_PREF_PREV_SIZE, null) == null) {
getFragmentManager().beginTransaction().replace(R.id.camera_preview, new SettingsFragment()).addToBackStack(null).commit();
getFragmentManager().executePendingTransactions();
}
即可像靜態(tài)加載默認(rèn)值那樣椒楣,動(dòng)態(tài)添加默認(rèn)值了。效果如下:
需要注意的地方
Update 20160504: 本塊內(nèi)容分割線以下為舊內(nèi)容
之前的方法在APP第一次運(yùn)行時(shí)會(huì)出現(xiàn)設(shè)置菜單需要手動(dòng)退出牡肉,雖然對用戶體驗(yàn)影響不太大捧灰,但終究是不符合常理。更新后的代碼不再存在這個(gè)問題统锤,且邏輯更為清晰毛俏。
這樣實(shí)現(xiàn)動(dòng)態(tài)添加默認(rèn)值后,造成的負(fù)面影響就是APP在首次運(yùn)行時(shí)跪另,會(huì)自動(dòng)出現(xiàn)設(shè)置菜單拧抖,需要手動(dòng)退出。但這種情況只會(huì)在APP第一次運(yùn)行時(shí)產(chǎn)生免绿,以后都不會(huì)再出現(xiàn),所以還是可以忍受的擦盾。其實(shí)有更復(fù)雜一些的方法解決這個(gè)問題嘲驾,但為了這個(gè)介紹的簡介,就不考慮那些方法了迹卢。
APP啟動(dòng)時(shí)加載偏好
我們自然希望相機(jī)APP每次啟動(dòng)時(shí)都能自動(dòng)加載好之前偏好設(shè)置辽故,思路也很簡單,在SettingsFragment
中創(chuàng)建init()
方法腐碱,負(fù)責(zé)設(shè)置相機(jī)誊垢;在MainActivity
的onCreate()
中掉弛,在設(shè)置完靜態(tài)和動(dòng)態(tài)默認(rèn)值后調(diào)用init()
方法,完成相機(jī)設(shè)置喂走。
修改SettingsFragment
在SettingsFragment
中添加
Java
import android.content.SharedPreferences;
public static void init(SharedPreferences sharedPref) {
setPreviewSize(sharedPref.getString(KEY_PREF_PREV_SIZE, ""));
setPictureSize(sharedPref.getString(KEY_PREF_PIC_SIZE, ""));
setFlashMode(sharedPref.getString(KEY_PREF_FLASH_MODE, ""));
setFocusMode(sharedPref.getString(KEY_PREF_FOCUS_MODE, ""));
setWhiteBalance(sharedPref.getString(KEY_PREF_WHITE_BALANCE, ""));
setSceneMode(sharedPref.getString(KEY_PREF_SCENE_MODE, ""));
setExposComp(sharedPref.getString(KEY_PREF_EXPOS_COMP, ""));
setJpegQuality(sharedPref.getString(KEY_PREF_JPEG_QUALITY, ""));
setGpsData(sharedPref.getBoolean(KEY_PREF_GPS_DATA, false));
mCamera.stopPreview();
mCamera.setParameters(mParameters);
mCamera.startPreview();
}
private static void setPreviewSize(String value) {
String[] split = value.split("x");
mParameters.setPreviewSize(Integer.parseInt(split[0]), Integer.parseInt(split[1]));
}
private static void setPictureSize(String value) {
String[] split = value.split("x");
mParameters.setPictureSize(Integer.parseInt(split[0]), Integer.parseInt(split[1]));
}
private static void setFocusMode(String value) {
mParameters.setFocusMode(value);
}
private static void setFlashMode(String value) {
mParameters.setFlashMode(value);
}
private static void setWhiteBalance(String value) {
mParameters.setWhiteBalance(value);
}
private static void setSceneMode(String value) {
mParameters.setSceneMode(value);
}
private static void setExposComp(String value) {
mParameters.setExposureCompensation(Integer.parseInt(value));
}
private static void setJpegQuality(String value) {
mParameters.setJpegQuality(Integer.parseInt(value));
}
private static void setGpsData(Boolean value) {
if (value.equals(false)) {
mParameters.removeGpsData();
}
}
SharedPreferences
即APP存儲(chǔ)的key-value對殃饿;getString()
即獲取指定key的value值,對于ListPreference
是getString()
芋肠,對于SwitchPreference
是getBoolean()
乎芳。這些set就是分別修改相機(jī)參數(shù)mParameters
的不同屬性,很簡單不詳細(xì)解釋了帖池。最后奈惑,首先停止相機(jī)預(yù)覽,然后應(yīng)用修改后的mParameters
到相機(jī)睡汹,最后再開始相機(jī)預(yù)覽肴甸。這樣就完成了相機(jī)的偏好設(shè)置。
修改MainActivity
在MainActivity
的onCreate()
設(shè)置完靜態(tài)和動(dòng)態(tài)默認(rèn)值后添加
Java
SettingsFragment.init(PreferenceManager.getDefaultSharedPreferences(this));
其中PreferenceManager.getDefaultSharedPreferences(this)
就是得到此APP的SharedPreferences
囚巴。
運(yùn)行一下看看
打開APP雷滋,修改偏好設(shè)置后關(guān)閉APP(需要退出后臺(tái),下一篇文章會(huì)介紹一個(gè)不需要這么麻煩的方法)文兢,再次打開APP就能看到效果了晤斩。
條目value變化時(shí)立即應(yīng)用設(shè)置
我們肯定不希望修改設(shè)置后,需要重啟APP才能看到效果∧芳幔現(xiàn)在來實(shí)現(xiàn)修改設(shè)置后澳泵,相機(jī)立即應(yīng)用新的設(shè)置。
我們只需要監(jiān)聽條目value變化就好了兼呵,一旦出現(xiàn)變化兔辅,就根據(jù)其key立即應(yīng)用新的value到相機(jī)。
給SettingsFragment添加監(jiān)聽接口
修改
Java
public class SettingsFragment extends PreferenceFragment
為
Java
public class SettingsFragment extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener
OnSharedPreferenceChangeListener
為監(jiān)聽Preference
變化的接口
給SettingsFragment添加監(jiān)聽事件
在SettingsFragment
中添加
Java
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
switch (key) {
case KEY_PREF_PREV_SIZE:
setPreviewSize(sharedPreferences.getString(key, ""));
break;
case KEY_PREF_PIC_SIZE:
setPictureSize(sharedPreferences.getString(key, ""));
break;
case KEY_PREF_FOCUS_MODE:
setFocusMode(sharedPreferences.getString(key, ""));
break;
case KEY_PREF_FLASH_MODE:
setFlashMode(sharedPreferences.getString(key, ""));
break;
case KEY_PREF_WHITE_BALANCE:
setWhiteBalance(sharedPreferences.getString(key, ""));
break;
case KEY_PREF_SCENE_MODE:
setSceneMode(sharedPreferences.getString(key, ""));
break;
case KEY_PREF_EXPOS_COMP:
setExposComp(sharedPreferences.getString(key, ""));
break;
case KEY_PREF_JPEG_QUALITY:
setJpegQuality(sharedPreferences.getString(key, ""));
break;
case KEY_PREF_GPS_DATA:
setGpsData(sharedPreferences.getBoolean(key, false));
break;
}
mCamera.stopPreview();
mCamera.setParameters(mParameters);
mCamera.startPreview();
}
@Override
public void onResume() {
super.onResume();
getPreferenceScreen().getSharedPreferences()
.registerOnSharedPreferenceChangeListener(this);
}
@Override
public void onPause() {
super.onPause();
getPreferenceScreen().getSharedPreferences()
.unregisterOnSharedPreferenceChangeListener(this);
}
onSharedPreferenceChanged()
為監(jiān)聽事件回調(diào)击喂,干的事情就像之前說的维苔,代碼也很簡單。onResume()
和onPause()
是Android推薦寫法懂昂,防止由于Fragment
的不斷調(diào)用導(dǎo)致事件監(jiān)聽失效介时。
運(yùn)行一下看看
現(xiàn)在在APP中修改設(shè)置馬上就能看到效果了!
設(shè)置菜單中顯示ListPreference的當(dāng)前值
目前功能已經(jīng)全部實(shí)現(xiàn)了凌彬,現(xiàn)在做一點(diǎn)美化沸柔。設(shè)置菜單中的ListPreference
只顯示了其條目的標(biāo)題,而當(dāng)前用戶可見的value只有在點(diǎn)擊條目后才會(huì)顯示铲敛,現(xiàn)在就來實(shí)現(xiàn)讓當(dāng)前值直接顯示在條目上褐澎。
添加方法
讓ListPreference
顯示當(dāng)前值,就是為其summary賦值伐蒋,即將當(dāng)前值賦給其summary工三。
在SettingsFragment
中添加
Java
private static void initSummary(Preference pref) {
if (pref instanceof PreferenceGroup) {
PreferenceGroup prefGroup = (PreferenceGroup) pref;
for (int i = 0; i < prefGroup.getPreferenceCount(); i++) {
initSummary(prefGroup.getPreference(i));
}
} else {
updatePrefSummary(pref);
}
}
private static void updatePrefSummary(Preference pref) {
if (pref instanceof ListPreference) {
pref.setSummary(((ListPreference) pref).getEntry());
}
}
initSummary()
處理全部的Preference
迁酸,主要是處理含有PreferenceGroup
的情況,這個(gè)APP目前沒有這個(gè)情況俭正,但還是保留這個(gè)功能奸鬓。updatePrefSummary()
則處理具體的ListPreference
,將其用戶可見的值getEntry()
通過setSummary()
賦給summary段审。
調(diào)用方法
有兩個(gè)調(diào)用上述方法的地方全蝶。
首先是SettingsFragment
的onCreate()
中,給所有的Preference
都設(shè)置summary寺枉。在onCreate
最后添加
Java
initSummary(getPreferenceScreen());
其次是在onSharedPreferenceChanged()
中抑淫,每次條目value發(fā)生變化時(shí),summary也隨機(jī)變化姥闪。在onSharedPreferenceChanged()
最頭上添加
Java
updatePrefSummary(findPreference(key));
運(yùn)行一下看看
現(xiàn)在設(shè)置菜單的ListPreference
都顯示其當(dāng)前值了始苇。效果如下:
一點(diǎn)嘮叨
現(xiàn)在看來實(shí)現(xiàn)偏好設(shè)置還是有些麻煩的,其實(shí)主要難點(diǎn)在動(dòng)態(tài)加載和默認(rèn)值上筐喳,為了能夠自適應(yīng)不同設(shè)備就是要這么折騰催式。像系統(tǒng)自帶的相機(jī)可能就可以根據(jù)設(shè)備本身直接將分辨率等都寫到xml文件中,這樣工作量小了不少避归。本篇還是沒有實(shí)現(xiàn)基本的拍照功能荣月,不過已經(jīng)是一步之遙了呢。
DEMO
本文實(shí)現(xiàn)的相機(jī)APP源碼都放在GitHub上梳毙,如果需要請點(diǎn)擊zhantong/AndroidCamera-EnableSettings哺窄。
參考
- Settings | Android Developers
- Preference | Android Developers
- ListPreference | Android Developers
- SharedPreferences | Android Developers
- PreferenceFragment | Android Developers
- PreferenceScreen | Android Developers
- Camera | Android Developers
- Camera.Parameters | Android Developers
- android - How to pass a variable from Activity to Fragment, and pass it back? - Stack Overflow
- Android Shared Preferences example using PreferenceFragment - Store, fetch and edit
- android - How to fill ListPreference dynamically when onPreferenceClick is triggered? - Stack Overflow
- How to initialize a new Camera.Size in Android - Stack Overflow
- java - Android, how to populate a CharSequence array dynamically (not initializing?) - Stack Overflow
- Killing android application on pause - Stack Overflow
- user interface - How do I display the current value of an Android Preference in the Preference summary? - Stack Overflow