注意:本頁面所述是指 Camera2 套件。除非應用程式需要 Camera2 的特定低階功能,否則建議使用 CameraX。CameraX 和 Camera2 均支援 Android 5.0 (API 級別 21) 以上版本。
在 Android 上,相機和相機預覽畫面不一定相同 裝置。
無論裝置為何,攝影機都位於裝置上的固定位置 代表手機、平板電腦或電腦。裝置方向變更時,攝影機方向也會變更。
因此,相機應用程式通常會假設 裝置方向和相機預覽畫面的顯示比例。如果 手機為直向,系統會假設相機預覽畫面的高度較高 而不是寬度當手機 (和相機) 旋轉為橫向時,相機預覽畫面的寬度應大於高度。
不過,這些假設受到了新的板型規格 (例如折疊式裝置) 和顯示模式 (例如多視窗和多螢幕) 的挑戰。折疊式裝置可在不變更螢幕方向的情況下,變更顯示大小和顯示比例。多視窗模式會將相機應用程式限制在 螢幕,無論裝置螢幕方向為何,系統都會縮放相機預覽畫面。 多螢幕模式可讓您使用次要螢幕,但次要螢幕的方向可能與主要螢幕不同。
切換前後鏡頭
Android 相容性定義 指定相機影像感應器的方向「必須經過調整, 相機的尺寸會與螢幕的長邊對齊。也就是說,當裝置以橫向方向握持時,相機就必須以橫向方向拍攝影像。無論裝置的自然方向為何,這項規則都適用於橫向為主的裝置和直向為主的裝置。」
相機依螢幕的排列方式可最大化相機的顯示區域 相機應用程式的觀景窗。此外,影像感應器一般會將資料輸出為 橫向顯示比例最多 4:3

相機感應器的自然方向為橫向。在圖 1 中,感應器 前置鏡頭 (相機指向與 螢幕) 將配合手機旋轉 270 度,符合 Android 相容性定義。
如要向應用程式公開感應器旋轉資訊,
camera2 API 包含
SENSOR_ORIENTATION
常數。對於大多數手機和平板電腦,裝置會回報前置鏡頭的傳感器方向為 270 度,後置鏡頭的傳感器方向為 90 度 (從裝置背面觀看的角度),這會讓傳感器的長邊與裝置的長邊對齊。筆記型電腦相機通常會回報 0 或 180 度的感應器方向。
因為相機的影像感應器會將其資料 (影像緩衝區) 在
感應器的自然方向 (橫向),圖片緩衝區就必須旋轉圖像緩衝區
SENSOR_ORIENTATION
為相機預覽畫面指定的度數
會以自然方向直立的方式呈現對於前置鏡頭
旋轉是逆時針方向以順時針方向使用後置鏡頭
舉例來說,針對圖 1 中的前置鏡頭,相機感應器產生的影像緩衝區如下所示:

圖片必須逆時針旋轉 270 度,才能讓預覽畫面與裝置方向一致:

後置鏡頭會產生方向相同的影像緩衝區
做為上述緩衝區,但 SENSOR_ORIENTATION
是 90 度。因此,緩衝區會順時針旋轉 90 度。
裝置旋轉
裝置旋轉是指裝置從自然旋轉的角度 方向。舉例來說,橫向擺放的手機會旋轉 90 度或 270 度,具體取決於旋轉方向。
相機感應器圖像緩衝區必須旋轉的度數必須與裝置旋轉度數相同 (除了感應器方向的度數),相機預覽畫面才能顯示為直立。
方向計算
相機預覽畫面的正確方向會考量感應器方向和裝置旋轉。
可使用 以下公式:
rotation = (sensorOrientationDegrees - deviceOrientationDegrees * sign + 360) % 360
其中 sign
是 1
(前置鏡頭) 或 -1
(後置鏡頭)。
對於前置鏡頭,圖片緩衝區會逆時針旋轉 (從 感應器的自然方向)。對於後置鏡頭,感應器圖片緩衝區會順時針旋轉。
運算式 deviceOrientationDegrees * sign + 360
會將後置鏡頭的裝置旋轉方向從逆時針轉為順時針 (例如將逆時針 270 度轉為順時針 90 度)。模運算會將結果縮放至小於 360 度的值 (例如將 540 度旋轉縮放至 180 度)。
不同 API 回報的裝置旋轉資訊會有不同:
Display#getRotation()
提供裝置逆時針旋轉 (從使用者的角度) )。這個值會直接插入上述公式。OrientationEventListener#onOrientationChanged()
會傳回裝置的順時針旋轉方向 (從使用者角度來看)。針對要用於上方公式的值表示否定。
前置鏡頭

