评论

收藏

[HarmonyOS] 卡片服务开发之如何开发一个地图服务卡片

移动开发 移动开发 发布于:2021-07-30 19:47 | 阅读数:658 | 评论:0

前言
处于隐私保护借用熊猫基地定位,代码层实现了获取实时定位功能。
代码已开源至gitee:https://gitee.com/panda-coder/harmonyos-apps/tree/master/AMapCard

DSC0000.png
DSC0001.png
DSC0002.png
DSC0003.png
关键技术及实现原理
卡片现有支持的基础组件有: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
DSC0004.png

DSC0005.png
创建后的结构:
DSC0006.png

首先修改程序的配置文件:
打开config.json,修改卡片支持类型情况:
DSC0007.png
添加权限:
DSC0008.png

配置完成还需要在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);
  }
需要注意:卡片的事件不能使用表达式,不能使用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,并修改构造函数
DSC0009.png
然后进入FormControllerManager找到createFormController、getController、newInstance进行修改。
createFormController:
在newInstance方法中添加参数formId,如下图
DSC00010.png
getController:
在newInstance方法中添加参数formId,如下图
DSC00011.png
newInstace:
该方法是动态的创建WidgetImpl方法,类似于IOC作用
DSC00012.png
找到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;
  }
}
--------至此一个地图周边的卡片即可开发完成,后续会增加卡片的编辑功能可关注gitee---------
觉得该文章有帮助的小伙伴麻烦帮我[点个赞]





关注下面的标签,发现更多相似文章