【基于高德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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值