You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在基于utsmannn版OSMdroid的跑步追踪应用中获取地图截图

跑步追踪应用地图截图实现方案(基于utsmannn的OSMdroid Compose库)

一、直接用当前库实现截图

你用的这个Compose库是对原生OSMdroid的封装,完全可以通过获取内部的MapView实例实现截图,步骤如下:

  1. 维护MapView实例的引用
    在TrackerMap可组合函数中,用remember保存MapView的可变状态,替换原来的全局变量:
@Composable
fun TrackerMap(
    modifier: Modifier = Modifier,
    isRunFinished: Boolean,
    currentLocation: Location?,
    locations: List<List<LocationTimestamp>>,
    onSnapshot: (Bitmap) -> Unit
) {
    val context = LocalContext.current
    // 用remember保存MapView实例,避免重组时丢失引用
    val mapInstance = remember { mutableStateOf<MapView?>(null) }
    
    // ...原有cameraState、overlayManagerState、mapProperties等代码
    
    OpenStreetMap(
        modifier = modifier.fillMaxSize(),
        cameraState = cameraState,
        properties = mapProperties,
        overlayManagerState = overlayManagerState,
        onFirstLoadListener = {
            // 将MapView实例存入状态
            mapInstance.value = overlayManagerState.getMap()
            val copyright = CopyrightOverlay(context)
            overlayManagerState.overlayManager.add(copyright)
        }
    ) {
        RunikPolylines(locations = locations)
    }

    // ...添加截图逻辑
}
  1. 监听跑步结束事件,触发截图
    添加LaunchedEffect监听isRunFinished状态,当状态变为true时执行截图:
// 在TrackerMap函数内部,OpenStreetMap代码之后添加
LaunchedEffect(isRunFinished) {
    if (isRunFinished && mapInstance.value != null) {
        // 延迟500ms,确保地图轨迹、UI元素完全渲染完成,避免截图空白
        delay(500)
        // 调用MapView的原生方法生成截图Bitmap
        val screenshotBitmap = mapInstance.value?.drawToBitmap()
        screenshotBitmap?.let {
            // 将Bitmap回调给上层处理(比如保存到相册)
            onSnapshot(it)
        }
    }
}

二、原生OSMdroid方法(和库实现逻辑一致)

utsmannn的库本质是对原生OSMdroid MapView的Compose封装,所以上面的方法其实就是调用了原生的截图能力。如果库存在兼容问题,直接操作原生MapView的逻辑完全相同:

  • 核心还是获取MapView实例,调用drawToBitmap()生成截图
  • 区别仅在于获取MapView的方式(原生是findViewById,这里是通过库的overlayManagerState.getMap())

三、将截图保存到相册

拿到Bitmap后,用以下方法可以将截图保存到系统相册,注意权限差异:

import android.content.ContentValues
import android.content.Context
import android.graphics.Bitmap
import android.os.Build
import android.os.Environment
import android.provider.MediaStore

fun saveRunScreenshotToGallery(context: Context, bitmap: Bitmap): Boolean {
    // 生成唯一文件名
    val fileName = "Running_Track_${System.currentTimeMillis()}.png"
    val contentValues = ContentValues().apply {
        put(MediaStore.Images.Media.DISPLAY_NAME, fileName)
        put(MediaStore.Images.Media.MIME_TYPE, "image/png")
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            // Android Q及以上,指定保存到Pictures下的RunningTracker文件夹
            put(MediaStore.Images.Media.RELATIVE_PATH, "${Environment.DIRECTORY_PICTURES}/RunningTracker")
            put(MediaStore.Images.Media.IS_PENDING, 1)
        } else {
            // Android Q以下,需要WRITE_EXTERNAL_STORAGE权限
            put(MediaStore.Images.Media.DATA, "${Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)}/RunningTracker/$fileName")
        }
    }

    val contentResolver = context.contentResolver
    val imageUri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues) ?: return false

    return try {
        // 将Bitmap写入文件
        contentResolver.openOutputStream(imageUri)?.use { outputStream ->
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
        }
        // Android Q及以上,标记文件为已完成
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            contentValues.put(MediaStore.Images.Media.IS_PENDING, 0)
            contentResolver.update(imageUri, contentValues, null, null)
        }
        true
    } catch (e: Exception) {
        // 保存失败,删除临时Uri
        contentResolver.delete(imageUri, null, null)
        e.printStackTrace()
        false
    }
}

四、新手必看注意事项

  • 权限处理:
    • Android 10(Q)及以上无需申请WRITE_EXTERNAL_STORAGE权限
    • Android 9及以下需要在Manifest中添加权限,并在运行时申请:
      <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28"/>
      
    • Compose中可通过rememberLauncherForActivityResult处理运行时权限申请
  • 延迟截图的必要性:跑步结束时地图可能还在更新轨迹或调整相机位置,短延迟能避免截图空白
  • 内存管理:截图后若不再需要Bitmap,记得调用bitmap.recycle()释放内存

内容的提问来源于stack exchange,提问作者esmail.unlinea

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 21:08:13