以下是相機感應器產生的圖片緩衝區 (如圖 2 所示):

緩衝區必須逆時針旋轉 270 度,以便調整感應器方向 (請參閱上方的「相機方向」):

接著,緩衝區會逆時針旋轉 90 度 進而使裝置螢幕的正確方向 相機預覽畫面 (如圖 2 所示):

以下是將相機轉向右側,以橫向模式拍攝的畫面:

以下是圖片緩衝區:

緩衝區必須逆時針旋轉 270 度,以便調整感應器方向:

接著將緩衝區逆時針旋轉 270 度 如何控制裝置旋轉方向

後置鏡頭
後置鏡頭的感應器方向通常為 90 度 (從裝置背面觀看)。指定相機預覽畫面方向時, 感應器圖片緩衝區會依據感應器旋轉程度順時針旋轉 (而不是以逆時針方向這類前置鏡頭),然後拍攝圖像 緩衝區會依照裝置旋轉程度逆時針旋轉。

以下是相機感應器的圖片緩衝區 (如圖 4 所示):

緩衝區必須順時針旋轉 90 度,以便調整感應器方向:

接著,緩衝區會逆時針旋轉 270 度,以因應裝置旋轉:

顯示比例
螢幕顯示比例會在裝置螢幕方向變更時變動,但當裝置螢幕方向改變時, 在多視窗模式下調整視窗大小時,折疊式裝置可折疊及展開 以及應用程式在次要顯示器上開啟的時間。
相機感應器圖片緩衝區必須方向及縮放,以符合 觀景窗 UI 元素的方向和顯示比例 無論是否變更裝置,系統都會動態變更螢幕方向 方向。
在新的板型規格或多視窗/多螢幕環境中,如果應用程式假設相機預覽畫面的方向與裝置 (直向或橫向) 相同,預覽畫面可能會顯示錯誤的方向或縮放比例,甚至兩者皆有錯誤。

在圖 5 中,應用程式誤以為裝置逆時針旋轉 90 度,因此將預覽畫面旋轉相同角度。

在圖 6 中,應用程式未將圖片緩衝區的長寬比調整為 使其能夠調整大小,以符合相機預覽 UI 的新尺寸 元素。
固定方向的相機應用程式通常會在摺疊式裝置和其他大螢幕裝置 (例如筆記型電腦) 上發生問題:

在圖 7 中,由於應用程式的方向,相機應用程式的 UI 會傾斜 僅限於直向。觀景窗圖片相對於相機感應器的方向正確。
插邊直向模式
不支援多視窗模式的相機應用程式
(resizeableActivity="false"
)
並限制螢幕方向
(screenOrientation="portrait"
)。
或 screenOrientation="landscape"
)
若是大螢幕裝置,該視窗會處於嵌入式直向模式,以便適當調整方向
相機預覽畫面。
即使顯示比例為橫向,也能在直向模式下以邊框 (內嵌) 方式顯示僅限直向的應用程式。即使螢幕顯示比例為直向,橫向模式的應用程式仍會加上黑邊。相機圖片會旋轉至與應用程式 UI 對齊,並裁剪為相機預覽畫面的顯示比例,然後縮放至填滿預覽畫面。
相機圖片的顯示比例時,會觸發插邊模式 感應器與應用程式主要活動的顯示比例不相符。

