【基于高德api与和风api的天气记录App】一个集成的小demo,主要用于记录学习过程中设计及构思——上

最终实现效果:

WeatherTest

代码目录结构

代码结构目录

app功能拆解

关键字定位搜索

这里通过引入高德API 或者高德SDK的接口去实现,可以去高德开发者平台进行注册申请。
通过网络请求(url:https://restapi.amap.com/v3/assistant/inputtips?key=&keywords=)数据,这样就能返回对应data形如:

*PlaceResponse(tips=[Place(id=B0JBTOADDY, name=商丘·D.C服饰, district=河南省商丘市夏邑县, adcode=411426, location=116.138028,34.236249, address=县府西路与人和街中段交叉口东40, typecode=061100, city=[]), Place(id=BV10266369, name=大草坊首末站(公交站), district=广东省东莞市, adcode=441900, location=113.798080,23.048478, address=35, typecode=150700, city=[]), Place(id=B0JBH705HE, name=大厂房烧烤总店, district=山东省淄博市张店区, adcode=370303, location=118.015559,36.866099, address=世纪路与裕民路交叉口东南120, typecode=050101, city=[]), Place(id=B0J277H5VB, name=德长防水补漏公司, district=广东省东莞市, adcode=441900, location=113.784463,23.026726, address=东城中路23-25, typecode=070000, city=[]), Place(id=B0J2F73W9Q, name=东创防水补漏店, district=广东省东莞市, adcode=441900, location=113.759255,23.008477, address=东城街道嘉宏振兴中心606, typecode=071200, city=[]), Place(id=B0JD3MC057, name=大厨房烧烤涮三店, district=河北省唐山市路北区, adcode=130203, location=118.143903,39.665534, address=友谊东辅路与长宁西道交叉口, typecode=050100, city=[]), Place(id=B0JUJMMKB6, name=稻草坊手擀面(翡翠100), district=河南省驻马店市驿城区, adcode=411702, location=114.016413,33.006228, address=文明路与通达路东100米路南, typecode=050100, city=[]), Place(id=B0JUK7WD03, name=德诚服饰, district=浙江省杭州市临平区, adcode=330113, location=120.330442,30.373481, address=钱塘社区五科村滨岸上44, typecode=170300, city=[]), Place(id=B0JAZMWU5A, name=多彩服饰, district=河南省平顶山市叶县, adcode=410422, location=113.456958,33.533393, address=004县道与330省道交叉口西北120, typecode=061100, city=[]), Place(id=B0JD47RGK4, name=大成防水, district=广东省广州市白云区, adcode=440111, location=113.230580,23.145573, address=高桥路58号A02铺, typecode=060603, city=[])], status=1, info=OK, infocode=10000, count=10)*

talk is cheap ,just see the fucking code

searchPlaceEdit.addTextChangedListener { editable ->
   recyclerView.visibility = View.GONE
   bgImageView.visibility = View.VISIBLE
   viewModel.placeList.clear()
   viewModel.currentPlaceList.clear()
   adapter.notifyDataSetChanged()

   val content = editable.toString()
   if (content.isNotEmpty()){
       viewModel.searchPlaces(content)
   }
 }
............
val placeLiveData = Transformations.switchMap(searchLiveData){ query ->
    Repository.searchPlaces(query)
}

fun searchPlaces(query: String){
    searchLiveData.value = query
}
...........
fun searchPlaces(query: String) = liveData(Dispatchers.IO) {
    val result = try {
        val placeResponse = WeatherNetwork.searchPlaces(query)
        if (placeResponse.count != "0"){
             val places = placeResponse.tips
             Result.success(places)
        }else{
             Result.failure(RuntimeException("response counts is ${placeResponse.count}"))
        }
    }catch (e: Exception){
        Result.failure<List<Place>>(e)
    }
    emit(result)
}
..........
suspend fun searchPlaces(query: String) = placeService.searchPlaces(query).await()
..........
private suspend fun <T> Call<T>.await(): T {
   return suspendCoroutine { continuation ->
            //异步发送网络请求
            enqueue(object : Callback<T> {
                //请求成功时回调
                override fun onResponse(call: Call<T>, response: Response<T>) {
                    val body = response.body()
                    Log.d("gaorui", "WeatherNetwork - body = " + body)

                    if(body != null){
                        continuation.resume(body)
                    }else{
                        continuation.resumeWithException(RuntimeException("response body is null"))
                    }
                }

                //请求失败时回调
                override fun onFailure(call: Call<T>, t: Throwable) {
                    Log.d("gaorui", "WeatherNetwork - onFailure ")
                    continuation.resumeWithException(t)
                }
            })
        }
}
.............
@GET("v3/assistant/inputtips?key=${WeatherApplication.KEY}")
fun searchPlaces(@Query("keywords") keywords: String): Call<PlaceResponse>
..............
viewModel.placeLiveData.observe(this, Observer { result ->
      val places = result.getOrNull()
      if (places != null){
           recyclerView.visibility = View.VISIBLE
           bgImageView.visibility = View.GONE
           viewModel.placeList.clear()
           viewModel.currentPlaceList.clear()
           viewModel.placeList.addAll(places)
           adapter.notifyDataSetChanged()
      }else{
           recyclerView.visibility = View.GONE
           bgImageView.visibility = View.VISIBLE
           viewModel.placeList.clear()
           viewModel.currentPlaceList.clear()
           adapter.notifyDataSetChanged()

           Toast.makeText(activity, "未能查询到任何地点", Toast.LENGTH_SHORT).show()
           result.exceptionOrNull()?.printStackTrace()
       }
})
..............
data class PlaceResponse(val tips: ArrayList<Place>, val status: String, val info: String, val infocode: String, var count: String)

data class Place(val id: Any, var name: String, var district: String, val adcode: String,
                val location: Any, val address: Any, val typecode: String, val city: ArrayList<String>)

这里参考了guolin大佬的网络数据请求设计流程,此种设计可以说是面向切面的一种方式。期间实现此功能的时候,有遇到过返回的数据为空的情况,没有url请求报错。这个时候需要查看数据类的类型是否和本次查询到的json数据对应上了,有的时候某个字段会返回null,造成字段匹配错误,数据为空的情况。到这里此功能基本上是跑通了,剩下的就是代码优化,本次不做考虑。

精准定位

这里使用高德SDK的接口,进行精准定位,因为使用api的话,本地发现在局域网的时候竟然返回的数据为空(本身json数据为空,区别于上方提到过的情况),但是使用SDK 进行查询position的时候就不会出现链接局域网为空 的情况,不太清楚SDK 内做了啥特殊处理,没有深究,有知道的大佬可以指导下ha。
对于SDK 的使用,官网也有指导文档,这里就不用多说了,show the fucking code。

placeFragment_current_place.setOnClickListener { floatingButton ->

//            viewModel.setCurrentIP(Util.getIpAddress(mContext))
//            viewModel.sendRequestWithOKHttp()
   val option: AMapLocationClientOption = AMapLocationClientOption()

   //声明定位回调监听器
   val mLocationListener = AMapLocationListener {

       if (it != null) {
           if (it.getErrorCode() == 0) {
                //可在其中解析amapLocation获取相应内容。
                Log.e("gaorui", "AmapSuccess - placeFragment - AMapLocationListener - lat = ${it.latitude} , lng = ${it.longitude}, Street = ${it.poiName}")

                recyclerView.visibility = View.VISIBLE
                bgImageView.visibility = View.GONE
                viewModel.placeList.clear()
                viewModel.currentPlaceList.clear()
                progress_main_fragment.visibility = View.GONE
                adapter.notifyDataSetChanged()

                viewModel.searchPlaces(it.poiName)
           }else {
                //定位失败时,可通过ErrCode(错误码)信息来确定失败的原因,errInfo是错误信息,详见错误码表。
                Log.e("gaorui","location Error, ErrCode:"
                                + it.getErrorCode() + ", errInfo:"
                                + it.getErrorInfo())
                mLocationClient?.stopLocation();
                progress_main_fragment.visibility = View.GONE
                Toast.makeText(context, "get location error!", Toast.LENGTH_SHORT).show()
             }
        }

    }
    AMapLocationClient.updatePrivacyShow(context,true,true)
    AMapLocationClient.updatePrivacyAgree(context,true)

    //初始化定位
    mLocationClient = AMapLocationClient(context)
    //设置定位回调监听
    mLocationClient?.setLocationListener(mLocationListener)

    option.setOnceLocation(true)

    mLocationClient?.setLocationOption(option)
    progress_main_fragment.visibility = View.VISIBLE

    mLocationClient?.startLocation()
    Log.e("gaorui", "startLocation ")
}

到这里关于定位的功能基本就搞好了,接下来就是天气。

天气查询

这里使用免费的和风天气API进行请求数据,有更好的选择也可以替换掉。当上面两个定位功能做好之后,接下来需要把获取到的location位置、location name传递给显示temperature的界面。
show fucking code:

recyclerView.setOnItemClickListener(object : RecyclerViewExt.OnItemClickListener{

   override fun onItemClick(
                parent: RecyclerView.Adapter<*>?,
                vh: RecyclerView.ViewHolder,
                position: Int
     ) {
          Log.d("gaorui", "setOnItemClickListener - position = " + vh.position +
                        ", transitionName = " + (vh.itemView.findViewById(R.id.placeName) as View).transitionName )

          val bundle: Bundle? = this@PlaceFragment.activity?.let {
                    ActivityOptionsCompat.makeSceneTransitionAnimation(
                        it,
                        Pair(vh.itemView.findViewById(R.id.placeName) as View, (vh.itemView.findViewById(R.id.placeName) as View).transitionName),
                        Pair(placeFragment_current_place, resources.getString(R.string.share_place_name))
                    ).toBundle()
           }
           val intent = Intent(context, TempActivity::class.java).apply {
                if (viewModel.currentPlaceList.isEmpty()) {
                        putExtra(TempActivity.PLCAE_NAME, viewModel.placeList[position].name)
                        putExtra(TempActivity.PLCAE_POSITION, (vh.itemView.findViewById(R.id.placeName) as View).transitionName)
                        putExtra(TempActivity.PLCAE_LOCATION, viewModel.placeList[position].location.toString())
                } else {
                        putExtra(TempActivity.PLCAE_NAME, viewModel.currentPlaceList[position].name)
                        putExtra(TempActivity.PLCAE_POSITION, (vh.itemView.findViewById(R.id.placeName) as View).transitionName)
                        putExtra(TempActivity.PLCAE_LOCATION, viewModel.currentPlaceList[position].location)
                    }
            }
            startActivity(intent, bundle)

//                fragmentToActivity?.transformToActivity(viewModel.placeList[position].location.toString(), viewModel.placeList[position].name)
  }

  override fun onItemLongClick(vh: RecyclerView.ViewHolder?, position: Int) {
          Toast.makeText(context, " onItemLongClick", Toast.LENGTH_SHORT).show()
  }
  })

这里使用transition动画,添加了一点点的效果。废话不多说,接下来查询天气。
查询天气用的和风SDK,具体申请详见官网。这里把上一步传递过来的locaiton传递给SDK,然后剩下就是展示数据,写的有点粗鄙,凑合看,当然也可以使用dispatcher优雅下,show the fucking code:

fun searchTempOrRefresh(location:String) {
        thread {
            viewModel.searchPlaceTempUsingSDK(query = location, listener = object : QWeather.OnResultWeatherNowListener {

                override fun onError(e: Throwable) {
                    Log.e("gaorui", "getWeather onError: " + e)
                    temp_swipe.isRefreshing = false
                }

                override fun onSuccess(weatherBean : WeatherNowBean) {
                    Log.e("gaorui", "getWeather onSuccess: " + Gson().toJson(weatherBean))
                    //先判断返回的status是否正确,当status正确时获取数据,若status不正确,可查看status对应的Code值找到原因
                    if (Code.OK == weatherBean.getCode()) {
                        val now :WeatherNowBean.NowBaseBean = weatherBean.getNow()

                        toDayDate = now.obsTime.split("T")[0]

                        runOnUiThread {
                            temp_placetemp.text = now.temp
                            temp_placetemp_feel.text = now.feelsLike
                            temp_placetemp_weatherkind.text = now.text
                            temp_placetemp_windscale.text = now.windScale
                            temp_placetemp_humidity.text = now.humidity
                        }

                    } else {
                        //在此查看返回数据失败的原因
                        val code = weatherBean.getCode();
                        Log.e("gaorui", "failed code: " + code);
                        Toast.makeText(this@TempActivity, "未能查询到任何地点", Toast.LENGTH_SHORT).show()
                    }
                    temp_swipe.isRefreshing = false
                }
            }, isFore = false)
        }
}

天气预测

与上方天气查询大同小异,这里就不多说了,show the fucking code:

fun searchTempOrRefresh(location:String) {
        thread{
            Thread.sleep(1000)
            viewModel.searchPlaceTempUsingSDK(query = location, isFore = true, listener = object : QWeather.OnResultWeatherDailyListener {

                override fun onError(e: Throwable) {
                    Log.e("gaorui", "3d - getWeather onError: " + e)
                    temp_swipe.isRefreshing = false
                }

                override fun onSuccess(p0: WeatherDailyBean?) {
                    //先判断返回的status是否正确,当status正确时获取数据,若status不正确,可查看status对应的Code值找到原因
                    if (Code.OK == p0?.getCode()) {
                        val dailyList :List<WeatherDailyBean.DailyBean> = p0.daily

                        for (daily : WeatherDailyBean.DailyBean in dailyList) {
                            Log.e("gaorui", "3d - getWeather onSuccess: " + Gson().toJson(daily))
                        }

                        tempInfoArrayList.clear()
                        tempInfoArrayList.addAll(dailyList)

                        runOnUiThread {
                            adapter.notifyDataSetChanged()
                        }


                    } else {
                        //在此查看返回数据失败的原因
                        val code = p0?.getCode();
                        Log.e("gaorui", "3d - failed code: " + code);
                        Toast.makeText(this@TempActivity, "未能查询到任何地点", Toast.LENGTH_SHORT).show()
                    }
                    temp_swipe.isRefreshing = false
                }
            })
        }
  .............
}

天气刷新

此功能还是比较简单的,刷新,意思就是重新请求下数据呗,直接调用上方的 searchTempOrRefresh方法即可。不多说了。

调用系统相机拍照

下回分解

心情历程记录

下回分解

历史心情历程查看

下回分解

源码地址:
https://gitee.com/kanecong/weather-test

微信小程序Demo-和天气天气API,设置城市-附完整源代码.rar 资源介绍 本资源是一个基于微信小程序的Demo项目,名为“和天气”,主要功能是展示当前天气信息以及提供设置城市的功能。本项目采用和天气API获取实时天气数据,方便开发者学习和实践微信小程序与第三方API集成。同时,该项目源码完全开源,可以进行二次开发定制,满足个性化需求。 主要特点: 1. 实时天气信息展示:通过和天气API获取用户指定城市的实时天气信息,包括气温、湿度、向、力等详细数据。 2. 城市设置功能:用户可以在小程序自定义设置所需查询的城市,方便随时查看不同地区的天气状况。 3. 界面简洁美观:采用微信小程序原生组件进行界面设计,整体格简约大方,符合用户使用习惯。 4. 数据缓存优化:为了提高用户体验,减少网络请求,本项目对天气数据进行本地缓存,避免频繁调用API。 5. 完整源码及文档:提供完整的项目源码和详细的开发文档,方便开发者快速上手并进行二次开发定制。 适用场景: 1. 学习微信小程序开发:本项目可以作为学习微信小程序开发的实践案例,帮助初学者熟悉微信小程序的基本功能和开发流程。 2. 天气预报应用:可以根据本项目源码进行二次开发,打造一个个性化的天气预报应用,满足特定用户需求。 3. 其他领域拓展:通过修改源码API接口和数据解析逻辑,可以将本项目应用于其他类似场景,如交通出行、旅游指南等。 总之,微信小程序Demo-和天气天气API,设置城市-附完整源代码.rar 资源为开发者提供了一个很好的学习和实践平台,无论是初学者还是有一定经验的开发者,都可以从受益。
这里提供一个简单的使用高德天气API编写天气Android应用的步骤: 1. 注册高德开发者账号,创建应用并开通天气API服务。 2. 在Android Studio创建一个新项目,并在AndroidManifest.xml文件添加以下权限: ``` <uses-permission android:name="android.permission.INTERNET"/> ``` 3. 在build.gradle文件的dependencies添加以下依赖: ``` implementation 'com.amap.api:3dmap:7.7.0' implementation 'com.amap.api:location:5.0.0' implementation 'com.amap.api:search:7.7.0' ``` 4. 在MainActivity添加以下代码,获取当前定位信息: ```java public class MainActivity extends AppCompatActivity implements AMapLocationListener { private AMapLocationClient locationClient; private TextView tvCity; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tvCity = findViewById(R.id.tv_city); //初始化定位 locationClient = new AMapLocationClient(getApplicationContext()); //设置定位回调监听 locationClient.setLocationListener(this); //启动定位 locationClient.startLocation(); } @Override public void onLocationChanged(AMapLocation aMapLocation) { if (aMapLocation != null) { if (aMapLocation.getErrorCode() == 0) { //定位成功回调信息,设置相关消息 tvCity.setText(aMapLocation.getCity()); } else { //显示错误信息ErrCode是错误码,errInfo是错误信息,详见错误码表。 Log.e("AmapError", "location Error, ErrCode:" + aMapLocation.getErrorCode() + ", errInfo:" + aMapLocation.getErrorInfo()); } } } @Override protected void onDestroy() { super.onDestroy(); //销毁定位客户端,同时销毁本地定位服务。 locationClient.onDestroy(); } } ``` 5. 在高德开放平台获取天气信息,可以使用以下代码: ```java public class WeatherUtil { private static final String URL = "https://restapi.amap.com/v3/weather/weatherInfo"; private static final String KEY = "YOUR_KEY"; //替换成你的高德开放平台应用Key public static void getWeatherInfo(String city, Callback<WeatherResult> callback) { OkHttpClient client = new OkHttpClient.Builder().build(); HttpUrl httpUrl = HttpUrl.parse(URL).newBuilder() .addQueryParameter("key", KEY) .addQueryParameter("city", city) .addQueryParameter("extensions", "base") .addQueryParameter("output", "json") .build(); Request request = new Request.Builder() .url(httpUrl) .build(); Call call = client.newCall(request); call.enqueue(callback); } } ``` 6. 在MainActivity调用getWeatherInfo方法获取天气信息,并更新UI: ```java public class MainActivity extends AppCompatActivity implements AMapLocationListener { private AMapLocationClient locationClient; private TextView tvCity; private TextView tvWeather; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tvCity = findViewById(R.id.tv_city); tvWeather = findViewById(R.id.tv_weather); //初始化定位 locationClient = new AMapLocationClient(getApplicationContext()); //设置定位回调监听 locationClient.setLocationListener(this); //启动定位 locationClient.startLocation(); } @Override public void onLocationChanged(AMapLocation aMapLocation) { if (aMapLocation != null) { if (aMapLocation.getErrorCode() == 0) { //定位成功回调信息,设置相关消息 tvCity.setText(aMapLocation.getCity()); WeatherUtil.getWeatherInfo(aMapLocation.getCity(), new Callback<WeatherResult>() { @Override public void onFailure(Call call, IOException e) { e.printStackTrace(); } @Override public void onResponse(Call call, Response response) throws IOException { String json = response.body().string(); WeatherResult result = new Gson().fromJson(json, WeatherResult.class); runOnUiThread(() -> tvWeather.setText(result.getWeather())); } }); } else { //显示错误信息ErrCode是错误码,errInfo是错误信息,详见错误码表。 Log.e("AmapError", "location Error, ErrCode:" + aMapLocation.getErrorCode() + ", errInfo:" + aMapLocation.getErrorInfo()); } } } @Override protected void onDestroy() { super.onDestroy(); //销毁定位客户端,同时销毁本地定位服务。 locationClient.onDestroy(); } } ``` 这样,我们就可以根据当前定位获取到城市,然后调用高德天气API获取天气信息,并将结果展示在UI上了。当然,还有很多细节需要处理,比如异常处理、UI美化等等,这里只是提供一个简单的示例供参考。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值