Android相機(jī)開發(fā)(二): 給相機(jī)加上偏好設(shè)置

轉(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

Settings Fragment File List

內(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文件

Preferences File List

內(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_jpegQualityarray缤剧,這個(gè)稍后解釋馅袁;最后為這個(gè)條目設(shè)定一個(gè)默認(rèn)值即defaultValue,設(shè)為100荒辕。

添加array值

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

Arrays File List

內(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_jpegQualityarray汗销,供上一步的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)選中。效果如下:

Screenshot Simple Settings

但是現(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都沒有entriesentryValues郑叠,這些值就是我們需要在代碼中動(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ī)的傳遞荆烈。

首先需要修改一下CameraPreviewgetCameraInstance()方法,使其返回正在使用的相機(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)目。效果如下:

Screenshot Settings

為菜單條目設(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)值煤墙,但還是要用代碼讓其加載梅惯。在MainActivityonCreate()適當(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";
}

SharedPreferencesMainActivity提供枣申,是操作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)我們只需要用到SharedPreferencesmParameters祭芦,所以只需要在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ì)在SettingsFragmentonCreate()方法執(zhí)行時(shí)執(zhí)行焚挠,而SettingsFragmentonCreate()會(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ā)SettingsFragmentonCreate()榛泛。

我想到的一個(gè)很“笨”的方法是,在MainActivityonCreate()中噩斟,也對“相機(jī)預(yù)覽分辨率”是否有value進(jìn)行判斷曹锨,如果沒有value則強(qiáng)行調(diào)用SettingsFragment,觸發(fā)其onCreate()剃允;如果有value則不進(jìn)行任何操作沛简。這樣,在MainActivityonCreate()中斥废,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)值了。效果如下:

Screenshot Preview Size Settings

需要注意的地方

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ī)誊垢;在MainActivityonCreate()中掉弛,在設(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值,對于ListPreferencegetString()芋肠,對于SwitchPreferencegetBoolean()乎芳。這些set就是分別修改相機(jī)參數(shù)mParameters的不同屬性,很簡單不詳細(xì)解釋了帖池。最后奈惑,首先停止相機(jī)預(yù)覽,然后應(yīng)用修改后的mParameters到相機(jī)睡汹,最后再開始相機(jī)預(yù)覽肴甸。這樣就完成了相機(jī)的偏好設(shè)置。

修改MainActivity

MainActivityonCreate()設(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)用上述方法的地方全蝶。

首先是SettingsFragmentonCreate()中,給所有的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)前值了始苇。效果如下:

Screenshot Summary Settings

一點(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哺窄。

參考

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市账锹,隨后出現(xiàn)的幾起案子萌业,更是在濱河造成了極大的恐慌,老刑警劉巖奸柬,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件生年,死亡現(xiàn)場離奇詭異,居然都是意外死亡廓奕,警方通過查閱死者的電腦和手機(jī)抱婉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來懂从,“玉大人授段,你說我怎么就攤上這事》Γ” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵届搁,是天一觀的道長缘薛。 經(jīng)常有香客問我窍育,道長,這世上最難降的妖魔是什么宴胧? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任漱抓,我火速辦了婚禮,結(jié)果婚禮上恕齐,老公的妹妹穿的比我還像新娘乞娄。我一直安慰自己,他們只是感情好显歧,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布仪或。 她就那樣靜靜地躺著,像睡著了一般士骤。 火紅的嫁衣襯著肌膚如雪范删。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天拷肌,我揣著相機(jī)與錄音到旦,去河邊找鬼。 笑死巨缘,一個(gè)胖子當(dāng)著我的面吹牛添忘,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播若锁,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼搁骑,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了拴清?” 一聲冷哼從身側(cè)響起靶病,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎口予,沒想到半個(gè)月后娄周,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡沪停,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年煤辨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片木张。...
    茶點(diǎn)故事閱讀 38,039評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡众辨,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出舷礼,到底是詐尸還是另有隱情鹃彻,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布妻献,位于F島的核電站蛛株,受9級特大地震影響团赁,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜谨履,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一欢摄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧笋粟,春花似錦怀挠、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至吨艇,卻和暖如春躬它,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背东涡。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工冯吓, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人疮跑。 一個(gè)月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓组贺,卻偏偏與公主長得像,于是被迫代替她去往敵國和親祖娘。 傳聞我的和親對象是個(gè)殘疾皇子失尖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容