最近開發一款自定義相機采集照片的demo,花了一個上午開發了一個在測試機上功能正常的apk連同測試機一起交付(需求方反饋沒有Android設備),然而晚上被喊去說是在華為暢玩某型號上預覽會變形,拍到的圖片邊界都移位了,只要加個班處理一下機型適配的問題。根據開發經驗,防止預覽圖像變形的終極奧義就是保持照相機硬件支持的某個size(每個特定的手機都有其支持的camera分辨率)、屏幕寬高比、界面上SurfaceView的寬高比盡量一致,同時保持拍照圖與預覽圖分辨率一致,拍出來的照片就和預覽看到的相同。
下面給出實現代碼:
private fun getOptimalPreviewSize(sizes: List<Camera.Size>?, w: Int, h: Int): Camera.Size? { val ASPECT_TOLERANCE = 0.1 val targetRatio = h.toDouble() / w if (sizes == null) return null var optimalSize: Camera.Size? = null var minDiff = java.lang.Double.MAX_VALUE for (size in sizes) { val ratio = size.width.toDouble() / size.height if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue if (Math.abs(size.height - h) < minDiff) { optimalSize = size minDiff = Math.abs(size.height - h).toDouble() } } if (optimalSize == null) { minDiff = java.lang.Double.MAX_VALUE for (size in sizes) { if (Math.abs(size.height - h) < minDiff) { optimalSize = size minDiff = Math.abs(size.height - h).toDouble() } } } return optimalSize }
其中第一個參數sizes是camera支持的預覽size列表,第二個是View的寬,第三個是View的高,由於我這里的surfaceview是全屏的,所以傳入的寬和高可以是屏幕的寬和高
下面給出完整代碼:
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_camera) screenWidth = ScreenUtil.getWindowWidth(this) screenHeight = ScreenUtil.getWindowHeigh(this) camera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK) surface.holder.addCallback(this) } override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) { camera?.setPreviewDisplay(holder) camera?.startPreview() } override fun surfaceDestroyed(holder: SurfaceHolder?) { } override fun surfaceCreated(holder: SurfaceHolder?) { if (camera == null) { camera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK) } val parameters = camera?.parameters var sizes = parameters!!.supportedPreviewSizes var preSize = getOptimalPreviewSize(sizes, screenWidth, screenHeight) parameters.setPreviewSize(preSize!!.width, preSize.height) //重新給定surfaceView寬高 surface.resize(preSize.width, preSize.height) if (parameters.supportedFocusModes!!.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) { parameters.focusMode = Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO } parameters.sceneMode = Camera.Parameters.SCENE_MODE_AUTO //保持picturesize與presize一致 parameters.setPictureSize(preSize.width, preSize.height) parameters.previewFormat = ImageFormat.NV21 camera?.parameters = parameters camera?.setPreviewDisplay(holder) camera?.startPreview() } private fun getOptimalPreviewSize(sizes: List<Camera.Size>?, w: Int, h: Int): Camera.Size? { val ASPECT_TOLERANCE = 0.1 val targetRatio = h.toDouble() / w if (sizes == null) return null var optimalSize: Camera.Size? = null var minDiff = java.lang.Double.MAX_VALUE for (size in sizes) { val ratio = size.width.toDouble() / size.height if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue if (Math.abs(size.height - h) < minDiff) { optimalSize = size minDiff = Math.abs(size.height - h).toDouble() } } if (optimalSize == null) { minDiff = java.lang.Double.MAX_VALUE for (size in sizes) { if (Math.abs(size.height - h) < minDiff) { optimalSize = size minDiff = Math.abs(size.height - h).toDouble() } } } return optimalSize } override fun onWindowFocusChanged(hasFocus: Boolean) { val decorView = this@CameraActivity.window.decorView decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY) } override fun onPause() { super.onPause() camera?.stopPreview() } override fun onDestroy() { super.onDestroy() camera?.setPreviewCallback(null) camera?.release() }
