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

序言

接下来该说说后面几个功能了:调用系统相机拍照、心情历程记录、历史心情历程查看。前面几个功能有兴趣的可以看看前面一篇文章:【基于高德api与和风api的天气记录App】一个集成的小demo,主要用于记录学习过程中设计及构思——上

app功能拆解

调用系统相机拍照

android 7.0之后的调用需要使用provider,不然的话容易发生报错闪退的情况,而Android 7.0之前则还是可以随便调用相机进行拍照的,这里只讲讲Android 7.0之后改怎么搞。
首先,show the fucking code:

capturebtn.setOnClickListener { btn ->

     val path = externalCacheDir?.absolutePath.toString() + "Camera/"
     val direcFile = File(path)

      if (!direcFile.exists()) {
            direcFile.mkdirs()
      }

      mFile = File(path, "${System.currentTimeMillis()}_home.jpg")

      if (!mFile.exists()) {
            mFile.createNewFile()
      }

      val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE).apply {
                addFlags(Intent.FLAG_ACTIVITY_NEW_TASK and Intent.FLAG_GRANT_READ_URI_PERMISSION)
                putExtra(MediaStore.EXTRA_OUTPUT, FileProvider.getUriForFile(this@TempActivity, "com.example.weathertest.fileprovider", mFile))
       }

       startActivityForResult(intent, 2)
}
............
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path
        name="external_files"
        path="."/>

</paths>
..........
<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="com.example.weathertest.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
         android:name="android.support.FILE_PROVIDER_PATHS"
         android:resource="@xml/provider_paths" />
</provider>

这里定义了一个provider用来转换uri给系统相机使用,其他的倒没有什么变化。
后面从系统相机界面回来的时候逻辑也很简单,先判断下file的length,是否存在数据,存在,咱就展示,不存在,那就直接return得了。然后把 uri 转递给展示拍照数据的界面即可,show the fucking code:

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == 2) {

            if (mFile.length() <= 0){
                mFile.delete()
                Log.e("gaorui", "onActivityResult - return")
                return
            }

            captureFragment = CaptureFragment()
            fragmentMgr = supportFragmentManager

            captureFragment?.let {
                it.arguments = Bundle().apply {
                    putString("fromFile", Uri.fromFile(mFile).toString())
                }

                fragmentMgr!!.beginTransaction()
                    .add(R.id.temp_manager, it, "captureFragment")
                    .commitNowAllowingStateLoss()
            }
        }
}

进入拍照数据返回界面后,把传递过来的uri 存储在list中,然后构建个 recyclerView 用来展示,因为在此界面,还是支持继续调用相机拍照的,所以使用单一的imageView的话不太适合,另外拍几张也不固定,最终选择了recyclerView。list数组中一直存有一个图片,用来点击跳转拍照的,仿照微信发朋友圈的界面,这里没有设计跳转进入图库选择资源,全都是进入系统相机拍照,show the fucking code:

............
val bundle = arguments
val fileUri = Uri.parse(bundle?.getString("fromFile"))
fileList.add(TempCaptureImageData(true, fileUri, null))
fileList.add(TempCaptureImageData(false, null, R.drawable.mango_pic))
............
temp_capture_fragment_recyclerview.setOnItemClickListener(object :RecyclerViewExt.OnItemClickListener{
            override fun onItemClick(
                parent: RecyclerView.Adapter<*>?,
                vh: RecyclerView.ViewHolder?,
                position: Int
            ) {
                Toast.makeText(context, " onItemClick - ${vh?.position}", Toast.LENGTH_SHORT).show()

                if (!fileList[position].isUri) {
                    val path = activity?.externalCacheDir?.absolutePath.toString() + "Camera/"
                    val direcFile = File(path)

                    if (!direcFile.exists()) {
                        direcFile.mkdirs()
                    }

                    mFile = File(path, "${System.currentTimeMillis()}_home.jpg")

                    if (!mFile.exists()) {
                        mFile.createNewFile()
                    }

                    val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE).apply {
                        putExtra(MediaStore.EXTRA_OUTPUT,
                            activity?.let {
                                FileProvider.getUriForFile(it, "com.example.weathertest.fileprovider", mFile)
                            })
                    }

                    startActivityForResult(intent, 3)
                }
            }

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

        })

