卡片服务开发之如何开发一个地图服务卡片 原创 精华

发布于 2021-7-25 17:00
浏览
8收藏

前言

处于隐私保护借用熊猫基地定位,代码层实现了获取实时定位功能。
代码已开源至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

觉得该文章有帮助的小伙伴麻烦帮我[点个赞]

:::

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2021-7-27 12:03:02修改
11
收藏 8
回复
举报
回复
添加资源
添加资源将有机会获得更多曝光,你也可以直接关联已上传资源 去关联
    相关推荐