I am using surfaceview inside a fragment, however the methods of the surfaceHolder.Callback(surfaceCreated,surfaceChanged, and surfaceDestroyed) seems to overrides nothing.and I also get the following error " object is not abstract and does not implement abstract member".
Below is the error in the image.
e: /Users/malorimorow/AndroidStudioProjects/CapstoneProject/app/src/main/java/com/example/capstoneproject/main/ScannerFragment.kt: (158, 32): Object is not abstract and does not implement abstract member public abstract fun surfaceChanged(p0: SurfaceHolder, p1: Int, p2: Int, p3: Int): Unit defined in android.view.SurfaceHolder.Callback
Below is the source code and you can find the error in the overlay.apply method.I am new to kotlin and would greatly appreciate the help.
class ScannerFragment : Fragment() {
companion object {
fun newInstance() = ScannerFragment()
// We only need to analyze the part of the image that has text, so we set crop percentages
// to avoid analyze the entire image from the live camera feed.
const val DESIRED_WIDTH_CROP_PERCENT = 8
const val DESIRED_HEIGHT_CROP_PERCENT = 74
// This is an arbitrary number we are using to keep tab of the permission
// request. Where an app has multiple context for requesting permission,
// this can help differentiate the different contexts
private const val REQUEST_CODE_PERMISSIONS = 10
// This is an array of all the permission specified in the manifest
private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA)
private const val RATIO_4_3_VALUE = 4.0 / 3.0
private const val RATIO_16_9_VALUE = 16.0 / 9.0
private const val TAG = "ScannerFragment"
}
private var displayId: Int = -1
private val viewModel: MainViewModel by viewModels()
private var cameraProvider: ProcessCameraProvider? = null
private var camera: Camera? = null
private var imageAnalyzer: ImageAnalysis? = null
private lateinit var container: ConstraintLayout
private lateinit var viewFinder: PreviewView
/** Blocking camera operations are performed using this executor */
private lateinit var cameraExecutor: ExecutorService
private lateinit var scopedExecutor: ScopedExecutor
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return inflater.inflate(R.layout.fragment_scanner, container, false)
}
override fun onDestroyView() {
super.onDestroyView()
// Shut down our background executor
cameraExecutor.shutdown()
scopedExecutor.shutdown()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
container = view as ConstraintLayout
viewFinder = container.findViewById(R.id.viewfinder)
// Initialize our background executor
cameraExecutor = Executors.newSingleThreadExecutor()
scopedExecutor = ScopedExecutor(cameraExecutor)
// Request camera permissions
if (allPermissionsGranted()) {
// Wait for the views to be properly laid out
viewFinder.post {
// Keep track of the display in which this view is attached
displayId = viewFinder.display.displayId
// Set up the camera and its use cases
setUpCamera()
}
} else {
requestPermissions(
REQUIRED_PERMISSIONS,
REQUEST_CODE_PERMISSIONS
)
}
// Get available language list and set up the target language spinner
// with default selections.
val adapter = ArrayAdapter(
requireContext(),
android.R.layout.simple_spinner_dropdown_item, viewModel.availableLanguages
)
targetLangSelector.adapter = adapter
targetLangSelector.setSelection(adapter.getPosition(Language("en")))
targetLangSelector.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(
parent: AdapterView<*>,
view: View?,
position: Int,
id: Long
) {
viewModel.targetLang.value = adapter.getItem(position)
}
override fun onNothingSelected(parent: AdapterView<*>) {}
}
viewModel.sourceLang.observe(viewLifecycleOwner, Observer { srcLang.text = it.displayName })
viewModel.translatedText.observe(viewLifecycleOwner, Observer { resultOrError ->
resultOrError?.let {
if (it.error != null) {
translatedText.error = resultOrError.error?.localizedMessage
} else {
translatedText.text = resultOrError.result
}
}
})
viewModel.modelDownloading.observe(viewLifecycleOwner, Observer { isDownloading ->
progressBar.visibility = if (isDownloading) {
View.VISIBLE
} else {
View.INVISIBLE
}
progressText.visibility = progressBar.visibility
})
overlay.apply {
setZOrderOnTop(true)
holder.setFormat(PixelFormat.TRANSPARENT)
holder.addCallback(object : SurfaceHolder.Callback {
override fun surfaceChanged(
holder: SurfaceHolder?,
format: Int,
width: Int,
height: Int
) {
}
override fun surfaceDestroyed(holder: SurfaceHolder?) {
}
override fun surfaceCreated(holder: SurfaceHolder?) {
holder?.let { drawOverlay(it,
DESIRED_HEIGHT_CROP_PERCENT,
DESIRED_WIDTH_CROP_PERCENT
) }
}
})
}
}
/** Initialize CameraX, and prepare to bind the camera use cases */
private fun setUpCamera() {
val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())
cameraProviderFuture.addListener(Runnable {
// CameraProvider
cameraProvider = cameraProviderFuture.get()
// Build and bind the camera use cases
bindCameraUseCases()
}, ContextCompat.getMainExecutor(requireContext()))
}
private fun bindCameraUseCases() {
val cameraProvider = cameraProvider
?: throw IllegalStateException("Camera initialization failed.")
// Get screen metrics used to setup camera for full screen resolution
val metrics = DisplayMetrics().also { viewFinder.display.getRealMetrics(it) }
Log.d(TAG, "Screen metrics: ${metrics.widthPixels} x ${metrics.heightPixels}")
val screenAspectRatio = aspectRatio(metrics.widthPixels, metrics.heightPixels)
Log.d(TAG, "Preview aspect ratio: $screenAspectRatio")
val rotation = viewFinder.display.rotation
val preview = Preview.Builder()
.setTargetAspectRatio(screenAspectRatio)
.setTargetRotation(rotation)
.build()
// Build the image analysis use case and instantiate our analyzer
imageAnalyzer = ImageAnalysis.Builder()
// We request aspect ratio but no resolution
.setTargetAspectRatio(screenAspectRatio)
.setTargetRotation(rotation)
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
.also {
it.setAnalyzer(
cameraExecutor
, TextAnalyzer(
requireContext(),
lifecycle,
viewModel.sourceText,
viewModel.imageCropPercentages
)
)
}
viewModel.sourceText.observe(viewLifecycleOwner, Observer { srcText.text = it })
viewModel.imageCropPercentages.observe(viewLifecycleOwner,
Observer { drawOverlay(overlay.holder, it.first, it.second) })
// Select back camera since text detection does not work with front camera
val cameraSelector =
CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build()
try {
// Unbind use cases before rebinding
cameraProvider.unbindAll()
// Bind use cases to camera
camera = cameraProvider.bindToLifecycle(
this, cameraSelector, preview, imageAnalyzer
)
preview.setSurfaceProvider(viewFinder.createSurfaceProvider())
} catch (exc: IllegalStateException) {
Log.e(TAG, "Use case binding failed. This must be running on main thread.", exc)
}
}
private fun drawOverlay(
holder: SurfaceHolder,
heightCropPercent: Int,
widthCropPercent: Int
) {
val canvas = holder.lockCanvas()
val bgPaint = Paint().apply {
alpha = 140
}
canvas.drawPaint(bgPaint)
val rectPaint = Paint()
rectPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
rectPaint.style = Paint.Style.FILL
rectPaint.color = Color.WHITE
val outlinePaint = Paint()
outlinePaint.style = Paint.Style.STROKE
outlinePaint.color = Color.WHITE
outlinePaint.strokeWidth = 4f
val surfaceWidth = holder.surfaceFrame.width()
val surfaceHeight = holder.surfaceFrame.height()
val cornerRadius = 25f
// Set rect centered in frame
val rectTop = surfaceHeight * heightCropPercent / 2 / 100f
val rectLeft = surfaceWidth * widthCropPercent / 2 / 100f
val rectRight = surfaceWidth * (1 - widthCropPercent / 2 / 100f)
val rectBottom = surfaceHeight * (1 - heightCropPercent / 2 / 100f)
val rect = RectF(rectLeft, rectTop, rectRight, rectBottom)
canvas.drawRoundRect(
rect, cornerRadius, cornerRadius, rectPaint
)
canvas.drawRoundRect(
rect, cornerRadius, cornerRadius, outlinePaint
)
val textPaint = Paint()
textPaint.color = Color.WHITE
textPaint.textSize = 50F
val overlayText = getString(R.string.overlay_help)
val textBounds = Rect()
textPaint.getTextBounds(overlayText, 0, overlayText.length, textBounds)
val textX = (surfaceWidth - textBounds.width()) / 2f
val textY = rectBottom + textBounds.height() + 15f // put text below rect and 15f padding
canvas.drawText(getString(R.string.overlay_help), textX, textY, textPaint)
holder.unlockCanvasAndPost(canvas)
}
/**
* [androidx.camera.core.ImageAnalysisConfig] requires enum value of
* [androidx.camera.core.AspectRatio]. Currently it has values of 4:3 & 16:9.
*
* Detecting the most suitable ratio for dimensions provided in @params by comparing absolute
* of preview ratio to one of the provided values.
*
* @param width - preview width
* @param height - preview height
* @return suitable aspect ratio
*/
private fun aspectRatio(width: Int, height: Int): Int {
val previewRatio = ln(max(width, height).toDouble() / min(width, height))
if (abs(previewRatio - ln(RATIO_4_3_VALUE))
<= abs(previewRatio - ln(RATIO_16_9_VALUE))
) {
return AspectRatio.RATIO_4_3
}
return AspectRatio.RATIO_16_9
}
/**
* Process result from permission request dialog box, has the request
* been granted? If yes, start Camera. Otherwise display a toast
*/
override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<String>, grantResults: IntArray
) {
if (requestCode == REQUEST_CODE_PERMISSIONS) {
if (allPermissionsGranted()) {
viewFinder.post {
// Keep track of the display in which this view is attached
displayId = viewFinder.display.displayId
// Set up the camera and its use cases
setUpCamera()
}
} else {
Toast.makeText(
context,
"Permissions not granted by the user.",
Toast.LENGTH_SHORT
).show()
}
}
}
/**
* Check if all permission specified in the manifest have been granted
*/
private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
ContextCompat.checkSelfPermission(
requireContext(), it
) == PackageManager.PERMISSION_GRANTED
}
}
