注意:本頁面所述是指 Camera2 套件。除非應用程式需要 Camera2 的特定低階功能,否則建議使用 CameraX。CameraX 和 Camera2 均支援 Android 5.0 (API 級別 21) 以上版本。
在 Android 上,相機和相機預覽畫面不一定相同 裝置。
無論裝置為何,攝影機都位於裝置上的固定位置 代表手機、平板電腦或電腦。裝置螢幕方向改變時, 相機方向變更。
因此,相機應用程式通常會假設 裝置方向和相機預覽畫面的顯示比例。如果 手機為直向,系統會假設相機預覽畫面的高度較高 而不是寬度當手機 (和相機) 旋轉為橫向時, 相機預覽畫面應大於高度。
但這些假設卻受到新的板型規格 (例如折疊式裝置) 的影響 裝置和顯示模式 例如 多視窗模式 和 多螢幕。 摺疊式裝置不會變更螢幕大小和長寬比 方向。多視窗模式會將相機應用程式限制在 螢幕,無論裝置螢幕方向為何,系統都會縮放相機預覽畫面。 多螢幕模式可讓您使用次要顯示器, 和主要顯示畫面的方向相同。
切換前後鏡頭
Android 相容性定義 指定相機影像感應器的方向「必須經過調整, 相機的尺寸會與螢幕的長邊對齊。也就是說 將裝置保持橫向,相機必須「必須」 橫向模式無論裝置本身是否自然 方向;適用於橫向和橫向裝置 設為直向及橫向的裝置)
相機依螢幕的排列方式可最大化相機的顯示區域 相機應用程式的觀景窗。此外,影像感應器一般會將資料輸出為 橫向顯示比例最多 4:3
相機感應器的自然方向為橫向。在圖 1 中,感應器 前置鏡頭 (相機指向與 螢幕) 將配合手機旋轉 270 度,符合 Android 相容性定義。
如要向應用程式公開感應器旋轉資訊,
camera2 API 包含
SENSOR_ORIENTATION
敬上
常數。對於大多數手機和平板電腦,裝置會回報感應器方向
前置鏡頭為 270 度 (從
裝置背面),適用於後置鏡頭 (對齊裝置背面的長邊)
長邊感應器。一般而言,筆記型電腦的攝影機會
感應器方向為 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"
)
若是大螢幕裝置,該視窗會處於嵌入式直向模式,以便適當調整方向
相機預覽畫面。
直向模式下,只有直向顯示的應用程式會加上黑邊 (插邊) 橫向和直向顯示 在橫向模式下,僅限橫向的應用程式仍會加上黑邊 螢幕顯示比例為直向相機圖片已旋轉並對齊 並裁剪成符合相機預覽畫面的顯示比例。 再縮放以填滿預覽畫面
相機圖片的顯示比例時,會觸發插邊模式 感應器與應用程式主要活動的顯示比例不相符。
在圖 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 程式庫提供一系列工具,可簡化相機預覽畫面的建立程序。 這款工具並非依附於 CameraX Core,因此你可以順暢將其整合至自家的 現有 Camera2 程式碼集
您不需要使用
Surface
敬上
您可以直接使用
CameraViewfinder
小工具,顯示 Camera2 的相機畫面。
CameraViewfinder
內部使用 TextureView
或 SurfaceView
即可顯示相機畫面,並對其套用必要的轉換
正確顯示觀景窗
包括修正長寬比、縮放和旋轉問題。
如要從 CameraViewfinder
物件要求介面,您需要
建立 ViewfinderSurfaceRequest
。
這項要求包含介面解析度和相機裝置的需求
資訊,來源:CameraCharacteristics
。
正在撥打 requestSurfaceAsync()
將要求傳送至介面提供者,可能是 TextureView
或
SurfaceView
,並獲得 Surface
的 ListenableFuture
。
正在撥打 markSurfaceSafeToRelease()
通知表面供應商表示不需要該途徑,和相關
保持在所需狀態
Kotlin
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)) }
Java
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
的比率,您可以縮放內容達到這個比例
更新元件中的 SurfaceView
onMeasure()
方法:
(computeRelativeRotation()
原始碼位於
下文的「相對輪播」)。
Kotlin
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) }
Java
@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
類別會計算
相機感應器的相對旋轉角度:
Kotlin
/** * 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 }
Java
/** * 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
敬上
並查看裝置旋轉狀態,
Display#getRotation()
的
onDisplayChanged()
回呼。
專屬資源
在 Android 10 以下版本中,只有多視窗模式下最頂端的活動
環境處於 RESUMED
狀態這會讓使用者感到困惑
系統並不會顯示重新啟用的活動。
Android 10 (API 級別 29) 導入了多視窗運作,其中所有可見活動
處於 RESUMED
狀態可見的活動仍可進入PAUSED
例如「透明活動」位在活動上方
該活動不成為焦點,例如子母畫面模式 (請參閱
支援子母畫面)。
應用程式使用相機、麥克風或任何專屬或
API 級別 29 以上的單例模式資源必須支援多視窗運作。適用對象
例如,如果三個重新啟用的活動想要使用相機,則只有其中一項可以
取得這項專屬資源每個活動都必須執行
onDisconnected()
敬上
回呼,以留意優先順序較高的相機存取權
活動。
若需更多資訊,請參閲 多視窗運作:
其他資源
- 如需 Camera2 範例,請參閱 Camera2Basic 應用程式 。
- 如要瞭解 CameraX 預覽用途,請參閱 CameraX 導入預覽。
- 如需 CameraX 相機預覽的實作範例,請參閱 CameraXBasic 託管在 GitHub 上
- 如要進一步瞭解 ChromeOS 的相機預覽畫面,請參閱 相機方向。
- 如要進一步瞭解如何為摺疊式裝置開發應用程式,請參閱 進一步瞭解摺疊式裝置。