kotlin 编写一个简单的天气预报app (八)获取设备坐标

2024-05-25 06:36

本文主要是介绍kotlin 编写一个简单的天气预报app (八)获取设备坐标,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、请求获取设备坐标的权限

1.1 对应的权限需要申请在AndroidManifest

    <uses-permission android:name="android.permission.INTERNET" /><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_NETWORK_STATE" />

1.2 新建一个LocationManagerUtils类

class LocationManagerUtils(val context: Context, ) {private val permissionRequestCode = 1interface PermissionCallback {fun onPermissionGranted()fun onPermissionDenied()}private var permissionCallback: PermissionCallback? = nullfun setPermissionCallback(callback: PermissionCallback) {permissionCallback = callback}private fun checkLocationPermissions() : Boolean {val fineLocationPermission =ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION)val coarseLocationPermission =ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION)return (fineLocationPermission  == PackageManager.PERMISSION_GRANTED&& coarseLocationPermission == PackageManager.PERMISSION_GRANTED)}fun requestLocationPermission(activity: Activity) {if(checkLocationPermissions()) {ActivityCompat.requestPermissions(activity, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION,Manifest.permission.ACCESS_COARSE_LOCATION), permissionRequestCode)}}fun onRequestPermissionsResult(requestCode: Int,grantResults: IntArray) {if (requestCode == permissionRequestCode) {var allPermissionsGranted = truefor (result in grantResults) {if (result != PackageManager.PERMISSION_GRANTED) {allPermissionsGranted = falsebreak}}if (!allPermissionsGranted) {permissionCallback?.onPermissionDenied()} else {permissionCallback?.onPermissionGranted()}}}
}

这个LocationManagerUtils类是一个用于处理位置权限和位置更新的实用工具类。

它提供了一种方便的方法来检查和请求位置权限,并通过回调函数来处理权限请求的结果和位置更新。

下面是这个类的一些关键部分的解释:

context: Context: 这是一个构造函数参数,用于获取应用的上下文。

permissionRequestCode = 1: 这是一个私有变量,用于标识权限请求的请求码。

PermissionCallback: 这是一个内部接口,定义了两个个方法:onPermissionGranted()onPermissionDenied()。这些方法分别用于处理权限被授予、权限被拒绝的情况。

permissionCallback: PermissionCallback? = null: 这是一个私有变量,用于存储PermissionCallback的实例。

setPermissionCallback(callback: PermissionCallback): 这是一个公开方法,用于设置PermissionCallback的实例。

checkLocationPermissions(): 这是一个私有方法,用于检查应用是否已经获得了细粒度和粗粒度位置权限。它返回一个布尔值,表示是否已经获得了这两个权限。

requestLocationPermission(activity: Activity): 这是一个公开方法,用于请求位置权限。如果应用尚未获得位置权限,它会调用ActivityCompat.requestPermissions()方法来请求权限。

onRequestPermissionsResult(requestCode: Int, grantResults: IntArray): 这是一个公开方法,用于处理权限请求的结果。它会检查请求码是否匹配,并根据grantResults数组中的结果来调用PermissionCallback的相应方法。

总的来说,这个类提供了一种封装好的方式来处理位置权限的请求和结果,以及位置更新。通过设置一个PermissionCallback实例,你可以在权限被授予或被拒绝时收到通知,并在位置发生变化时收到更新。

1.3 在MainActivity里:

class MainActivity : AppCompatActivity(), LocationManagerUtils.PermissionCallback {private lateinit var locationManagerUtils: LocationManagerUtilsoverride fun onCreate(savedInstanceState: Bundle?) {locationManagerUtils = LocationManagerUtils(this).apply {setPermissionCallback(this@MainActivity)requestLocationPermission(this@MainActivity)}}override fun onRequestPermissionsResult(requestCode: Int,permissions: Array<out String>,grantResults: IntArray) {super.onRequestPermissionsResult(requestCode, permissions, grantResults)locationManagerUtils.onRequestPermissionsResult(requestCode, grantResults)}override fun onPermissionGranted() {Log.d(tag, "onPermissionGranted")locationManagerUtils.requestNetworkLocationUpdates()}override fun onPermissionDenied() {Toast.makeText(this, R.string.requiredPermissionPrompt, Toast.LENGTH_SHORT).show()ActivityCompat.finishAffinity(this)}}

