fix: Fix PreviewView stretching on Android (now finally a real fix) (#2564)
				
					
				
			* fix: Only resolve once SurfaceHolder actually resized * fix: Fix onMeasure not being called for `PreviewView` * fix: Auto-trigger layout computation on Surface Change * fix: Add proper LayoutParams to `PreviewView` * Format
This commit is contained in:
		@@ -4,6 +4,7 @@ import android.annotation.SuppressLint
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.hardware.camera2.CameraManager
 | 
			
		||||
import android.util.Log
 | 
			
		||||
import android.view.Gravity
 | 
			
		||||
import android.view.ScaleGestureDetector
 | 
			
		||||
import android.widget.FrameLayout
 | 
			
		||||
import com.facebook.react.bridge.ReadableMap
 | 
			
		||||
@@ -110,6 +111,11 @@ class CameraView(context: Context) :
 | 
			
		||||
    clipToOutline = true
 | 
			
		||||
    cameraSession = CameraSession(context, cameraManager, this)
 | 
			
		||||
    previewView = cameraSession.createPreviewView(context)
 | 
			
		||||
    previewView.layoutParams = LayoutParams(
 | 
			
		||||
      LayoutParams.MATCH_PARENT,
 | 
			
		||||
      LayoutParams.MATCH_PARENT,
 | 
			
		||||
      Gravity.CENTER
 | 
			
		||||
    )
 | 
			
		||||
    addView(previewView)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,7 @@ import android.view.SurfaceView
 | 
			
		||||
import android.widget.FrameLayout
 | 
			
		||||
import com.facebook.react.bridge.UiThreadUtil
 | 
			
		||||
import com.mrousavy.camera.extensions.getMaximumPreviewSize
 | 
			
		||||
import com.mrousavy.camera.extensions.installHierarchyFitter
 | 
			
		||||
import com.mrousavy.camera.extensions.resize
 | 
			
		||||
import com.mrousavy.camera.types.Orientation
 | 
			
		||||
import com.mrousavy.camera.types.ResizeMode
 | 
			
		||||
@@ -19,7 +20,9 @@ import kotlinx.coroutines.Dispatchers
 | 
			
		||||
import kotlinx.coroutines.withContext
 | 
			
		||||
 | 
			
		||||
@SuppressLint("ViewConstructor")
 | 
			
		||||
class PreviewView(context: Context, callback: SurfaceHolder.Callback) : SurfaceView(context) {
 | 
			
		||||
class PreviewView(context: Context, callback: SurfaceHolder.Callback) :
 | 
			
		||||
  FrameLayout(context),
 | 
			
		||||
  SurfaceHolder.Callback {
 | 
			
		||||
  var size: Size = getMaximumPreviewSize()
 | 
			
		||||
    private set
 | 
			
		||||
  var resizeMode: ResizeMode = ResizeMode.COVER
 | 
			
		||||
@@ -31,28 +34,6 @@ class PreviewView(context: Context, callback: SurfaceHolder.Callback) : SurfaceV
 | 
			
		||||
        invalidate()
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  init {
 | 
			
		||||
    Log.i(TAG, "Creating PreviewView...")
 | 
			
		||||
    layoutParams = FrameLayout.LayoutParams(
 | 
			
		||||
      FrameLayout.LayoutParams.MATCH_PARENT,
 | 
			
		||||
      FrameLayout.LayoutParams.MATCH_PARENT,
 | 
			
		||||
      Gravity.CENTER
 | 
			
		||||
    )
 | 
			
		||||
    holder.setKeepScreenOn(true)
 | 
			
		||||
    holder.addCallback(callback)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  suspend fun setSurfaceSize(width: Int, height: Int) {
 | 
			
		||||
    withContext(Dispatchers.Main) {
 | 
			
		||||
      size = Size(width, height)
 | 
			
		||||
      Log.i(TAG, "Setting PreviewView Surface Size to $size...")
 | 
			
		||||
      requestLayout()
 | 
			
		||||
      invalidate()
 | 
			
		||||
      holder.resize(width, height)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private val viewSize: Size
 | 
			
		||||
    get() {
 | 
			
		||||
      val displayMetrics = context.resources.displayMetrics
 | 
			
		||||
@@ -60,6 +41,36 @@ class PreviewView(context: Context, callback: SurfaceHolder.Callback) : SurfaceV
 | 
			
		||||
      val dpY = height / displayMetrics.density
 | 
			
		||||
      return Size(dpX.toInt(), dpY.toInt())
 | 
			
		||||
    }
 | 
			
		||||
  private val surfaceView = SurfaceView(context)
 | 
			
		||||
 | 
			
		||||
  init {
 | 
			
		||||
    Log.i(TAG, "Creating PreviewView...")
 | 
			
		||||
    this.installHierarchyFitter()
 | 
			
		||||
    surfaceView.layoutParams = LayoutParams(
 | 
			
		||||
      LayoutParams.MATCH_PARENT,
 | 
			
		||||
      LayoutParams.MATCH_PARENT,
 | 
			
		||||
      Gravity.CENTER
 | 
			
		||||
    )
 | 
			
		||||
    surfaceView.holder.setKeepScreenOn(true)
 | 
			
		||||
    surfaceView.holder.addCallback(this)
 | 
			
		||||
    surfaceView.holder.addCallback(callback)
 | 
			
		||||
    addView(surfaceView)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun surfaceCreated(holder: SurfaceHolder) = Unit
 | 
			
		||||
  override fun surfaceDestroyed(holder: SurfaceHolder) = Unit
 | 
			
		||||
  override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
 | 
			
		||||
    Log.i(TAG, "PreviewView Surface size changed: $size -> ${width}x$height, re-computing layout...")
 | 
			
		||||
    size = Size(width, height)
 | 
			
		||||
    requestLayout()
 | 
			
		||||
    invalidate()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  suspend fun setSurfaceSize(width: Int, height: Int) {
 | 
			
		||||
    withContext(Dispatchers.Main) {
 | 
			
		||||
      surfaceView.holder.resize(width, height)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fun convertLayerPointToCameraCoordinates(point: Point, cameraDeviceDetails: CameraDeviceDetails): Point {
 | 
			
		||||
    val sensorOrientation = Orientation.fromRotationDegrees(cameraDeviceDetails.sensorOrientation)
 | 
			
		||||
 
 | 
			
		||||
@@ -6,32 +6,36 @@ import androidx.annotation.UiThread
 | 
			
		||||
import kotlin.coroutines.resume
 | 
			
		||||
import kotlinx.coroutines.suspendCancellableCoroutine
 | 
			
		||||
 | 
			
		||||
private const val TAG = "SurfaceHolder"
 | 
			
		||||
 | 
			
		||||
@UiThread
 | 
			
		||||
suspend fun SurfaceHolder.resize(width: Int, height: Int) {
 | 
			
		||||
suspend fun SurfaceHolder.resize(targetWidth: Int, targetHeight: Int) {
 | 
			
		||||
  return suspendCancellableCoroutine { continuation ->
 | 
			
		||||
    val currentSize = this.surfaceFrame
 | 
			
		||||
    if (currentSize.width() == width && currentSize.height() == height) {
 | 
			
		||||
    if (currentSize.width() == targetWidth && currentSize.height() == targetHeight) {
 | 
			
		||||
      // Already in target size
 | 
			
		||||
      continuation.resume(Unit)
 | 
			
		||||
      return@suspendCancellableCoroutine
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Log.i("SurfaceHolder", "Resizing SurfaceHolder to $width x $height...")
 | 
			
		||||
    Log.i(TAG, "Resizing SurfaceHolder to $targetWidth x $targetHeight...")
 | 
			
		||||
 | 
			
		||||
    val callback = object : SurfaceHolder.Callback {
 | 
			
		||||
      override fun surfaceCreated(holder: SurfaceHolder) = Unit
 | 
			
		||||
      override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
 | 
			
		||||
        if (width == targetWidth && height == targetHeight) {
 | 
			
		||||
          holder.removeCallback(this)
 | 
			
		||||
        Log.i("SurfaceHolder", "Resized SurfaceHolder to $width x $height!")
 | 
			
		||||
          Log.i(TAG, "Resized SurfaceHolder to $width x $height!")
 | 
			
		||||
          continuation.resume(Unit)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      override fun surfaceDestroyed(holder: SurfaceHolder) {
 | 
			
		||||
        holder.removeCallback(this)
 | 
			
		||||
        Log.e("SurfaceHolder", "Failed to resize SurfaceHolder to $width x $height!")
 | 
			
		||||
        Log.e(TAG, "Failed to resize SurfaceHolder to $targetWidth x $targetHeight!")
 | 
			
		||||
        continuation.cancel(Error("Tried to resize SurfaceView, but Surface has been destroyed!"))
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    this.addCallback(callback)
 | 
			
		||||
    this.setFixedSize(width, height)
 | 
			
		||||
    this.setFixedSize(targetWidth, targetHeight)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user