此界面仿照微信朋友圈发表界面,也定义了一个发表button,然后由此button调用宿主activity的方法进行更新recyclerView,展示数据,show the fucking code:

capture_fragment_broadcast.setOnClickListener {
            (activity as TempActivity).sendTextOrImageFromFragment(fileList, capture_fragment_want_to_say.text.toString())
}

这个地方就牵扯到了数据库了,这里没有使用花钱的云,花钱的都不香,hhh,这里使用Android自带的sqlite数据库进行存储,后面有钱再考虑把数据迁移存储到云上,先专注流程,凑合看。
在进入天气界面的时候,oncreate中初始化一个数据库FriendCircle,然后再从数据库中拿出数据,recyclerview更新数据。
先来看看数据库的流程吧,首先继承SQLiteOpenHelper进行建表等操作,然后创建个mangaer 来管理sqlite的CURD操作,有点子SQL基础的话这块还是蛮简单 的。show the fucking code:

class MyDataBaseHelper(mContext: Context, name:String, factory:SQLiteDatabase.CursorFactory?, version:Int)
    : SQLiteOpenHelper(mContext, name, factory, version) {

    val mConTxt = mContext

    val CREATE_BASE:String = "create table FriendCircle (" +
            "id integer primary key autoincrement," +
            "author BLOB," +
            "dateOrName text," +
            "wantToSay text," +
            "groupkey integer DEFAULT 0," +
            "wantShowPic BLOB)"

    override fun onCreate(db: SQLiteDatabase?) {
        db?.let {
            it.execSQL(CREATE_BASE)
            Toast.makeText(mConTxt, "create success", Toast.LENGTH_SHORT).show()
        }

    }

    override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
        db?.let {
            it.execSQL("drop table if exists FriendCircle")
            onCreate(it)
        }
    }
}
.............
class MyDataBaseManager {
    companion object {
        
        fun setDB(base: SQLiteDatabase) {
            sqlLitebase = base
        }

        fun closeDB() {
            Log.e("gaorui", "MyDataBaseManager - closeDB ")
            sqlLitebase.close()
        }

        fun addDataToDB(author:ByteArray, date:String, wantToSay: String?, addPic:ByteArray?, groupOrNot:Int?) : Long {
            val values = ContentValues().let {
                it.put("author", author)
                it.put("dateOrName", date)
                it.put("wantToSay", wantToSay)
                it.put("wantShowPic", addPic)
                groupOrNot?.let { grouKey ->
                    it.put("groupkey", grouKey)
                }
                it
            }

            val result = sqlLitebase.insert("FriendCircle", null, values)

            Log.e("gaorui", "addDataToDB - insert - result = $result")

            return result
        }

        fun updateData(index:Int, groupOrNot:Int) : Int{
            val values = ContentValues().let {
                it.put("groupkey", groupOrNot)
                it
            }
            val result = sqlLitebase.update("FriendCircle", values, "id = ?", arrayOf("$index"))
            Log.e("gaorui", "addDataToDB - update - result = $result")
            return result
        }

        fun selectAllData(recycleLoading:Boolean) : ArrayList<TempCaptureDataTransition>?{
            try {
                val cursor = if (recycleLoading) {
                    Log.e("gaorui", "selectAllData - first - mCurrentId = $mCurrentId")

                    if (mCurrentId == 0) {
                        sqlLitebase.query("FriendCircle", null,
                            null ,
                            null,null,null,"id desc")
                    } else {
                        sqlLitebase.query("FriendCircle", null,
                            "id < ?" ,
                            arrayOf("$mCurrentId"),null,null,"id desc")
                    }
                } else {
                    sqlLitebase.query("FriendCircle", null,
                        null ,
                        null,null,null,"id desc")
                }
                while (cursor.moveToNext()) {
                    itemId = cursor.getInt(cursor.getColumnIndex("id"))
                    author = cursor.getBlob(cursor.getColumnIndex("author"))
                    dateName = cursor.getString(cursor.getColumnIndex("dateOrName"))

                    if (recycleLoading && itemId >= mHasInitId ) {
                        Log.e("gaorui", "selectAllData - itemId = $itemId, mHasInitId = $mHasInitId , so continue")
                        continue
                    }

                    if (mCurrentId != 0 && recycleLoading && itemId >= mCurrentId ) {
                        Log.e("gaorui", "selectAllData - itemId = $itemId, mCurrentId = $mCurrentId , so continue")
                        continue
                    }

                    wantToSay = cursor.getString(cursor.getColumnIndex("wantToSay"))
                    Log.e("gaorui", "selectAllData - current - wantToSay = $wantToSay")

                    addPic = cursor.getBlob(cursor.getColumnIndex("wantShowPic"))

                    groupOrNot = cursor.getInt(cursor.getColumnIndex("groupkey"))
                    Log.e("gaorui", "selectAllData - current - groupOrNot = $groupOrNot")

                    if (groupOrNot != 0) {
                        if (lastGroupKey == -1) {
                            byteListData.clear()

                            if (isGroupFirst) {
                                lastGroupKey = groupOrNot
                                isGroupFirst = false
                                transitionItem = TempCaptureDataTransitionItem(dateName, wantToSay, author)
                            }

                            byteListData.add(addPic)
                            Log.e("gaorui", "selectAllData - currentgroup - NEW - newSIZE = ${byteListData.size}")

                        } else if (lastGroupKey == groupOrNot) {
                            byteListData.add(addPic)
                            Log.e("gaorui", "selectAllData - currentgroup - add - newSIZE = ${byteListData.size}")
                        } else {
                            lastGroupKey = groupOrNot
                            Log.e("gaorui", "selectAllData - currentgroup - anotherAdd - size = ${byteListData.size}")
                            val tempData = TempCaptureDataTransition(transitionItem!!.dateOrName, transitionItem.wantToSay, transitionItem.icon, byteListData, false)
                            captureData.add(tempData)
                            byteListData.clear()

                            if (captureData.size >= 2) {
                                isGroupFirst = true
                                hasBlocked = true
                                itemId = lastItemId
                                break
                            }
                            transitionItem = TempCaptureDataTransitionItem(dateName, wantToSay, author)
                            byteListData.add(addPic)
                            Log.e("gaorui", "selectAllData - currentgroup - anotherAdd - newSize = ${byteListData.size}")
                        }
                    } else {
                        if (!isGroupFirst) {
                            isGroupFirst = true
                            Log.e("gaorui", "selectAllData - single - group - size = ${byteListData.size}")
TempCaptureDataTransition(transitionItem!!.dateOrName, transitionItem.wantToSay, transitionItem.icon, byteListData, false)
                            captureData.add(tempData)
                            byteListData.clear()

                            if (captureData.size >= 2) {
                                itemId = lastItemId
                                hasBlocked = true
                                break
                            }
                        }
                        lastGroupKey = -1
                        val listData = ArrayList<ByteArray?>()
                        listData.add(addPic)
                        val tempData = TempCaptureDataTransition(dateName, wantToSay, author, listData, false)
                        captureData.add(tempData)
                        Log.e("gaorui", "selectAllData - single - add - newSize = ${byteListData.size}")
                        if (captureData.size >= 2) {
                            hasBlocked = true
                            break
                        }
                    }

                    lastItemId = itemId
                    hasBlocked = false
                }

                Log.e("gaorui", "selectAllData - current - mCurrentId = $mCurrentId - isLast = ${cursor.isLast}")

                if (recycleLoading && (hasBlocked || !cursor.moveToNext())) {
                    mCurrentId = itemId
                    Log.e("gaorui", "selectAllData - current - mCurrentId = $mCurrentId")
                }
                if (!recycleLoading && (hasBlocked || !cursor.moveToNext())) {
                    mHasInitId = itemId
                    Log.e("gaorui", "selectAllData - init - mHasInitId = $mHasInitId")
                }

                if (!isGroupFirst) {
                    Log.e("gaorui", "selectAllData - end - addAll - size = ${byteListData.size}")
                    val tempData = TempCaptureDataTransition(transitionItem!!.dateOrName, transitionItem.wantToSay, transitionItem.icon, byteListData, false)
                    captureData.add(tempData)
                }
                cursor.close()
                Log.e("gaorui", "selectAllData - size - captureData = ${captureData.size}")

                return captureData

            } catch (e: Exception) {
                e.printStackTrace()
                return null
            }
        }
    }
}