MainActivity中,LocationManagerUtils类被用来处理位置权限和位置更新。这里是MainActivity中使用LocationManagerUtils的步骤:

MainActivity继承了AppCompatActivity并实现了LocationManagerUtils.PermissionCallback接口。这意味着MainActivity需要实现PermissionCallback接口中的方法。

MainActivity中,有一个locationManagerUtils属性,它被延迟初始化(lateinit),这意味着它将在后面被初始化。

onCreate方法中,locationManagerUtils被初始化为一个LocationManagerUtils实例,并通过apply函数设置了权限回调(setPermissionCallback(this@MainActivity))并请求位置权限(requestLocationPermission(this@MainActivity))。

onRequestPermissionsResult方法被重写以处理权限请求的结果。它首先调用父类的onRequestPermissionsResult方法,然后将结果传递给locationManagerUtilsonRequestPermissionsResult方法。

onPermissionGranted方法被实现以处理权限被授予的情况。在这个方法中,日志被记录,并且调用了locationManagerUtilsrequestNetworkLocationUpdates方法来请求网络位置更新。

onPermissionDenied方法被实现以处理权限被拒绝的情况。在这个方法中,一个提示被显示,告诉用户权限被拒绝,并且使用ActivityCompat.finishAffinity方法来结束当前的Activity及其所有关联的任务。

总的来说,MainActivity使用LocationManagerUtils来请求位置权限,并在权限被授予时请求位置更新。如果权限被拒绝,则显示提示并结束Activity

二、获取坐标

class LocationManagerUtils(val context: Context, ) {//...interface PermissionCallback {//...fun onLocationChanged(location: Location)}private val locationManager: LocationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManagerprivate val locationListener = object : LocationListener {override fun onLocationChanged(location: Location) {permissionCallback?.onLocationChanged(location)}@Deprecated("Deprecated in Java")override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {}override fun onProviderEnabled(provider: String) {}override fun onProviderDisabled(provider: String) {}}@SuppressLint("MissingPermission")fun requestNetworkLocationUpdates() {locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,1000L,1000f,locationListener)}
}

这个LocationManagerUtils类是一个用于管理和监听Android设备位置更新的实用工具类。它提供了一种方便的方法来请求网络提供的位置更新,并通过一个回调接口来处理位置变化。下面是这个类的一些关键部分的解释:

locationManager: LocationManager: 这是一个私有属性,用于获取系统的定位服务。

locationListener: 这是一个私有属性,是一个匿名内部类,实现了LocationListener接口。它用于监听位置更新,并在位置发生变化时调用PermissionCallbackonLocationChanged方法。

requestNetworkLocationUpdates(): 这是一个公开方法,用于请求网络提供的位置更新。它使用LocationManagerrequestLocationUpdates方法来注册位置监听器,并设置了更新频率(每1000毫秒或1秒更新一次,位置变化至少1000米时更新)。

总的来说,这个类提供了一种封装好的方式来请求和监听网络提供的位置更新,并通过设置一个PermissionCallback实例来处理位置更新。

在MainAcitivity里,继承onLocationChanged来获取坐标变化

class MainActivity : AppCompatActivity(), LocationManagerUtils.PermissionCallback {override fun onLocationChanged(location: Location) {}
}

三、天气坐标api的添加

3.1 添加opeanweathermap接口

interface WeatherService {/* https://openweathermap.org/current */@GET("weather")fun getWeatherByCityName(@Query("q") cityName : String,@Query("appid") apiKey : String) : Call<WeatherResponse>@GET("weather")fun getWeatherByLocation(@Query("lat") latitude: Double,@Query("lon") longitude: Double,@Query("appid") apkKey: String) : Call<WeatherResponse>/* https://openweathermap.org/forecast5 */@GET("forecast")fun getForecastByCityName(@Query("q") cityName : String,@Query("appid") apiKey : String) : Call<ForecastResponse>@GET("forecast")fun getForecastByLocation(@Query("lat") latitude: Double,@Query("lon") longitude: Double,@Query("appid") apkKey: String) : Call<ForecastResponse>}

