├── .gitignore ├── README.md ├── app ├── build.gradle.kts ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── cn │ │ └── bingoogolapple │ │ └── camera │ │ ├── CameraPreview.kt │ │ ├── MainActivity.kt │ │ ├── PlayActivity.kt │ │ ├── SettingsFragment.kt │ │ ├── StudyActivity.kt │ │ └── StudyFragment.kt │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ └── ic_launcher_background.xml │ ├── layout-land │ └── activity_main.xml │ ├── layout │ ├── activity_main.xml │ └── activity_study.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── values │ ├── arrays.xml │ ├── colors.xml │ ├── strings.xml │ └── styles.xml │ └── xml │ ├── preferences.xml │ └── study_preference_fragment.xml ├── build.gradle.kts ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle.kts /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | 15 | # Gradle files 16 | .gradle/ 17 | build/ 18 | /*/build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Eclipse project files 30 | .classpath 31 | .project 32 | .settings/ 33 | 34 | # Intellij project files 35 | *.iml 36 | *.ipr 37 | *.iws 38 | .idea/ 39 | 40 | # Mac system files 41 | .DS_Store 42 | 43 | *.keystore 44 | 45 | captures 46 | 47 | .externalNativeBuild/ 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | :running:BGACamera-Android:running: 2 | ============ 3 | 4 | Android 相机开发学习笔记,参考 [Android 相机开发系列](https://www.polarxiong.com/archives/Android%E7%9B%B8%E6%9C%BA%E5%BC%80%E5%8F%91%E7%B3%BB%E5%88%97.html) 5 | 6 | ## 作者联系方式 7 | 8 | | 个人主页 | 邮箱 | 9 | | ------------- | ------------ | 10 | | bingoogolapple.cn | bingoogolapple@gmail.com | 11 | 12 | | 个人微信号 | 微信群 | 公众号 | 13 | | ------------ | ------------ | ------------ | 14 | | 个人微信号 | 微信群 | 公众号 | 15 | 16 | | 个人 QQ 号 | QQ 群 | 17 | | ------------ | ------------ | 18 | | 个人 QQ 号 | QQ 群 | 19 | 20 | ## 打赏支持作者 21 | 22 | 如果您觉得 BGA 系列开源库或工具软件帮您节省了大量的开发时间,可以扫描下方的二维码打赏支持。您的支持将鼓励我继续创作,打赏后还可以加我微信免费开通一年 [上帝小助手浏览器扩展/插件开发平台](https://github.com/bingoogolapple/bga-god-assistant-config) 的会员服务 23 | 24 | | 微信 | QQ | 支付宝 | 25 | | ------------- | ------------- | ------------- | 26 | | 微信 | QQ | 支付宝 | 27 | 28 | ## 作者项目推荐 29 | 30 | * 欢迎您使用我开发的第一个独立开发软件产品 [上帝小助手浏览器扩展/插件开发平台](https://github.com/bingoogolapple/bga-god-assistant-config) 31 | -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.config.KotlinCompilerVersion 2 | 3 | plugins { 4 | id("com.android.application") 5 | id("kotlin-android") 6 | id("kotlin-android-extensions") 7 | } 8 | 9 | android { 10 | compileSdkVersion(28) 11 | 12 | defaultConfig { 13 | minSdkVersion(19) 14 | targetSdkVersion(15) 15 | versionCode = 100 16 | versionName = "1.0.0" 17 | } 18 | 19 | dataBinding { 20 | isEnabled = true 21 | } 22 | 23 | buildTypes { 24 | getByName("release") { 25 | isMinifyEnabled = true 26 | proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") 27 | } 28 | } 29 | } 30 | 31 | dependencies { 32 | implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar")))) 33 | implementation("com.android.support:appcompat-v7:28.0.0") 34 | implementation("com.android.support.constraint:constraint-layout:1.1.3") 35 | 36 | implementation(kotlin("stdlib", KotlinCompilerVersion.VERSION)) 37 | implementation("org.jetbrains.anko:anko-commons:0.10.5") 38 | implementation("pub.devrel:easypermissions:1.0.1") 39 | } 40 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bingoogolapple/BGACamera-Android/23873c64de42fcb87d4bc751decb01c559578b29/app/proguard-rules.pro -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | 24 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 37 | 38 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /app/src/main/java/cn/bingoogolapple/camera/CameraPreview.kt: -------------------------------------------------------------------------------- 1 | package cn.bingoogolapple.camera 2 | 3 | import android.content.Context 4 | import android.graphics.Rect 5 | import android.graphics.RectF 6 | import android.hardware.Camera 7 | import android.media.CamcorderProfile 8 | import android.media.MediaActionSound 9 | import android.media.MediaRecorder 10 | import android.media.ThumbnailUtils 11 | import android.net.Uri 12 | import android.os.Environment 13 | import android.preference.PreferenceManager 14 | import android.provider.MediaStore 15 | import android.support.v4.math.MathUtils 16 | import android.util.Log 17 | import android.view.* 18 | import android.widget.ImageView 19 | import java.io.File 20 | import java.io.FileOutputStream 21 | import java.io.IOException 22 | import java.text.SimpleDateFormat 23 | import java.util.* 24 | 25 | 26 | /** 27 | * 作者:王浩 28 | * 创建时间:2018/9/28 29 | * 描述: 30 | */ 31 | class CameraPreview(context: Context) : SurfaceView(context), SurfaceHolder.Callback, Camera.PreviewCallback { 32 | private var mCamera: Camera? = null 33 | private var mOutputMediaFileUri: Uri? = null 34 | private var mOutputMediaFileType: String? = null 35 | private var mMediaRecorder: MediaRecorder? = null 36 | private var oldDist = 1f 37 | private var touchFocusing = false 38 | 39 | init { 40 | holder.addCallback(this) 41 | } 42 | 43 | fun getCamera(): Camera? { 44 | return mCamera 45 | } 46 | 47 | private fun log(parameters: Camera.Parameters) { 48 | val log = StringBuilder() 49 | log.append("\n相机信息如下:") 50 | log.append("\n 支持的预览尺寸有:\n ") 51 | for (previewSize in parameters.supportedPreviewSizes) { 52 | log.append("${previewSize.width}x${previewSize.height}").append("、") 53 | } 54 | log.append("\n支持的拍照尺寸有:\n ") 55 | for (pictureSize in parameters.supportedPictureSizes) { 56 | log.append("${pictureSize.width}x${pictureSize.height}").append("、") 57 | } 58 | log.append("\n支持的录像尺寸有:\n ") 59 | for (videoPictureSize in parameters.supportedVideoSizes) { 60 | log.append("${videoPictureSize.width}x${videoPictureSize.height}").append("、") 61 | } 62 | log.append("\n支持的对焦模式有:\n ") 63 | for (focusMode in parameters.supportedFocusModes) { 64 | log.append(focusMode).append("、") 65 | } 66 | log.append("\n支持的白平衡有:\n ") 67 | for (whiteBalance in parameters.supportedWhiteBalance) { 68 | log.append(whiteBalance).append("、") 69 | } 70 | parameters.supportedSceneModes?.let { 71 | log.append("\n支持的场景模式有:\n ") 72 | for (sceneMode in it) { 73 | log.append(sceneMode).append("、") 74 | } 75 | } 76 | log.append("\n支持的闪光灯模式有:\n ") 77 | for (flashMode in parameters.supportedFlashModes) { 78 | log.append(flashMode).append("、") 79 | } 80 | log.append("\n曝光补偿范围为:\n ${parameters.minExposureCompensation}~${parameters.maxExposureCompensation}") 81 | Log.d(TAG, log.toString()) 82 | } 83 | 84 | private fun adjustDisplayRatio(rotation: Int) { 85 | mCamera?.let { 86 | val parent = parent as ViewGroup 87 | val rect = Rect() 88 | parent.getLocalVisibleRect(rect) 89 | val width = rect.width() 90 | val height = rect.height() 91 | val previewSize = it.parameters.previewSize 92 | val previewWidth: Int 93 | val previewHeight: Int 94 | if (rotation == 90 || rotation == 270) { 95 | previewWidth = previewSize.height 96 | previewHeight = previewSize.width 97 | } else { 98 | previewWidth = previewSize.width 99 | previewHeight = previewSize.height 100 | } 101 | 102 | if (width * previewHeight > height * previewWidth) { 103 | val scaledChildWidth = previewWidth * height / previewHeight 104 | layout((width - scaledChildWidth) / 2, 0, (width + scaledChildWidth) / 2, height) 105 | } else { 106 | val scaledChildHeight = previewHeight * width / previewWidth 107 | layout(0, (height - scaledChildHeight) / 2, width, (height + scaledChildHeight) / 2) 108 | } 109 | } 110 | } 111 | 112 | private fun openCamera() { 113 | if (mCamera != null) { 114 | return 115 | } 116 | 117 | try { 118 | mCamera = Camera.open() 119 | } catch (e: Exception) { 120 | Log.e(TAG, "相机资源被占用:" + e.message) 121 | } 122 | } 123 | 124 | public fun startPreview() { 125 | openCamera() 126 | try { 127 | mCamera?.apply { 128 | // 告知将预览帧数据交给谁 129 | setPreviewDisplay(holder) 130 | // 每当有预览帧生成时就会回调 onPreviewFrame 方法 131 | setPreviewCallback(this@CameraPreview) 132 | // 开始预览 133 | startPreview() 134 | log(parameters) 135 | } 136 | } catch (e: IOException) { 137 | Log.e(TAG, "开始预览失败:" + e.message) 138 | } 139 | } 140 | 141 | public fun stopPreview() { 142 | holder.removeCallback(this) 143 | // 相机是共享资源,使用完后需要释放相机资源 144 | mCamera?.apply { 145 | setPreviewCallback(null) 146 | stopPreview() 147 | release() 148 | } 149 | mCamera = null 150 | } 151 | 152 | override fun onPreviewFrame(data: ByteArray?, camera: Camera?) { 153 | try { 154 | Log.i(TAG, "模拟处理预览帧数据") 155 | Thread.sleep(500) 156 | } catch (e: InterruptedException) { 157 | e.printStackTrace() 158 | } 159 | } 160 | 161 | override fun surfaceCreated(surfaceHolder: SurfaceHolder) { 162 | startPreview() 163 | } 164 | 165 | override fun surfaceDestroyed(surfaceHolder: SurfaceHolder) { 166 | stopPreview() 167 | } 168 | 169 | override fun surfaceChanged(holder: SurfaceHolder, format: Int, w: Int, h: Int) { 170 | mCamera?.apply { 171 | val rotation = getDisplayOrientation() 172 | val newParameters = parameters 173 | // 设置预览帧数据,以及拍摄照片的方向 174 | newParameters.setRotation(rotation) 175 | parameters = newParameters 176 | // 指定预览的旋转角度 177 | setDisplayOrientation(getDisplayOrientation()) 178 | // 实时调整预览纵横比 179 | adjustDisplayRatio(rotation) 180 | } 181 | } 182 | 183 | private fun getDisplayOrientation(): Int { 184 | val display = (context.getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay 185 | val rotation = display.rotation 186 | var degrees = 0 187 | when (rotation) { 188 | Surface.ROTATION_0 -> degrees = 0 189 | Surface.ROTATION_90 -> degrees = 90 190 | Surface.ROTATION_180 -> degrees = 180 191 | Surface.ROTATION_270 -> degrees = 270 192 | } 193 | 194 | val camInfo = Camera.CameraInfo() 195 | Camera.getCameraInfo(Camera.CameraInfo.CAMERA_FACING_BACK, camInfo) 196 | return (camInfo.orientation - degrees + 360) % 360 197 | } 198 | 199 | private fun getAppName(): String { 200 | return try { 201 | context.packageManager.getPackageInfo(context.packageName, 0).applicationInfo.loadLabel(context.packageManager).toString() 202 | } catch (e: Exception) { 203 | // 利用系统api getPackageName()得到的包名,这个异常根本不可能发生 204 | "" 205 | } 206 | } 207 | 208 | private fun getOutputMediaFile(type: Int): File? { 209 | val mediaStorageDir = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), getAppName()) 210 | if (!mediaStorageDir.exists()) { 211 | if (!mediaStorageDir.mkdirs()) { 212 | Log.d(TAG, "创建媒体文件目录失败,请检查存储权限") 213 | return null 214 | } 215 | } 216 | val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date()) 217 | val mediaFile: File 218 | when (type) { 219 | MEDIA_TYPE_IMAGE -> { 220 | mediaFile = File(mediaStorageDir.path + File.separator + "IMG_" + timeStamp + ".jpg") 221 | mOutputMediaFileType = "image/*" 222 | } 223 | MEDIA_TYPE_VIDEO -> { 224 | mediaFile = File(mediaStorageDir.path + File.separator + "VID_" + timeStamp + ".mp4") 225 | mOutputMediaFileType = "video/*" 226 | } 227 | else -> return null 228 | } 229 | mOutputMediaFileUri = Uri.fromFile(mediaFile) 230 | return mediaFile 231 | } 232 | 233 | fun getOutputMediaFileUri(): Uri? { 234 | return mOutputMediaFileUri 235 | } 236 | 237 | fun getOutputMediaFileType(): String? { 238 | return mOutputMediaFileType 239 | } 240 | 241 | fun takePicture(previewIv: ImageView) { 242 | mCamera?.apply { 243 | takePicture(Camera.ShutterCallback { 244 | Log.d(TAG, "按下了快门,播放声音") 245 | MediaActionSound().play(MediaActionSound.SHUTTER_CLICK) 246 | }, Camera.PictureCallback { data, camera -> 247 | Log.d(TAG, "原始数据,不知道为什么返回的 data 一直为空") 248 | }, Camera.PictureCallback { data, camera -> 249 | Log.d(TAG, "jpeg 数据。主线程回调的 " + data.size) 250 | // TODO 子线程保存图片文件 251 | val pictureFile = getOutputMediaFile(MEDIA_TYPE_IMAGE) 252 | if (pictureFile == null) { 253 | Log.d(TAG, "创建媒体文件失败,请检查存储权限") 254 | return@PictureCallback 255 | } 256 | try { 257 | FileOutputStream(pictureFile).use { 258 | it.write(data) 259 | } 260 | 261 | previewIv.setImageURI(mOutputMediaFileUri) 262 | 263 | camera.startPreview() 264 | } catch (e: Exception) { 265 | Log.d(TAG, "保存图片失败 " + e.message) 266 | } 267 | }) 268 | } 269 | } 270 | 271 | fun startRecording(): Boolean { 272 | if (prepareVideoRecorder()) { 273 | mMediaRecorder?.start() 274 | return true 275 | } else { 276 | releaseMediaRecorder() 277 | } 278 | return false 279 | } 280 | 281 | fun stopRecording(previewIv: ImageView) { 282 | mMediaRecorder?.stop() 283 | mOutputMediaFileUri?.apply { 284 | val thumbnail = ThumbnailUtils.createVideoThumbnail(path, MediaStore.Video.Thumbnails.MINI_KIND) 285 | previewIv.setImageBitmap(thumbnail) 286 | } 287 | releaseMediaRecorder() 288 | } 289 | 290 | fun isRecording(): Boolean { 291 | return mMediaRecorder != null 292 | } 293 | 294 | private fun prepareVideoRecorder(): Boolean { 295 | openCamera() 296 | mMediaRecorder = MediaRecorder() 297 | 298 | mCamera?.unlock() 299 | mMediaRecorder?.apply { 300 | 301 | setCamera(mCamera) 302 | 303 | setAudioSource(MediaRecorder.AudioSource.CAMCORDER) 304 | setVideoSource(MediaRecorder.VideoSource.CAMERA) 305 | setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH)) 306 | 307 | val prefs = PreferenceManager.getDefaultSharedPreferences(context) 308 | val prefVideoSize = prefs.getString("video_size", "") 309 | if (prefVideoSize.isNotBlank()) { 310 | val split = prefVideoSize.split("x".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() 311 | setVideoSize(Integer.parseInt(split[0]), Integer.parseInt(split[1])) 312 | } 313 | setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString()) 314 | setPreviewDisplay(holder.surface) 315 | 316 | try { 317 | // 视频的旋转并不是编码层面的旋转,视频帧数据并没有发生旋转,而只是在视频中增加了参数,希望播放器按照指定的旋转角度旋转后播放,所以具体效果因播放器而异 318 | setOrientationHint(getDisplayOrientation()) 319 | prepare() 320 | } catch (e: IllegalStateException) { 321 | Log.d(TAG, "IllegalStateException preparing MediaRecorder: " + e.message) 322 | releaseMediaRecorder() 323 | return false 324 | } catch (e: IOException) { 325 | Log.d(TAG, "IOException preparing MediaRecorder: " + e.message) 326 | releaseMediaRecorder() 327 | return false 328 | } 329 | } 330 | return true 331 | } 332 | 333 | private fun releaseMediaRecorder() { 334 | mMediaRecorder?.apply { 335 | reset() 336 | release() 337 | mMediaRecorder = null 338 | } 339 | mCamera?.apply { lock() } 340 | } 341 | 342 | override fun onTouchEvent(event: MotionEvent): Boolean { 343 | mCamera?.let { 344 | if (event.pointerCount == 1) { 345 | if (event.action and MotionEvent.ACTION_MASK == MotionEvent.ACTION_UP) { 346 | handleFocusMetering(event, it) 347 | } 348 | } else { 349 | when (event.action and MotionEvent.ACTION_MASK) { 350 | MotionEvent.ACTION_POINTER_DOWN -> oldDist = getFingerSpacing(event) 351 | MotionEvent.ACTION_UP, MotionEvent.ACTION_MOVE -> { 352 | val newDist = getFingerSpacing(event) 353 | if (newDist > oldDist) { 354 | handleZoom(true, it) 355 | } else if (newDist < oldDist) { 356 | handleZoom(false, it) 357 | } 358 | oldDist = newDist 359 | } 360 | } 361 | } 362 | } 363 | return true 364 | } 365 | 366 | private fun handleFocusMetering(event: MotionEvent, camera: Camera) { 367 | if (touchFocusing) { 368 | return 369 | } 370 | touchFocusing = true 371 | 372 | var isNeedUpdate = false 373 | val newParams = camera.parameters 374 | val currentFocusMode = newParams.focusMode 375 | Log.i(TAG, "老的对焦模式为$currentFocusMode") 376 | if (newParams.maxNumFocusAreas > 0) { 377 | Log.i(TAG, "支持触摸对焦") 378 | isNeedUpdate = true 379 | val focusAreas = arrayListOf() 380 | val focusRect = calculateTapArea(event.x, event.y, 1f, width, height) 381 | focusAreas.add(Camera.Area(focusRect, 800)) 382 | newParams.focusAreas = focusAreas 383 | newParams.focusMode = Camera.Parameters.FOCUS_MODE_MACRO 384 | } else { 385 | Log.i(TAG, "不支持触摸对焦") 386 | } 387 | 388 | if (newParams.maxNumMeteringAreas > 0) { 389 | Log.i(TAG, "支持触摸测光") 390 | isNeedUpdate = true 391 | val meteringAreas = arrayListOf() 392 | val meteringRect = calculateTapArea(event.x, event.y, 1.5f, width, height) 393 | meteringAreas.add(Camera.Area(meteringRect, 800)) 394 | newParams.meteringAreas = meteringAreas 395 | } else { 396 | Log.i(TAG, "不支持触摸测光") 397 | } 398 | 399 | if (isNeedUpdate) { 400 | camera.cancelAutoFocus() 401 | camera.parameters = newParams 402 | camera.autoFocus { success, camera -> 403 | if (success) { 404 | Log.i(TAG, "对焦成功") 405 | } else { 406 | Log.i(TAG, "对焦失败") 407 | } 408 | touchFocusing = false 409 | val recoverParams = camera.parameters 410 | recoverParams.focusMode = currentFocusMode 411 | camera.parameters = recoverParams 412 | Log.i(TAG, "还原对焦模式") 413 | } 414 | } else { 415 | touchFocusing = false 416 | } 417 | } 418 | 419 | private fun handleZoom(isZoomIn: Boolean, camera: Camera) { 420 | val params = camera.parameters 421 | if (params.isZoomSupported) { 422 | var zoom = params.zoom 423 | if (isZoomIn && zoom < params.maxZoom) { 424 | Log.i(TAG, "放大") 425 | zoom++ 426 | } else if (!isZoomIn && zoom > 0) { 427 | Log.i(TAG, "缩小") 428 | zoom-- 429 | } else { 430 | Log.i(TAG, "既不放大也不缩小") 431 | } 432 | params.zoom = zoom 433 | camera.parameters = params 434 | } else { 435 | Log.i(TAG, "不支持缩放") 436 | } 437 | } 438 | 439 | companion object { 440 | private val TAG = CameraPreview::class.java.simpleName 441 | private const val MEDIA_TYPE_IMAGE = 1 442 | private const val MEDIA_TYPE_VIDEO = 2 443 | 444 | private fun calculateTapArea(x: Float, y: Float, coefficient: Float, width: Int, height: Int): Rect { 445 | val focusAreaSize = 300f 446 | val areaSize = (focusAreaSize * coefficient).toInt() 447 | val centerX = (x / width * 2000 - 1000).toInt() 448 | val centerY = (y / height * 2000 - 1000).toInt() 449 | 450 | val halfAreaSize = areaSize / 2 451 | val rectF = RectF(MathUtils.clamp(centerX - halfAreaSize, -1000, 1000).toFloat(), 452 | MathUtils.clamp(centerY - halfAreaSize, -1000, 1000).toFloat(), 453 | MathUtils.clamp(centerX + halfAreaSize, -1000, 1000).toFloat(), 454 | MathUtils.clamp(centerY + halfAreaSize, -1000, 1000).toFloat()) 455 | return Rect(Math.round(rectF.left), Math.round(rectF.top), Math.round(rectF.right), Math.round(rectF.bottom)) 456 | } 457 | 458 | private fun getFingerSpacing(event: MotionEvent): Float { 459 | val x = event.getX(0) - event.getX(1) 460 | val y = event.getY(0) - event.getY(1) 461 | return Math.sqrt((x * x + y * y).toDouble()).toFloat() 462 | } 463 | } 464 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/bingoogolapple/camera/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package cn.bingoogolapple.camera 2 | 3 | import android.Manifest 4 | import android.app.Activity 5 | import android.content.Intent 6 | import android.os.Bundle 7 | import android.preference.PreferenceManager 8 | import android.view.View 9 | import android.widget.Button 10 | import android.widget.FrameLayout 11 | import android.widget.ImageView 12 | import pub.devrel.easypermissions.AfterPermissionGranted 13 | import pub.devrel.easypermissions.EasyPermissions 14 | 15 | class MainActivity : Activity(), EasyPermissions.PermissionCallbacks, View.OnClickListener { 16 | private lateinit var mPreViewContainerFl: FrameLayout 17 | private lateinit var mPreview: CameraPreview 18 | private lateinit var mPreviewIv: ImageView 19 | 20 | override fun onCreate(savedInstanceState: Bundle?) { 21 | super.onCreate(savedInstanceState) 22 | setContentView(R.layout.activity_main) 23 | mPreViewContainerFl = findViewById(R.id.fl_main_preview_container) 24 | 25 | mPreview = CameraPreview(this) 26 | mPreViewContainerFl.addView(mPreview) 27 | 28 | mPreviewIv = findViewById(R.id.iv_main_preview) 29 | 30 | findViewById