建表的类没啥好说的,网上一大堆,manager管理类倒是得说说了。这里为了简化工作量,一个demo要什么鼻子眼睛,刨除了删除的功能,保留了update、insert以及最重要的query功能,update和insert没啥好说的,可以参考其他大佬的文章,这里query的设计我说下思路:
1、判断是否属于recycler的滑动查询更新,因为我这边设计的查询更新有两种,一就是初始化的时候更新,读出2个item,二就是滑动recycler进行的查询更新,再次读出2个item。如果不属于recycler那么就读一种url,如果属于recycler滑动查询,那么就读另外一种url,两种url都是倒叙查询
2、记录id。当第一次的初始化更新完毕,记录下initID,当后面发生了recyler滑动查询的时候,也记录下ecyler滑动id
3、判断是否加入list。当目前item 的id 小于initID以及recyler滑动id的时候,就加入待显示的list,直到数据库末尾或者达到2的max值为止。
4、临近两次都是以group的形式存在。这种情况要区分开本次读的数据和上次拿到的item的数据是不一致的,千万不能混淆,不然就会发生数据被覆盖的情况。
5、临近两次,一次是group,一次是single。这种情况要区分下single在group前,还是在group后的情况,要考虑两种,不能漏了。
6、来回多试试,查询的功能也就ok了。
感觉这个功能有点子像图库的不同展示列表,包括了单一的照片、连拍、gif、或者视频等,当然目前做的这个功能比不上图库,但是可以以小见大。
后面如果要优化的话,可以考虑数据库分表建索引,android本身自带的sqlite操作语句有点局限性,考虑使用SQL语句,调用db的execSQL方法,进行union或者笛卡尔链接等。后面也考虑加上数据库图片删除的功能,有兴趣再说。