WeatherService 接口定义了一组用于从天气服务 API(如 OpenWeatherMap)访问天气和预报数据的方法。以下是这些方法及其参数的详细说明:

getWeatherByCityName:此方法通过城市名称获取当前天气数据。它需要两个参数:cityNameapiKeycityName 是城市的名称,apiKey 是进行身份验证所需的 API 密钥。

getWeatherByLocation:此方法通过纬度和经度获取当前天气数据。它需要三个参数:latitudelongitudeapkKey(应该是 apiKey)。latitudelongitude 指定位置的坐标,apkKey 是进行身份验证所需的 API 密钥。

getForecastByCityName:此方法通过城市名称获取预报数据。它需要两个参数:cityName 和 apiKey。cityName 是城市的名称,apiKey 是进行身份验证所需的 API 密钥。

getForecastByLocation:此方法通过纬度和经度获取预报数据。它需要三个参数:latitude、longitude 和 apkKey(应该是 apiKey)。latitudelongitude 指定位置的坐标,apkKey 是进行身份验证所需的 API 密钥。

每个方法都返回一个泛型类型为 WeatherResponseForecastResponse 的 Call 对象,具体取决于它是当前天气数据还是预报数据。这些 Call 对象代表可以入队的异步请求,可以执行网络请求并提供响应或潜在错误的回调。

3.2 在RetrofitClient里添加根据坐标获取天气的接口

object RetrofitClient {private fun callGetCurrentWeather(call: Call<WeatherResponse>) {call.enqueue(object : Callback<WeatherResponse> {override fun onResponse(call: Call<WeatherResponse>,response: Response<WeatherResponse>) {if (response.isSuccessful) {val weatherData = response.body()handleWeatherData(weatherData)} else {handleWeatherFailure(response.message())}}override fun onFailure(call: Call<WeatherResponse>, t: Throwable) {handleWeatherFailure(t.message!!)}})}fun getWeatherByCityName(cityName: String) {val call = weatherService.getWeatherByCityName(cityName, API_KEY)callGetCurrentWeather(call)}fun getWeatherByLocation(location: Location) {val call = weatherService.getWeatherByLocation(location.latitude, location.longitude, API_KEY)Log.d(TAG, "lat:${location.latitude},lon:${location.longitude}")callGetCurrentWeather(call)}private fun callGetForecast(call: Call<ForecastResponse>) {call.enqueue(object : Callback<ForecastResponse> {override fun onResponse(call : Call<ForecastResponse>, response: Response<ForecastResponse>) {if(response.isSuccessful) {val forecastData = response.body()handleForecastData(forecastData)} else {handleForecastFailure(response.message())}}override fun onFailure(call: Call<ForecastResponse>, t: Throwable) {handleForecastFailure(t.message!!)}})}fun getForecastByCityName(cityName: String) {val call = weatherService.getForecastByCityName(cityName, API_KEY)callGetForecast(call)}fun getForecastByLocation(location: Location) {val call = weatherService.getForecastByLocation(location.latitude, location.longitude, API_KEY)callGetForecast(call)}//...
}

其他都一样的,只是参数改成了Location

四、获取最近城市的信息

4.1 在CityDataAdapter获取最近的城市数据

class CityDataAdapter(private val originCityDataList: List<CityData>) :RecyclerView.Adapter<CityDataAdapter.ViewHolder>() {fun getClosestCityName(deviceLocation: Location) : CityData? {var closestCityData : CityData ?= nullvar minDistance = Double.MAX_VALUEfor(cityData in originCityDataList) {val cityLocation = Location("")cityLocation.latitude = cityData.coord.latcityLocation.longitude = cityData.coord.lonval distance = deviceLocation.distanceTo(cityLocation)if(distance < minDistance) {minDistance = distance.toDouble()closestCityData = cityData}}Log.d("CityDataAdapter", "city name: ${closestCityData?.name}, " +"${closestCityData?.coord?.lat},${closestCityData?.coord?.lon}, "+"distance:${minDistance}")return closestCityData}
}

