安卓开发:实现拍照/图库图片添加GeoTag功能求助
嘿,我之前做过类似的功能,给你整理了一套完整的解决方案,一步步来应该能解决你的问题:
安卓拍照/选图添加并显示GeoTag 实现方案
一、先搞定基础权限配置
GeoTag依赖GPS位置信息,同时拍照和访问图库也需要对应权限,先在AndroidManifest.xml里声明:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> <!-- Android 13+ 访问图库 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
Android 6.0以上需要动态申请权限,在Activity里加这段逻辑:
private val requiredPermissions = arrayOf(Manifest.permission.CAMERA, Manifest.permission.ACCESS_FINE_LOCATION) private const val PERMISSION_REQUEST_CODE = 1001 private fun checkAndRequestPermissions() { val isPermissionsGranted = requiredPermissions.all { ContextCompat.checkSelfPermission(this, it) == PackageManager.PERMISSION_GRANTED } if (!isPermissionsGranted) { ActivityCompat.requestPermissions(this, requiredPermissions, PERMISSION_REQUEST_CODE) } } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode == PERMISSION_REQUEST_CODE) { if (grantResults.any { it != PackageManager.PERMISSION_GRANTED }) { Toast.makeText(this, "相机和位置权限是功能必需的哦", Toast.LENGTH_SHORT).show() } } }
二、拍照时嵌入GeoTag(用谷歌推荐的CameraX)
CameraX比旧版Camera API更稳定适配性更好,先在build.gradle添加依赖:
dependencies { def camerax_version = "1.3.0" implementation "androidx.camera:camera-core:$camerax_version" implementation "androidx.camera:camera-camera2:$camerax_version" implementation "androidx.camera:camera-lifecycle:$camerax_version" implementation "androidx.camera:camera-view:$camerax_version" }
然后实现拍照+获取位置+写入EXIF的完整流程:
- 先获取当前位置(用FusedLocationProviderClient):
private lateinit var fusedLocationClient: FusedLocationProviderClient private fun getCurrentLocation(): Task<Location> { fusedLocationClient = LocationServices.getFusedLocationProviderClient(this) return fusedLocationClient.lastLocation }
- 拍照并写入GeoTag到图片EXIF:
private val imageCapture = ImageCapture.Builder().build() // 拍照按钮点击事件 fun takePhoto() { getCurrentLocation().addOnSuccessListener { location -> val photoFile = File(externalMediaDirs.first(), "${System.currentTimeMillis()}.jpg") val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build() imageCapture.takePicture(outputOptions, ContextCompat.getMainExecutor(this), object : ImageCapture.OnImageSavedCallback { override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) { location?.let { writeGeoTagToExif(photoFile.absolutePath, it.latitude, it.longitude) } // 拍完直接显示图片和GeoTag displayImageWithGeoTag(photoFile.absolutePath) } override fun onError(exception: ImageCaptureException) { Toast.makeText(this@MainActivity, "拍照失败:${exception.message}", Toast.LENGTH_SHORT).show() } }) }.addOnFailureListener { Toast.makeText(this, "获取位置失败:${it.message}", Toast.LENGTH_SHORT).show() } } // 将经纬度写入图片EXIF的工具方法 private fun writeGeoTagToExif(imagePath: String, latitude: Double, longitude: Double) { try { val exifInterface = ExifInterface(imagePath) // 转换经纬度为EXIF要求的度分秒格式 val latDeg = latitude.toInt() val latMin = ((latitude - latDeg) * 60).toInt() val latSec = ((latitude - latDeg - latMin/60.0) * 3600 * 1000).toInt() val lonDeg = longitude.toInt() val lonMin = ((longitude - lonDeg) * 60).toInt() val lonSec = ((longitude - lonDeg - lonMin/60.0) * 3600 * 1000).toInt() exifInterface.setAttribute(ExifInterface.TAG_GPS_LATITUDE, "$latDeg/1,$latMin/1,$latSec/1000") exifInterface.setAttribute(ExifInterface.TAG_GPS_LATITUDE_REF, if (latitude >= 0) "N" else "S") exifInterface.setAttribute(ExifInterface.TAG_GPS_LONGITUDE, "$lonDeg/1,$lonMin/1,$lonSec/1000") exifInterface.setAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF, if (longitude >= 0) "E" else "W") exifInterface.saveAttributes() } catch (e: IOException) { e.printStackTrace() } }
三、从图库选图读取GeoTag
实现图库选择逻辑,并读取图片的EXIF地理信息:
private const val GALLERY_REQUEST_CODE = 1002 // 打开图库按钮点击事件 fun openGallery() { val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI) startActivityForResult(intent, GALLERY_REQUEST_CODE) } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == GALLERY_REQUEST_CODE && resultCode == RESULT_OK && data != null) { val imageUri = data.data imageUri?.let { uri -> val imagePath = getRealPathFromUri(uri) imagePath?.let { displayImageWithGeoTag(it) } } } } // 适配不同系统版本,获取图片真实路径 private fun getRealPathFromUri(uri: Uri): String? { var path: String? = null val projection = arrayOf(MediaStore.Images.Media.DATA) contentResolver.query(uri, projection, null, null, null)?.use { cursor -> if (cursor.moveToFirst()) { val columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA) path = cursor.getString(columnIndex) } } return path } // 读取图片EXIF中的GeoTag private fun readGeoTagFromExif(imagePath: String): Pair<Double, Double>? { try { val exifInterface = ExifInterface(imagePath) val latStr = exifInterface.getAttribute(ExifInterface.TAG_GPS_LATITUDE) val latRef = exifInterface.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF) val lonStr = exifInterface.getAttribute(ExifInterface.TAG_GPS_LONGITUDE) val lonRef = exifInterface.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF) if (!latStr.isNullOrEmpty() && !latRef.isNullOrEmpty() && !lonStr.isNullOrEmpty() && !lonRef.isNullOrEmpty()) { // 把度分秒格式转成十进制经纬度 val latParts = latStr.split(",").map { part -> val split = part.split("/") split[0].toDouble() / split[1].toDouble() } val latitude = latParts[0] + latParts[1]/60 + latParts[2]/3600 val correctedLat = if (latRef == "S") -latitude else latitude val lonParts = lonStr.split(",").map { part -> val split = part.split("/") split[0].toDouble() / split[1].toDouble() } val longitude = lonParts[0] + lonParts[1]/60 + lonParts[2]/3600 val correctedLon = if (lonRef == "W") -longitude else longitude return Pair(correctedLat, correctedLon) } } catch (e: IOException) { e.printStackTrace() } return null }
四、显示图片和GeoTag
有两种显示方式,你可以选一种:
方式1:用TextView叠加在ImageView上
private fun displayImageWithGeoTag(imagePath: String) { val imageView = findViewById<ImageView>(R.id.imageView) imageView.setImageBitmap(BitmapFactory.decodeFile(imagePath)) val geoTagTextView = findViewById<TextView>(R.id.geoTagTextView) val geoTag = readGeoTagFromExif(imagePath) geoTagTextView.text = if (geoTag != null) { "📍 经纬度:${String.format("%.6f", geoTag.first)}, ${String.format("%.6f", geoTag.second)}" } else { "⚠️ 无GeoTag信息" } }
方式2:直接把GeoTag绘制在图片上
private fun drawGeoTagOnImage(imagePath: String): Bitmap { val originalBitmap = BitmapFactory.decodeFile(imagePath) val mutableBitmap = originalBitmap.copy(Bitmap.Config.ARGB_8888, true) val canvas = Canvas(mutableBitmap) val paint = Paint().apply { color = Color.WHITE textSize = 40f strokeWidth = 2f style = Paint.Style.FILL_AND_STROKE shadowLayer = 5f, 2f, 2f, Color.BLACK // 加阴影让文字更清晰 } val geoTag = readGeoTagFromExif(imagePath) val text = geoTag?.let { "📍 ${String.format("%.6f", it.first)}, ${String.format("%.6f", it.second)}" } ?: "⚠️ 无GeoTag信息" // 在图片左下角绘制文字 canvas.drawText(text, 20f, mutableBitmap.height - 20f, paint) return mutableBitmap } // 调用时: imageView.setImageBitmap(drawGeoTagOnImage(imagePath))
内容的提问来源于stack exchange,提问作者Krish
相关产品推荐
相关产品推荐