在圖 8 中,僅限直向的相機應用程式已旋轉,以便在筆電螢幕上顯示直立的 UI。應用程式有上下黑邊, 顯示比例。相機 系統已旋轉預覽圖片,以填補應用程式的使用者介面旋轉 (因為 插邊模式),圖片也經過裁剪並縮放,以符合 減少螢幕方向。
旋轉、裁剪、縮放
螢幕上僅限直向的相機應用程式叫用插邊模式 長寬比為橫向的影片:

在直向模式下,應用程式會加上黑邊:

相機圖像會旋轉 90 度,以便調整應用程式的方向:

系統會將圖片裁剪為相機預覽畫面的顯示比例,然後縮放至填滿預覽畫面 (視野範圍會縮小):

在摺疊式裝置上,相機感應器的方向可以是直向 螢幕顯示比例為橫向時:

因為相機預覽畫面會旋轉以配合感應器方向 圖片的方向正確,可在觀景窗中正確顯示,但僅限直向應用程式 有些不同凡響
在內嵌直向模式中,您只需要在直向模式下為應用程式加上黑邊,即可正確調整應用程式和相機預覽畫面的方向:

API
自 Android 12 (API 級別 31) 起,應用程式也可以明確控制插邊直向
管理模式
SCALER_ROTATE_AND_CROP
CaptureRequest
的屬性
類別
預設值為 SCALER_ROTATE_AND_CROP_AUTO
,可讓系統叫用內嵌直向模式。SCALER_ROTATE_AND_CROP_90
是上述所述內嵌直向模式的行為。
並非所有裝置都支援所有 SCALER_ROTATE_AND_CROP
值。如需支援的值清單,請參閱 CameraCharacteristics#SCALER_AVAILABLE_ROTATE_AND_CROP_MODES
。
CameraX
Jetpack CameraX 程式庫 打造出可以配合感應器方向和感應器的相機觀景窗 旋轉一項簡單的工作
PreviewView
版面配置元素
建立相機預覽畫面,並根據感應器方向自動調整。
裝置旋轉和縮放PreviewView
會維持
方法是套用
FILL_CENTER
縮放類型,這會將圖片置中,但可能會配合尺寸裁剪
「PreviewView
」。如要將相機影像套用上下黑邊,請將縮放類型設為 FIT_CENTER
。
如要瞭解使用 PreviewView
建立相機預覽的基本概念,請參閱
導入預覽。
如需完整導入範例,請參閱
CameraXBasic
託管在 GitHub 上
CameraViewfinder
與預覽用途類似,CameraViewfinder 程式庫提供一組工具,可簡化相機預覽畫面的建立作業。這項功能不依賴 CameraX Core,因此您可以將其完美整合至現有的 Camera2 程式碼庫。
您可以使用 CameraViewfinder
小工具,而非直接使用 Surface
,顯示 Camera2 的相機畫面。
CameraViewfinder
在內部使用 TextureView
或 SurfaceView
來顯示相機動態饋給,並對其套用必要的轉換,以便正確顯示觀景窗。包括修正顯示比例、縮放和旋轉。
如要從 CameraViewfinder
物件要求介面,您需要
建立 ViewfinderSurfaceRequest
。
這項要求包含 CameraCharacteristics
的表面解析度和相機裝置資訊相關規定。
呼叫 requestSurfaceAsync()
會將要求傳送至表面供應器 (即 TextureView
或 SurfaceView
),並取得 Surface
的 ListenableFuture
。
正在撥打 markSurfaceSafeToRelease()
通知表面供應商表示不需要該途徑,和相關
保持在所需狀態
fun startCamera(){ val previewResolution = Size(width, height) val viewfinderSurfaceRequest = ViewfinderSurfaceRequest(previewResolution, characteristics) val surfaceListenableFuture = cameraViewfinder.requestSurfaceAsync(viewfinderSurfaceRequest) Futures.addCallback(surfaceListenableFuture, object : FutureCallback<Surface> { override fun onSuccess(surface: Surface) { /* create a CaptureSession using this surface as usual */ } override fun onFailure(t: Throwable) { /* something went wrong */} }, ContextCompat.getMainExecutor(context)) }
void startCamera(){ Size previewResolution = new Size(width, height); ViewfinderSurfaceRequest viewfinderSurfaceRequest = new ViewfinderSurfaceRequest(previewResolution, characteristics); ListenableFuture<Surface> surfaceListenableFuture = cameraViewfinder.requestSurfaceAsync(viewfinderSurfaceRequest); Futures.addCallback(surfaceListenableFuture, new FutureCallback<Surface>() { @Override public void onSuccess(Surface result) { /* create a CaptureSession using this surface as usual */ } @Override public void onFailure(Throwable t) { /* something went wrong */} }, ContextCompat.getMainExecutor(context)); }
SurfaceView
SurfaceView
是
在沒有預覽的情況下
不需要以動畫呈現
SurfaceView
會自動旋轉相機感應器圖片緩衝區,確保符合圖像
包括感應器方向和裝置
並輪替金鑰不過,圖片緩衝區會縮放以符合 SurfaceView
不考慮顯示比例。
您必須確保圖片緩衝區的顯示比例與 SurfaceView
的顯示比例相符,方法是在元件 onMeasure()
方法中縮放 SurfaceView
的內容:
(computeRelativeRotation()
原始碼位於下方的「相對旋轉」)。
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { val width = MeasureSpec.getSize(widthMeasureSpec) val height = MeasureSpec.getSize(heightMeasureSpec) val relativeRotation = computeRelativeRotation(characteristics, surfaceRotationDegrees) if (previewWidth > 0f && previewHeight > 0f) { /* Scale factor required to scale the preview to its original size on the x-axis. */ val scaleX = if (relativeRotation % 180 == 0) { width.toFloat() / previewWidth } else { width.toFloat() / previewHeight } /* Scale factor required to scale the preview to its original size on the y-axis. */ val scaleY = if (relativeRotation % 180 == 0) { height.toFloat() / previewHeight } else { height.toFloat() / previewWidth } /* Scale factor required to fit the preview to the SurfaceView size. */ val finalScale = min(scaleX, scaleY) setScaleX(1 / scaleX * finalScale) setScaleY(1 / scaleY * finalScale) } setMeasuredDimension(width, height) }
@Override void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); int relativeRotation = computeRelativeRotation(characteristics, surfaceRotationDegrees); if (previewWidth > 0f && previewHeight > 0f) { /* Scale factor required to scale the preview to its original size on the x-axis. */ float scaleX = (relativeRotation % 180 == 0) ? (float) width / previewWidth : (float) width / previewHeight; /* Scale factor required to scale the preview to its original size on the y-axis. */ float scaleY = (relativeRotation % 180 == 0) ? (float) height / previewHeight : (float) height / previewWidth; /* Scale factor required to fit the preview to the SurfaceView size. */ float finalScale = Math.min(scaleX, scaleY); setScaleX(1 / scaleX * finalScale); setScaleY(1 / scaleY * finalScale); } setMeasuredDimension(width, height); }
如要進一步瞭解如何將 SurfaceView
實作為相機預覽,請參閱
相機方向。
紋理檢視
TextureView
的效能不如 SurfaceView
,且需要更多工作,但 TextureView
可讓您全面控管相機預覽畫面。
TextureView
會根據感應器方向旋轉感應器圖片緩衝區,但不會處理裝置旋轉或預覽縮放。
您可以在 Matrix 轉換中編碼縮放和旋轉功能。如要瞭解如何正確縮放及旋轉 TextureView
,請參閱「在相機應用程式中支援可調整大小的介面」
相對旋轉
相機感應器的相對旋轉角度,是指相機感應器輸出內容與裝置方向對齊時,所需的旋轉角度。
相對旋轉功能由 SurfaceView
和 TextureView
等元件使用
來決定預覽圖片的 x 和 y 縮放比例係數。作用
指定感應器圖片緩衝區的旋轉角度。
CameraCharacteristics
和
Surface
類別會計算
相機感應器的相對旋轉角度:
/** * Computes rotation required to transform the camera sensor output orientation to the * device's current orientation in degrees. * * @param characteristics The CameraCharacteristics to query for the sensor orientation. * @param surfaceRotationDegrees The current device orientation as a Surface constant. * @return Relative rotation of the camera sensor output. */ public fun computeRelativeRotation( characteristics: CameraCharacteristics, surfaceRotationDegrees: Int ): Int { val sensorOrientationDegrees = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)!! // Reverse device orientation for back-facing cameras. val sign = if (characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT ) 1 else -1 // Calculate desired orientation relative to camera orientation to make // the image upright relative to the device orientation. return (sensorOrientationDegrees - surfaceRotationDegrees * sign + 360) % 360 }
/** * Computes rotation required to transform the camera sensor output orientation to the * device's current orientation in degrees. * * @param characteristics The CameraCharacteristics to query for the sensor orientation. * @param surfaceRotationDegrees The current device orientation as a Surface constant. * @return Relative rotation of the camera sensor output. */ public int computeRelativeRotation( CameraCharacteristics characteristics, int surfaceRotationDegrees ){ Integer sensorOrientationDegrees = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); // Reverse device orientation for back-facing cameras. int sign = characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT ? 1 : -1; // Calculate desired orientation relative to camera orientation to make // the image upright relative to the device orientation. return (sensorOrientationDegrees - surfaceRotationDegrees * sign + 360) % 360; }
視窗指標
請勿使用螢幕大小來判斷相機觀景窗的尺寸;相機應用程式可能會在螢幕的某個部分執行,例如行動裝置的多視窗模式或 ChromeOS 的自由模式。
WindowManager#getCurrentWindowMetrics()
(在 API 級別 30 中新增) 會傳回應用程式視窗的大小,
螢幕大小。Jetpack WindowManager 程式庫方法 WindowMetricsCalculator#computeCurrentWindowMetrics()
和 WindowInfoTracker#currentWindowMetrics()
提供類似的支援,可回溯相容至 API 級別 14。
旋轉 180 度
裝置旋轉 180 度 (例如從自然方向旋轉到倒置的自然方向) 不會觸發 onConfigurationChanged()
回呼。因此,相機預覽畫面可能會上下顛倒。
如要偵測 180 度旋轉,請實作 DisplayListener
,並在 onDisplayChanged()
回呼中呼叫 Display#getRotation()
,藉此檢查裝置旋轉。
專屬資源
在 Android 10 以下版本中,只有多視窗模式下最頂端的活動
環境處於 RESUMED
狀態這會讓使用者感到困惑,因為系統並未指出要恢復哪個活動。
Android 10 (API 級別 29) 推出了多重繼續功能,其中所有顯示的活動都處於 RESUMED
狀態。舉例來說,如果活動上有公開活動,或是活動無法聚焦 (例如在子母畫面模式下),可見的活動仍可進入 PAUSED
狀態 (請參閱「子母畫面支援」)。
在 API 級別 29 以上版本中,使用相機、麥克風或任何專屬或單例資源的應用程式都必須支援多工處理。適用對象
例如,如果三個重新啟用的活動想要使用相機,則只有其中一項可以
取得這項專屬資源每個活動都必須執行
onDisconnected()
回呼,以留意優先順序較高的相機存取權
活動。
若需更多資訊,請參閲 多視窗運作:
其他資源
- 如需 Camera2 範例,請參閱 Camera2Basic 應用程式 。
- 如要瞭解 CameraX 預覽用途,請參閱 CameraX 導入預覽。
- 如需 CameraX 相機預覽畫面實作範例,請參閱 GitHub 上的 CameraXBasic 存放區。
- 如要瞭解 ChromeOS 上的相機預覽功能,請參閱「相機方向」。
- 如要瞭解如何為摺疊式裝置開發應用程式,請參閱「進一步瞭解摺疊式裝置」。