这个CityDataAdapter类是一个为RecyclerView设计的适配器,用于展示一个城市数据列表。它包含了一个方法getClosestCityName,用于根据设备的位置找到最近的城市的功能。这里是这个类的详细描述:

originCityDataList: List<CityData>: 这是一个构造函数参数,用于接收一个城市数据列表。

getClosestCityName(deviceLocation: Location) : CityData?: 这是一个方法,用于找到与设备位置最近的城市的城市数据。它接受一个Location对象作为参数,表示设备的位置。

closestCityData: CityData? = null: 这是一个局部变量,用于存储最近的城市的城市数据。

minDistance = Double.MAX_VALUE: 这是一个局部变量,用于存储最近城市的最小距离。

方法中的for循环遍历originCityDataList中的每个城市数据,并创建一个Location对象来表示城市的位置。

使用distanceTo方法计算设备位置与城市位置之间的距离。

如果计算出的距离小于minDistance,则更新minDistanceclosestCityData

方法最后,通过日志输出最近城市的相关信息,并返回closestCityData

总的来说,这个适配器不仅用于展示城市数据列表,还提供了一个方便的方法来找到用户设备位置最近的城市的城市数据。

4.2 在MainActivity里补全:

class MainActivity : AppCompatActivity(), LocationManagerUtils.PermissionCallback {override fun onLocationChanged(location: Location) {val cityDataAdapter = binding.cityDataRecyclerView.adapter as CityDataAdapterval closestCityData = cityDataAdapter.getClosestCityName(location)val locationMessage = "latitude:${location.latitude}, longitude:${location.longitude}"val cityName = "closet CityName:${closestCityData?.name}"val message = "$locationMessage, $cityName"if(closestCityData == null) {RetrofitClient.getWeatherByLocation(location)RetrofitClient.getForecastByLocation(location)Toast.makeText(this, "update by location $locationMessage", Toast.LENGTH_SHORT).show()} else {val name = closestCityData.nameRetrofitClient.getWeatherByCityName(name)RetrofitClient.getForecastByCityName(name)Toast.makeText(this, "update by city name $cityName", Toast.LENGTH_SHORT).show()}Log.d(tag, message)}
}

在MainActivity中,实现了LocationManagerUtils.PermissionCallback接口的onLocationChanged方法。这个方法会在设备的位置发生变化时被调用,用于根据新的位置信息更新天气数据。这里是这个方法的详细描述:

cityDataAdapter = binding.cityDataRecyclerView.adapter as CityDataAdapter: 这行代码获取了与RecyclerView绑定的适配器,并将其转换为CityDataAdapter类型。

closestCityData = cityDataAdapter.getClosestCityName(location): 这行代码调用了CityDataAdapter的getClosestCityName方法,传入了当前设备的位置信息,以获取最近的城市的城市数据。

locationMessage = "latitude:${location.latitude}, longitude:${location.longitude}": 这行代码创建了一个包含当前位置信息的字符串。

cityName = "closet CityName:${closestCityData?.name}": 这行代码创建了一个包含最近城市的城市名称的字符串。

message = "$locationMessage, $cityName": 这行代码将位置信息和城市名称合并为一个消息字符串。

如果closestCityDatanull,表示没有找到最近的城市的城市数据,那么将通过位置信息来更新天气数据:

  • RetrofitClient.getWeatherByLocation(location):
    这行代码调用RetrofitClientgetWeatherByLocation方法,传入了当前设备的位置信息,以获取天气数据。
  • RetrofitClient.getForecastByLocation(location):
    这行代码调用RetrofitClientgetForecastByLocation方法,传入了当前设备的位置信息,以获取天气预报数据。
  • 显示一个提示,告诉用户天气数据正在通过位置信息更新。

如果closestCityData不为null,表示找到了最近的城市的城市数据,那么将通过城市名称来更新天气数据:

  • name = closestCityData.name: 这行代码获取了最近城市的城市名称。
  • RetrofitClient.getWeatherByCityName(name): 这行代码调用RetrofitClientgetWeatherByCityName方法,传入了最近城市的城市名称,以获取天气数据。
  • RetrofitClient.getForecastByCityName(name): 这行代码调用RetrofitClientgetForecastByCityName方法,传入了最近城市的城市名称,以获取天气预报数据。
  • 显示一个提示,告诉用户天气数据正在通过城市名称更新。
  • Log.d(tag, message): 这行代码记录了一个日志,包含了位置信息和城市名称的消息。

