Fix compilation errors: remove duplicate methods, add missing imports, fix location manager

This commit is contained in:
xiajiid
2026-02-08 16:33:44 +08:00
parent 81c12711d0
commit bac12c1808
2 changed files with 239 additions and 341 deletions

View File

@@ -55,348 +55,14 @@ class CameraActivity : AppCompatActivity() {
private lateinit var photoPreviewLayout: LinearLayout
private var imageCapture: ImageCapture? = null
private lateinit var cameraExecutor: ExecutorService
private var locationManagerHelper: LocationManagerHelper? = null
private var currentLocation: Location? = null
// 存储拍摄的图片URI
private val capturedImageUris = mutableListOf<Uri>()
// 图片合成器
private val compositor = ImageCompositor()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 初始化视图
viewFinder = findViewById(R.id.viewFinder)
captureButton = findViewById(R.id.captureButton)
settingsButton = findViewById(R.id.settingsButton)
photoPreviewLayout = findViewById(R.id.photoPreviewLayout)
// 请求权限
if (allPermissionsGranted()) {
startCamera()
} else {
ActivityCompat.requestPermissions(
this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS
)
}
// 设置点击监听器
captureButton.setOnClickListener { takePhoto() }
settingsButton.setOnClickListener { openSettings() }
// 初始化线程池
cameraExecutor = Executors.newSingleThreadExecutor()
}
private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
ContextCompat.checkSelfPermission(baseContext, it) == PackageManager.PERMISSION_GRANTED
}
private fun startCamera() {
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener({
val 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
try {
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(
this, cameraSelector, preview, imageCapture
)
} catch (exc: Exception) {
Log.e(TAG, "相机绑定失败", exc)
}
}, ContextCompat.getMainExecutor(this))
}
private fun takePhoto() {
val imageCapture = imageCapture ?: return
val outputFileOptions = ImageCapture.OutputFileOptions.Builder(
createImageUri()
).build()
imageCapture.takePicture(
outputFileOptions,
ContextCompat.getMainExecutor(this),
object : ImageCapture.OnImageSavedCallback {
override fun onError(exception: ImageCaptureException) {
Log.e(TAG, "照片拍摄失败: ${exception.message}", exception)
Toast.makeText(
baseContext,
"照片拍摄失败: ${exception.message}",
Toast.LENGTH_SHORT
).show()
}
override fun onImageSaved(output: ImageCapture.OutputFileResults) {
// 获取保存的URI
val savedUri = output.savedUri ?: return
// 添加水印到已保存的照片
addWatermarkToImage(savedUri)
// 添加到捕获的图片列表
capturedImageUris.add(savedUri)
// 更新预览
updatePhotoPreviews()
Toast.makeText(
baseContext,
"照片已保存",
Toast.LENGTH_SHORT
).show()
// 刷新相册
refreshGallery(savedUri)
}
}
)
}
private fun createImageUri(): Uri {
val contentValues = ContentValues().apply {
put(MediaStore.Images.Media.DISPLAY_NAME, "巡检_${System.currentTimeMillis()}.jpg")
put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis())
}
}
return contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
?: throw IOException("无法创建图片URI")
}
private fun addWatermarkToImage(imageUri: Uri) {
try {
val inputStream = contentResolver.openInputStream(imageUri)
val originalBitmap = BitmapFactory.decodeStream(inputStream)
inputStream?.close()
val watermarkedBitmap = addWatermark(originalBitmap)
// 将带水印的图片保存回原URI
val outputStream = contentResolver.openOutputStream(imageUri, "rwt") // truncate and write
watermarkedBitmap.compress(Bitmap.CompressFormat.JPEG, 95, outputStream)
outputStream?.close()
// 回收位图以释放内存
if (watermarkedBitmap != originalBitmap) {
originalBitmap.recycle()
}
watermarkedBitmap.recycle()
} catch (e: Exception) {
Log.e(TAG, "添加水印失败: ${e.message}")
}
}
private fun addWatermark(bitmap: Bitmap): Bitmap {
val watermarkedBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true)
val canvas = Canvas(watermarkedBitmap)
val paint = Paint().apply {
color = Color.WHITE
textSize = 48f
typeface = Typeface.DEFAULT_BOLD
textAlign = Paint.Align.LEFT
}
// 绘制时间水印
val timestamp = formatDate(System.currentTimeMillis())
canvas.drawText(timestamp, 50f, watermarkedBitmap.height - 100f, paint)
// 绘制位置水印
getCurrentLocation()?.let { location ->
val address = getAddressFromLocation(location.latitude, location.longitude)
canvas.drawText(address, 50f, watermarkedBitmap.height - 50f, paint)
}
return watermarkedBitmap
}
private fun updatePhotoPreviews() {
// 清空当前预览
photoPreviewLayout.removeAllViews()
// 限制最多显示4张预览图
val displayCount = minOf(capturedImageUris.size, 4)
for (i in 0 until displayCount) {
val imageView = ImageView(this)
val params = LinearLayout.LayoutParams(
200, 200
).apply {
setMargins(8, 8, 8, 8)
}
imageView.layoutParams = params
imageView.scaleType = ImageView.ScaleType.CENTER_CROP
// 加载并设置图片
val uri = capturedImageUris[capturedImageUris.size - displayCount + i] // 显示最新的几张
imageView.setImageURI(uri)
// 添加点击事件以查看大图
imageView.setOnClickListener {
// 可以在这里实现查看大图的功能
}
photoPreviewLayout.addView(imageView)
}
// 如果已有4张图片添加合成按钮
if (capturedImageUris.size >= 4) {
addComposeButton()
}
}
private fun addComposeButton() {
// 添加合成按钮
val composeButton = Button(this)
composeButton.text = "合成图片"
composeButton.setBackgroundColor(ContextCompat.getColor(this, R.color.purple_200))
composeButton.setTextColor(Color.WHITE)
val params = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
).apply {
setMargins(8, 8, 8, 8)
gravity = android.view.Gravity.CENTER_HORIZONTAL
}
composeButton.layoutParams = params
composeButton.setOnClickListener {
composeLastFourImages()
}
photoPreviewLayout.addView(composeButton)
}
private fun composeLastFourImages() {
// 获取最近的4张图片
val recentUris = if (capturedImageUris.size >= 4) {
capturedImageUris.takeLast(4)
} else {
capturedImageUris
}
// 将URI转换为Bitmap
val bitmaps = mutableListOf<Bitmap>()
for (uri in recentUris) {
try {
val inputStream = contentResolver.openInputStream(uri)
val bitmap = BitmapFactory.decodeStream(inputStream)
inputStream?.close()
bitmaps.add(bitmap)
} catch (e: Exception) {
Log.e(TAG, "加载图片失败: ${e.message}")
}
}
if (bitmaps.isEmpty()) {
Toast.makeText(this, "没有图片可合成", Toast.LENGTH_SHORT).show()
return
}
// 合成图片
val composedBitmap = compositor.composeImagesToGrid(bitmaps, 2000, 2000) // 目标尺寸2000x2000
// 添加标题和内容
val finalBitmap = compositor.addTextOverlay(
composedBitmap,
"巡检报告",
"这是巡检内容的详细描述"
)
// 保存合成后的图片
saveCompositedImage(finalBitmap)
// 回收位图
for (bitmap in bitmaps) {
if (bitmap != finalBitmap) {
bitmap.recycle()
}
}
if (finalBitmap != composedBitmap) {
composedBitmap.recycle()
}
}
private fun saveCompositedImage(bitmap: Bitmap) {
try {
val fileName = compositor.generateFileName()
val contentValues = ContentValues().apply {
put(MediaStore.Images.Media.DISPLAY_NAME, fileName)
put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis())
}
}
val uri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
?: throw IOException("无法创建图片URI")
val outputStream = contentResolver.openOutputStream(uri)
bitmap.compress(Bitmap.CompressFormat.JPEG, 95, outputStream)
outputStream?.close()
Toast.makeText(this, "合成图片已保存", Toast.LENGTH_SHORT).show()
// 刷新相册
refreshGallery(uri)
} catch (e: Exception) {
Log.e(TAG, "保存合成图片失败: ${e.message}")
Toast.makeText(this, "保存合成图片失败", Toast.LENGTH_SHORT).show()
}
}
private fun refreshGallery(uri: Uri) {
// 通知相册更新
val contentValues = ContentValues().apply {
put(MediaStore.Images.Media.IS_PENDING, 0)
}
contentResolver.update(uri, contentValues, null, null)
}
// 将时间戳转换为指定格式的日期时间字符串
fun formatDate(timestamp: Long): String {
val sdf = SimpleDateFormat("yyyy年-MM月-dd日 HH:mm:ss", Locale.getDefault())
return sdf.format(Date(timestamp))
}
// 使用Geocoder将经纬度转换为地址
private fun getAddressFromLocation(latitude: Double, longitude: Double): String? {
return try {
val geocoder = Geocoder(this, Locale.getDefault())
val addresses = geocoder.getFromLocation(latitude, longitude, 1)
if (addresses?.isEmpty() == false) {
val address = addresses[0]
val addressLine = address.getAddressLine(0)
addressLine ?: "${latitude}, ${longitude}"
} else {
"${latitude}, ${longitude}"
}
} catch (e: Exception) {
Log.e(TAG, "无法解析地址: ${e.message}")
"${latitude}, ${longitude}"
}
}
private var locationManagerHelper: LocationManagerHelper? = null
private var currentLocation: Location? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
@@ -451,6 +117,241 @@ class CameraActivity : AppCompatActivity() {
cameraExecutor.shutdown()
}
private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
ContextCompat.checkSelfPermission(baseContext, it) == PackageManager.PERMISSION_GRANTED
}
private fun startCamera() {
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener({
val 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
try {
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(
this, cameraSelector, preview, imageCapture
)
} catch (e: Exception) {
Log.e(TAG, "相机绑定失败", e)
}
}, ContextCompat.getMainExecutor(this))
}
@SuppressLint("MissingPermission")
private fun takePhoto() {
val imageCapture = this.imageCapture ?: return
val outputOptions = createOutputFileOptions()
imageCapture.takePicture(
outputOptions,
ContextCompat.getMainExecutor(this),
object : ImageCapture.OnImageSavedCallback {
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
val savedUri = outputFileResults.savedUri ?: return
capturedImageUris.add(savedUri)
showPhotoPreview(savedUri)
// 如果已经有4张照片显示合成按钮
if (capturedImageUris.size >= 4) {
showComposeButton()
}
val msg = "照片已保存: ${savedUri.lastPathSegment}"
Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
Log.d(TAG, msg)
}
override fun onError(exception: ImageCaptureException) {
Log.e(TAG, "拍照失败: ${exception.message}", exception)
Toast.makeText(baseContext, "拍照失败", Toast.LENGTH_SHORT).show()
}
}
)
}
private fun createOutputFileOptions(): ImageCapture.OutputFileOptions {
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)
return ImageCapture.OutputFileOptions.Builder(contentResolver, uri).build()
}
private fun showPhotoPreview(uri: Uri) {
val imageView = ImageView(this)
val layoutParams = LinearLayout.LayoutParams(120, 120)
layoutParams.marginEnd = 8
imageView.layoutParams = layoutParams
imageView.scaleType = ImageView.ScaleType.CENTER_CROP
try {
val inputStream = contentResolver.openInputStream(uri)
val bitmap = BitmapFactory.decodeStream(inputStream)
inputStream?.close()
imageView.setImageBitmap(bitmap)
} catch (e: IOException) {
Log.e(TAG, "预览图片加载失败", e)
}
photoPreviewLayout.addView(imageView)
}
private fun showComposeButton() {
// 添加合成按钮到预览区域
val composeButton = Button(this)
composeButton.text = "合成图片"
composeButton.setOnClickListener { composeImages() }
val layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
layoutParams.marginStart = 16
composeButton.layoutParams = layoutParams
photoPreviewLayout.addView(composeButton)
}
private fun composeImages() {
if (capturedImageUris.size < 4) {
Toast.makeText(this, "需要至少4张图片进行合成", Toast.LENGTH_SHORT).show()
return
}
// 获取最近4张图片
val recentUris = capturedImageUris.takeLast(4)
val bitmaps = mutableListOf<Bitmap>()
for (uri in recentUris) {
try {
val inputStream = contentResolver.openInputStream(uri)
val bitmap = BitmapFactory.decodeStream(inputStream)
inputStream?.close()
bitmap?.let { bitmaps.add(it) }
} catch (e: IOException) {
Log.e(TAG, "加载图片失败: $uri", e)
}
}
if (bitmaps.size < 4) {
Toast.makeText(this, "部分图片加载失败", Toast.LENGTH_SHORT).show()
return
}
// 合成图片
val composedBitmap = compositor.composeImages(bitmaps, "巡检报告", "合成时间: ${Date()}")
// 保存合成图片
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) {
try {
val outputStream: OutputStream? = contentResolver.openOutputStream(uri)
if (outputStream != null) {
composedBitmap.compress(Bitmap.CompressFormat.JPEG, 90, outputStream)
outputStream.close()
Toast.makeText(this, "图片合成成功", Toast.LENGTH_SHORT).show()
}
} catch (e: IOException) {
Log.e(TAG, "保存合成图片失败", e)
Toast.makeText(this, "保存失败", Toast.LENGTH_SHORT).show()
}
}
}
private fun getAddressFromLocation(location: Location): String {
val geocoder = Geocoder(this, Locale.getDefault())
return try {
val addresses = geocoder.getFromLocation(
location.latitude,
location.longitude,
1
)
if (addresses != null && addresses.isNotEmpty()) {
val address = addresses[0]
"${address.locality}, ${address.subLocality}, ${address.thoroughfare}"
} else {
"${location.latitude.roundToInt()}, ${location.longitude.roundToInt()}"
}
} catch (e: Exception) {
Log.e(TAG, "无法解析地址: ${e.message}")
"${location.latitude.roundToInt()}, ${location.longitude.roundToInt()}"
}
}
private fun getLocationString(): String {
val location = getCurrentLocation()
return if (location != null) {
getAddressFromLocation(location)
} else {
"未知位置"
}
}
private fun addWatermarkToBitmap(originalBitmap: Bitmap): Bitmap {
val timestamp = SimpleDateFormat("yyyy年-MM月-dd日 HH:mm:ss", Locale.getDefault()).format(Date())
val location = getLocationString()
val watermarkText = "$timestamp\n$location"
val mutableBitmap = originalBitmap.copy(Bitmap.Config.ARGB_8888, true)
val canvas = Canvas(mutableBitmap)
val paint = Paint().apply {
color = Color.WHITE
textSize = 32f
typeface = Typeface.DEFAULT_BOLD
setShadowLayer(5f, 0f, 0f, Color.BLACK)
}
val textWidth = paint.measureText(watermarkText)
val textHeight = paint.descent() - paint.ascent()
val x = 20f
val y = mutableBitmap.height - textHeight - 20f
canvas.drawText(watermarkText, x, y, paint)
return mutableBitmap
}
private fun openSettings() {
// 打开设置页面
val settingsFragment = SettingsFragment()
@@ -472,9 +373,4 @@ class CameraActivity : AppCompatActivity() {
}
}
}
override fun onDestroy() {
super.onDestroy()
cameraExecutor.shutdown()
}
}

View File

@@ -1,6 +1,8 @@
package com.example.app
import android.graphics.*
import android.text.StaticLayout
import android.text.Layout
import java.io.File
import java.text.SimpleDateFormat
import java.util.*