这里把数据库的东西放到这里来叙述刚刚好,因为接下来还是和数据库打交道了。

心情历程记录

这里的记录界面,就是上一步用来展示调用系统相机拍照数据的界面。分为text、recylerView、button,三类。当text该写的话语写完,图片也拍完了,点击了button 发表,这里又分为两步了,一是更新到当前recycler中,二是插入到数据库中。更新到recyler中没啥好说的,就正常加list,无论是调用notifyItemchanged也好,还是notifyiteminsert也罢,调用就行了,主要是第二步的插入数据库。这个时候要区分是否是group的图片,如果是单一照片的话,直接调用manager类的add操作即可,如果是group图片的话,首先将第一张图片优先插入数据库,返回插入id之后再次更新第一张图片的group key,然后依次将list数据结合groupkey依次插入数据库中即可。ok,show the fucking code:

thread {
            val imageList = ArrayList<Bitmap?>()

            for (tmpData in arrayList) {
                if (!tmpData.isUri){
                    continue
                }

                val bitm = UriToBitmap(tmpData)?.let {
                    imageList.add(it)
                    it
                }

            }

            Log.e("ymc", "sendTextOrImageFromFragment - toDayDate = $toDayDate , wether = ${temp_placetemp_weatherkind.text}")
            val tempData = TempCaptureData(false, toDayDate + "     ${temp_placetemp_weatherkind.text} - $placeName", sayWord, BitmapFactory.decodeResource(resources, R.drawable.mango_pic), imageList, false)
            GlobalScope.launch {
                val authorBit = BitmapFactory.decodeResource(resources, R.drawable.mango_pic)
                var addpic :Bitmap?
                val authorbyteOutputStream = ByteArrayOutputStream()
                val picbyteOutputStream = ByteArrayOutputStream()
                authorBit.compress(Bitmap.CompressFormat.PNG, 100, authorbyteOutputStream)
                Log.e("ymc", "sendTextOrImageFromFragment - imageList.size = ${imageList.size}")
                if (imageList.size > 1) {
                    var dataresult:Long? = null
                    for ((index, pic) in imageList.withIndex()) {
                        picbyteOutputStream.reset()
                        pic?.compress(Bitmap.CompressFormat.PNG, 100, picbyteOutputStream)
                        if (index == 0) {
                            dataresult = MyDataBaseManager.addDataToDB(authorbyteOutputStream.toByteArray(), tempData.dateOrName, sayWord, picbyteOutputStream.toByteArray(), null)
                            Log.e("ymc", "sendTextOrImageFromFragment - first insert = $dataresult")
                            if (dataresult > 0) {
                                val firstresult = MyDataBaseManager.updateData(dataresult.toInt(), dataresult.toInt())
                                Log.e("ymc", "sendTextOrImageFromFragment - first update = $firstresult")
                            }
                        } else{
                            val otherresult = MyDataBaseManager.addDataToDB(authorbyteOutputStream.toByteArray(), tempData.dateOrName, sayWord, picbyteOutputStream.toByteArray(), dataresult?.toInt())
                            Log.e("ymc", "sendTextOrImageFromFragment - other insert = $otherresult")
                        }
                    }

                } else {
                    picbyteOutputStream.reset()
                    addpic = imageList[0]
                    addpic?.compress(Bitmap.CompressFormat.PNG, 100, picbyteOutputStream)
                    val dataresult = MyDataBaseManager.addDataToDB(authorbyteOutputStream.toByteArray(), tempData.dateOrName, sayWord, picbyteOutputStream.toByteArray(), null)
                    Log.e("ymc", "sendTextOrImageFromFragment - single insert = $dataresult")
                }
            }
            if (mIsFirstEvent) {
                mIsFirstEvent = false
                tempCaptureArrayList.removeFirst()
            }
            tempCaptureArrayList.add(0, tempData)

            runOnUiThread {
                tempCaptureAdapter.notifyDataSetChanged()
                place_temp_recyclerView_selfinfo.layoutManager?.scrollToPosition(0)
            }

}