总的来说,这个onLocationChanged方法用于根据设备的位置信息来更新天气数据,并通过提示和日志来通知用户更新情况。

这篇关于kotlin 编写一个简单的天气预报app (八)获取设备坐标的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1000743

相关文章

基于Qt开发一个简单的OFD阅读器

《基于Qt开发一个简单的OFD阅读器》这篇文章主要为大家详细介绍了如何使用Qt框架开发一个功能强大且性能优异的OFD阅读器,文中的示例代码讲解详细,有需要的小伙伴可以参考一下... 目录摘要引言一、OFD文件格式解析二、文档结构解析三、页面渲染四、用户交互五、性能优化六、示例代码七、未来发展方向八、结论摘要

React实现原生APP切换效果

《React实现原生APP切换效果》最近需要使用Hybrid的方式开发一个APP,交互和原生APP相似并且需要IM通信,本文给大家介绍了使用React实现原生APP切换效果,文中通过代码示例讲解的非常... 目录背景需求概览技术栈实现步骤根据 react-router-dom 文档配置好路由添加过渡动画使用

python获取当前文件和目录路径的方法详解

《python获取当前文件和目录路径的方法详解》:本文主要介绍Python中获取当前文件路径和目录的方法,包括使用__file__关键字、os.path.abspath、os.path.realp... 目录1、获取当前文件路径2、获取当前文件所在目录3、os.path.abspath和os.path.re

Java子线程无法获取Attributes的解决方法(最新推荐)

《Java子线程无法获取Attributes的解决方法(最新推荐)》在Java多线程编程中,子线程无法直接获取主线程设置的Attributes是一个常见问题,本文探讨了这一问题的原因,并提供了两种解决... 目录一、问题原因二、解决方案1. 直接传递数据2. 使用ThreadLocal(适用于线程独立数据)

MyBatis框架实现一个简单的数据查询操作

《MyBatis框架实现一个简单的数据查询操作》本文介绍了MyBatis框架下进行数据查询操作的详细步骤,括创建实体类、编写SQL标签、配置Mapper、开启驼峰命名映射以及执行SQL语句等,感兴趣的... 基于在前面几章我们已经学习了对MyBATis进行环境配置,并利用SqlSessionFactory核

使用Java编写一个文件批量重命名工具

《使用Java编写一个文件批量重命名工具》这篇文章主要为大家详细介绍了如何使用Java编写一个文件批量重命名工具,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录背景处理1. 文件夹检查与遍历2. 批量重命名3. 输出配置代码片段完整代码背景在开发移动应用时,UI设计通常会提供不

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

hdu2289(简单二分)

虽说是简单二分,但是我还是wa死了  题意:已知圆台的体积,求高度 首先要知道圆台体积怎么求:设上下底的半径分别为r1,r2,高为h,V = PI*(r1*r1+r1*r2+r2*r2)*h/3 然后以h进行二分 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#includ

usaco 1.3 Prime Cryptarithm(简单哈希表暴搜剪枝)

思路: 1. 用一个 hash[ ] 数组存放输入的数字,令 hash[ tmp ]=1 。 2. 一个自定义函数 check( ) ,检查各位是否为输入的数字。 3. 暴搜。第一行数从 100到999,第二行数从 10到99。 4. 剪枝。 代码: /*ID: who jayLANG: C++TASK: crypt1*/#include<stdio.h>bool h

uva 10387 Billiard(简单几何)

题意是一个球从矩形的中点出发,告诉你小球与矩形两条边的碰撞次数与小球回到原点的时间,求小球出发时的角度和小球的速度。 简单的几何问题,小球每与竖边碰撞一次,向右扩展一个相同的矩形;每与横边碰撞一次,向上扩展一个相同的矩形。 可以发现,扩展矩形的路径和在当前矩形中的每一段路径相同,当小球回到出发点时,一条直线的路径刚好经过最后一个扩展矩形的中心点。 最后扩展的路径和横边竖边恰好组成一个直