需求实现:带跑马灯效果的TextView显示用户实时地址及时间(附定位代码)
我来帮你完整实现这个需求,分为带跑马灯的TextView布局和实时定位+地址解析逻辑两部分,直接上可运行的代码:
1. 实现带跑马灯效果的TextView
首先补全你的XML布局,跑马灯效果需要特定属性才能生效,以下是完整的布局文件:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:padding="16dp"> <TextView android:id="@+id/marquee_location_tv" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="正在获取位置信息..." android:textSize="16sp" android:textColor="#333333" <!-- 跑马灯核心属性 --> android:ellipsize="marquee" android:marqueeRepeatLimit="marquee_forever" android:focusable="true" android:focusableInTouchMode="true" android:singleLine="true" android:paddingVertical="8dp"/> </LinearLayout>
属性说明:
android:ellipsize="marquee":指定文本溢出时触发跑马灯滚动android:marqueeRepeatLimit="marquee_forever":设置无限循环滚动android:focusable="true"+android:focusableInTouchMode="true":确保TextView能获取焦点(跑马灯的前提)android:singleLine="true":跑马灯仅支持单行文本
2. 实时定位与地址解析逻辑
使用Google推荐的FusedLocationProviderClient实现高准确率的定位,同时结合Geocoder解析经纬度为具体地址,还要记录停留时间并更新TextView。
第一步:添加权限到AndroidManifest.xml
<!-- 前台定位权限 --> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <!-- 如需后台定位,添加以下权限 --> <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
第二步:Activity完整代码(Kotlin示例)
import android.Manifest import android.content.Context import android.content.pm.PackageManager import android.location.Address import android.location.Geocoder import android.location.Location import android.os.Bundle import android.os.Looper import android.widget.TextView import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import com.google.android.gms.location.FusedLocationProviderClient import com.google.android.gms.location.LocationCallback import com.google.android.gms.location.LocationRequest import com.google.android.gms.location.LocationResult import com.google.android.gms.location.LocationServices import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.io.IOException import java.util.Locale class MainActivity : AppCompatActivity() { private lateinit var fusedLocationClient: FusedLocationProviderClient private lateinit var locationRequest: LocationRequest private var locationCallback: LocationCallback? = null private var lastLocationTimestamp: Long = 0L private val REQUEST_LOCATION_PERMISSION = 1001 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // 初始化定位客户端 fusedLocationClient = LocationServices.getFusedLocationProviderClient(this) setupLocationRequest() setupLocationCallback() // 检查权限并启动定位 checkLocationPermission() } // 配置定位请求参数 private fun setupLocationRequest() { locationRequest = LocationRequest.create().apply { interval = 10000 // 每10秒更新一次位置 fastestInterval = 5000 // 最快5秒更新一次 priority = LocationRequest.PRIORITY_HIGH_ACCURACY // 高准确率模式 } } // 定位回调:获取到位置后处理 private fun setupLocationCallback() { locationCallback = object : LocationCallback() { override fun onLocationResult(locationResult: LocationResult) { super.onLocationResult(locationResult) locationResult.lastLocation?.let { location -> lastLocationTimestamp = System.currentTimeMillis() // 解析地址并更新UI updateLocationAddress(location) } } } } // 检查并申请定位权限 private fun checkLocationPermission() { when { ContextCompat.checkSelfPermission( this, Manifest.permission.ACCESS_FINE_LOCATION ) == PackageManager.PERMISSION_GRANTED -> { // 权限已授予,启动定位 startLocationUpdates() } else -> { // 动态申请权限 ActivityCompat.requestPermissions( this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), REQUEST_LOCATION_PERMISSION ) } } } // 权限申请结果回调 override fun onRequestPermissionsResult( requestCode: Int, permissions: Array<out String>, grantResults: IntArray ) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode == REQUEST_LOCATION_PERMISSION) { if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { startLocationUpdates() } else { Toast.makeText(this, "需要定位权限才能获取位置信息", Toast.LENGTH_SHORT).show() } } } // 启动位置更新 private fun startLocationUpdates() { try { fusedLocationClient.requestLocationUpdates( locationRequest, locationCallback!!, Looper.getMainLooper() ) } catch (e: SecurityException) { e.printStackTrace() } } // 解析经纬度为地址,并更新TextView private fun updateLocationAddress(location: Location) { val geocoder = Geocoder(this, Locale.getDefault()) CoroutineScope(Dispatchers.IO).launch { try { // 获取第一个匹配的地址 val addresses: List<Address>? = geocoder.getFromLocation( location.latitude, location.longitude, 1 ) addresses?.firstOrNull()?.let { address -> // 拼接显示文本:地址 + 停留时间 val displayText = buildString { append("当前位置:${address.getAddressLine(0)}") append(" | 停留时间:${formatElapsedTime(System.currentTimeMillis() - lastLocationTimestamp)}") } // 回到主线程更新UI withContext(Dispatchers.Main) { val marqueeTv = findViewById<TextView>(R.id.marquee_location_tv) marqueeTv.text = displayText // 确保TextView获取焦点,触发跑马灯 marqueeTv.isFocusable = true marqueeTv.isFocusableInTouchMode = true marqueeTv.requestFocus() } } } catch (e: IOException) { e.printStackTrace() withContext(Dispatchers.Main) { Toast.makeText(this@MainActivity, "地址解析失败,显示经纬度:${location.latitude}, ${location.longitude}", Toast.LENGTH_SHORT).show() } } } } // 格式化停留时间为HH:mm:ss private fun formatElapsedTime(elapsedMs: Long): String { val seconds = (elapsedMs / 1000).toInt() % 60 val minutes = (elapsedMs / (1000 * 60)).toInt() % 60 val hours = (elapsedMs / (1000 * 60 * 60)).toInt() return String.format("%02d:%02d:%02d", hours, minutes, seconds) } // 停止定位更新,避免内存泄漏 override fun onDestroy() { super.onDestroy() locationCallback?.let { fusedLocationClient.removeLocationUpdates(it) } } }
注意事项
- 跑马灯触发问题:如果布局中有其他可焦点控件,可能会抢走TextView的焦点,导致跑马灯不生效。可以自定义一个始终返回焦点的TextView:
class AlwaysFocusedTextView(context: Context, attrs: AttributeSet?) : TextView(context, attrs) { override fun isFocused(): Boolean { return true } }
然后在XML中替换原TextView即可。
Geocoder兼容性:部分设备或地区可能无法使用Geocoder解析地址,建议在异常处理中显示经纬度作为备选。
后台定位:如果需要App在后台也能更新位置,除了添加
ACCESS_BACKGROUND_LOCATION权限,还需要在系统设置中允许App的后台定位权限。
内容的提问来源于stack exchange,提问作者Yusuf Dalhatu
相关产品推荐
相关产品推荐