到这里的话,基本上是ok了。

历史心情历程查看

此功能的逻辑在上方也有叙述,这里再赘述下。这个历史查看界面,目前布局在查询天气界面的下方,当首次进入的时候,如果数据库中没有数据的话,显示自定义的一个无效item,如果数据库中存在数据,那么读取2条item用来展示,主要逻辑就是这样,show the fucking code:

fun tryLoadData(recycleLoad:Boolean) {
        GlbalScope.aunch(Dispatchers.IO) {

            /* just for sleep,so we can see progressCircle. */
            try {
                Thread.sleep(1000)
            }catch (e: Exception) {
                e.printStackTrace()
            }
            val tempList = yDataBaseManager.selectAllData(recycleLoad)
            if (mInLoadingNext) {
                mInLoadingNext = false
                removeLoaItem()
                tempLst?.let {
                    if (it.size == 0) {
                        HasReachedEnd = true
                    }
                }
            }
            if (tempList != null) {
                tempList.forEach { temptransition ->
                    var tempBit : itmap? = null
                    val listtemp = ArrayList<Bitmap?>()
                    temptranstion.wantShowImage?.let {
                        it.forEach {
                            tempBit = null
                            it?.let {
                                tempBit = BitmaFactory.decodeByteArray(it, 0, it.size)
                            }
                            temBit?.let {
                                listtemp.add(it)
                            }
                    }
                    val authorBit = BitapFactory.decodeByteArray(temptransition.icon, 0, temptransition.icon.size)
                    val tempData = TempCaptureData(false, temptransiton.dateOrName, temptransition.wantToSay, authorit, listtemp, false)
                    if (tempCaptureArrayList.size == 1 && tempCaptureArayList[0].isRemoved) {
                        tempCaptureArrayList.removeAt(0)
                    }
                    tempCaptueArrayList.add(tempata)
                    mIsFirstEet = false
                    mDataReady = true
                    place_temp_recyclerViewselfinfo.pst {
                        tempCaptureAapter.notifyDatSetChanged()
                    }
                }
            }
            if (reycleLoad) {
                runOnUihread {
                    tepCaptureAdapter.notifyDataStChanged()
                }
            }
        }
}

到这里的话,整个demo app的功能就拆解完了,代码书写比较粗糙,专注于流程的设计,其他凑合看看,不对的地方欢迎指导
源码地址:
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、付费专栏及课程。

余额充值