卡片服务开发之如何开发一个地图服务卡片 原创 精华
前言
处于隐私保护借用熊猫基地定位,代码层实现了获取实时定位功能。
代码已开源至gitee:https://gitee.com/panda-coder/harmonyos-apps/tree/master/AMapCard
本人正在参加鸿蒙HarmonyOS征文的活动,觉得该文章有帮助的小伙伴麻烦帮我点个赞
B站效果视频链接:https://www.bilibili.com/video/BV1Rq4y1X7BC?share_source=copy_web
效果图
卡片效果 | 2x2+2x4 | 4x4 | 缩放 |
---|---|---|---|
关键技术及实现原理
卡片现有支持的基础组件有:button、calendar、chart、clock、divider、image、input、progress、span、text
可以看到现有的卡片组件并不支持地图的开发,那么如何在卡片上显示地图尼?
通过image组件+高德地图WebAPI的静态地图即可实现地图的显示。
::: hljs-center
-----------------以上方便有开发卡片经验的开发者提供思路,具体方式方法如下---------------------
:::
从零开始
创建项目
打开DevEco Studio工具,点击File->New->New Project创建一个Empty Ability(JS)如下图:
SDK选用API 5
新建 | 参数调整 |
---|---|
创建后的结构:
首先修改程序的配置文件:
打开config.json,修改卡片支持类型情况:
添加权限:
配置完成还需要在MainAbility中显示的申明使用权限信息,详情参考文档配置相关内容:
打开MainAbility添加方法:
//获取权限
private void requestPermission() {
String[] permission = {
"ohos.permission.LOCATION",
"ohos.permission.LOCATION_IN_BACKGROUND",
};
List<String> applyPermissions = new ArrayList<>();
for (String element : permission) {
if (verifySelfPermission(element) != 0) {
if (canRequestPermission(element)) {
applyPermissions.add(element);
}
}
}
requestPermissionsFromUser(applyPermissions.toArray(new String[0]), 0);
}
并在onStart方法中调用requestPermission();方法。
修改界面:
打开widget下的pages/index/imdex.hml
<div class="container">
<div class="container-inner" >
<div class="container-img">
<stack>
<image src="{{imgSrc}}" class="bg-img"></image>
<div class="container-show-text" >
<text class="show-text" >当前检索项:</text>
<text class="show-text" style="color: coral;" >{{searchText}}</text>
</div>
<div class="container-map-ctl">
<button class="map-ctl-btn" @click="mapAddEvent" type="circle">+</button>
<button class="map-ctl-btn" @click="mapReduceEvent" type="circle">-</button>
</div>
<div show="{{showCtlButton}}" class="container-ctl" >
<button class="ctl-btn" @click="searchCheckedEvent0">{{searchBtns[0]}}</button>
<button class="ctl-btn" @click="searchCheckedEvent1">{{searchBtns[1]}}</button>
<button class="ctl-btn" @click="searchCheckedEvent2">{{searchBtns[2]}}</button>
<button class="ctl-btn" @click="searchCheckedEvent3">{{searchBtns[3]}}</button>
<button class="ctl-btn" @click="searchCheckedEvent4">{{searchBtns[4]}}</button>
</div>
</stack>
</div>
</div>
</div>
需要注意:卡片的事件不能使用表达式,不能使用for语句循环构建
样式调整文件pages/index/imdex.css:
.container {
flex-direction: column;
justify-content: center;
align-items: center;
}
.bg-img {
flex-shrink: 0;
height: 100%;
object-fit: cover;
}
.container-ctl{
opacity: 0.9;
width: 100%;
height: 100%;
justify-content: center;
flex-direction: row;
align-items: flex-end;
bottom: 3px;
}
.ctl-btn{
padding: 3px 6px;
margin:3px 6px;
font-size: 12px;
border-radius: 3px;
background-color: #409eff;
border: 1px solid #cbcbcb;
box-shadow: 1px 1px 3px #a8a8a8;
}
.container-map-ctl{
opacity: 0.8;
justify-content: flex-end;
margin-right: 3px;
}
.map-ctl-btn{
background-color: #409eff;
border: 1px solid #cbcbcb;
box-shadow: 1px 1px 3px #a8a8a8;
width: 24px;
height: 24px;
margin:3px;
}
.container-show-text{
padding: 9px;
}
.show-text{
font-size: 8px;
font-weight: bolder;
}
json配置信息修改pages/index/index.json:
{
"data": {
"showCtlButton": false,//是否显示button。由Java传值且在2x2的界面不显示
"imgSrc": "/common/ic_default_image@3x.png",//默认图片
"searchText": "",
"searchBtns": []//配置的button按钮信息
},
"actions": {
"searchCheckedEvent0": {
"action": "message",
"params": {
"index": 0,
"name": "checkSearch"
}
},
"searchCheckedEvent1": {
"action": "message",
"params": {
"index": 1,
"name": "checkSearch"
}
},
"searchCheckedEvent2": {
"action": "message",
"params": {
"index": 2,
"name": "checkSearch"
}
},
"searchCheckedEvent3": {
"action": "message",
"params": {
"index": 3,
"name": "checkSearch"
}
},
"searchCheckedEvent4": {
"action": "message",
"params": {
"index": 4,
"name": "checkSearch"
}
},
"mapAddEvent": {
"action": "message",
"params": {
"name": "mapAdd"
}
},
"mapReduceEvent": {
"action": "message",
"params": {
"name": "mapReduce"
}
}
}
}
后台逻辑
由于更新卡片时需要提供formId,我们对FormController及FormControllerManager这两个帮助类进行一个修改
打开java目录下的FormController文件并添加受保护的属性 formId,并修改构造函数
然后进入FormControllerManager找到createFormController、getController、newInstance进行修改。
createFormController:
在newInstance方法中添加参数formId,如下图
getController:
在newInstance方法中添加参数formId,如下图
newInstace:
该方法是动态的创建WidgetImpl方法,类似于IOC作用
找到java目录下的widget/widget/widgetImpl,卡片的所有逻辑都在该文件内
首先修改构造函数及定义基础属性等
因上述修改了FormController及FormControllerManager构造函数必须增加Long formId参数
private static Location slocation=null;//当前位置信息
private Boolean slocationChanged=false;//位置是否修改
private int dimension=2;//当前卡片模式 2x2=2;2x4=3;4x4=4;
private List<String> defualtBtn=new ArrayList<>();//界面下方的按钮列表
private static Locator locator=null;//坐标获取类
private LocatorCallBack locatorCallBack=new LocatorCallBack();//坐标获取后返回调用类
private int mRoom=16;//静态地图显示层级
private String markType="";//静态地图周边搜索关键字
private String mSize="500*500";//静态地图大小
private List<String> mKeyLocation=new ArrayList<>();//静态地图获取周边标记的坐标
RequestParam requestParam = new RequestParam(RequestParam.PRIORITY_ACCURACY, 20, 0);
public WidgetImpl(Context context, String formName, Integer dimension,Long formId) {
super(context, formName, dimension,formId);
this.dimension=dimension;
//获取当前定位
if(locator==null){
locator=new Locator(context);
locator.startLocating(requestParam,locatorCallBack);
}
switch (dimension){
case 2:{
mSize="300*300";
mRoom=13;
break;
}
case 3:{
mSize="500*250";
mRoom=13;
break;
}
case 4:{
mSize="500*500";
mRoom=15;
break;
}
}
}
public class LocatorCallBack implements LocatorCallback{
@Override
public void onLocationReport(Location location) {
slocation=location;
//周边信息接口额度有限,限制为当坐标改变时刷新坐标mark信息,并更新卡片
if(location==slocation || slocation==null)
return;
refreshMark();
updateFormData(formId);
}
@Override
public void onStatusChanged(int i) {
}
@Override
public void onErrorReport(int i) {
}
}
修改createFormController,该方法在卡片创建时调用,我们需要把页面需要的参数传递过去
注意网络图片需要使用“通过内存图片方式使用image组件”
@Override
public ProviderFormInfo bindFormData(){
defualtBtn=new ArrayList<>();
defualtBtn.add("酒店");
defualtBtn.add("餐饮");
defualtBtn.add("景点");
defualtBtn.add("加油站");
if(defualtBtn.size()<5){
for(int i=defualtBtn.size();i<5;i++){
defualtBtn.add("未设置");
}
}
this.markType=defualtBtn.get(0);
this.refreshMark();
FormBindingData formBindingData=null;
ZSONObject zsonObject =new ZSONObject();
zsonObject.put("imgSrc","memory://amap.png");
zsonObject.put("showCtlButton",this.dimension!=2);
zsonObject.put("searchBtns",defualtBtn);
zsonObject.put("searchText",markType);
formBindingData=new FormBindingData(zsonObject);
ProviderFormInfo formInfo = new ProviderFormInfo();
formInfo.setJsBindingData(formBindingData);
String amapUrl=getMapImageUrl(mKeyLocation);
byte[] bytes= HttpImageUtils.doGetRequestForFile(amapUrl);
formBindingData.addImageData("amap.png",bytes);
return formInfo;
}
初始化卡片后改进onTriggerFormEvent
该方法为接收卡片事件,message为事件传递的params参数
@Override
public void onTriggerFormEvent(long formId, String message) {
ZSONObject request=ZSONObject.stringToZSON(message);
String EventName=request.getString("name");
switch (EventName){
case "checkSearch":{
int index=request.getIntValue("index");
markType=defualtBtn.get(index);
this.refreshMark();
break;
}
case "mapAdd":{
if(mRoom<17){
mRoom+=1;
}
break;
}
case "mapReduce":{
if(mRoom>0){
mRoom-=1;
}
break;
}
}
updateFormData(formId);
}
修改更新卡片信息的方法(此方法不仅是系统会定时刷新,也有主动刷新的调用如:卡片事件改变后调用,坐标改变后的调用,这也是需要修改FormController、FormControllerManager增加formId属性的原因,因为在主动刷新时需要formId参数)
此处还有一个重点就是 ((Ability)context).updateForm(formId,bindingData);
@Override
public void updateFormData(long formId, Object... vars) {
ZSONObject zsonObject=new ZSONObject();
zsonObject.put("searchBtns",defualtBtn);
zsonObject.put("searchText",markType);
String mapName="amap"+System.currentTimeMillis()+".png";
zsonObject.put("imgSrc","memory://"+mapName);
FormBindingData bindingData = new FormBindingData(zsonObject);
String amapUrl=getMapImageUrl(mKeyLocation);
byte[] bytes= HttpImageUtils.doGetRequestForFile(amapUrl);
bindingData.addImageData(mapName,bytes);
try{
((Ability)context).updateForm(formId,bindingData);
}catch (Exception ex){
ex.printStackTrace();
}
}
其它一些上述方法中调用的私有方法及类
私有方法:
private void refreshMark(){
try{
this.mKeyLocation= HttpImageUtils.SearchByKeyUrl(getMapMarkUrl(10));
}catch (Exception ex){
ex.printStackTrace();
}
}
private String getMapImageUrl(List<String> Position){
String url="https://restapi.amap.com/v3/staticmap";
String params="key=";
params+="&zoom="+mRoom;
params+="&size="+mSize;
if(slocation!=null)
params+="&location="+slocation.getLongitude()+","+slocation.getLatitude();
params+="&markers=large,0xea7700,H:"+slocation.getLongitude()+","+slocation.getLatitude();
if(Position==null || Position.size()==0)
return url+"?"+params;
String markers="|mid,0xFF0000,:";
for(int i=0;i<Position.size();i++){
markers+=Position.get(i)+";";
}
params+=markers.substring(0,markers.length()-1);
return url+"?"+params;
}
private String getMapMarkUrl(int size){
String Url="https://restapi.amap.com/v5/place/around?key=";
Url+="&keywords="+(markType=="未设置"?"":markType);
if(slocation!=null)
Url+="&location="+slocation.getLongitude()+","+slocation.getLatitude();
Url+="&size="+size;
return Url;
}
HttpImageUtils类
package com.panda_coder.amapcard.utils;
import com.panda_coder.amapcard.MainAbility;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import ohos.utils.zson.ZSONArray;
import ohos.utils.zson.ZSONObject;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
public class HttpImageUtils {
private static final HiLogLabel TAG = new HiLogLabel(HiLog.DEBUG, 0x0, MainAbility.class.getName());
public final static byte[] doGetRequestForFile(String urlStr) {
InputStream is = null;
HttpURLConnection conn = null;
byte[] buff = new byte[1024];
try {
URL url = new URL(urlStr);
conn = (HttpURLConnection) url.openConnection();
conn.setDoInput(true);
conn.setRequestMethod("GET");
conn.setReadTimeout(6000);
conn.connect();
is = conn.getInputStream();
if (conn.getResponseCode() == 200) {
buff = readInputStream(is);
} else{
buff=null;
}
} catch (Exception e) {
HiLog.error(TAG,"【获取图片异常】",e);
}
finally {
try {
if(is != null){
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
conn.disconnect();
}
return buff;
}
public static byte[] readInputStream(InputStream is) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int length = -1;
try {
while ((length = is.read(buffer)) != -1) {
baos.write(buffer, 0, length);
}
baos.flush();
} catch (IOException e) {
e.printStackTrace();
}
byte[] data = baos.toByteArray();
try {
is.close();
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
return data;
}
public static String httpGet(String urlStr){
InputStream is = null;
HttpURLConnection conn = null;
String response="";
StringBuffer buffer = new StringBuffer();
try {
URL url = new URL(urlStr);
conn = (HttpURLConnection) url.openConnection();
conn.setDoInput(true);
conn.setRequestMethod("GET");
conn.setReadTimeout(6000);
conn.connect();
is = conn.getInputStream();
if (conn.getResponseCode() == 200) {
String str=null;
InputStreamReader isr = new InputStreamReader(is,"utf-8");
BufferedReader br = new BufferedReader(isr);
while((response = br.readLine())!=null){
buffer.append(response);
}
}
response=buffer.toString();
} catch (Exception e) {
HiLog.error(TAG,"【访问异常】",e);
}
finally {
try {
if(is != null){
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
conn.disconnect();
}
return response;
}
public final static List<String> SearchByKeyUrl(String urlStr){
List<String> result=new ArrayList<>();
String response= httpGet(urlStr);
if(response==null || response=="")
return result;
ZSONObject zson=ZSONObject.stringToZSON(response);
if(zson.getIntValue("infocode")!=10000)
return result;
ZSONArray zsonArray=zson.getZSONArray("pois");
for(int i=0;i<zsonArray.size();i++){
ZSONObject child= (ZSONObject)zsonArray.get(i);
String location=child.getString("location");
result.add(location);
}
return result;
}
}
::: hljs-center
--------至此一个地图周边的卡片即可开发完成,后续会增加卡片的编辑功能可关注gitee---------
:::
::: hljs-center
觉得该文章有帮助的小伙伴麻烦帮我[点个赞]
:::
厉害,B站跟过来,三连一波!
厉害厉害
很实用啊 鸿蒙的地图应用快更新卡片特性吧!!