feat: merge three camera modes into unified CameraActivity
- Added mode toggle button to switch between photo, watermark, and compose modes - Integrated watermark functionality from WatermarkCameraActivity - Integrated simple camera functionality from SimpleCameraActivity - Updated MainActivity to launch CameraActivity directly - Cleaned up AndroidManifest.xml by removing unused activities - Updated layout with mode selection button - All three camera modes now available in single unified interface
This commit is contained in:
@@ -28,14 +28,6 @@
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".SimpleCameraActivity"
|
||||
android:exported="false"
|
||||
android:screenOrientation="portrait" />
|
||||
<activity
|
||||
android:name=".WatermarkCameraActivity"
|
||||
android:exported="false"
|
||||
android:screenOrientation="portrait" />
|
||||
<activity
|
||||
android:name=".CameraActivity"
|
||||
android:exported="false"
|
||||
|
||||
@@ -35,7 +35,7 @@ import java.util.concurrent.Executors
|
||||
|
||||
class CameraActivity : AppCompatActivity() {
|
||||
|
||||
companion object {
|
||||
companion object {
|
||||
private const val TAG = "LogCam"
|
||||
private const val REQUEST_CODE_PERMISSIONS = 10
|
||||
private val REQUIRED_PERMISSIONS = arrayOf(
|
||||
@@ -43,11 +43,27 @@ class CameraActivity : AppCompatActivity() {
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
private lateinit var viewFinder: PreviewView
|
||||
private lateinit var captureButton: ImageButton
|
||||
private lateinit var settingsButton: ImageButton
|
||||
private lateinit var modeButton: Button // 模式切换按钮
|
||||
private lateinit var photoPreviewLayout: LinearLayout
|
||||
private var imageCapture: ImageCapture? = null
|
||||
|
||||
// 拍照模式相关变量
|
||||
private var captureMode = MODE_PHOTO // 默认为普通拍照模式
|
||||
private var watermarkEnabled = true // 是否启用时间戳水印
|
||||
|
||||
// 常量定义
|
||||
companion object {
|
||||
private const val MODE_PHOTO = 0 // 普通拍照
|
||||
private const val MODE_WATERMARK = 1 // 水印模式
|
||||
private const val MODE_COMPOSE = 2 // 合成模式
|
||||
}
|
||||
|
||||
|
||||
private lateinit var cameraExecutor: ExecutorService
|
||||
private var cameraProvider: ProcessCameraProvider? = null
|
||||
|
||||
@@ -66,6 +82,10 @@ class CameraActivity : AppCompatActivity() {
|
||||
settingsButton = findViewById(R.id.settingsButton)
|
||||
photoPreviewLayout = findViewById(R.id.photoPreviewLayout)
|
||||
|
||||
// 初始化模式切换按钮
|
||||
modeButton = findViewById(R.id.modeButton)
|
||||
modeButton.setOnClickListener { showModeSelectionDialog() }
|
||||
|
||||
// 初始化线程池
|
||||
cameraExecutor = Executors.newSingleThreadExecutor()
|
||||
|
||||
@@ -81,6 +101,53 @@ class CameraActivity : AppCompatActivity() {
|
||||
// 设置点击监听器
|
||||
captureButton.setOnClickListener { takePhoto() }
|
||||
settingsButton.setOnClickListener { openSettings() }
|
||||
private fun showModeSelectionDialog() {
|
||||
val popupMenu = PopupMenu(this, modeButton)
|
||||
popupMenu.menu.apply {
|
||||
add("普通模式").setOnMenuItemClickListener {
|
||||
setCaptureMode(MODE_PHOTO)
|
||||
true
|
||||
}
|
||||
add("水印模式").setOnMenuItemClickListener {
|
||||
setCaptureMode(MODE_WATERMARK)
|
||||
true
|
||||
}
|
||||
add("合成模式").setOnMenuItemClickListener {
|
||||
setCaptureMode(MODE_COMPOSE)
|
||||
true
|
||||
}
|
||||
}
|
||||
popupMenu.show()
|
||||
}
|
||||
|
||||
private fun setCaptureMode(mode: Int) {
|
||||
captureMode = mode
|
||||
when (mode) {
|
||||
MODE_PHOTO -> {
|
||||
modeButton.text = "模式: 普通"
|
||||
watermarkEnabled = false
|
||||
}
|
||||
MODE_WATERMARK -> {
|
||||
modeButton.text = "模式: 水印"
|
||||
watermarkEnabled = true
|
||||
}
|
||||
MODE_COMPOSE -> {
|
||||
modeButton.text = "模式: 合成"
|
||||
watermarkEnabled = false
|
||||
}
|
||||
}
|
||||
Toast.makeText(this, "已切换到${getModeName(mode)}", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
private fun getModeName(mode: Int): String {
|
||||
return when (mode) {
|
||||
MODE_PHOTO -> "普通模式"
|
||||
MODE_WATERMARK -> "水印模式"
|
||||
MODE_COMPOSE -> "合成模式"
|
||||
else -> "未知模式"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
@@ -330,6 +397,110 @@ class CameraActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
)
|
||||
private fun addWatermarkAndSave(originalUri: Uri) {
|
||||
try {
|
||||
// 读取原始图片
|
||||
val inputStream = contentResolver.openInputStream(originalUri)
|
||||
val originalBitmap = BitmapFactory.decodeStream(inputStream)
|
||||
inputStream?.close()
|
||||
|
||||
if (originalBitmap == null) {
|
||||
Log.e(TAG, "无法读取图片: $originalUri")
|
||||
return
|
||||
}
|
||||
|
||||
// 添加水印
|
||||
val watermarkedBitmap = addWatermark(originalBitmap)
|
||||
|
||||
// 保存带水印的图片
|
||||
val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
|
||||
val contentValues = ContentValues().apply {
|
||||
put(MediaStore.MediaColumns.DISPLAY_NAME, "巡检_${timestamp}_水印.jpg")
|
||||
put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
|
||||
put(MediaStore.MediaColumns.RELATIVE_PATH, "Pictures/LogCam")
|
||||
}
|
||||
|
||||
val uri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
|
||||
|
||||
if (uri != null) {
|
||||
val outputStream = contentResolver.openOutputStream(uri)
|
||||
|
||||
if (outputStream != null) {
|
||||
watermarkedBitmap.compress(Bitmap.CompressFormat.JPEG, 95, outputStream)
|
||||
outputStream.close()
|
||||
|
||||
// 通知相册更新
|
||||
MediaStore.Images.Media.insertImage(contentResolver, watermarkedBitmap, contentValues.getAsString(MediaStore.MediaColumns.DISPLAY_NAME), "")
|
||||
|
||||
runOnUiThread {
|
||||
Toast.makeText(baseContext, "图片已保存并添加水印", Toast.LENGTH_LONG).show()
|
||||
addPhotoToPreview(uri)
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "无法打开输出流: $uri")
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "无法创建新文件")
|
||||
}
|
||||
|
||||
// 回收位图
|
||||
originalBitmap.recycle()
|
||||
watermarkedBitmap.recycle()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "添加水印失败: ${e.message}", e)
|
||||
runOnUiThread {
|
||||
Toast.makeText(baseContext, "添加水印失败: ${e.message}", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun addWatermark(originalBitmap: Bitmap): Bitmap {
|
||||
val timestamp = SimpleDateFormat("yyyy年-MM月-dd日 HH:mm:ss", Locale.getDefault()).format(Date())
|
||||
|
||||
// 创建可修改的位图副本
|
||||
val mutableBitmap = originalBitmap.copy(Bitmap.Config.ARGB_8888, true)
|
||||
val canvas = Canvas(mutableBitmap)
|
||||
|
||||
// 创建水印画笔
|
||||
val paint = Paint().apply {
|
||||
color = Color.WHITE
|
||||
textSize = 48f
|
||||
isAntiAlias = true
|
||||
typeface = Typeface.create(Typeface.DEFAULT, Typeface.BOLD)
|
||||
textAlign = Paint.Align.RIGHT
|
||||
}
|
||||
|
||||
// 计算文本大小
|
||||
val bounds = android.graphics.Rect()
|
||||
paint.getTextBounds(timestamp, 0, timestamp.length, bounds)
|
||||
|
||||
// 在右下角绘制时间戳水印
|
||||
val padding = 50f
|
||||
canvas.drawText(
|
||||
timestamp,
|
||||
mutableBitmap.width - padding,
|
||||
mutableBitmap.height - padding - bounds.height(),
|
||||
paint
|
||||
)
|
||||
|
||||
// 绘制白色边框以提高可读性
|
||||
val borderPaint = Paint().apply {
|
||||
color = Color.BLACK
|
||||
strokeWidth = 4f
|
||||
style = Paint.Style.STROKE
|
||||
isAntiAlias = true
|
||||
}
|
||||
|
||||
canvas.drawText(
|
||||
timestamp,
|
||||
mutableBitmap.width - padding,
|
||||
mutableBitmap.height - padding - bounds.height(),
|
||||
borderPaint
|
||||
)
|
||||
|
||||
return mutableBitmap
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun createOutputFileOptions(): ImageCapture.OutputFileOptions {
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
package com.example.app
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.widget.Button
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
|
||||
class CameraSelectorActivity : AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_camera_selector)
|
||||
|
||||
val btnSimpleCamera = findViewById<Button>(R.id.btnSimpleCamera)
|
||||
val btnWatermarkCamera = findViewById<Button>(R.id.btnWatermarkCamera)
|
||||
val btnMultiCamera = findViewById<Button>(R.id.btnMultiCamera)
|
||||
val btnSettings = findViewById<Button>(R.id.btnSettings)
|
||||
|
||||
btnSimpleCamera.setOnClickListener {
|
||||
startActivity(Intent(this, SimpleCameraActivity::class.java))
|
||||
}
|
||||
|
||||
btnWatermarkCamera.setOnClickListener {
|
||||
startActivity(Intent(this, WatermarkCameraActivity::class.java))
|
||||
}
|
||||
|
||||
btnMultiCamera.setOnClickListener {
|
||||
startActivity(Intent(this, CameraActivity::class.java))
|
||||
}
|
||||
|
||||
btnSettings.setOnClickListener {
|
||||
startActivity(Intent(this, SettingsFragment::class.java))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,33 +2,15 @@ package com.example.app
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.widget.Button
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_camera_selector)
|
||||
setContentView(R.layout.activity_main)
|
||||
|
||||
val btnSimpleCamera = findViewById<Button>(R.id.btnSimpleCamera)
|
||||
val btnWatermarkCamera = findViewById<Button>(R.id.btnWatermarkCamera)
|
||||
val btnMultiCamera = findViewById<Button>(R.id.btnMultiCamera)
|
||||
val btnSettings = findViewById<Button>(R.id.btnSettings)
|
||||
|
||||
btnSimpleCamera.setOnClickListener {
|
||||
startActivity(Intent(this, SimpleCameraActivity::class.java))
|
||||
}
|
||||
|
||||
btnWatermarkCamera.setOnClickListener {
|
||||
startActivity(Intent(this, WatermarkCameraActivity::class.java))
|
||||
}
|
||||
|
||||
btnMultiCamera.setOnClickListener {
|
||||
startActivity(Intent(this, CameraActivity::class.java))
|
||||
}
|
||||
|
||||
btnSettings.setOnClickListener {
|
||||
startActivity(Intent(this, SettingsActivity::class.java))
|
||||
}
|
||||
// 启动综合相机Activity
|
||||
startActivity(Intent(this, CameraActivity::class.java))
|
||||
finish() // 关闭MainActivity,避免返回键回到空白页面
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,320 +0,0 @@
|
||||
package com.example.app
|
||||
|
||||
import android.Manifest
|
||||
import android.content.ContentValues
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.MediaStore
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.camera.core.CameraSelector
|
||||
import androidx.camera.core.ImageCapture
|
||||
import androidx.camera.core.ImageCaptureException
|
||||
import androidx.camera.core.Preview
|
||||
import androidx.camera.lifecycle.ProcessCameraProvider
|
||||
import androidx.camera.view.PreviewView
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
class SimpleCameraActivity : AppCompatActivity() {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "SimpleCamera"
|
||||
private const val REQUEST_CODE_PERMISSIONS = 10
|
||||
private val REQUIRED_PERMISSIONS = arrayOf(
|
||||
Manifest.permission.CAMERA
|
||||
)
|
||||
}
|
||||
|
||||
private lateinit var viewFinder: PreviewView
|
||||
private lateinit var captureButton: FloatingActionButton
|
||||
private var imageCapture: ImageCapture? = null
|
||||
private lateinit var cameraExecutor: ExecutorService
|
||||
private var cameraProvider: ProcessCameraProvider? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_simple_camera)
|
||||
|
||||
// 记录应用启动信息
|
||||
Log.d(TAG, "SimpleCameraActivity 启动")
|
||||
Log.d(TAG, "Android SDK: ${Build.VERSION.SDK_INT}")
|
||||
Log.d(TAG, "设备型号: ${Build.MODEL}")
|
||||
|
||||
// 初始化视图
|
||||
viewFinder = findViewById(R.id.viewFinder)
|
||||
captureButton = findViewById(R.id.captureButton)
|
||||
|
||||
// 初始化线程池
|
||||
cameraExecutor = Executors.newSingleThreadExecutor()
|
||||
|
||||
// 请求权限
|
||||
if (allPermissionsGranted()) {
|
||||
Log.d(TAG, "权限已授予,启动相机")
|
||||
startCamera()
|
||||
} else {
|
||||
Log.d(TAG, "请求相机权限")
|
||||
ActivityCompat.requestPermissions(
|
||||
this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS
|
||||
)
|
||||
}
|
||||
|
||||
// 设置拍照按钮点击监听器
|
||||
captureButton.setOnClickListener {
|
||||
Log.d(TAG, "拍照按钮被点击")
|
||||
takePhoto()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
cameraExecutor.shutdown()
|
||||
cameraProvider?.unbindAll()
|
||||
}
|
||||
|
||||
private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
|
||||
ContextCompat.checkSelfPermission(baseContext, it) == PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
|
||||
private fun startCamera() {
|
||||
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
|
||||
|
||||
cameraProviderFuture.addListener({
|
||||
try {
|
||||
cameraProvider = cameraProviderFuture.get()
|
||||
|
||||
// 预览
|
||||
val preview = Preview.Builder()
|
||||
.build()
|
||||
.also {
|
||||
it.setSurfaceProvider(viewFinder.surfaceProvider)
|
||||
}
|
||||
|
||||
imageCapture = ImageCapture.Builder()
|
||||
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
|
||||
.build()
|
||||
|
||||
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
|
||||
|
||||
cameraProvider?.unbindAll()
|
||||
cameraProvider?.bindToLifecycle(
|
||||
this, cameraSelector, preview, imageCapture
|
||||
)
|
||||
|
||||
Log.d(TAG, "相机启动成功")
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "相机初始化失败: ${e.message}", e)
|
||||
Toast.makeText(this, "相机启动失败: ${e.message}", Toast.LENGTH_LONG).show()
|
||||
finish()
|
||||
}
|
||||
}, ContextCompat.getMainExecutor(this))
|
||||
}
|
||||
|
||||
private fun takePhoto() {
|
||||
val imageCapture = this.imageCapture ?: run {
|
||||
Toast.makeText(this, "相机未准备好", Toast.LENGTH_SHORT).show()
|
||||
return
|
||||
}
|
||||
|
||||
val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
|
||||
|
||||
val contentValues = ContentValues().apply {
|
||||
put(MediaStore.MediaColumns.DISPLAY_NAME, "巡检_$timestamp")
|
||||
put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
put(MediaStore.MediaColumns.RELATIVE_PATH, "Pictures/LogCam")
|
||||
}
|
||||
}
|
||||
|
||||
val contentResolver = contentResolver
|
||||
val uri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
|
||||
|
||||
if (uri == null) {
|
||||
Toast.makeText(this, "创建文件失败", Toast.LENGTH_SHORT).show()
|
||||
return
|
||||
}
|
||||
|
||||
val outputOptions = ImageCapture.OutputFileOptions.Builder(contentResolver, uri, contentValues).build()
|
||||
|
||||
imageCapture.takePicture(
|
||||
outputOptions,
|
||||
ContextCompat.getMainExecutor(this),
|
||||
object : ImageCapture.OnImageSavedCallback {
|
||||
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
|
||||
val savedUri = outputFileResults.savedUri
|
||||
if (savedUri != null) {
|
||||
val msg = "照片已保存: ${savedUri.lastPathSegment}"
|
||||
Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
|
||||
Log.d(TAG, msg)
|
||||
|
||||
// 验证文件是否真的存在
|
||||
try {
|
||||
val cursor = contentResolver.query(savedUri, null, null, null, null)
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
Log.d(TAG, "文件验证成功: ${savedUri}")
|
||||
// 确认文件已保存
|
||||
runOnUiThread {
|
||||
Toast.makeText(baseContext, "照片已成功保存", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "文件验证失败,但onImageSaved被调用: ${savedUri}")
|
||||
runOnUiThread {
|
||||
Toast.makeText(baseContext, "照片可能未保存", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
cursor?.close()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "文件验证异常: ${e.message}")
|
||||
runOnUiThread {
|
||||
Toast.makeText(baseContext, "照片已保存", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "onImageSaved被调用但savedUri为null")
|
||||
runOnUiThread {
|
||||
Toast.makeText(baseContext, "照片保存完成", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(exception: ImageCaptureException) {
|
||||
val errorMsg = "拍照失败: ${exception.message}"
|
||||
Log.e(TAG, errorMsg, exception)
|
||||
|
||||
// 检查错误类型
|
||||
when (exception.imageCaptureError) {
|
||||
ImageCapture.ERROR_CAMERA_CLOSED -> {
|
||||
Log.e(TAG, "相机已关闭")
|
||||
runOnUiThread {
|
||||
Toast.makeText(baseContext, "相机已关闭", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
ImageCapture.ERROR_FILE_IO -> {
|
||||
Log.e(TAG, "文件保存失败")
|
||||
runOnUiThread {
|
||||
Toast.makeText(baseContext, "文件保存失败", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
ImageCapture.ERROR_INVALID_CAMERA -> {
|
||||
Log.e(TAG, "无效的相机")
|
||||
runOnUiThread {
|
||||
Toast.makeText(baseContext, "无效的相机", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
ImageCapture.ERROR_CAPTURE_FAILED -> {
|
||||
// 捕获失败,但可能文件已保存
|
||||
Log.e(TAG, "图片捕获失败,但可能已保存")
|
||||
// 验证是否有文件被保存
|
||||
try {
|
||||
// 查询最近保存的文件
|
||||
val projection = arrayOf(MediaStore.Images.Media._ID, MediaStore.Images.Media.DISPLAY_NAME)
|
||||
val sortOrder = "${MediaStore.Images.Media.DATE_ADDED} DESC LIMIT 1"
|
||||
|
||||
val cursor = contentResolver.query(
|
||||
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
|
||||
projection, null, null, sortOrder
|
||||
)
|
||||
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
val displayName = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME))
|
||||
Log.d(TAG, "图片捕获失败但文件已存在: $displayName")
|
||||
runOnUiThread {
|
||||
Toast.makeText(baseContext, "图片已保存: $displayName", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
} else {
|
||||
runOnUiThread {
|
||||
Toast.makeText(baseContext, "图片捕获失败", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
cursor?.close()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "检查保存文件时出错: ${e.message}")
|
||||
runOnUiThread {
|
||||
Toast.makeText(baseContext, "图片处理出错,但可能已保存", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
ImageCapture.ERROR_UNKNOWN -> {
|
||||
Log.e(TAG, "未知错误")
|
||||
// 特殊处理:如果错误消息包含"processing",可能是内部处理问题
|
||||
if (exception.message?.contains("processing", ignoreCase = true) == true) {
|
||||
Log.d(TAG, "检测到processing错误,但可能图片已保存")
|
||||
// 尝试查询最新保存的图片
|
||||
try {
|
||||
val projection = arrayOf(MediaStore.Images.Media._ID, MediaStore.Images.Media.DISPLAY_NAME, MediaStore.Images.Media.DATE_ADDED)
|
||||
val sortOrder = "${MediaStore.Images.Media.DATE_ADDED} DESC LIMIT 1"
|
||||
|
||||
val cursor = contentResolver.query(
|
||||
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
|
||||
projection, null, null, sortOrder
|
||||
)
|
||||
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
val displayName = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME))
|
||||
val dateAdded = cursor.getLong(cursor.getColumnIndex(MediaStore.Images.Media.DATE_ADDED))
|
||||
val currentTime = System.currentTimeMillis() / 1000
|
||||
|
||||
// 检查是否在最近几秒内保存了文件
|
||||
if (Math.abs(currentTime - dateAdded) <= 5) {
|
||||
Log.d(TAG, "processing错误但图片已保存: $displayName")
|
||||
runOnUiThread {
|
||||
Toast.makeText(baseContext, "图片已保存: $displayName", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
} else {
|
||||
runOnUiThread {
|
||||
Toast.makeText(baseContext, "图片处理错误", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
runOnUiThread {
|
||||
Toast.makeText(baseContext, "图片处理错误", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
cursor?.close()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "检查保存文件时出错: ${e.message}")
|
||||
runOnUiThread {
|
||||
Toast.makeText(baseContext, "图片处理错误", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
runOnUiThread {
|
||||
Toast.makeText(baseContext, "未知错误", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
Log.e(TAG, "其他错误: ${exception.imageCaptureError}")
|
||||
runOnUiThread {
|
||||
Toast.makeText(baseContext, "拍照失败: ${exception.message}", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
permissions: Array<String>,
|
||||
grantResults: IntArray
|
||||
) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
if (requestCode == REQUEST_CODE_PERMISSIONS) {
|
||||
if (allPermissionsGranted()) {
|
||||
startCamera()
|
||||
} else {
|
||||
Toast.makeText(this, "相机权限被拒绝,应用无法使用相机功能", Toast.LENGTH_LONG).show()
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,391 +0,0 @@
|
||||
package com.example.app
|
||||
|
||||
import android.Manifest
|
||||
import android.content.ContentValues
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Typeface
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.MediaStore
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.camera.core.CameraSelector
|
||||
import androidx.camera.core.ImageCapture
|
||||
import androidx.camera.core.ImageCaptureException
|
||||
import androidx.camera.core.Preview
|
||||
import androidx.camera.lifecycle.ProcessCameraProvider
|
||||
import androidx.camera.view.PreviewView
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import java.io.OutputStream
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
class WatermarkCameraActivity : AppCompatActivity() {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "WatermarkCamera"
|
||||
private const val REQUEST_CODE_PERMISSIONS = 10
|
||||
private val REQUIRED_PERMISSIONS = arrayOf(
|
||||
Manifest.permission.CAMERA
|
||||
)
|
||||
}
|
||||
|
||||
private lateinit var viewFinder: PreviewView
|
||||
private lateinit var captureButton: FloatingActionButton
|
||||
private var imageCapture: ImageCapture? = null
|
||||
private lateinit var cameraExecutor: ExecutorService
|
||||
private var cameraProvider: ProcessCameraProvider? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_simple_camera)
|
||||
|
||||
// 初始化视图
|
||||
viewFinder = findViewById(R.id.viewFinder)
|
||||
captureButton = findViewById(R.id.captureButton)
|
||||
|
||||
// 初始化线程池
|
||||
cameraExecutor = Executors.newSingleThreadExecutor()
|
||||
|
||||
// 请求权限
|
||||
if (allPermissionsGranted()) {
|
||||
startCamera()
|
||||
} else {
|
||||
ActivityCompat.requestPermissions(
|
||||
this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS
|
||||
)
|
||||
}
|
||||
|
||||
// 设置拍照按钮点击监听器
|
||||
captureButton.setOnClickListener { takePhoto() }
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
cameraExecutor.shutdown()
|
||||
cameraProvider?.unbindAll()
|
||||
}
|
||||
|
||||
private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
|
||||
ContextCompat.checkSelfPermission(baseContext, it) == PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
|
||||
private fun startCamera() {
|
||||
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
|
||||
|
||||
cameraProviderFuture.addListener({
|
||||
try {
|
||||
cameraProvider = cameraProviderFuture.get()
|
||||
|
||||
// 预览
|
||||
val preview = Preview.Builder()
|
||||
.build()
|
||||
.also {
|
||||
it.setSurfaceProvider(viewFinder.surfaceProvider)
|
||||
}
|
||||
|
||||
imageCapture = ImageCapture.Builder()
|
||||
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
|
||||
.build()
|
||||
|
||||
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
|
||||
|
||||
cameraProvider?.unbindAll()
|
||||
cameraProvider?.bindToLifecycle(
|
||||
this, cameraSelector, preview, imageCapture
|
||||
)
|
||||
|
||||
Log.d(TAG, "相机启动成功")
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "相机初始化失败: ${e.message}", e)
|
||||
Toast.makeText(this, "相机启动失败: ${e.message}", Toast.LENGTH_LONG).show()
|
||||
finish()
|
||||
}
|
||||
}, ContextCompat.getMainExecutor(this))
|
||||
}
|
||||
|
||||
private fun takePhoto() {
|
||||
val imageCapture = this.imageCapture ?: run {
|
||||
Toast.makeText(this, "相机未准备好", Toast.LENGTH_SHORT).show()
|
||||
return
|
||||
}
|
||||
|
||||
val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
|
||||
|
||||
val contentValues = ContentValues().apply {
|
||||
put(MediaStore.MediaColumns.DISPLAY_NAME, "巡检_$timestamp")
|
||||
put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
put(MediaStore.MediaColumns.RELATIVE_PATH, "Pictures/LogCam")
|
||||
}
|
||||
}
|
||||
|
||||
val contentResolver = contentResolver
|
||||
val uri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
|
||||
|
||||
if (uri == null) {
|
||||
Toast.makeText(this, "创建文件失败", Toast.LENGTH_SHORT).show()
|
||||
return
|
||||
}
|
||||
|
||||
val outputOptions = ImageCapture.OutputFileOptions.Builder(contentResolver, uri, contentValues).build()
|
||||
|
||||
imageCapture.takePicture(
|
||||
outputOptions,
|
||||
ContextCompat.getMainExecutor(this),
|
||||
object : ImageCapture.OnImageSavedCallback {
|
||||
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
|
||||
val savedUri = outputFileResults.savedUri
|
||||
if (savedUri != null) {
|
||||
val msg = "照片已保存: ${savedUri.lastPathSegment}"
|
||||
Log.d(TAG, msg)
|
||||
|
||||
// 验证文件是否真的存在
|
||||
try {
|
||||
val cursor = contentResolver.query(savedUri, null, null, null, null)
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
Log.d(TAG, "文件验证成功: ${savedUri}")
|
||||
// 添加水印
|
||||
addWatermarkToImage(savedUri)
|
||||
runOnUiThread {
|
||||
Toast.makeText(baseContext, "照片已保存并添加水印", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "文件验证失败,但onImageSaved被调用: ${savedUri}")
|
||||
runOnUiThread {
|
||||
Toast.makeText(baseContext, "照片可能未保存", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
cursor?.close()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "文件验证异常: ${e.message}")
|
||||
// 即使验证失败,也尝试添加水印
|
||||
addWatermarkToImage(savedUri)
|
||||
runOnUiThread {
|
||||
Toast.makeText(baseContext, "照片已保存并添加水印", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "onImageSaved被调用但savedUri为null")
|
||||
runOnUiThread {
|
||||
Toast.makeText(baseContext, "照片保存完成", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(exception: ImageCaptureException) {
|
||||
val errorMsg = "拍照失败: ${exception.message}"
|
||||
Log.e(TAG, errorMsg, exception)
|
||||
|
||||
// 检查错误类型
|
||||
when (exception.imageCaptureError) {
|
||||
ImageCapture.ERROR_CAMERA_CLOSED -> {
|
||||
Log.e(TAG, "相机已关闭")
|
||||
runOnUiThread {
|
||||
Toast.makeText(baseContext, "相机已关闭", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
ImageCapture.ERROR_FILE_IO -> {
|
||||
Log.e(TAG, "文件保存失败")
|
||||
runOnUiThread {
|
||||
Toast.makeText(baseContext, "文件保存失败", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
ImageCapture.ERROR_INVALID_CAMERA -> {
|
||||
Log.e(TAG, "无效的相机")
|
||||
runOnUiThread {
|
||||
Toast.makeText(baseContext, "无效的相机", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
ImageCapture.ERROR_CAPTURE_FAILED -> {
|
||||
// 捕获失败,但可能文件已保存
|
||||
Log.e(TAG, "图片捕获失败,但可能已保存")
|
||||
// 验证是否有文件被保存
|
||||
try {
|
||||
// 查询最近保存的文件
|
||||
val projection = arrayOf(MediaStore.Images.Media._ID, MediaStore.Images.Media.DISPLAY_NAME)
|
||||
val sortOrder = "${MediaStore.Images.Media.DATE_ADDED} DESC LIMIT 1"
|
||||
|
||||
val cursor = contentResolver.query(
|
||||
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
|
||||
projection, null, null, sortOrder
|
||||
)
|
||||
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
val displayName = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME))
|
||||
Log.d(TAG, "图片捕获失败但文件已存在: $displayName")
|
||||
// 对最新文件添加水印
|
||||
val latestUri = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media._ID))
|
||||
val latestUriObj = Uri.parse("${MediaStore.Images.Media.EXTERNAL_CONTENT_URI}/$latestUri")
|
||||
addWatermarkToImage(latestUriObj)
|
||||
runOnUiThread {
|
||||
Toast.makeText(baseContext, "图片已保存并添加水印: $displayName", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
} else {
|
||||
runOnUiThread {
|
||||
Toast.makeText(baseContext, "图片捕获失败", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
cursor?.close()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "检查保存文件时出错: ${e.message}")
|
||||
runOnUiThread {
|
||||
Toast.makeText(baseContext, "图片处理出错,但可能已保存", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
ImageCapture.ERROR_UNKNOWN -> {
|
||||
Log.e(TAG, "未知错误")
|
||||
// 特殊处理:如果错误消息包含"processing",可能是内部处理问题
|
||||
if (exception.message?.contains("processing", ignoreCase = true) == true) {
|
||||
Log.d(TAG, "检测到processing错误,但可能图片已保存")
|
||||
// 尝试查询最新保存的图片
|
||||
try {
|
||||
val projection = arrayOf(MediaStore.Images.Media._ID, MediaStore.Images.Media.DISPLAY_NAME, MediaStore.Images.Media.DATE_ADDED)
|
||||
val sortOrder = "${MediaStore.Images.Media.DATE_ADDED} DESC LIMIT 1"
|
||||
|
||||
val cursor = contentResolver.query(
|
||||
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
|
||||
projection, null, null, sortOrder
|
||||
)
|
||||
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
val displayName = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME))
|
||||
val dateAdded = cursor.getLong(cursor.getColumnIndex(MediaStore.Images.Media.DATE_ADDED))
|
||||
val currentTime = System.currentTimeMillis() / 1000
|
||||
|
||||
// 检查是否在最近几秒内保存了文件
|
||||
if (Math.abs(currentTime - dateAdded) <= 5) {
|
||||
Log.d(TAG, "processing错误但图片已保存: $displayName")
|
||||
// 对最新文件添加水印
|
||||
val latestUri = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media._ID))
|
||||
val latestUriObj = Uri.parse("${MediaStore.Images.Media.EXTERNAL_CONTENT_URI}/$latestUri")
|
||||
addWatermarkToImage(latestUriObj)
|
||||
runOnUiThread {
|
||||
Toast.makeText(baseContext, "图片已保存并添加水印: $displayName", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
} else {
|
||||
runOnUiThread {
|
||||
Toast.makeText(baseContext, "图片处理错误", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
runOnUiThread {
|
||||
Toast.makeText(baseContext, "图片处理错误", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
cursor?.close()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "检查保存文件时出错: ${e.message}")
|
||||
runOnUiThread {
|
||||
Toast.makeText(baseContext, "图片处理错误", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
runOnUiThread {
|
||||
Toast.makeText(baseContext, "未知错误", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
Log.e(TAG, "其他错误: ${exception.imageCaptureError}")
|
||||
runOnUiThread {
|
||||
Toast.makeText(baseContext, "拍照失败: ${exception.message}", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun addWatermarkToImage(imageUri: Uri) {
|
||||
try {
|
||||
// 读取图片
|
||||
val inputStream = contentResolver.openInputStream(imageUri)
|
||||
val originalBitmap = BitmapFactory.decodeStream(inputStream)
|
||||
inputStream?.close()
|
||||
|
||||
if (originalBitmap == null) {
|
||||
Log.e(TAG, "无法读取图片: $imageUri")
|
||||
return
|
||||
}
|
||||
|
||||
// 添加水印
|
||||
val watermarkedBitmap = addWatermark(originalBitmap)
|
||||
|
||||
// 保存回原文件
|
||||
val outputStream: OutputStream? = contentResolver.openOutputStream(imageUri, "w")
|
||||
if (outputStream != null) {
|
||||
watermarkedBitmap.compress(Bitmap.CompressFormat.JPEG, 90, outputStream)
|
||||
outputStream.close()
|
||||
Log.d(TAG, "水印添加成功: $imageUri")
|
||||
} else {
|
||||
Log.e(TAG, "无法写入图片: $imageUri")
|
||||
}
|
||||
|
||||
// 回收位图
|
||||
originalBitmap.recycle()
|
||||
watermarkedBitmap.recycle()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "添加水印失败: ${e.message}", e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addWatermark(originalBitmap: Bitmap): Bitmap {
|
||||
val timestamp = SimpleDateFormat("yyyy年-MM月-dd日 HH:mm:ss", Locale.getDefault()).format(Date())
|
||||
|
||||
// 创建可修改的位图副本
|
||||
val mutableBitmap = originalBitmap.copy(Bitmap.Config.ARGB_8888, true)
|
||||
val canvas = Canvas(mutableBitmap)
|
||||
|
||||
// 创建水印画笔
|
||||
val paint = Paint().apply {
|
||||
color = Color.WHITE
|
||||
textSize = 48f
|
||||
typeface = Typeface.DEFAULT_BOLD
|
||||
setShadowLayer(5f, 0f, 0f, Color.BLACK)
|
||||
}
|
||||
|
||||
// 计算文字尺寸
|
||||
val textWidth = paint.measureText(timestamp)
|
||||
val textHeight = paint.descent() - paint.ascent()
|
||||
|
||||
// 计算水印位置(左下角,留边距)
|
||||
val margin = 20f
|
||||
val x = margin
|
||||
val y = mutableBitmap.height - textHeight - margin
|
||||
|
||||
// 绘制水印
|
||||
canvas.drawText(timestamp, x, y, paint)
|
||||
|
||||
return mutableBitmap
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
permissions: Array<String>,
|
||||
grantResults: IntArray
|
||||
) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
if (requestCode == REQUEST_CODE_PERMISSIONS) {
|
||||
if (allPermissionsGranted()) {
|
||||
startCamera()
|
||||
} else {
|
||||
Toast.makeText(this, "相机权限被拒绝,应用无法使用相机功能", Toast.LENGTH_LONG).show()
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp"
|
||||
android:gravity="center"
|
||||
tools:context=".CameraSelectorActivity">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="选择相机模式"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="48dp"
|
||||
android:textColor="@color/black" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnSimpleCamera"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="简单相机"
|
||||
android:textSize="18sp"
|
||||
android:padding="20dp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnWatermarkCamera"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="水印相机"
|
||||
android:textSize="18sp"
|
||||
android:padding="20dp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnMultiCamera"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="多图合成相机"
|
||||
android:textSize="18sp"
|
||||
android:padding="20dp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnSettings"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="32dp"
|
||||
android:text="设置"
|
||||
android:textSize="16sp"
|
||||
android:padding="16dp" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -5,7 +5,7 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".MainActivity">
|
||||
tools:context=".CameraActivity">
|
||||
|
||||
<!-- CameraView for displaying the camera preview -->
|
||||
<androidx.camera.view.PreviewView
|
||||
@@ -35,6 +35,16 @@
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center">
|
||||
|
||||
<!-- Mode button -->
|
||||
<ImageButton
|
||||
android:id="@+id/modeButton"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_margin="16dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:src="@drawable/ic_camera"
|
||||
android:contentDescription="Change mode" />
|
||||
|
||||
<!-- Capture button -->
|
||||
<ImageButton
|
||||
android:id="@+id/captureButton"
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".SimpleCameraActivity">
|
||||
|
||||
<!-- 相机预览视图 -->
|
||||
<androidx.camera.view.PreviewView
|
||||
android:id="@+id/viewFinder"
|
||||
android:layout_width="0dp"
|
||||
@@ -16,31 +15,14 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<!-- 拍照按钮 -->
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/captureButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="32dp"
|
||||
android:src="@android:drawable/ic_menu_camera"
|
||||
app:backgroundTint="@color/primary"
|
||||
android:layout_margin="24dp"
|
||||
android:src="@drawable/ic_camera"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
app:tint="@android:color/white" />
|
||||
|
||||
<!-- 状态提示 -->
|
||||
<TextView
|
||||
android:id="@+id/statusText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="极简相机 - 点击下方按钮拍照"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="14sp"
|
||||
android:background="#80000000"
|
||||
android:padding="8dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
Reference in New Issue
Block a user