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 = 1
    interface PermissionCallback {
        fun onPermissionGranted()
        fun onPermissionDenied()
    }

    private var permissionCallback: PermissionCallback? = null

    fun 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 = true
            for (result in grantResults) {
                if (result != PackageManager.PERMISSION_GRANTED) {
                    allPermissionsGranted = false
                    break
                }
            }

            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: LocationManagerUtils

	override 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 LocationManager
    private 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 ?= null
        var minDistance = Double.MAX_VALUE
        for(cityData in originCityDataList) {
            val cityLocation = Location("")
            cityLocation.latitude = cityData.coord.lat
            cityLocation.longitude = cityData.coord.lon

            val 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 CityDataAdapter
        val 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.name
            RetrofitClient.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方法用于根据设备的位置信息来更新天气数据,并通过提示和日志来通知用户更新情况。

  • 16
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

习惯就好zz

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值