轉(zhuǎn)載自:Penguin
Android Camera Develop: orientation/rotation and aspect ratio
概述
接上篇寻歧,在實(shí)現(xiàn)了相機(jī)的基礎(chǔ)功能后掌栅,著眼于解決預(yù)覽、拍照與錄像時(shí)屏幕的旋轉(zhuǎn)码泛,以及預(yù)覽時(shí)的縱橫比等問題猾封。上篇實(shí)現(xiàn)的相機(jī)APP只能以橫屏方向預(yù)覽、拍照和錄像噪珊,在實(shí)際使用時(shí)會(huì)很不方便晌缘;本篇就會(huì)實(shí)現(xiàn)APP隨設(shè)備的旋轉(zhuǎn)而旋轉(zhuǎn)齐莲,且可以在任意旋轉(zhuǎn)角度上進(jìn)行預(yù)覽、拍照和錄像磷箕。上篇實(shí)現(xiàn)的相機(jī)APP中选酗,預(yù)覽畫面是充滿整個(gè)屏幕的,即使調(diào)整預(yù)覽分辨率后岳枷,預(yù)覽畫面所占尺寸也不變芒填,這樣會(huì)造成實(shí)際畫面被拉長(zhǎng)或壓扁,十分不友好空繁;本篇會(huì)實(shí)現(xiàn)預(yù)覽畫面縱橫比與分辨率縱橫比保持相同殿衰,即預(yù)覽畫面不會(huì)出現(xiàn)拉長(zhǎng)或壓扁的情況。
預(yù)覽畫面隨設(shè)備旋轉(zhuǎn)
如果只是簡(jiǎn)單地將AndroidManifest中橫屏鎖去掉家厌,雖然可以實(shí)現(xiàn)APP隨設(shè)備旋轉(zhuǎn)播玖,但預(yù)覽畫面不會(huì)隨之旋轉(zhuǎn),如下圖所示:
原因主要在于Camera的預(yù)覽顯示方向需要單獨(dú)設(shè)置饭于,不會(huì)隨著設(shè)備的旋轉(zhuǎn)而自動(dòng)改變蜀踏。而通過設(shè)置Camera的預(yù)覽顯示方向,可以自由指定旋轉(zhuǎn)的角度掰吕。
解除旋轉(zhuǎn)鎖定
在AndroidManifest.xml
中刪去
XML
android:screenOrientation="landscape"
這樣就解除了之前設(shè)定的APP的旋轉(zhuǎn)鎖定(注意與設(shè)備的旋轉(zhuǎn)鎖定不同)果覆,現(xiàn)在APP可以隨設(shè)備旋轉(zhuǎn)而旋轉(zhuǎn)了。
計(jì)算旋轉(zhuǎn)角度
相機(jī)預(yù)覽的旋轉(zhuǎn)角度需要根據(jù)相機(jī)預(yù)覽目前的旋轉(zhuǎn)角度殖熟,以及設(shè)備屏幕的旋轉(zhuǎn)角度計(jì)算得到局待,不過還好Android官方給了示例代碼。
在CameraPreview中加入
Java
public int getDisplayOrientation() {
Display display = ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
int rotation = display.getRotation();
int degrees = 0;
switch (rotation) {
case Surface.ROTATION_0:
degrees = 0;
break;
case Surface.ROTATION_90:
degrees = 90;
break;
case Surface.ROTATION_180:
degrees = 180;
break;
case Surface.ROTATION_270:
degrees = 270;
break;
}
android.hardware.Camera.CameraInfo camInfo =
new android.hardware.Camera.CameraInfo();
android.hardware.Camera.getCameraInfo(Camera.CameraInfo.CAMERA_FACING_BACK, camInfo);
int result = (camInfo.orientation - degrees + 360) % 360;
return result;
}
getDisplayOrientation()
用來獲取相機(jī)預(yù)覽需要旋轉(zhuǎn)的角度菱属。前面一部分獲得設(shè)備屏幕的旋轉(zhuǎn)角度钳榨,即由重力傳感器自動(dòng)旋轉(zhuǎn)屏幕的角度;然后得到相機(jī)原先的旋轉(zhuǎn)角度camInfo.orientation
纽门,最后通過運(yùn)算得到新的相機(jī)預(yù)覽需要旋轉(zhuǎn)的角度薛耻。
預(yù)覽畫面隨設(shè)備旋轉(zhuǎn)
設(shè)備可能經(jīng)常在旋轉(zhuǎn),那什么時(shí)候?qū)⒂?jì)算得到的旋轉(zhuǎn)角度應(yīng)用到相機(jī)呢赏陵?當(dāng)然是要等待相應(yīng)事件觸發(fā)了饼齿。還記得之前一直沒有派上用場(chǎng)的surfaceChanged()
嗎?現(xiàn)在就要用到它了蝙搔,surfaceChanged()
會(huì)在surface的大小或格式發(fā)生改變時(shí)調(diào)用缕溉,而屏幕的旋轉(zhuǎn)恰恰會(huì)使得surface大小發(fā)生變化(橫屏變?yōu)樨Q屏、豎屏變?yōu)闄M屏吃型,surface的長(zhǎng)寬都會(huì)發(fā)生變化)证鸥,所以調(diào)整相機(jī)預(yù)覽旋轉(zhuǎn)角度就在這里了。
在surfaceChanged()
中加入
Java
int rotation = getDisplayOrientation();
mCamera.setDisplayOrientation(rotation);
即surfaceChanged()
觸發(fā)后,計(jì)算相機(jī)預(yù)覽需要旋轉(zhuǎn)的角度rotation
敌土,再通過setDisplayOrientation()
將此角度應(yīng)用到Camera
镜硕。
運(yùn)行試試
現(xiàn)在運(yùn)行APP,試試旋轉(zhuǎn)設(shè)備返干,相機(jī)預(yù)覽也隨之旋轉(zhuǎn)了兴枯,不會(huì)再出現(xiàn)之前的怪異畫面了。
優(yōu)化UI布局
有沒有發(fā)現(xiàn)上圖中在豎屏下控制按鈕在屏幕右邊很別扭矩欠?這是因?yàn)樨Q屏下APP仍然應(yīng)用的橫屏下的布局财剖,本意是為了讓布局的相對(duì)位置不隨旋轉(zhuǎn)而改動(dòng),但在這里明顯不滿足我們的需求了癌淮。我們考慮為APP配置橫屏和豎屏兩個(gè)布局躺坟,讓Android根據(jù)屏幕方向自動(dòng)選擇。
新建橫屏布局文件
雙擊打開activity_main.xml
乳蓄,點(diǎn)擊面板左下角切換到Design
界面咪橙,在如下圖所示處點(diǎn)擊Create Landscape Variation
。
這樣在項(xiàng)目文件列表中虚倒,activity_main.xml
就會(huì)變成一個(gè)文件夾美侦,其內(nèi)含有兩個(gè)activity_main.xml
文件,其中有(land)
的是橫屏布局文件魂奥,另一個(gè)是豎屏布局文件菠剩。
加入豎屏布局
新建的橫屏布局文件自動(dòng)填充了之前的布局內(nèi)容,所以橫屏布局就不用修改了耻煤。
豎屏布局文件內(nèi)容修改為:
XML
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:id="@+id/camera_preview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1" />
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/button_settings"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:text="設(shè)置" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:orientation="horizontal">
<Button
android:id="@+id/button_capture_photo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="拍照" />
<Button
android:id="@+id/button_capture_video"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="錄像" />
</LinearLayout>
<ImageView
android:id="@+id/media_preview"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:background="#000"
android:visibility="visible" />
</RelativeLayout>
</LinearLayout>
按照標(biāo)準(zhǔn)具壮,不應(yīng)該在android:text
中直接寫入文字,而應(yīng)當(dāng)在res/values/strings.xml
中寫入哈蝇,在layout中引用棺妓。本APP實(shí)際也是這樣寫的,但此處為演示方便炮赦,還是直接在layout中寫入
運(yùn)行試試
運(yùn)行APP涧郊,此時(shí)在橫屏下還是之前的布局,但在豎屏下已經(jīng)應(yīng)用新的布局了眼五,如下圖所示
拍照與錄像隨預(yù)覽旋轉(zhuǎn)
現(xiàn)在預(yù)覽和界面沒問題了,但如果你拍張照彤灶,或者錄像的話會(huì)發(fā)現(xiàn)照片或視頻的方向沒有隨著預(yù)覽方向而改變看幼,也就是說不是“所見即所得”,現(xiàn)在來著手解決這個(gè)問題幌陕。
這個(gè)問題不是bug诵姜,而是Android故意的,我們也是需要手動(dòng)指定拍照和錄像的旋轉(zhuǎn)角度搏熄。
修改拍照旋轉(zhuǎn)角度
照片的旋轉(zhuǎn)角度是在Camera
的Parameters
中指定的棚唆。這里有一個(gè)很困惑的地方暇赤,我們之前設(shè)置的setDisplayOrientation()
是直接對(duì)Camera
操作的,指定預(yù)覽的旋轉(zhuǎn)角度宵凌;而在Parameters
中鞋囊,有個(gè)方法setRotation()
同樣也是設(shè)置旋轉(zhuǎn)角度,這兩個(gè)有什么區(qū)別呢瞎惫?這里我們不作深入追究溜腐,可以理解為setRotation()
是設(shè)置預(yù)覽幀數(shù)據(jù),以及拍攝照片的方向瓜喇,而setDisplayOrientation()
僅設(shè)置預(yù)覽顯示的方向挺益。
所以現(xiàn)在要做的就是在修改預(yù)覽顯示旋轉(zhuǎn)角度時(shí),同時(shí)設(shè)置拍照旋轉(zhuǎn)角度乘寒。在surfaceChanged()
中加入
Java
Camera.Parameters parameters = mCamera.getParameters();
parameters.setRotation(rotation);
mCamera.setParameters(parameters);
即首先獲取Parameters
望众,設(shè)置旋轉(zhuǎn)角度,再將Parameters
應(yīng)用到Camera
伞辛。
這樣拍照得到的照片就和預(yù)覽的方向一致了烂翰。
修改錄像旋轉(zhuǎn)角度
因?yàn)殇浵袷墙唤oMediaRecorder
實(shí)際實(shí)現(xiàn)的,所以應(yīng)當(dāng)是給MediaRecorder
設(shè)置旋轉(zhuǎn)參數(shù)始锚。
在prepareVideoRecorder()
中刽酱,mMediaRecorder.prepare()
之前加入
Java
int rotation = getDisplayOrientation();
mMediaRecorder.setOrientationHint(rotation);
首先得到旋轉(zhuǎn)角度,然后通過setOrientationHint()
應(yīng)用到MediaRecorder
瞧捌,只需要注意在prepare()
前應(yīng)用就好了棵里。
這樣錄像得到的視頻就和預(yù)覽的方向一致了。
注意正如setOrientationHint()
中的Hint所表示的姐呐,視頻的旋轉(zhuǎn)并不是編碼層面的旋轉(zhuǎn)殿怜,視頻幀數(shù)據(jù)并沒有發(fā)生旋轉(zhuǎn),而只是在視頻中增加了參數(shù)曙砂,希望播放器按照指定的旋轉(zhuǎn)角度旋轉(zhuǎn)后播放头谜,所以具體效果因播放器而異
實(shí)時(shí)調(diào)整預(yù)覽縱橫比
如上圖所示,黑色矩形框?yàn)槠聊火海疑匦慰驗(yàn)?code>SurfaceView的父級(jí)FrameLayout
柱告。我們的APP現(xiàn)在是SurfaceView
充滿整個(gè)FrameLayout
即灰色矩形框,而這樣會(huì)造成實(shí)際畫面被拉長(zhǎng)或壓扁笑陈。解決方法是將SurfaceView
的縱橫比與預(yù)覽分辨率的縱橫比調(diào)整為相同际度,這樣從屏幕上看到拍攝到的物體與實(shí)際情況就只是產(chǎn)生了縮放,而不會(huì)出現(xiàn)變形涵妥。為了達(dá)到最好的顯示效果乖菱,我們希望SurfaceView
能盡可能充滿FrameLayout
,如圖中紅色和綠色矩形框所示。通過觀察可以很快發(fā)現(xiàn)窒所,如果預(yù)覽分辨率的縱橫比大于FrameLayout
的縱橫比鹉勒,則將SurfaceView
的縱向長(zhǎng)度設(shè)定為FrameLayout
的縱向長(zhǎng)度,而SurfaceView
的橫向長(zhǎng)度由縱橫比計(jì)算得到吵取,如圖中綠色矩形框所示禽额;反之如紅色矩形框所示。
計(jì)算尺寸
在CameraPreview
中加入
Java
private void adjustDisplayRatio(int rotation) {
ViewGroup parent = ((ViewGroup) getParent());
Rect rect = new Rect();
parent.getLocalVisibleRect(rect);
int width = rect.width();
int height = rect.height();
Camera.Size previewSize = mCamera.getParameters().getPreviewSize();
int previewWidth;
int previewHeight;
if (rotation == 90 || rotation == 270) {
previewWidth = previewSize.height;
previewHeight = previewSize.width;
} else {
previewWidth = previewSize.width;
previewHeight = previewSize.height;
}
if (width * previewHeight > height * previewWidth) {
final int scaledChildWidth = previewWidth * height / previewHeight;
layout((width - scaledChildWidth) / 2, 0,
(width + scaledChildWidth) / 2, height);
} else {
final int scaledChildHeight = previewHeight * width / previewWidth;
layout(0, (height - scaledChildHeight) / 2,
width, (height + scaledChildHeight) / 2);
}
}
首先得到SurfaceView
的父級(jí)parent
(在這里就是FrameLayout
)海渊,記錄父級(jí)的長(zhǎng)和寬绵疲;然后得到預(yù)覽分辨率(需要注意處理橫屏和豎屏的問題),通過比較兩者的縱橫比臣疑,確定SurfaceView
的調(diào)整方法盔憨,計(jì)算出需要調(diào)整的長(zhǎng)度,并使SurfaceView
居中讯沈;最后通過layout()
將新的SurfaceView
的位置應(yīng)用到布局中郁岩,完成縱橫比的調(diào)整。
應(yīng)用新的尺寸
預(yù)覽分辨率的變化也會(huì)觸發(fā)surfaceChanged()
缺狠,所以調(diào)用adjustDisplayRatio()
也是在surfaceChanged()
中问慎。在surfaceChanged()
最后加入
Java
adjustDisplayRatio(rotation);
運(yùn)行試試
運(yùn)行APP,馬上就能發(fā)現(xiàn)相機(jī)預(yù)覽不再是充滿整個(gè)屏幕了挤茄,而是在邊界有一定的空白如叼,這樣就保持了與預(yù)覽分辨率相同的縱橫比。另外穷劈,如果在預(yù)覽的設(shè)置中修改了預(yù)覽分辨率笼恰,新的縱橫比也會(huì)立即應(yīng)用到屏幕顯示中。如下所示
美化
以上完成了本篇需要實(shí)現(xiàn)的全部功能歇终。這里提一點(diǎn)APP的美化社证,這里只是指出進(jìn)行美化的地方,具體細(xì)節(jié)參見DEMO评凝。
布局
首先可以將整個(gè)布局背景設(shè)置為黑色追葡,使布局空白地方為黑色,像電影一樣奕短。在activity_main
中宜肉,LinearLayout
中加入
XML
android:background="@color/black"
其次將控制部分背景顏色區(qū)分開,同時(shí)將控制部分預(yù)留一部分邊界翎碑,使布局結(jié)構(gòu)明顯谬返。在activity_main
中,RelativeLayout
中加入
XML
android:background="@color/darkGray"
android:padding="5dp"
偏好設(shè)置文本顏色
偏好設(shè)置文本顏色默認(rèn)為黑色杈女,對(duì)于相機(jī)預(yù)覽來說不容易分辨,將文本顏色設(shè)置為白色更好。這里我們給SettingsFragment
設(shè)置一個(gè)主題达椰。
在res/valuse/styles.xml
中翰蠢,resources
下加入
XML
<style name="PreferenceTheme">
<item name="android:textColor">#FFF</item>
<item name="android:textColorSecondary">#FFF</item>
</style>
創(chuàng)建一個(gè)名為PreferenceTheme
的主題,主題只是修改文本顏色為純白啰劲。
在SettingsFragment
的onCreate()
中梁沧,addPreferencesFromResource()
之后加入
XML
getActivity().setTheme(R.style.PreferenceTheme);
即應(yīng)用此主題。
這樣偏好設(shè)置文本顏色就變?yōu)榘咨擞悖菀讌^(qū)分多了吧廷支!
一點(diǎn)嘮叨
本篇完成后,這個(gè)相機(jī)APP就離實(shí)用的APP更近了一步栓辜,甚至已經(jīng)滿足了大多數(shù)的需求恋拍。其實(shí)在旋轉(zhuǎn)與縱橫比的實(shí)現(xiàn)上,需要仔細(xì)研究諸如繼承關(guān)系藕甩、生命周期等的問題施敢,但本文沒有談及這些內(nèi)容,還望想要了解其中細(xì)節(jié)的讀者自己去研究狭莱。在縱橫比的實(shí)現(xiàn)中僵娃,我采用的是在子類中獲取父類,整個(gè)過程在子類中完成腋妙;但典型的方法是在父類中獲取子類默怨,整個(gè)過程在父類中完成,兩種方法各有優(yōu)缺點(diǎn)骤素,但我認(rèn)為在子類中操作更好匙睹。
另外我嘗試過只使用一個(gè)布局文件,通過代碼只讓一部分的View
或Layout
旋轉(zhuǎn)谆甜,從而得到更好的相機(jī)預(yù)覽旋轉(zhuǎn)效果垃僚,但沒有成功。不過從一些典型的相機(jī)APP中可以發(fā)現(xiàn)规辱,還是可以只使用一個(gè)布局文件谆棺,但又能夠完美處理相機(jī)預(yù)覽旋轉(zhuǎn)的。參考中列出了相關(guān)的嘗試罕袋。
DEMO
本文實(shí)現(xiàn)的相機(jī)APP源碼都放在GitHub上改淑,如果需要請(qǐng)點(diǎn)擊zhantong/AndroidCamera-OrientationAndRatio。
參考
- Camera | Android Developers
- Camera.Parameters | Android Developers
- MediaRecorder | Android Developers
- ViewGroup | Android Developers
- Layouts | Android Developers
- Styles and Themes | Android Developers
- How to set Android camera orientation properly? - Stack Overflow
- android - Controlling the camera to take pictures in portrait does not rotate the final images - Stack Overflow
- rotation - Is it possible to just rotate some views in screen when rotate my android phone? - Stack Overflow