关于weex:mac环境下weex代码跑Android-Studio和xcode

Android Studio装置jdk去到官网下载1.8版本的jdk配置jdk环境变量关上终端cd ~/回到首页open .zshrc关上环境变量配置文件,若没有该文件,则应用touch .zshrc创立文件向.zshrc写入jdk配置代码 JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_301.jdk/Contents/HomeCLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jarexport JAVA_HOMEexport CLASSPATHPATH=$JAVA_HOME/bin:$PATHjava -version查看是否配置胜利 装置android studio下载并装置android studio配置环境变量关上.zshrc文件,向开端写入 export ANDROID_HOME=/Users/fantasy/Library/Android/sdk // android studio 右上角 sdk manage 查看sdk门路export PATH=/usr/local/bin:$ANDROID_HOME/platform-tools:$ANDROID_HOME/tools:$PATHadb查看是否配置胜利 真机联调手机通过数据线连贯电脑,关上开发者选项,装置相干的app(通过mac端adb install 电脑端apk门路装置或者间接装置apk)拉取代码并初始化,npm start启动我的项目android studio关上android文件夹,此时软件会主动装置一些执行环境定位到classroom->java->IndexActivity.java->getIndexUrl办法,将js门路改为本地启动的服务android studio下面可看到关上的我的项目和真机型号,点击左边的绿色三角形启动,编译实现后手机主动跳转到相应的app服务,自此,所有配置已结束当初能够实时编辑文件进行代码调试了 xcode初始化ios我的项目终端进入ClassRoom->ios文件夹,pod install初始化碰到问题libwebp下载呈现连贯不到地址问题,然而浏览器能够关上相干链接,解决方案:换源 pod repo查看Path find .cocoapods/repos/cocoapods -iname libwebp找到相干目录值 cd ~/.cocoapods/repos/cocoapods/Specs/1/9/2/libwebp切进找到的目录 cd 1.0.2切进须要下载的版本文件,open libwebp.podspec.json关上文件,批改其中的git源切回ios目录,从新执行pod install连贯git源出错,解决方案:git之前设置了代理,勾销掉即可 git config --global --unset http.proxy git config --global --unset https.proxy 切记:此时须要新建终端进入ios目录执行install,否则会生效所有的依赖下载结束后,关上xcode,关上ios文件夹底下的class.xcworkspace文件或者间接双击这个文件接下来,间接进行真机调试或者模拟器调试即可,因为还没有ios证书,故前面临时不记录了

September 15, 2021 · 1 min · jiezi

关于weex:入选-Apache-孵化器-4-年多阿里捐赠的-Weex-项目未能成功毕业

近日,由阿里开发并捐献给 Apache 软件基金会(ASF)的 Weex 我的项目未从 Apache 孵化器胜利毕业,目前 Weex 的孵化器状态已更改为「retired」。依据 Apache 软件基金会「Guide to Retirement」,retirement(退出)不代表我的项目进行保护,仅示意不再在 Apache 孵化器开发。 Weex 是阿里巴巴研发的一款轻量级的跨平台挪动开发工具,旨在帮忙挪动开发者通过简捷的前端语法写出 Native 级别的性能体验,反对 iOS、安卓、YunOS 及 Web 等多端部署。 2016 年 4 月,阿里发表开源 Weex;16 年 12 月,Weex 被捐献给 Apache 软件基金会开始孵化。往年 4 月,因 Weex 我的项目 PPMC(项目管理委员会)不够沉闷,Apache 孵化器发动了对于 Weex 退出流程(retirement process)的投票。最终,Weex 我的项目未能胜利从 Apache 孵化器毕业。 ASF 董事、Apache SkyWalking 创始人吴晟感叹,「退出基金会的潮水褪去,一些开源我的项目开始走向失败。」 ASF 孵化器导师、Apache 北京外乡社区发起人姜宁示意:「在将我的项目捐献给 ASF 时,你须要确定想要的后果。如果只是为了取得基金会的『认证』,而不为构建社区付出致力,那么你将无奈构建可继续的 OSS 我的项目。」 ASF 我的项目孵化流程ASF 孵化器创立于 2002 年,旨在反对和激励新我的项目。截至 2019 年 11 月,ASF 孵化器帮忙了 315 个我的项目,其中超过 200 个我的项目顺利毕业,300 余名导师参加领导和反对孵化我的项目。 ...

May 15, 2021 · 1 min · jiezi

关于weex:女朋友为我写了一个防猝死插件

https://tieba.baidu.com/p/720...https://tieba.baidu.com/p/720...https://tieba.baidu.com/p/720...

January 25, 2021 · 1 min · jiezi

关于weex:记-weex-坑爹过程

款式问题box-shadow 仅反对 ios只反对 px 写法在 weex 中,flexbox 是惟一的布局模式,不反对内联布局 display: inline/floatweex 不反对 z-index 设置元素层级关系,但靠后的元素层级更高border 不反对缩写,必须离开写背景色必须写残缺,background-color: redimage 必须设置宽高款式动静替换 class,只能应用数组表达式如果定位元素超过容器边界,在 Android 下,超出局部将不可见,起因在于在 Android 端元素 overflow 默认值为 hidden,且 overflow 没有其余值不反对背景图 可是应用定位来解决Android 上解决圆角,必须在外层div中设置 border-radius不反对负边距 margin-left: -10px; --> transform: translateX(-10px);伪类选择器只反对active focus disabled enabledweex 反对 position 定位 :relative | absolute |fixed |sticky ,默认值是relative反对线性突变:linea-gradient,不反对径向突变:radius-gradient子元素的款式不会继承自父元素,比方 color 和 font-size 等款式作用在 <text> 标签的下层 <div> 是有效的文字必须放在 <text> 标签中,不能够间接放在 <div> 标签中<text> 标签,有个 lines 款式,用于限度文本行数,并呈现省略号,然而 lines:1 必须放在 css 外面,不能放在作为属性放在 <text> 标签中,相似这样 <text lines:"2"></text>,这样不失效<input> 标签,必须带结束符,网页端浏览无奈聚焦没关系,因为模拟器不反对;必须编辑 <input> 标签的高度,否则聚焦光标会不显示。布局 - 定位层级问题:weex 反对 position 定位 :relative | absolute |fixed |sticky ,默认值是 relative ,应用绝对定位 absolute 时,在 web 端和 native 端的绝对地位会有偏差须要做容错解决,并且如果定位元素超过容器边界,在 Android 下,超出局部将不可见,起因在于 Android 端元素 overflow 默认值为 hidden,且 overflow 没有其余值,不可批改。当应用定位时文档脱离文档流,因为 weex 在 native 端不反对 z-index 设置元素层级关系,而所依附的是越靠后的元素层级更高,然而在咱们的 app 中越靠后的元素层级更高,并没有失效。据网上传言, v-if 会影响元素的层级,例如:三个 div 都应用了定位,此时的层级关系是只能看到第三个 div,给第二个 div 加上 v-if ,在操作 v-if 时 加上 v-if 的第二个元素会产生有时可见有时不可见的问题,此问题临时没有复现过。weex 官网组件 wxc-overlay / wxc-popupwxc-overlay / wxc-popup 的弹出成果默认都是通过 v-if 管制组件从利用边缘显示暗藏,应用相对定位使其脱离文档流,以及应用 weex.requireModule('animation'),做显示暗藏的适度动画;wxc-overlay 蒙层默认能够传入 top left 值,批改间隔边距的间隔,hasAnimation 字段管制是否有动画状态,默认为 true,duration 管制动画适度工夫,timingFunction 字段管制动画执行规定,opacity 字段管制蒙层的透明度,管制点击蒙层是否暗藏wxc-popup 弹层首先应用了 wxc-overlay 蒙层,standOut 批改间隔边距的间隔的默认值,pos 字段管制组件弹出方向,popupColor 管制弹层背景色彩,overlayCfg 字段管制组件动画规定,我的项目中有 wxf-popup 弹出源码,然而批改 wxf-popup 源码给 wexx wxc-overlay 组件增加 :top="standOut" 管制间隔并不失效

December 16, 2020 · 1 min · jiezi

适合前端Vue开发童鞋的跨平台Weex

基于 Vue 技术栈的你如果需要选用一种移动端跨平台框架,是 Weex?React-Native?还是Flutter? 无疑,相对于后两者,因为你现在已有比较熟练的 Vue 基础,如果在其他条件一致的情况,Weex 无疑是最佳选择;但是 Weex 真的适合在实际项目中进行移动端跨平台开发吗?Weex 的开发效率、Weex 的质量是否满足需求? 一、开发环境在这个 Weex app 开发中,我的开发环境相关配置如下: 工具名称版本号Node.js8.2.1Npm5.3.0Android Studio3.2Weex2.0.0-beta.17JDK1.8Weex-ui0.6.14二、Weex 介绍2.1、Weex 理念“Write once, run everywhere”, Weex 的定义就像是:写个 vue 前端,顺便帮你编译成性能还不错的 apk 和 ipa(当然,现实有时很骨感)。基于 Vue 设计模式,支持 web、android、ios 三端,原生端同样通过中间层转化,将控件和操作转化为原生逻辑来提高用户体验。 在 weex 中,主要包括三大部分:JS Bridge、Render、Dom,分别对应WXBridgeManager、WXRenderManager、WXDomManager,三部分通过 WXSDKManager 统一管理。其中 JS Bridge 和 Dom 都运行在独立的 HandlerThread 中,而 Render 运行在 UI 线程。 JS Bridge 主要用来和 JS 端实现进行双向通信,比如把 JS 端的 dom 结构传递给 Dom 线程。Dom 主要是用于负责 dom 的解析、映射、添加等等的操作,最后通知 UI 线程更新,而 Render 负责在 UI 线程中对 dom 实现渲染。Weex 所有的标签也不是真实控件,JS 代码中所生成存的 dom,最后都是由 Native 端解析,再得到对应的 Native控件渲染,如 Android 中标签对应 WXTextView 控件。 Weex 中文件默认为 .vue ,而 vue 文件是被无法直接运行的,所以 vue 会被编译成 .js 格式的文件,Weex SDK会负责加载渲染这个 js 文件。Weex 可以做到跨三端的原理在于:在开发过程中,代码模式、编译过程、模板组件、数据绑定、生命周期等上层语法是一致的。不同的是在 JS Framework 层的最后,web 平台和 Native 平台,对 Virtual DOM 执行的解析方法是有区别的。 ...

October 16, 2019 · 5 min · jiezi

勾三股四用技术追寻世界的确定答案

2015年,他发起阿里Weex项目,实现「一次编写,三端运行」。他被称为「Weex之父」。 2016年,阿里宣布开源Weex项目,年底将其捐赠Apache基金会。2017年,他在微博宣布退出Weex团队。 外界对Weex的讨论也好,争议也罢,他淡出了大众的视线。 下一次外界听到他的消息,是在2017年底,却完全与技术无关。原来,他和几个同事一起,组成了阿里996乐队。大家才发现,原来程序员们不仅会写代码,写歌也蛮好听。 他和Weex的故事是怎样的?他是怎样的一个人?今天,图灵访谈带你走近勾三股四,一位自称「有点叛逆」的前端工程师。 文 | 李冰采访 | 乐馨,李冰移动端崛起勾股2007年大学毕业。他大二学完基础课程之后,就转到了自己很着迷的软件工程专业。毕业后,他去傲游浏览器面试,笔试之后现场对方只问了三个问题:「HTML熟不熟?JavaScript熟不熟?CSS熟不熟?」他不怵,说了三个「熟」。面试官竟然没多问,直接给了他offer。 那是中国互联网风起云涌的时代。当时浏览器才刚兴起,大家用得最多的还是Office、QQ这种桌面传统软件。他们经历了所谓的浏览器大战。 傲游做了大量革命性的创新,不仅推出免费版,支持多语言,用单窗口多标签代替了多窗口,推出广告过滤功能,而且它独创了如今浏览器必备的在线收藏系统。2007年,傲游霸占了非常高的市场份额,市场占有率仅次于IE浏览器。 勾股说:「在一个蓬勃发展的创业公司,每天都给你很多成就感。而且个人也在随着公司一起发展,不仅是技术,而是能够真正感受到整个行业的方方面面。」 然而,随着移动端崛起,PC浏览器市场开始呈现疲态。2010年,傲游的用户大量流失。2012年,它在中国大陆的市场占有率已经不足1%。 勾股坦言:「我们在PC端有非常狂热的忠实用户的支持,收入也比较稳定。公司其实也看到了移动互联网的趋势,但可能没有抓住这个机会。」 与此同时,2013年,阿里的战略是「All-in 无线」。 一方面,勾股看到移动互联网的发展,很想参与和探索;另外,很多他认为很厉害或者关系很好的朋友在阿里,淘宝的居多。于是,在winter的推荐下,他加入了阿里。「winter打电话给我,说:‘有一个好消息,有一个坏消息。好消息是我们发offer给你。坏消息就是我们要996。’ 「我当时的第一反应当然是比较兴奋啊!反正我刚去那边,也需要花比别人更多的时间熟悉工作。既然这样的话,就有更多的人晚上陪我了。当时我是这么想的。」 那段时间他印象深刻的就是每个人都很拼。 「从最上边到最一线的员工,每个人都是在玩命。可能一方面是工作压力,另外一方面,我看到很多人谈到移动技术的发展这些东西时,真的是两眼放光,有讲不完的话想跟你分享。确实是有那股热情在。」 2016年,手机淘宝和淘宝的合并标志着一个时代——移动端的崛起。手淘的创业之路算是告一段落,无论从技术还是从业务,都和淘宝网的PC业务做了更深度的整合。 Weex的故事2013年,阿里「All in 无线」战略如火如荼地推进,大量资源向无线业务倾斜。当时,勾股所在团队负责手机淘宝的前端业务,在快速向前的同时,他们面前也摆着大量的问题。 其中,iOS、安卓,还有Web,这三端的业务,可不可以只用一套代码解决? iOS和安卓的发布节奏有限制,比如iOS每月发一版。但淘宝每天都要做新促销活动或者上新功能。有些东西这个月没赶上,就只能赶下一个月。所以可不可以找到一种方式,能够随时发布更新? 他们一直在寻找解决方案。 2015年初,React Native正式开源发布了。其实,自从2014年底的一个技术会议对它有所介绍之后,大家就一直很期待,因为它看上去能够解决这两个问题。 但在尝试之后,两大困难让他们无法忽视。 第一个困难是当时React Native的版本还很早,迭代非常快,并且每次都有大幅度的改动,团队跟不上这个节奏。 第二个就是,很难在它上面加自己的东西。这个技术方案的定位并非解决他们面临的动态性问题,导致很多细节用起来比较牵强。 归根到底,他们还是绕不开面前的难题,外来的解决方案似乎并不合适。 干脆还是靠自己吧! 2015年夏天,勾股发起了Weex项目,一颗探路石,一个灵活的解决方案。 「可能我在这个过程当中扮演的角色,是在一个特定的阶段,我同时看到很多信息和知识,其他人未必同时看得到。然后我有机会想到把它们组合在一起。 「第一个东西是在阿里内部的项目,它可以接受一段JSON的数据,然后渲染成Native的界面。这个工程我2013年加入阿里的时候就已经在做了,为Weex提供了技术积累。 「另外,第二个东西是Vue.js,当时知道它的人并不多。我发现它能很好地优化前端开发体验,以及处理前端框架中间JavaScript的这部分。2015年夏天深圳的JSConf上,刚好尤雨溪也一同参会,我们当面聊了几句,交流了这个项目。他当时给了我很多鼓励,也跟我分享了他对一些技术细节的看法。 「第三个当然就是Web standard,包括W3C,以及其他Web规范。首先,它可以解决很多通用的一些技术问题。而且,它的一些规范和API的设计是由很多厉害的科技公司沉淀下来的结论,非常经得起考验。」 三大武器虽已备齐,现实遇到的困难却比预估的更复杂。 「最大的困难,坦白讲就是三端不一致。 「不光是从技术角度,就是API的设计不一样。更困难的在于,因为大家的技术栈不一样,整个思考问题的方式,甚至工作习惯都不一样。所以,要让整个团队在一起工作,做出一个让大家能够信服的技术方案,这是真正具有挑战的地方。」 这个难题,他们写出了怎样的答案呢? 2015年的双十一Weex首秀,历史上第一次把Native级别的体验和稳定性带到了天猫双十一的移动会场。 如此惊喜的效果不仅让Weex团队感到兴奋,也获得了技术团队负责人的认可,给了Weex项目更多的支持和信任。 2016年双十一,Weex覆盖了99.6%的会场页面,页面的打开速度、滚动的流畅性都保证了良好的用户体验。 Weex的意义目前为止,一切都很顺利。 2016年初,阿里决定将Weex开源,年底宣布将Weex项目捐赠给Apache基金会,并且宣布了与Vue.js的官方合作。然而,这却使Weex引来了巨大的争议,外界对项目后续维护问题产生了质疑。 2017年初,项目发起人勾股发表了一份声明:不再是Weex团队的一员,离开淘宝,新的工作内容已与Weex无关。 事情的发展让外界迷惑不已。 Weex项目以及手机淘宝内部,经历了怎样的变动? 起初,在移动端崛起的背景下,每个团队都在摸索前进。移动端的开发形态和模式应该是怎样的?Native工程师有自己的想法,前端工程师也有自己的想法。直到Weex出现,越来越多的业务团队的技术栈逐渐地稳定下来。 这种变化,从技术角度来说,大家做事更有层次感了。有些人会维护Weex开源项目的核心的框架,有人做一些Weex插件或者扩展,还有一些人会专注在最上层的业务开发。大家的分工和团队协作方式变得更加清晰。 另一方面,人员结构也在逐渐地发生变化。有人适应,有人不适应,有人来有人走。每个人都在变化的进程里,包括勾股。 他说,一面的确有被动的因素存在,另一方面自己在想,不如主动地去拥抱变化。 而今时隔两年,他坦然分享了自己对这个问题的思考:Weex项目的意义是什么? 「Weex项目是阿里巴巴第一个愿意以官方的身份,并且主动地去拥抱开源社区的。 「其实阿里在Weex之前也开源了很多东西,都是以个人名义开源。当时在阿里内部,你想开源一个东西,流程很简单,就是你自己做一个申请。公司只会做一件事情,就是看有没有泄露公司的内部信息。公司不是在主动地面对这个问题。名义上是阿里的,但其实就是个人的。 「其实我们也讨论过这个问题,但担心将它开源会给公司惹麻烦。 ...

July 8, 2019 · 1 min · jiezi

移动端跨平台方案如何选择

May 31, 2019 · 0 min · jiezi

用weexplus从0到1写一个app2页面跳转和文章列表及文章详情的编写

说明结束连续几天的加班,最近的项目终于告一段落,今天抽点时间开始继续写我这篇拖了很久的《用weexplus从0到1写一个app》系列文章。写这篇文章的时候,weexplus的作者已经把weexplus重构了一下,可以同时打包出web端和native端,我这边的ui界面和项目结构也跟着做了一点变化。这里有weexplus官方放出的一个电影APP的demo,有需要的可以去下载看看,然后顺便给weexplus一个star吧! 文章可能会很长,在此分几篇文章来写,先占个坑: 用weexplus从0到1写一个app(1)-环境搭建和首页编写用weexplus从0到1写一个app(2)-参数跳转和文章列表及文章详情、搜索页面的编写用weexplus从0到1写一个app(3)-视频列表和视频详情的编写开始写代码页面跳转和接收参数在第一篇的文章《环境搭建和首页编写》中已经写好了首页的代码,现在要从首页的某个文章跳转到文章详情应该怎么做呢?在vue里我们知道是用vue-router来跳转,weexplus中也给我们封装好了类似的导航控制器navigator。具体使用请看navigtor模块文档 页面传参数跳转主要用后面这段navigator.pushParam('跳转路径String','传递的参数Object'),如果不需要传参数直接用navigator.push('跳转路径String')就好了。以下为示例代码: //commponet/home/news.vue省略n多代码const navigator = weex.requireModule("navigator");//先引入navigator模块gotonews(item) { if (item.category) { if (item.category.name == "专题") { //navigator传参数跳转页面 navigator.pushParam( "root:/busi/news/list.js", { cid: item.id, from: "zhuanti" } ); } if (item.category.name != "专题") { navigator.pushParam("root:/busi/news/detail.js", { id: item.id }); } } else { navigator.pushParam("root:/busi/news/detail.js", { id: item }); }},接收参数//busi/news/detail.vue省略n多代码const navigator = weex.requireModule("navigator");//先引入navigator模块created(options) { const globalEvent = weex.requireModule("globalEvent"); globalEvent.addEventListener("onPageInit", () => { const query = navigator.param();//接收上一个页面传递的参数 this.query = query; });},文章列表和文章详情先来看文章列表和文章详情的UI界面: ...

May 7, 2019 · 3 min · jiezi

编写chameleon跨端组件的正确姿势(上篇)

在chameleon项目中我们实现一个跨端组件一般有两种思路:使用第三方组件封装与基于chameleon语法统一实现。本篇是编写chameleon跨端组件的正确姿势系列文章的上篇,以封装一个跨端的indexlist组件为例,首先介绍如何优雅的使用第三方库封装跨端组件,然后给出编写chameleon跨端组件的建议。使用chameleon语法统一实现跨端组件请关注文章《编写chameleon跨端组件的正确姿势(下篇)》依靠强大的多态协议,chameleon项目中可以轻松使用各端的第三方组件封装自己的跨端组件库。基于第三方组件可以利用现有生态迅速实现需求,但是却存在很多缺点,例如多端第三方组件本身的功能与样式差异、组件质量得不到保证以及绝大部分组件并不需要通过多态组件差异化实现,这样反而提升了长期的维护成本;使用chameleon语法统一实现则可以完美解决上述问题,并且扩展一个新的端时现有组件可以直接运行。本文的最后也会详细对比一下两种方案的优劣。 因此,建议将通过第三方库实现跨端组件库作为临时方案,从长期维护的角度来讲,建议开发者使用chameleon语法统一实现绝大部分跨端组件,只有一些特别复杂并且已有成熟第三方库或者框架能力暂时不支持的组件,才考虑使用第三方组件封装成对应的跨端组件。由于本文介绍的是使用第三方库封装跨端组件, 因此示例的indexlist组件采用第三方组件封装来实现, 通过chameleon统一实现跨端组件的方法可以看《编写chameleon跨端组件的正确姿势(下篇)》。最终实现的indexlist效果图:前期准备使用各端第三方组件实现chameleon跨端组件需要如下前期准备:项目初始化创建一个新项目 cml-demo cml init project 进入项目cd cml-demo组件设计开发一个模块时我们首先应该根据功能确定其输入与输出,对应到组件开发上来说,就是要确定组件的属性和事件,其中属性表示组件接受的输入,而事件则表示组件在特定时机对外的输出。 为了方便说明,本例暂时实现一个具备基础功能的indexlist。一个indexlist组件至少应该在用户选择某一项时抛出一个onselect事件,传递用户当前所选中项的数据;至少应该接受一个datalist,作为其渲染的数据源,这个datalist应该是一个类似于以下结构的对象数组: const dataList = [ { name: ‘阿里’, pinYin: ‘ali’, py: ‘al’ }, { name: ‘北京’, pinYin: ‘beijing’, py: ‘bj’ }, ….. ]寻找第三方组件库由于本文介绍的是如何使用第三方库封装跨端组件,因此在确定组件需求以及实现思路后去寻找符合要求的第三方库。在开发之前,作者调研了目前较为流行的各端组件库,推荐如下:web端:cube-uivuxmint-uivantwx端:iview weappvant weappweuiweex端:weex-ui除了上述组件库之外,开发者也可以根据自己的实际需求去寻找经过包装之后符合预期的第三方库。截止文章编写时,作者未找到较成熟的支付宝及百度小程序第三方库,因此暂时先实现web、微信小程序以及weex端,这也体现出了使用第三方库扩展跨端组件的局限性:当没有成熟的对应端第三方库时,无法完成该端的组件开发;而使用chameleon语法统一实现.md)则可以解决上述问题,扩展新的端时已有组件能够直接运行,无需额外扩展。 本文在实现indexlist组件时分别使用了cube-ui, iview weapp以及weex-ui, 以下会介绍具体的开发过程.组件开发初始化创建多态组件cml init component选择“多态组件”, 并输入组件名字“indexlist”, 完成组件的创建, 创建之后的组件位于src/components/indexlist文件夹下。接口校验多态组件中的.interface文件利用接口校验语法对组件的属性和事件进行类型定义,保证各端的属性和事件一致。确定了组件的属性与事件之后就开始编写.interface文件, 修改src/components/indexlist/indexlist.interface: type eventDetail = { name: String, pinYin: String, py: String}type arrayItem = { name: String, pinYin: String, py: String}type arr = [arrayItem];interface IndexlistInterface { dataList: arr, onselect(eventDetail: eventDetail): void}具体的interface文件语法可以参考此处, 本文不再赘述。web端组件开发安装cube-ui npm i cube-ui -S在src/components/indexlist/indexlist.web.cml的json文件中引入cube-ui的indexlist组件"base": { “usingComponents”: { “cube-index-list”: “cube-ui/src/components/index-list/index-list” }}修改src/components/indexlist/indexlist.web.cml中的模板代码,引用cube-ui的indexlist组件: <view class=“index-list-wrapper”> <cube-index-list :data=“list” @select=“onItemSelect”/></view>修改src/components/indexlist/indexlist.web.cml中的js代码, 根据cube-ui文档将数据处理成符合其组件预期的结构, 并向上抛出onselect事件:const words = [“A”,“B”,“C”,“D”,“E”,“F”,“G”,“H”,“I”,“J”,“K”,“L”,“M”,“N”,“O”,“P”,“Q”,“R”,“S”,“T”,“U”,“V”,“W”,“X”,“Y”,“Z”];class Indexlist implements IndexlistInterface {props = { dataList: { type: Array, default() { return [] } }}data = { list: [],}methods = { initData() { const cityData = []; words.forEach((item, index) => { cityData[index] = {}; cityData[index].items = []; cityData[index].name = item; }); this.dataList.forEach((item) => { let firstName = item.pinYin.substring(0, 1).toUpperCase(); let index = words.indexOf(firstName); cityData[index].items.push(item) }); this.list = cityData; }, onItemSelect(item) { this.$cmlEmit(‘onselect’, item); }}mounted() { this.initData();}}export default new Indexlist();编写必要的样式: .index-list-wrapper { width: 750cpx; height: 1200cpx;}以上便使用cube-ui完成了web端indexlist组件的开发,效果如下: weex端组件开发安装weex-uinpm i weex-ui -S在src/components/indexlist/indexlist.weex.cml的json文件中引入weex-ui的wxc-indexlist组件:“base”: { “usingComponents”: { “wex-indexlist”: “weex-ui/packages/wxc-indexlist” } }修改src/components/indexlist/indexlist.weex.cml中的模板代码,引用weex-ui的wxc-indexlist组件: <view class=“index-list-wrapper”> <wex-indexlist :normal-list=“list” @wxcIndexlistItemClicked=“onItemSelect” /> </view>修改src/components/indexlist/indexlist.weex.cml中的js代码:class Indexlist implements IndexlistInterface { props = { dataList: { type: Array, default() { return [] } } } data = { list: [], } mounted() { this.initData(); } methods = { initData() { this.list = this.dataList; }, onItemSelect(e) { this.$cmlEmit(‘onselect’, e.item); } }}export default new Indexlist();编写必要样式,此时发现weex端与web端有部分重复样式,因此将样式抽离出来创建indexlist.less,在web端与weex端的cml文件中引入该样式<style lang=“less”> @import ‘./indexlist.less’;</style> indexlist.less文件内容:.index-list-wrapper { width: 750cpx; height: 1200cpx;}以上便使用weex-ui完成了weex端indexlist组件的开发,效果如下: wx端组件编写根据iview weapp文档, 首先到Github下载iview weapp代码,将dist目录拷贝到项目的src目录下,然后在src/components/indexlist/indexlist.wx.cml的json文件中引入iview的index与index-item组件:“base”: { “usingComponents”: { “i-index”:"/iview/index/index", “i-index-item”: “/iview/index-item/index” }},修改src/components/indexlist/indexlist.wx.cml中的模板代码,引用iview的index与index-item组件: <view class=“index-list-wrapper”> <i-index height=“1200rpx” > <i-index-item wx:for="{{cities}}" wx:for-index=“index” wx:key="{{index}}" wx:for-item=“item” name="{{item.key}}" > <view class=“index-list-item” wx:for="{{item.list}}" wx:for-index=“in” wx:key="{{in}}" wx:for-item=“it” c-bind:tap=“onItemSelect(it)” > <text>{{it.name}}</text> </view> </i-index-item> </i-index> </view>修改src/components/indexlist/indexlist.wx.cml中的js代码, 根据iview weapp文档将数据处理成符合其组件预期的结构, 并向上抛出onselect事件:const words = [“A”,“B”,“C”,“D”,“E”,“F”,“G”,“H”,“I”,“J”,“K”,“L”,“M”,“N”,“O”,“P”,“Q”,“R”,“S”,“T”,“U”,“V”,“W”,“X”,“Y”,“Z”];class Indexlist implements IndexlistInterface { props = { dataList: { type: Array, default() { return [] } } } data = { cities: [] } methods = { initData() { let storeCity = new Array(26); words.forEach((item,index)=>{ storeCity[index] = { key: item, list: [] }; }); this.dataList.forEach((item)=>{ let firstName = item.pinYin.substring(0,1).toUpperCase(); let index = words.indexOf(firstName); storeCity[index].list.push(item); }); this.cities = storeCity; }, onItemSelect(item) { this.$cmlEmit(‘onselect’, item); } } mounted() { this.initData(); }}export default new Indexlist();编写必要样式:@import ‘indexlist.less’;.index-list { &-item { height: 90cpx; padding-left: 20cpx; justify-content: center; border-bottom: 1cpx solid #F7F7F7 }}以上便使用iview weapp完成了wx端indexlist组件的开发, 效果如下: 组件使用修改src/pages/index/index.cml文件里面的json配置,引用创建的indexlist组件"base": { “usingComponents”: { “indexlist”: “/components/indexlist/indexlist” }},修改src/pages/index/index.cml文件中的模板部分,引用创建的indexlist组件 <view class=“page-wrapper”> <indexlist dataList="{{dataList}}" c-bind:onselect=“onItemSelect” /> </view>其中dataList是一个对象数组,表示组件要渲染的数据源。具体结构为:const dataList = [ { name: ‘阿里’, pinYin: ‘ali’, py: ‘al’ }, { name: ‘北京’, pinYin: ‘beijing’, py: ‘bj’ }, ….. ]开发总结根据上述例子可以看出,chameleon项目可以轻松结合第三方库封装自己的跨端组件库。使用第三方组件封装跨端组件库的步骤大致如下:跨端组件设计根据实际需求引入合适的第三方组件根据第三方组件文档,将数据处理成符合预期的结构,并在适当时机抛出事件编写必要样式一些思考理解*.[web|wx|weex].cml根据组件多态文档, 像indexlist.web.cml、indexlist.wx.cml与indexlist.weex.cml的这些文件是灰度区, 它们是唯一可以调用下层端能力的CML文件,这里的下层端能力既包含下层端组件,例如在web端和weex端的.vue文件等;也包含下层端的api,例如微信小程序的wx.pageScrollTo等。这一层的存在是为了调用下层端代码,各端具体的逻辑实现应该在下层来实现, 这种规范的好处是显而易见的: 随着业务复杂度的提升,各个下层端维护的功能逐渐变多,其中通用的部分又可以通过普通cml文件抽离出来被统一调用,这样可以保证差异化部分始终是最小集合,灰度区是存粹的;如果将业务逻辑都放在了灰度区,随着功能复杂度的上升,三端通用功能/组件就无法达到合理的抽象,导致灰度层既有相同功能,又有差异化部分,这显然不是开发者愿意看到的场景。在灰度区的模板、逻辑、样式和json文件中分别具有如下规则:模板调用下层组件时,既可以使用chameleon语法,也可以使用各端原生语法;在灰度区chameleon编译器不会编译各个端原生语法,例如v-for,bindtap等。建议在模板部分仍然使用chameleon模板语法,只有在实现对应平台不支持的语法(例如web端v-html等)时才使用原生语法。引用下层全局组件时需要添加origin-前缀,这样可以“告诉”chameleon编译器是在引用下层的原生组件,chameleon编译器就不会对其进行处理了。这种做法同时解决了组件命名冲突问题,例如在微信小程序端引用<origin-button>表示调用小程序原生的button组件而不是chameleon内置的button组件。逻辑在script逻辑代码中,除了编写普通cml逻辑代码之外,开发者还可以使用下层端的全局变量和任意方法,包括生命周期函数。这种机制保证开发者可以灵活扩展各端特有功能,而不需要依赖多态接口。样式既可以使用cmss语法也可以使用下层端的css语法。json文件*web.cml:base.usingComponents可以引入普通cml组件和任意.vue扩展名组件,路径规则见组件配置。*wx.cml:base.usingComponents可以引入普通cml组件和普通微信小程序组件,路径规则见组件配置。*weex.cml:base.usingComponents可以引入普通cml组件和任意.vue扩展名组件,路径规则见组件配置。在各端对应的灰度区文件中均可以根据上述规范使用各端的原生语法,但是为了规范仍然建议使用chameleon体系的语法规则。总体来说,灰度区可以认为是chameleon体系与各端原生组件/方法的衔接点,向下使用各端功能/组件,向上通过多态协议提供各端统一的调用接口。 ...

March 7, 2019 · 2 min · jiezi

编写chameleon跨端组件的正确姿势(下篇)

在chameleon项目中我们实现一个跨端组件一般有两种思路:使用第三方组件封装与基于chameleon语法统一实现。在《编写chameleon跨端组件的正确姿势(上篇)》中, 我们介绍了如何使用第三方库封装跨端组件,但是绝大多数组件并不需要那样差异化实现,绝大多数情况下我们推荐使用chameleon语法统一实现跨端组件。本篇是编写chameleon跨端组件的正确姿势系列文章的下篇,与上篇给出的示例相同,本篇也以封装一个跨端的indexlist组件为例,首先介绍如何使用chameleon语法统一实现一个跨端组件,然后对比两种组件开发方式并给出开发建议。最终效果以下效果依此为weex端、web端、支付宝小程序端、微信小程序端以及百度小程序端: 开发项目初始化创建一个新项目 cml-democml init project 进入项目cd cml-demo组件创建cml init component选择“普通组件”, 并并输入组件名字“indexlist”, 完成组件的创建, 创建之后的组件位于src/components/indexlist文件夹下。组件设计为了方便说明,本例暂时实现一个具备基础功能的indexlist组件。从功能方面讲,indexlist组件主要由两部分组成,主列表区域和索引区域。在用户点击组件右侧索引时,主列表能够快速定位到对应区域;在用户滑动组件主列表时,右侧索引跟随滑动不停切换当前索引项。从输入输出方面讲,组件至少应该在用户选择某一项时抛出一个onselect事件,传递用户当前所选中项的数据;至少应该接受一个datalist,作为其渲染的数据源,这个datalist应该是一个类似于以下结构的对象数组: const dataList = [ { name: ‘阿里’, pinYin: ‘ali’, }, { name: ‘北京’, pinYin: ‘beijing’, }, ….. ]主要数据结构设计根据设计的组件功能与输入输出, 我们开始设计数据结构。 indexlist组件右侧的索引列对应的数据结构为一个数组,其中的每一项表示一个索引,具体结构如下: this.shortcut = [ ‘A’, ‘B’, ‘C’, ….]indexlist组件的主列表区域对应的数据结构也是一个数组,其中的每一项表示一个子列表区域(例如以首字母a开头的子列表)。下面我们考虑每一个子列表区域中至少应该包含的字段:一个name字段,表示该子列表区域的名称;一个items字段,该字段也是一个数组,数组中的每一项表示该子列表区域的每一项;一个offsetTop, 表示该子列表区域距离主列表顶部的距离,通过该字段实现点击右侧索引时能够通过滚动相应距离快速定位到该子列表;一个totalHeight字段,表示该子列表区域的所占的高度,通过该字段与offsetTop字段可以确定每个子列表所在的高度范围, 以此实现右侧索引跟随滑动不停切换当前索引项由上面分析可得主列表区域数据结构如下: this.list = [ { name: “B”, items:[ { name: “北京”, pinYin: “beijing” }, { name: “包头”, pinYin: “baotou” } … ], offsetTop: 190, totalHeight: 490 }, …. ]功能实现从前文可知,输入组件的datalist具有如下结构: const dataList = [ { name: ‘阿里’, pinYin: ‘ali’, }, { name: ‘北京’, pinYin: ‘beijing’, }, ….. ]可以发现该datalist结构是扁平并且缺乏很多信息(例如totalHeight等)的,因此首先要从输入数据中整理出来所需的数据结构,修改src/components/indexlist/indexlist.cml的js部分: initData() { // get shortcut this.dataList.forEach(item => { if (item.pinYin) { let firstName = item.pinYin.substring(0, 1); if (item.pinYin && this.shortcut.indexOf(firstName.toUpperCase()) === -1) { this.shortcut.push(firstName.toUpperCase()); }; }; }); // handle input data const cityData = this.shortcut.map(item => ({items:[], name: item})); this.dataList.forEach((item) => { let firstName = item.pinYin.substring(0, 1).toUpperCase(); let index = this.shortcut.indexOf(firstName); cityData[index].items.push(item); }); // calculate item offsetTop && totalHeight cityData.forEach((item, index) => { let arr = cityData.slice(0, index); item.totalHeight = this.itemNameHeight + item.items.length * this.itemContentHeight; item.offsetTop = arr.reduce((total, cur) => (total + this.itemNameHeight + cur.items.length * this.itemContentHeight), 0); }); this.list = cityData; },这样我们就拿到了主列表数组this.list与索引列表数组this.shortcut, 然后根据数组结构编写模板内容。模板内容分为两大部分,一个是主列表区域,修改src/components/indexlist/indexlist.cml文件模板部分: <scroller height="{{-1}}" class=“index-list-wrapper” scroll-top="{{offsetTop}}" c-bind:onscroll=“handleScroll” > <view c-for="{{list}}" c-for-item=“listitem” class=“index-list-item” > <view class=“index-list-item-name” style="{{compItemNameHeight}}"> <text class=“index-list-item-name-text”>{{listitem.name}}</text> </view> <view c-for="{{listitem.items}}" c-for-item=“subitem” class=“index-list-item-content” style="{{compItemContentHeight}}" c-bind:tap=“handleSelect(subitem)” > <text class=“index-list-item-content-text”> {{subitem.name}}</text> </view> </view> </scroller>其中scroller是一个chameleon提供的内置滚动组件,其属性值scrolltop表示当前滚动的距离,onscroll表示滚动时触发的事件。在主列表这一部分,我们要实现如下功能:在滚动时,右侧索引不停切换当前索引项的功能点击列表中的每一项时,向外抛出onselect事件修改src/components/indexlist/indexlist.cml文件js部分: handleScroll(e) { let { scrollTop } = e.detail; scrollTop = Math.ceil(scrollTop); this.activeIndex = this.list.findIndex(item => scrollTop >= item.offsetTop && scrollTop < item.totalHeight + item.offsetTop ) }, handleSelect(e) { this.$cmlEmit(‘onselect’, e) }当前激活的索引(this.activeIndex)经过计算得到,规则为:如果当前scroller滚动的距离在对应子列表所在的高度范围内,则认为该索引是激活的。另一部分是索引区域,修改src/components/indexlist/indexlist.cml文件模板部分,增加索引区域模板内容: <view class=“short-cut-wrapper” style="{{compScwStyle}}" > <view c-for="{{shortcut}}" class=“short-cut-item” c-bind:tap=“scrollToItem(item)” > <text class=“short-cut-item-text” style="{{activeIndex === index ? ‘color:orange’ : ‘’}}">{{item}}</text> </view> </view>在索引区域,我们要实现点击索引值主列表能够快速定位到对应区域,修改src/components/indexlist/indexlist.cml文件js部分: scrollToItem(shortcut) { let { offsetTop } = this.list.find(item => item.name === shortcut); this.offsetTop = offsetTop; }索引区域应该定位在视窗右侧并且上下居中。由于chameleon暂时不支持在css中使用百分比,因此我们通过chameleon-api提供的对外接口获取屏幕视窗高度,然后使用js计算得到位置, 配合部分css来实现索引区域定位在视窗右侧居中。修改src/components/indexlist/indexlist.cml文件js部分: // computed compScwStyle() { return top:${this.viewportHeight / 2}cpx } // method async getViewportHeight() { let res = await cml.getSystemInfo(); this.viewportHeight = res.viewportHeight; },至此便通过chameleon语法统一实现了一个跨端indexlist组件,该组件直接可以在web、weex、微信小程序、支付宝小程序与百度小程序五个端运行。为了方便描述,上述代码只是简单介绍了组件实现的核心代码,跳过了样式和一些逻辑细节。组件使用修改src/pages/index/index.cml文件里面的json配置,引用创建的indexlist组件"base": { “usingComponents”: { “indexlist”: “/components/indexlist/indexlist” }},修改src/pages/index/index.cml文件中的模板部分,引用创建的indexlist组件 <view class=“page-wrapper”> <indexlist dataList="{{dataList}}" c-bind:onselect=“onItemSelect” /> </view>其中dataList是一个对象数组,表示组件要渲染的数据源一些思考本篇文章主要介绍了如何通过chameleon语法实现跨端组件。对比编写chameleon跨端组件的正确姿势(上篇).md)介绍的通过第三方库封装的方法可以发现,两种方式是完全不同的,现详细对比一下这两种实现方式的优势与劣势, 并给出开发建议: 优势 劣势 开发建议 基于第三方组件库实现 - 可利用已有生态迅速完成跨端组件 - 组件的实现依赖第三方库,如果没有成熟的对应端第三方库则无法完成该端组件开发 - 由于各端第三方组件存在差异,封装的跨端组件样式与功能存在差异 - 第三方组件升级时,要对应调整跨端组件的实现,维护成本较大 - 第三方组件库质量不能得到保证 - 将基于各端第三方组件封装跨端组件库的方法作为临时方案 - 对于特别复杂并且已有成熟第三方库或者框架能力暂时不支持的组件,可以考虑使用第三方组件封装成对应的跨端组件,例如图表组件、地图组件等等 基于chameleon统一实现 - 新的端接入时,能够直接运行 - 一般情况下,不存在各端样式与功能差异 - 绝大部分组件不需要各端差异化实现,使用chameleon语法实现开发与维护成本更低- 能够导出原生组件供多端使用 - 从零搭建时间与技术成本较高 从长期维护的角度来讲,建议使用chameleon生态来统一实现跨端组件库 如果仅仅是各端api层面的不同,建议使用多态接口抹平差异,而不使用多态组件 ...

March 7, 2019 · 2 min · jiezi

Weex系列(9) —— Weex和安卓升级兼容

目录Weex系列(序) —— 总要知道原生的一点东东(iOS)Weex系列(序) —— 总要知道原生的一点东东(Android)Weex系列(1) —— Hello World项目Weex系列(2) —— 页面跳转和通信Weex系列(3) —— 单页面还是多页面Weex系列(4) —— 老生常谈的三端统一Weex系列(5) —— 封装原生组件和模块Weex系列(6) —— webview和web组件Weex系列(7) —— 踩坑填坑的总总[Weex系列(8) —— 原理流程简析]Weex系列(9) —— Weex和安卓升级兼容最近刚升级,先新开一章记录一下吧,怕忘了(doge)。1、Weex升级相关iOS:pod ‘WeexSDK’, ‘0.20.1’目前来看倒是没有什么问题,后续再说。Android:compile ‘com.taobao.android:weex_sdk:0.20.0.2’Android就有问题喽,一堆如下的问题。方法接口什么的直接移除,真是个狠人啊,这个只能大家一个个文件去改喽,官网链接:https://weex.apache.org/zh/gu…比如我这边是把public GifImage(WXSDKInstance instance, WXDomObject dom, WXVContainer parent) { super(instance, dom, parent); }换成:@Deprecated public GifImage(WXSDKInstance instance, WXVContainer parent, String instanceId, boolean isLazy, BasicComponentData basicComponentData) { this(instance, parent, basicComponentData); } public GifImage(WXSDKInstance instance, WXVContainer parent, BasicComponentData basicComponentData) { super(instance, parent, basicComponentData); }2、安卓升级相关升级之前先来解释3个sdk吧:原文章:https://medium.com/androiddev…compileSdkVersioncompileSdkVersion是告诉Gradle用哪个版本Android SDK编译应用程序。使用新API的时候,就需要升级对应版本的Android SDK了。应该强调的是,更改compileSdkVersion不会改变运行时行为。虽然更改compileSdkVersion时可能存在新的编译器警告/错误,但您的compileSdkVersion不包含在您的APK中:它纯粹在编译时使用。 (你应该确实修复这些警告 - 它们是因为某种原因而添加的!)因此,强烈建议您始终使用最新的SDK进行编译。您将获得对现有代码进行新编译检查的所有好处,避免新弃用的API,并准备好使用新API。minSdkVersion如果compileSdkVersion表示能否能用最新API,那minSdkVersion就是应用能运行的最低版本,如果用户的装置小于这个值,在Google Play商店就会不显示。我在网上搜了一下设置14、15基本就是底线了,能覆盖Google Play商店99.9%的用户吧。targetSdkVersion三者中最有趣的是targetSdkVersion。targetSdkVersion是Android 提供向前兼容的主要依据。比如用户系统是26,现在升级到了27,其中有一个方法26和27是不同的,但是如果应用的targetSdkVersion设置的是26,应用仍旧使用的是26的方法。在升级的过程中主要遇到的就是下面这个错误:在stackoverflow上找的解决办法:https://stackoverflow.com/que…A. Add this line in the defaultConfig section to enable multiDexmultiDexEnabled trueB. Than set the dexOptions, like this:dexOptions { incremental true javaMaxHeapSize “4G”}我这边改完如图:最后还是感谢大家,如果喜欢欢迎点赞收藏啊~ ...

March 6, 2019 · 1 min · jiezi

Weex系列(7) ——踩坑填坑的总总

目录Weex系列(序) —— 总要知道原生的一点东东(iOS)Weex系列(序) —— 总要知道原生的一点东东(Android)Weex系列(1) —— Hello World项目Weex系列(2) —— 页面跳转和通信Weex系列(3) —— 单页面还是多页面Weex系列(4) —— 老生常谈的三端统一Weex系列(5) —— 封装原生组件和模块Weex系列(6) —— webview和web组件Weex系列(7) —— 踩坑填坑的总总[Weex系列(8) —— 原理流程简析]使用weex已经一年半了,踩了很多坑,也流了很多泪填上,总结一波,希望对大家有所帮助。LaunchImage这是今年来的第一个调整,需要把 iOS8.0 and Later勾上,不然iPhone XR/XS Max默认会走iPhone X的尺寸375ptx812pt。build.gradle这个文件设置还挺多的,先说一点吧,比如配置打包信息,是debug还是release版本,这个对微博的分享签名配置是有影响的。image1、必须指定样式中的宽度和高度2、Android 默认的Image Adapter不支持 gif,需要自己封装,我是用的GifDrawable3、安卓图片太大太长,我是在安卓设置了属性hardwareAccelerated,但是内存好像会升高,最好还是避免出现又长又大的图,现在发现出来了个autoBitmapRecycleAndroid大家可以试一下refreshrefresh和pullingdown事件是在这个组件上不是加在list和scroller上,真的刚开始接触的时候,list和scroller用的又多,有一次就犯了这个错误,找了半天,手动dog吧。list和scroller1、尽量不要在list的cell上做处理,比如宽高啊、position定位啊,可能会不生效,还有可能会导致滚动加载不正常2、我遇到过scroller在安卓上下拉刷新不正常,跟初始加载数据,div绘制有关,上来一滚动就下拉刷新,最后是用list解决的,所以建议大家列表还是多用list。slider官网上没有像image那样强调一定要设置宽高,但是还是建议大家给个值,不然有时候会遇到点异常。pickerpicker的pick方法在安卓底下会崩溃,结果竟然是要在AndroidManifest.xml里面设置正确的android:theme,因为我是用官网的脚手架搭起来的项目,不知道大家会不会遇到,改一下android:theme就可以解决问题了。css相关1、Weex对于长度值目前只支持像素值,不支持相对单位(em、rem),也不支持百分比。2、遇到一些奇怪的现象的时候,可以找找是否有position:relative/fixed/absolute,比如slider嵌套list,和slider并列后面用了position:relative的div等,我这边就遇到了加载tab乱跳,还有封装了最外层position:fixed的3、Weex 目前不支持 z-index 设置元素层级关系,但靠后的元素层级更高,因此,对于层级高的元素,可将其排列在后面最后还是感谢大家,如果喜欢欢迎点赞收藏啊~

March 1, 2019 · 1 min · jiezi

开源中国专访:Chameleon原理首发,其它跨多端统一框架都是假的?

开源中国专访:Chameleon原理首发,其它跨多端统一框架都是假的?原创: 嘉宾-张楠 开源中国 以往我们说某一功能跨多端,往往是指在诸如 PC、移动等不同类型的设备之间都能实现;或者更加具体一点,指的是“跨平台”,可能是大到跨操作系统,比如 Windows、macOS、Linux、iOS 与 Android 等,可能是小到跨某个具体技术的不同实现库。但是今天我们要介绍的是关于跨 MVVM 架构模式各种环境的场景。Chameleon 是一套开源跨端解决方案,它的目标是让 MVVM 跨端环境大一统,实现任意使用 MVVM 架构设计的终端,都能使用其进行开发并运行。 在这样一个 MVVM 环境中,涉及到了 Weex、React-Native、WebView/浏览器与 Flutter 等各种跨端技术,还有它们实现的具体业务产品,比如微信小程序、快应用、支付宝小程序、百度智能小程序、今日头条小程序与其它各类小程序。也许你发现了,这里提到了许多种“小程序”,虽然最早微信小程序的概念甚至早期版本出现的时候,有过不少不看好的声音,但是随着它不断发展,目前已经成为了大众生活不可或缺的应用形态。马化腾透露过,截至 2018 年 11 月有 150 万微信小程序开发者,小程序应用数量超过 100 万,覆盖 200 多个细分行业,日活用户达到 2 亿。这样的成功经验与几乎触及到生活方方面面的巨大流量入口,大家都想入场,于是可以看到后来其它公司纷纷给出了类似的小程序方案。另一方面,除了小程序百花齐放,2018 年小米、华为、OPPO 等 10 家安卓手机厂商还结成了快应用联盟,并且先后发布了一系列快应用。Chameleon 目标就是要跨这些端,而随着各家不同实现越来越多,跨端场景也不断变得更加复杂。我们采访了 Chameleon 创始人张楠,请他为读者具体分享了 Chameleon 在这个过程中的成长。项目地址:https://github.com/didi/chame…本文是 Chameleon 首次对外公开实现原理!干货超多,包括:终端开发未来的开发模式Chameleon 跨端实现原理当前各种跨端方案原理对比(各种小程序、快应用等)与 Taro 的对比演进过程中遇到的困难与思考当初为什么去研发 Chameleon?关于这个问题可以从行业背景讲起。中国互联网络信息中心(CNNIC)发布的《中国互联网络发展状况统计报告》显示,截至 2018 年 6 月,我国网民规模达 8.02 亿人,微信月活 10 亿 、支付宝月活 4 亿、百度月活 3.3 亿;另一方面,2018 Q3 中国 Android 手机占智能手机整体的比例超过 80%,月活约 6 亿。BAT 与 Android 成为了中国互联网真正的用户入口。但凡流量高的入口级别 APP 都希望做平台,成为一个生态平台和互联网流量入口,大量第三方应用的接入,从业务层让公司 APP 关联上更多企业的利益,并且拥有更强的生命力;从技术层面可以利用“本地能力接口层”收集大量用户数据,从消费互联网到产业互联网需要大量各行各业基础用户数据线索进行驱动和决策。在这么一种背景下,再结合计算机技术的发展历史,我们知道每一种新技术的出现都会经历“各自为政”的阶段,小程序技术也不例外,所以我们看到了其它各种小程序平台出现。微信小程序作为首创者,虽然其它小程序都有在技术实现原理、接口设计上刻意模仿,但是作为一线开发者在不同平台发布小程序,往往还是需要重复开发、测试,从前 1 单位的工作量变成了 N 单位的工作量。而这还没算上快应用等其它入口。这种情况下,滴滴的研发工程师是其中最显著的“受害者”之一,滴滴出行在微信钱包、支付宝、Android 快应用都有相关入口,而且用户流量占比不低。研发同学在端内既追求 H5 的灵活性,也要追求性能趋近于原生。面对入口扩张,主端、独立端、微信小程序、支付宝小程序、百度小程序、安卓厂商联盟快应用,单一功能在各平台都要重复实现,开发和维护成本成倍增加。迫切需要一个只维护一套代码就可以构建多入口的解决方案,于是我们着手去打造了 Chameleon(CML,卡梅龙)这么一个项目,真正专注于让一套代码运行多端。Chameleon 核心是运用了 MVVM 架构,为什么它可以实现跨多端?MVVM 也就是 Model View ViewModel,它本质上是 MVC( Model View Controller)的进化版本,将 View 的状态和行为抽象化,使得视图 UI 和业务逻辑分开。它是一种让数据驱动反射视图的模式,发展到现在可能会偏离它的初衷了,更像是一个视图数据间的“通信协议”,让终端开发变得更加单纯,这是一种趋势,面向未来框架都采用这种模式。Facebook 在 2013 年开源 React,React 这个项目本身是一个 Web UI 引擎,随着不断发展,它衍生出 React Native 项目,用来编写原生移动应用。正是它给跨端方向带来了 MVVM 模式。Vue.js 于 2014 年左右发布,逆流而上占据了大量用户群体,2016 阿里巴巴也基于它发布了 Weex 项目,使得可以用 Vue 编写 Native App。Google 在 2018 年末正式发布了面向未来的跨 Android、iOS 端的 Flutter 1.0.0。原理我们知道终端开发离不开三大要素——界面表现(结构、外观)层、逻辑处理层与系统接口层(网络、存储与媒体等)。开发者编写代码时在初始化阶段(生命周期)调用“界面表现层”界面模型的接口绘制界面,当用户触摸界面时,“界面表现层”将事件发送给用户“逻辑处理层”,后者经过条件判断再处理并反馈到用户界面,处理过程可能需要调用“系统接口层”,反馈过程需要调用“界面表现层”的接口。常规的终端开发架构模式下,无论是 Web 端、Android 端还是 iOS 端的项目开发,都强依赖各端的环境接口,特别是依赖界面相关模型设计。iOS 系统下绘制界面基于 Objective-C 语言环境下的 UIKit 框架;Android 系统下用户绘制界面基于 Java 语言环境,由 LayoutInflater 处理 XML 结构层次树;Web 端使用 DOM 模型和 CSS 来描述绘制界面。 MVVM 中的关键是它通过 ViewModel 这一层将界面和逻辑层彻底隔离开来,负责关联界面表现和逻辑处理层的响应事件(update/notify)关系,这一“隔离层”上下通信足够规范、足够纯净单一。 Model 进行逻辑处理是纯业务响应逻辑,任何一种语言都可以实现,你可以用 Android 的 Java,也可以用 iOS 的 Objective-C,你心情好用“世界第一语言 PHP”也能实现。之所以普遍选择 JavaScript,很大程度是因为在这个领域内它的优点显著,如学习成本低、天生具备跨端属性、虚拟机(V8、JavaScriptCore)和各方向组件建设较好、生态活跃。而系统接口层则更简单了,只需穷举统一基础接口+可扩展接口能力即可。各种 MVVM 方案具体来看看各种 MVVM 方案都是怎么样的。React Native、Weex 与快应用的 MVVM开发者编写的代码在虚拟机(V8、JavaScriptCore)里面运行,虚拟机容器里面包含扩展的系统基础接口。运行时,将描述界面的数据(主要是 CSS+DSL 所描述内容)通过通信层传递给 Android、iOS 端的渲染引擎,用户触摸界面时,通过通信层传递给虚拟机里面的业务处理代码,业务处理代码可能调用网络、储存与媒体等接口,最后再次反馈到界面。Flutter 的 MVVMFlutter 和 RN 的最大区别在于将“JavascriptCore/V8+JS”替换成“C++ 实现的 engine+Dart 实现的 Framework+静态类型 Dart+编译成机器码”。Flutter 的方案如下图所示:Service 其实就是本地能力接口层,Widget 树是视图层模型。Flutter 和 RN 的使用面设计上类似,Flutter 文档中提到“In Flutter, almost everything is a widget.”,widget 的调用从 RN 的 JSX 变成 Flutter 的 widget 调用,UI 的外观描述从 RN 的 CSS(文本样式、布局模型、盒模型)到定制化 Flutter Widget(textStyle 、Layout Widget、Widget)。本质上 Flutter 也是 MVVM 架构,逻辑层通过 setState 通知视图层更新,一定程度上这也是为什么 Flutter 敢说能转成 Web 框架的原因,核心还是基于这类数据驱动视图架构模式,业务代码不会深度依赖任何一端特有的“视图模型”。各类小程序的 MVVM小程序本质上和 Weex、React Native 的设计思路基本一样,最大区别在于前者还是用浏览器 WebView 做渲染引擎,而后者是单独实现了渲染引擎(所以大量的 CSS 布局模型不支持)。具体到 Chameleon 上是怎么实现的?首先任何一份应用层的高级语言代码块分成几层:语言层(Language)、框架层(Framewrok)与库层(Library):Language —— 通俗来说,实现程序所需的基本逻辑命令:逻辑判断(if)、循环(for)与函数调用(foo())等。Framewrok —— 通俗来说,完成一个 App 应用交互任务所需规范,例如生命周期(onLoad、onShow)、模块化与数据管理等。Library —— 可以理解就是“方法封装集合”。比如 Web 前端中 Vue 更适合叫框架,而 jQuery 更适合叫库;Android 系统下 activity manager + window Manager View System 等的集合叫框架,而 SQLite 、libc 更适合叫库。对应到 Chameleon 就是这样:具体到实现原理全景架构图如下:你可以理解 Chameleon 为了实现“让 MVVM 跨端环境大统一”的目标做了以下工作:定义了标准的 Language(CML DSL)、Framework 与 Library(内置组件和 API)协议层。在线下编译时将 DSL 转译成各端 DSL,只编译 Language 层面足够基础且稳定的代码。在各个端运行时分别实现了 Framework 统一,在各个端尽量使用原有框架,方便利用其生态,这样很多组件可以直接用起来。在各个端运行时分别实现了 Library(内置组件和 API)。为用户提供多态协议,方便扩展以上几方面的内容,触达底层端特殊属性,同时提升可维护性。实现思路很简单,所有设计为了 MVVM 标准化,不做多余设计,所以宏观的角度就像 Node.js(libuv)同时运行在 Windows 和 macOS 系统,都提供了一个跨平台抽象层。从 MVVM 角度来看的话:View(展现层)第三方 Render Engine:各类框架已有框架,浏览器的 Vue、Webview 里的小程序引擎、Android、iOS 里面的 React Native/Weex 引擎、甚至 Flutter 里面的 Dart Framework。Chameleon 内置组件库:多态协议定义统一组件 view、input、text、block 与 cell 等,它是界面组层的原始基类,衍生出多复杂界面功能。ViewModel(关联层)Chameleon 语法转译组件调用循环条件判断事件回调关联父子关系……Model(逻辑响应层)JavaScript 代码CML Runtime 框架Chameleon API:多态协议定义统一接口,cml.request、cml.store 等Chameleon 的跨多端方案给开发者的开发带来了极大的便利,具体表现是怎么样的?一句话:基于 Chameleon 开发,效率会越来越高。各个端的涌现,让原本是 1 的工作量因为多端存在而变成 N 倍,使用 Chameleon,工作量会变回 1.2。这多出来的 0.2 工作量是要处理各端的差异化功能,比如以下场景:某业务线迁入 Chameleon 时,发现没有“passport登录组件”,在各类小程序里面能免密登录了,在 Web、Native 端是弹出登录框登录,不同业务用户交互形态不一样所以 Chameleon 没有提供组件;开发者需要基于多态协议扩展单独一个登录组件<passport/>,无论如何最后返回一个登录后的回调 token 即可,外部无需组件关心里面如何操作。用户需要分享功能,发现没有“share组件”,在微信 Web 端可以引导右上角分享,在小程序直接分享,不同业务用户交互形态不一样,用户需要基于多态协议扩展单独一个登录组件<share/>。这种各端差异较大的例子,随着业务的积累,可以变成了一个个业务组件单独维护,后面也不需要重复开发了,且反推产品体验一致化,组件三层结构“CML框架内置组件->CML扩展组件->业务开发者自己扩展的多态组件”达成 100% 统一。随着组件积累业务开发工作量越来少,工程师可以专注做更加有意义的事情,这就是 Chameleon 存在的目的。基于统一的跨端抽象,用户在 Chameleon 项目持续维护过程中,Chameleon 发布新增一个端之后,你的业务代码基本不用改动即可无缝发布成新端。比如这个 cml-yanxuan 项目开发时支持 3 个端,后面新增了百度、支付宝小程序端,原有代码直接能跑起来运行 5 个端,一端所见即多端所见。开发时只能跑 3 个端原有代码无缝支持 5 个端另外特别强调的是,对于大公司团队,如果有很强的技术能力,希望开发的代码掌控在自己手里,对输出结果有更好控制能力。其实 Chameleon 内置组件和内置 API 是可以替换的,那么所有组件都是业务方自己开发了,哪天不想用了直接导出原生组件即可离开 Chameleon,如下图:目前跨多端统一的方案中,Taro 是比较亮眼的,能否具体对比一下 Chameleon 与 Taro。我们觉得 Chameleon 与其它解决方案的最大区别在于其它框架都是小程序增强,即用 Vue 或者 React 写小程序,这些框架官方给的已接入例子也都是跑微信小程序。它们更加类似 Chameleon 的前身 MPV(Mini Program View),即考虑如何增强小程序开发。2017 年微信小程序发布时,滴滴作为白名单用户首先开始尝试接入,开始面对重复开发的难题。这时候我们专门成立了一个小项目组,完成一个名为 MPV 的项目,一期目标是“不影响用户发挥,不依赖框架方的原则性实现一套代码运行 Web 和微信小程序”。看着很美好,用这样的方案实现 Web 端和小程序端,也确实完成了超过 90% 代码重用,总体上开发效率和测试效率都有了一定提升,但是却不是真正意义上的跨多端统一。单独说到 Chameleon 与 Taro 的区别,总体上看,可以归为这样一个表:表中每一项都是在做跨端方案时需要考虑到的。我们说除了 Chameleon,其它方案都只是在对小程序进行增强,或者说是模仿微信小程序的 API 和组件的接口设计。Taro 是通过将 JSX 转成小程序模板,在其它端模拟微信小程序的接口和组件,让其它端更像微信小程序,业务开发时不一致的地方需要环境变量判断差异分别调用,会造成端差异逻辑和产品逻辑混合在一起。此外,它要跟随小程序更新,业务方会有双重依赖;其它端的和小程序不能保持一致,用户要各种差异化兼容,不利于维护。那 Chameleon 呢?Chameleon 把这些问题都考虑到了,所以在早期伪跨端 MiniProgram View 成型之后不断演进的过程中,把它发展成为一个真正的跨多端方案。前边的表格显示了,Chameleon 既考虑统一性,又考虑差异性,且差异性不会影响可维护性;当各端差异确实太大,那就不要用一套代码实现多个端同一页面,而是统一公用组件。这还只是拿 Chameleon 与 Taro 的重合点进行了对比,但是别忘了 Chameleon 不仅仅是前端框架,它:还有统一的 Chameleon Native SDK,Chameleon 不仅仅希望统一各类小程序,还要覆盖自家 APP,会持续通过 Native SDK 扩展 API 和组件,期望有与小程序一样的本地能力。理想情况下,一套代码就能在各类小程序、自家 APP 里面无缝平滑运行。还有待开源的后台管理系统。还有待开源的 XEdtior 非研发用编辑器,可以直接编辑跨端页面、直接发布。另外,未来还将带来以下能力:后端统一接口(消息推送、分享与支付等)基于统一的 MVVM 标准,更有基于 Flutter 的原生 APP当前的各类小程序和 Native 跨端框架,类似当年多个浏览器时,Safari、Chrome、Firefox、IE 6/7/8/9、Android 浏览器等盛行的时代。以这个来类比,那么 Chameleon 的接口组件设计上更像一个 jQuery。网络请求有的是 XHRHttprequest 有的是 ActiveXObject,jQuery 考虑的是用户需要什么,需要一个网路请求接口,支持 get、post 等,所以 jQuery 写一个既非 ActiveXObject 又非 XHRHttprequest 的名为 $.ajax 接口,提供一个封装网络接口,你不用关心内部在不同端怎么调用的,jQuery 内部会帮你兼容。Chameleon 也是一样的思路,所有的接口设计都是真正能兼容跨所有的端,没有差异性,而且只保留当前所在端的接口调用代码:IE 里面只保留 ActiveXObject,Chrome 只保留 XHRHttprequest。Chameleon 的接口设计上比 jQuery 更强的地方在于,使用标准的多态协议,保障可维护性,性能上只保留当前端代码,且将多态协议暴露出来,让用户也能扩展自己想要的 API(类比 $.xxx)。当然时代已经变了,监听视图不在是 $(’#xxx’).click(fn),而是 MVVM 数据驱动视图方式了,所以提供了 Chameleon 双向绑定这样的 VM 层。前边讲到了 Chameleon 的前身 MPV,那具体分享一下 Chameleon 的整个演进过程吧。出生期:选择转译还是模拟小程序环境?前面讲到,2017 年的时候,我们完成一个名为 MPV 的项目,一期目标是不影响用户发挥,不依赖框架方的原则性实现一套代码运行 Web 和微信小程序。当时缺乏小程序资料是遇到的最大问题(就更别提今天讲到的业内这么多解决方案了),当时唯一一个可以参考的开源项目是 WEPT,WEPT 是一个微信小程序实时开发环境,它的目标是为小程序开发提供高效、稳定、友好、无限制的运行环境。它的设计思路是在 Web 端模仿小程序环境执行。于是我们在开发 MPV 时考虑了两种实现策略:1、在 Web 端像 WEPT 一样 mock 小程序环境;就像微信开发者工具里面也模拟了小程序执行环境,WAServie、WAWebview 提供的两套环境源码做底层,在页面中开启三个独立运行环境运行并用 iframe 通讯模拟微信小程序的 3 个 Webview 之间的联通关系。2、逐个转译代码支持小程序,缺点是可能会有 edge case 需要处理以及潜在的 bug 会比较多。最终在看完 WEPT 源码和微信开发者工具的情况下,我们明确放弃了第 1 条实现策略,选择了逐个转译代码支持小程序的路线,主要原因是于 Web 端兼容微信所有的功能,尺寸过于庞大。经过三个月紧锣密鼓的开发终于实现了第一版本 MPV: 经过实现几个 demo 之后,开始执行迁移计划: MPV 在 Webapp 上实践最终实现效果如下:最终实现效果挺美好,也确实完成了超过 90% 的代码重用,总体上开发效率和测试效率都有了明显提升。但是在后续实践过程中,发现存在大量的问题,并且项目越大问题越凸显出来,总结如下:可维护性问题,没有隔离公用代码和各端差异代码。项目中不止有业务逻辑,还混杂着 Web 端和小程序端产品功能差异化逻辑。比如前边举过的例子,分享功能 Web 端无法实现(引导分享),小程序可以实现,意味着各种环境判断各种差异化逻辑,牵一发动全身,还要来回测试。方向选择错误,MPV 使用了小程序语法标准(小程序的生命周期、API 接口等),导致用户使用上无法清晰理解。不能直接使用各端已有生态组件,即缺乏标准规范接入某个端已有开源组件。比如 Web 端 pick.js 组件缺乏快速接入规范,用户要么重新开发,或者在模板和 js 代码中使用环境判断的方式针对引入。最终导致同一功能不同端的调用方式、输入与输出不一致。业务项目依赖 MPV 框架。框架依赖微信小程序接口(模板、生命周期与接口),扩展了统一接口。例如微信小程序更新了 wx.request 时,业务项目方无法立刻使用,需要等框架更新。文件夹结构混乱,混杂着多个端代码文件,且识别成本高。不支持 vuex、redux 等高效数据管理方式尺寸单位不统一,px 和 rpx 不一致周边小型差异点太多:协议不一致,例如 Web 端可以用 //:www.didiglobal.com/passenger/create ,小程序只能用 https://:www.didiglobal.com/passenger/create打开一个新页面时链接不统一,例如打开发单页时,Web 端是 //:www.didiglobal.com/passenger/create,小程序是 /page/create页面之间跳转时,传参不统一debug 成本高,修改完代码之后两端需要测试两端界面效果不一致,基础内置组件统一性建设不足工程化建设落后,例如不支持 liveroload、数据 mock、资源定位、proxy、多端统一预览接口设计不完整,生命周期、组件分层、本地 API 设计等模板 DSL 语法不规范成长期:从伪统一到大一统在 MPV 的实践积累下,有了一定的底气和把握,后续的规划更加明确。2018 年 4 月我们把跨端项目规模进一步扩大,想要做一个真正跨 N 端的解决方案,目标是提供标准的 MVVM 架构开发模式统一各类终端。这就是 Chameleon 的出现契机。Chameleon 真正想要一套代码运行多端,总结下来要解决几大问题:要全面完成端开发的所有细节的统一性,特别是界面统一性有大量细节要做要在完成上一条的前提下考虑差异化定制空间持续可维护目标理想业务形态是这样的:图中上半部分是传统开发方式,下半部分 Chameleon 的模式抽象出了 UI 渲染层和本地接口能力层,业务代码一部分简单页面由 XEditor(h5Editor 的前身)编辑工具产出,另一部分工程师使用 Chameleon 开发,不止解决跨端问题,还弥补改进了工程开发过程中的效率、质量、性能与稳定性问题,让工程师专注有意义的业务,成长更快。首个 Native 渲染引擎选择——小程序架构、RN/Weex 架构从 MPV 到 Chameleon,外界看来最明显的变化是从跨 2 端(Web、小程序)升级到跨多端(Web、小程序、Android、iOS),最开始纠结于首个端上版本的渲染引擎使用小程序架构还是 RN/Weex 架构。RN/Weex 网上有大量资料可查,但是小程序方面则不然。千辛万苦搜索之后,根据一位知道内情的朋友的描述分享,才有了一定的了解。 这里分享几个印象深刻的要点:小程序展现层使用 Webview,里面内置了一套 JS 框架用来和 Native 通信,真正业务代码执行在单独 JS 虚拟机容器实例中JS 虚拟机容器使用情况,iOS 系统是 JavaScriptCore,Android 系统使用 QQ 浏览器的 X5 内核小程序的各个 TAG 组件使用的数据驱动用的是 Web Components显而易见,部分性能要求较高的使用原生控件(视频、键盘等等)插入到 Webview 里面。原生控件的具体位置 Native 怎么获取?答案是由嵌入到 Webview 的一套小程序框架通知给原生层原生控件怎么保证在内部可滚动的元素(Scroll-view)里面正常滚动?答案是 CSS 设置 -webkit-over-scroll:touch 时,iOS 的实现是原生的 UIScrollView,Native 可以通过一些黑科技找到视图层级中的 UIScrollView,然后对原生控件进行插入和处理;而 Android 直接绘制没办法做到这点。现在(截至 4 月)仅仅是直接覆盖到 Webview 最外层的 scrollview 上,由内置到 Webview 的一套 JS 框架控制原生控件位置最终多方面分析如下:虽然小程序方案看起来很简单,但其实很多细节点需要大量打磨,从确认方案到真正可以跑起来可以线上发布,仅仅花费在终端上的研发人力为 20P*6 个月,微信小程序团队的目标和我们跨端目标不一样,他们投入这么多成本是值得的,我们为了跨端没必要投入这么高成本。所以我们选择放弃小程序渲染方案,而使用已开源的 RN/Weex 方案。第一个版本最终使用 Weex,包括团队同学去看了 Weex 源码实现。在整体设计上仅仅使用 Weex 渲染功能,外层包装接口,保障后续能有更高扩展性。Chameleon Native SDK针对 Native SDK 我们主要从原生能力扩展、性能与稳定等三个方面做了工作。 原生能力扩展:无论是 Webview 还是 React Native、Weex 甚至 Flutter 都只提供渲染能力(以及一些最基础本地接口),更多完成业务功能所需本地环境的能力(例如分享到微信)需要 Android 和 iOS 的 Native 往容器去扩展。本地能力包含 2 种,涉及 UI 界面的统一叫组件(UI 组件如登录、支付),涉及到纯能力调用的统一叫 API(网络、存储等)性能:界面展现和交互耗时关键取决于 2 块,资源加载耗时(非打包到安装包部分代码)、执行耗时稳定:主要关注灰度发布(风险可控)和线上止损,主要工作是按用户灰度发布、可以快速降级到 H5以下是性能方向中的首屏加载时间的优化数据,原有 H5 使用 SSR(Server Side Render)已经算是最快的 Web 首屏技术方案了(不考虑优化后端多模块耗时的 BIGPIPE),它保持在 1.5 秒以下,在优化后降到 0.5 秒左右。 性能优化中我们有一个关于执行速度的 TODO 计划。同样是跨端,Flutter 之所以比 Weex 和 RN 执行速度快,主要原因是前者是编译型,客户端机器运行前已经是 CPU 可识别的机器码;后者是解释型,到客户端运行前是字符串,边编译边执行,虽然做了 JIT 尽量优化,差距还是较大。其实在这中间还有一个抹平了不同 CPU 架构下机器码差异的中间码;当然前提是开发语言改成静态类型,这里不作展开。原本分 5 次开发的 Web 端、支付宝小程序、快应用、微信小程序、Native 端变成了 1.2 次左右开发了。最重要的是随着业务级别各端差异化的多态组件和跨端组件积累,后续 1.2 工作量也会变成 0.8,0.4 的优化主要来自两个方面:0.2 是普通跨端组件的积累,复用度变高0.2 是各类业务级别的差异化多态组件,例如登录功能,在 Web端、Native 端和小程序端实现和交互是不一致的,这时候业务形态不一样,设计的 <passport> 组件也不一样,只能各业务线去封装。介绍一下接下来的 roadmap。我们的最终目标是提供标准的 MVVM 架构开发模式统一各类终端。接下来的具体 roadmap 如下表所示:欢迎有共同愿景的同学加入我们一起共建,往仓库贡献自己的代码。项目地址:https://github.com/didi/chame…QQ 群:公众号:采访嘉宾介绍张楠,Chameleon 创始人,技术团队负责人,前百度资深工程师,终身学习者。 ...

February 26, 2019 · 4 min · jiezi

关于跨平台技术选型的思考

关于跨平台技术选型的思考在我们进行技术架构和技术选型的时候,我们经常犯一个错误就是,试图找一个完美的解决方案即:坑少、功能多。但是,无数次惨痛经历仍然难以记住这个事实,就是,好的架构是需要迭代的。比如我们团队选择vue(weex)而不是react(rn)主要是考虑到了当前情况和团队条件以及应用场景后做了一个艰难的权衡。对于跨平台,本身就不存在完美的方案。无论是全部原生还是weex再或者是rn都有令人心动的地方,但,也都有坑。关键并不是如何找到最好的方案,关键应该是如何驾驭这个方案,比如针对存在的坑,你如何找得到绕过坑的办法。比如。针对weex存在的坑。先不要草率地被网上的对于坑太多的情绪误导而放弃,而将注意力放在它是否能达到我们的目标,我并不关心坑多坑少,我关系的是如何填坑,我的做法是寻找能把坑填上的高手和组织,通过建立weex大前端社群。将国内几个weex领域的大牛联合起来,帮我填坑。RN的生态确实比weex大和成熟,但是对我们不适合,原因不仅仅是因为它更适合用react开发整个app而不是原生+业务模块的场景。而更在于我们已经对vue很熟悉了,不太能承担得起转换国籍的成本。说到底,架构和产品功能一样。也是需要不断迭代的。不管现在选择什么方案,可能随着业务变化和团队发展,后续都要根据情况调整甚至推到重来。要综合考虑现实人员、成本、业务需求。没有最优的,只有相对可行的方案。只要达到企业目标,实现了企业价值,就是好的架构。对于跨平台前端的未来,现在不管采用什么技术,或许都是临时的措施。未来或许是属于flutter的。但是目前,我仍然选择weex。weex坑很多,但是我已经找到了填坑的办法。因此对于我来说,就是好的架构方案。当团队能力达到了新的高度。如果经费充裕,甚至可以回到原生开发。或者迁移到RN。或者使用前卫一点的flutter。在考虑跨平台的时候不要忘了跨平台的目的。

February 26, 2019 · 1 min · jiezi

全球的weex资源都在这里

WeeX FAQQQ:Weex大前端 516682889Weexbox: 943913583WeeX相关资源weex官方资源weex官网Weex Market 已挂 : 一个提供 Weex 第三方组件的网站,您可以在这里找到你需要的 Weex 组件。Playground : Playground在线,直接在线编写代码并预览效果weex-toolkit : Weex 官方提供了 weex-toolkit 的脚手架工具来辅助开发和调试weex-ui官网weex团队的github主页Weex 中文聊天室轻舟(BOAT) 已挂 : 基于 Weex 技术快速开发跨平台 App 的一站式解决方案segmentfault weex专栏stackoverflow weex专栏weex jiraWeex增强框架WeexBox : WeexBox 致力于打造一套简单、高效的基于 weex 的APP混合开发解决方案。合摩 WeexBox 正式发布WeexBox 1.2.0 新增 Lottie 动画,妈妈再也不用担心我加班写动画了!weexplus侵入型较低用weexplus从0到1写一个app(1)-环境搭建和首页编写bmfe/eroseros 不是框架,是基于 weex 封装、面向前端的 vue 写法的一整套 APP 开源解决方案,是由本木医疗大前端团队经过大量实践沉淀而出。那些年没错过的弯路,WEEX-EROS 开源一年记weex eros框架源码解析教程:EROS集成到现有iOS应用Weex 第三方UI组件Weex UI :阿里巴巴的weex UIWeex AMUIbui-weex :专门为 Weex 前端开发者打造的一套高质量UI框架natjs 轻松为你构建的 Weex 应用加入原生功能hbteam/weex-droplet-uiweex实例官方提供的examples(weex代码中也有同样的例子)在线体验weex playground app : weex源码自带的demo[Weex集成到现有app的示例]Weex与swift集成的示例https://github.com/apache/inc…https://github.com/apache/inc...Weex开发的网易严选App(原始版zwwill版)Weex开发的网易严选App 使用Eros跨平台开发框架实现的严选功能Weex开发的网易严选App 这个是另一个版本,用eros框架Weex版本跨平台的开源Github客户端App : 有很多资源WEEX仿咸鱼appWeexListA complete Demo about Weex, including Custom UI, Network, Events, and the Drop-Down Refresh is Achieved By two ways.mpvue-weex一套 Vue 代码,五端可用(H5、小程序、PC、苹果App、安卓App),使用mpvue实现小程序,weex打包APP。系统含50+页面,30+组件(5端通用),170+元件(每个终端独立完成)weexext/weex-toutiao weex仿今日头条 Weex [今日头条]客户端 weexdemoHuahua-Chen/toutiao_Vue2.0 基于Vue2.0全家桶的低配版今日头条MasonLiuChn/WeexExplorer: Web端做SPA,Native端做多页应用并使用Vuexweex相关文章awesome-weex : 资料大全Weex从入门到超神系列[头条祁同伟系列文章]Weex系列(一)之Weex入门准备Weex系列(二)之列表页实战Weex系列(三)之列表页实战冲突解决深入Weex系列(四)之Module组件源码解析深入Weex系列(五)之Component组件源码解析深入Weex系列(六)之Weex渲染流程分析深入Weex系列(七)之Adapter组件源码解析深入Weex系列(八)之Weex SDK架构分析深入Weex系列(九)Weex SDK可借鉴细节总结深入Weex系列(十)Weex SDK可优化细节思考深入Weex系列(十一)使用Weex构建一个完整App的思考木羽系列【入门】WEEX快速创建工程 Hello World【Weex】网易严选 App 感受 Weex 开发记一次 Weex 的 iPhone X 适配【Weex】纯 Weex 开发一个小游戏Weex BindingX 尝鲜 使用BindingX开发客户端炫酷动画使用 Weex 和 Vue 开发原生应用教程weex官方入门教程weex 学习/实践指南Weex 开发教程集成Swift 中的 weex【iOS 开发】集成 Weex 注意事项Weex 开发小游戏是件很 high 的事儿一起来玩WeexiOS開發之Weex爬坑之路環境部署和Devtools Debug(一)进阶教程技术文章https://github.com/joggerplus…网易严选 App 感受 Weex 开发 Weex实战分享|企鹅电竞Weex实践和性能优化[Weex实战分享|Weex在极客时间APP中的实践](https://mp.weixin.qq.com/s/at...Weex BindingX 尝鲜拥抱大前端——从Weex开始深度文章深入了解 Weexweex页面传参Halfrost-Field官网 Halfrost-Field 冰霜之地:的Weex 源码解析系列 :Weex 是如何在 iOS 客户端上跑起来的由 FlexBox 算法强力驱动的 Weex 布局引擎 Weex 事件传递的那些事儿Weex 中别具匠心的 JS Framework iOS 开发者的 Weex 伪最佳实践指北Weex与原生页面间的相互跳转应用实践尚妆达人店 UI 组件化 工程实践(weex vue)Weex在达人店的一年实践Weex 技术在苏宁移动办公开发中的实践Weex避坑指南-理论篇基于weex的考拉移动端动态化方案Weex避坑指南-理论篇51信用卡 Android 架构演进实践网易严选App感受Weex开发(已完结)基于weex的有赞无线开发框架应用实践-企鹅电竞系列企鹅电竞weex实践——UI开发篇Weex实战分享|企鹅电竞Weex实践和性能优化企鹅电竞动效揭秘精准微动效解决之道-LottieWeeX相关pptweex conf2018大会资料Weex社区过去、现在和未来Weex技术演进Weex在盛大游戏中的应用实践Weex + UIWeex在极客时间App中的实践企鹅电竞Weex实践和性能优化howto用Swift写WeexDemo用Swift写WeexDemo 2用Swift写WeexDemo 3SimpleSwiftWeexDemo github上的配套源码知识树重点关注的知识点页面传参Native-JS通信JS 调用NativeNative调用JS屏幕适配配置下发降级集成到app (Native 接入)在Android方面,我们把weex的接入放入了自定义的WeexFragment。另外,新建WeexActivity,引用WeexFragment。这样使用起来更灵活。在iOS方面,我们把weex的接入放入了自定义的WeexViewController。分辨率和尺寸Weex 对于长度值目前只支持像素 px 值,还不支持相对单位(em、rem),需要 pt 和 px 的换算在 Weex 中定义的默认的屏幕宽度是750通信native 到 Weex js 通信Native 产生的一些事件,是怎么传回给 JSWeex 的事件传递一是 Module 模块的 callback,二是通过 Component 组件自定义的通知事件Weex js 到 native 之间的通信Weex js 之间的通信Web 到 Weex js 通信event轻扫事件长按事件拖动事件通用触摸事件Appear 事件Disappear 事件Page 事件生命周期事件工程化devops如何构建发布流程weex页面里,如何调用native的网络模块获取到数据多页面的跳转调试工具weex devtool的使用方法利用Weex DevTool调试Native应用-iOS篇如何解除App组件之间和App页面之间的耦合性?1.页面降级性能监控和埋点增量更新和全量更新首屏加载时间极致优化RouterApp之间跳转实现自家的一系列App之间如何相互跳转?从外部跳转到App内部一个很深层次的一个界面。如何在App任何界面都可以调用同一个界面或者同一个组件?如何能统一iOS和Android两端的页面跳转逻辑?甚至如何能统一三端的请求资源的方式?组件如何拆分?Native 产生的一些事件,是怎么传回给 JSWeex中js和Native的交互-iOS篇使用场景集成 Weex 到已有应用用weex实现活动模块原生的iOS工程局部页面嵌入weex的view如何在H5中使用扩展Module 扩展 非 UI 的特定功能。Component(UI)Adapterhandler扩展 Android 的功能扩展 iOS 的功能使用 swift 扩展 iOS 的功能扩展 HTML5 的功能扩展 Web 渲染器weex-vue-render《拓展JS framework》最佳实践需要优化的内容自定义网络adapter / handler,可以针对网络请求进行拦截修改挂接自己的 http 适配器,适度复用 Native 的 http 实现并定制到 weex 中,同时这也是打通 native 登陆凭证和 weex 登陆凭证的必要一环。wb-network [Weexbox]自定义图片适配器(adapter / handler),可以对图片进行压缩和缓存处理UI增强:confirm、toast、alert路由:自定义路由,跳转规则自定义a标签component 拦截url进行跳转weex native webview 无缝跳转相对地址 热更新 & 预加载weex-JS页面 提高渲染速度为了提升渲染效率,我们会提前把js文件下载到本地,使用时直接加载本地文件静态资源的缓冲和缓冲更新策略配置SonicRuntime VasSonic是腾讯开源的一套完整的Hybrid方案,Github地址: VasSonic,官方定义是一套轻量级和高性能的Hybrid框架,专注于提升H5首屏加载速度。预加载: 静态资源离线预推动态缓存:storage module增强wb-storage [Weexbox]页面局部刷新工程化脚手架: 对于公司接入来说我们不太会使用官方提供的脚手架工具,一般都是自己实现webpack建立组件库错误监控参考: 「大前端」Weex在达人店的一年实践A: 官方文档是最好的入门Q: 运行weex github上的代码报错:CMake Error: CMAKE_C_COMPILER not set, after EnableLanguageCMake Error: CMAKE_CXX_COMPILER not set, after EnableLanguage设置环境变量(mac下 ~/.bash_profile)export ANDROID_HOME=$HOME/Library/Android/sdkexport ANDROID_NDK=$HOME/Library/Android/sdk/ndk-bundlesource ~/.bash_profile同时修改weex_sdk/build.gradle 去掉armeabi(armeabi和mips已经不被cmake支持了)ndk { abiFilters “armeabi-v7a”,“x86” }Q: 如何从零开始,创建weex标准项目第一步:准备开发环境# 请确保你已经安装了 Node.js,然后全局安装 weex-toolkit。npm install weex-toolkit -g如果要添加ios或android支持,需要安装xcode或android studio创建一个空的Weex + Vue.js 模板项目:weex create awesome-app运行项目cd awesome-appnpm installnpm start或weex run web然后工具会启动一个本地的 web 服务,监听 8081 端口。你可以打开 http://localhost:8081 查看页面在 Web 下的渲染效果。 源代码在 src/ 目录中,你可以像一个普通的 Vue.js 项目一样来开发.添加特定ios或android支持默认情况下 weex create 命令并不初始化 iOS 和 Android 项目,需要执行以下命令:安装相关组件weex platform add iosweex platform add android在模拟器或真实设备上启动应用weex run iosweex run androidweex run webQ: 如何在自己的app项目中d导入weex官方正式发布的Weex SDK(集成 Weex 到已有应用)WEEX 会在jcenter 定期发布稳定版本。jcenter注:国内可能需要翻墙android集成:修改build.gradle 加入如下基础依赖(版本请替换为最新版)compile ‘com.android.support:recyclerview-v7:23.1.1’compile ‘com.android.support:support-v4:23.1.1’compile ‘com.android.support:appcompat-v7:23.1.1’compile ‘com.alibaba:fastjson:1.1.46.android’compile ‘com.taobao.android:weex_sdk:0.5.1@aar’iOS集成在 Podfile 文件中添加如下内容pod ‘WeexSDK’, ‘0.17.0’ ## 建议使用WeexSDK新版本pod installQ: 如何在自己的app项目中将Weex SDK源码作为模块导入项目中(集成 Weex 到已有应用)一般来说,直接使用官方正式发布的week-sdk库(android通过jcenter引入sdk,ios通过cocoaspod引入sdk),只有希望使用最新的weex功能时才需要将sdk源码导入到自己的app中(作为模块和app一起编译)。能够快速使用WEEX最新功能,可以根据自己项目的特性进行相关改进。Andorid通过以下步骤import SDK下载源码 git clone https://github.com/apache/incubator-weex.git创建 Android 工程。通过以下路径引入 SDK Module:File->New-Import Module-> 选择 WEEX SDK Module(weex/android/sdk) -> Finish设置app 的 build.gradle,添加如下依赖:compile project(’:weex_sdk’)iOS通过以下步骤import SDK自己编译好SDK后,将sdk(framework文件)导入自己app工程:,参考官方文档:https://weex.apache.org/cn/guide/integrate-to-your-app.htmlQ: 如何自己编译week SDK一般来说,推荐使用官方正式发布的week-sdk库(android通过jcenter引入sdk,ios通过cocoaspod引入sdk),只有希望使用最新的weex功能时才需要自己手工编译SDK编译sdk方法有两个,一种是用提供的编译脚本进行自动编译(请参考代码库中的HOW-TO-BUILD.md),一种是用android studio或xcode手工编译Andorid通过以下步骤编译并生成 SDK下载源码 git clone https://github.com/apache/incubator-weex.git在Android Studio中打开Weex SDK选择打开已存在的andorid项目:(weex/android/sdk) 选中代码目录中的weex_sdk模块Build -> make moudle weexsdk编译后的sdk:weex_sdk-debug.aar(位于weex/android/sdk/build/outputs/aar/) iOS通过以下步骤编译 SDKgit clone https://github.com/apache/incubator-weex.git打开 WeexSDK.xcodeproj in weex/ios/sdk切换到WeexSDK_MTL target编译当前target,可以直接用快捷键 ⌘ + b最后找到产物在 weex/ios/sdk/Products 目录Q: 能否将vue项目结构转换成weex项目?Q: Weex中的组件(component),模块(module),扩展,三者有何区别?组件一般指的是UI组件,module相当月插件,提供一些工具方法。扩展指的是开发组件的过程Q: 如何编译weex的playground app?直接用android studio打开 android/palyground项目即可Q: 解决iOS WeexSDK与WechatSDK中枚举WXLogLevel命名冲突https://bmfe.github.io/2018/0…工程化将 components 抽离出来,放到内部私有 npm 仓库中以 npm 包的形式去维护。也就是我们将 spon-ui(内部组件库名称)作为单独的一个项目去维护,加以约束形成组件库开发规范作者:尚妆产品技术刊读链接:https://juejin.im/post/5a2d3d…WeexBox FAQQ: 如果报错说lotties找不到某些文件,可能是需要升级cocaspods到1.6.0could not build Objective-C module ‘Lottiesudo gem install cocoapodsQ: ERROR: Failed to download Chromium r624492! Set “PUPPETEER_SKIP_CHROMIUM_DOWNLOAD” env variable to skip download. ...

February 22, 2019 · 3 min · jiezi

WeexBox 给你最好的图片加载方式

在讲WeexBox之前,我们先来看看Weex是如何做图片加载的。Weex提供了<image>来加载图片,更具体的说,<image>有3种使用方式。src=Base64谁会把Base64硬写到源码里?src=http那我是不是要先把图片部署到服务器,再把这个图片的地址拷贝来用,麻烦不麻烦?src=相对路径相对路径是相对bundle URL的,相对路径将被重写为绝对资源路径(本地或远程)。但是我在开发的时候,我更清楚的知道图片相对源码的路径而不是最终bundle的路径。综上,weex的这3种方式用着都很别扭。抛开weex的限制,我就问你开发中使用图片最舒服的方式是什么。答案当然是:卧槽用file-loader啊!如果把file-loader集成进weex项目,在项目里用file-loader的方式引用图片,跑weex debug肯定是会报错的,不信的童鞋可以试试。于是WeexBox提供了@weexbox/debugger和@weexbox/builder。它们不但能让你开心的用file-loader,还提供了一些便利的功能。假设你已经用@weexbox/cli初始化好了项目,并且会使用file-loader。使用图片大概是这样的。(点击查看完整例子)<template> <div> <image :src=“logo” class=“logo” /> </div></template><script>import logo from ‘../../../static/logo.png’export default { data() { return { logo, } },}</script>这种使用图片的方式是不是既熟悉又怀念。重点来了,打开config/update-config.json文件,你能看到形如下面的配置。const config = { develop: { // 从本机加载图片,只有在调试的时候有效 imagePublicPath: null, }, test: { // 从网络加载图片 imagePublicPath: ‘https://raw.githubusercontent.com/aygtech/weexbox-template/master/deploy', }, preRelease: { // 从app加载图片 imagePublicPath: ‘bundle://’, }, release: { // 从网络加载图片 imagePublicPath: ‘https://raw.githubusercontent.com/aygtech/weexbox-template/master/deploy', },}module.exports = config当imagePublicPath为null时,weexbox会把static部署到本机。 当imagePublicPath为http时,需要你自己把deploy部署到服务器,图片地址即是部署的地址。 当imagePublicPath为bundle://时,weexbox会自动拷贝static到app中。于是乎,调试的时候跑npm run debug xxx/App.vue的时候,本机图片可以正常显示了。部署的时候,任君挑选是要部署到服务器还是app中。由此可见,切换图片源不用你改动任何一行业务代码,weexbox全部帮你搞定了。

February 22, 2019 · 1 min · jiezi

小白学Weex(一) —— 环境搭建

Weex的环境搭建非常的麻烦,很多人可能在这一步就放弃了,包括我最开始想自学的时候。因为需要一些科学上网的方法才能保证环境正确搭建,同时还需要非常大的耐心,以下搭建方法建立在win10的基础上,其他系统环境可能会出现未知的问题。安卓环境(win10)1. Node环境node安装过程就不说了,会看这篇文章的人应该都会。检测:node -vnpm -v2. Java环境这一步比较复杂,因为前端程序员估计会比较少接触到这个环境,但是网上有很多教程,可自行搜索:JDK安装教程。检测:java -version3. Git这个不是必须的,但是一般搞开发的应该都会用吧,就不细说了。检测:git –version4. weex-toolkit这是一个weex的脚手架命令行工具,安装方法:npm install weex-toolkit -g检测:weex -v5. webpack这一步我不确定是否必须,以防万一装上。安装方法:npm install webpack webpack-cli -g检测:webpack -v6. android studio下载android studio,安装。安装完成后会自动接着安装android-sdk,但这一步不知是时间太久还是安装失败,我没有使用android studio自带的sdk,而是另外单独下载,有耐心的可以试一试。7. android-sdk下载SDK-Tools,注意安装地址中不能带有空格,否则会产生错误。之后的安装中必须科学上网,否则无法安装成功。安装方法参考android SDK安装以及环境变量配置(windows)。检测:adb到此全部环境就装好啦!

February 12, 2019 · 1 min · jiezi

Weex开发之mask

mask弹层示例在移动开发中,我们经常会做一些弹框相关的东西,在Weex跨平台框架中,实现mask效果也比较简单。下面是示例效果:以下是示例代码:<template> <div> <div class=“comment-btn” @click=“showAd()"> <text class=“comment-text”>广告</text> </div> <div class=“comment-btn” @click=“showStar()"> <text class=“comment-text”>评论</text> </div> <!–广告弹框–> <div class=“mask” v-if=“show”> <wxc-mask height=“700” width=“560” border-radius=“30” duration=“300” :has-overlay=“true” :show-close=“true” :show=“show” :has-animation=“hasAnimation” @wxcMaskSetHidden=“wxcMaskSetHidden”> <div> <image class=“mask-image” resize=“cover” src=“bmlocal://assets/home_mask.png”></image> </div> </wxc-mask> </div> <!–评分组件–> <div class=“mask” v-if=“showMask”> <div class=“mask-dialog”> <div class=“star-group”> <div class=“mr5” @click=“setScore(1)"><text class=“iconfont f20” :class="[score>0 ?‘star-on’:‘star-off’]">&#xec1e;</text></div> <div class=“mr5” @click=“setScore(2)"><text class=“iconfont f20” :class="[score>1?‘star-on’:‘star-off’]">&#xec1e;</text></div> <div class=“mr5” @click=“setScore(3)"><text class=“iconfont f20” :class="[score>2?‘star-on’:‘star-off’]">&#xec1e;</text></div> <div class=“mr5” @click=“setScore(4)"><text class=“iconfont f20” :class="[score>3?‘star-on’:‘star-off’]">&#xec1e;</text></div> <div @click=“setScore(5)"><text class=“iconfont f20” :class="[score>4?‘star-on’:‘star-off’]">&#xec1e;</text></div> </div> <div class=“grade-box”> <text class=“grade-txt”>差</text> <text class=“grade-txt”>一般</text> <text class=“grade-txt”>很好</text> </div> <textarea class=“textarea” rows=“5” placeholder=“发布留言” v-model=“message”></textarea> <div class=“mask-btngroup”> <div class=“mask-btngroup-item” @click=“cancel()"> <text class=“mask-item-txt”>取消</text> </div> <div class=“line”></div> <div class=“mask-btngroup-item” @click=“confirm()"> <text class=“mask-item-txt”>确认</text> </div> </div> </div> </div> </div></template><script> import {WxcMask} from ‘weex-ui’; let domModule = weex.requireModule(‘dom’) export default { components: {‘wxc-mask’: WxcMask}, data: () => ({ show: false, showMask: false, score:5, hasAnimation: true }), methods: { created() { this.initIconFont(); }, initIconFont() { domModule.addRule(‘fontFace’, { ‘fontFamily’: ‘iconfont’, ‘src’: “url(‘http://at.alicdn.com/t/font_1028673_vs1slhfmpy.ttf')" }) }, setScore(v){ if(v===this.score){ this.score = v-1; }else{ this.score = v; } }, wxcMaskSetHidden() { this.show = false; }, showAd(){ this.show = true; }, showStar(){ this.showMask = true; }, cancel(){ this.showMask = false; }, confirm(){ this.showMask = false; }, } };</script><style scoped> .iconfont { font-family: iconfont; } .wrapper { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: #e0000000; } .comment-btn { background-color: #419DFB; width: 176px; height: 60px; margin-top: 40px; align-self: center; justify-content: center; border-radius: 8px; } .comment-text { font-size: 28px; color: #ffffff; text-align: center; } .mask { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, .4); align-items: center; justify-content: center; flex: 1; } .mask-image { height: 700px; width: 560px; align-items: center; border-radius: 30px; } .mask-dialog { width: 540px; border-radius: 28px; background-color: #eeeeee; align-items: center; padding-top: 30px; } .star-on { color: #ffd900; } .star-off { color: #cfd9e6; } .grade-box { width: 230px; flex-direction: row; justify-content: space-between; margin-bottom: 30px; } .grade-txt { font-size: 28px; color: #5c7799; } .star-group{ flex-direction: row; height: 30px; width: 480px; margin-bottom:10px; justify-content: center; } .textarea { width: 480px; padding-top: 10px; padding-bottom: 10px; padding-left: 10px; padding-right: 10px; color: #5c7799; placeholder-color: #a4b6cc; font-size: 28px; background-color: #fff; } .mask-btngroup { width: 540px; height: 88px; flex-direction: row; border-top-width: 1px; border-top-color: #cfd9e6; margin-top: 30px; } .mask-btngroup-item { height: 88px; flex: 1; justify-content: center; align-items: center; border-radius: 100px; } .mask-item-txt{ font-size: 32px; color: #419dfb; } .line { height: 88px; width: 1px; background-color: #cfd9e6; } .mr5 { margin-right: 10px; } .bold { font-weight: bold; }</style>eros-yanxuan简介eros-yanxuan 是基于 eros 开发的Weex项目,部分页面参考了项目网易严选 weex 版本,欢迎star或fork。eros 文档eros github运行确保你本地已经集成了 eros 开发所需的环境。clone 项目到本地:$ git clone https://github.com/xiangzhihong/eros-yanxuan.git进入目录,下载前端所需的依赖:$ cd eros-yanxuan$ npm installiOS SDK打开platforms目录下的WeexEros项目,在WeexEros中使用pod添加依赖。$ cd platforms/ios/WeexEros$ pod update // 下载 iOS 依赖$ open WeexEros.xcworkspace // 自动打开项目选中模拟器,点击绿色箭头运行 app 即可。Android对于Android工程来说,使用Android Studio打开platforms目录下的WeexFrameworkWrapper的Android工程,然后使用install.sh安装Android工程的需要依赖包nexus和wxframework。具体可以参考自行导入项目,便可运行起来。运行项目根目录下运行 eros dev关闭调试,拦截器,打开热更新重新 build app效果Question运行过程中出现问题在以下地址解决方法,如果没有找到,可以参考eros快速入门新建一个Weex工程,然后将src和配置文件的代码拷贝过去。 如果还有问题,请加群:515980159移动跨平台技术总结Weex快速上手eros快速入门eros issueeros Q&A ...

February 1, 2019 · 3 min · jiezi

移动跨平台技术方案总结

“得移动端者得天下”,移动端取代PC端,成为了互联网行业最大的流量分发入口,因此不少公司制定了“移动优先”的发展策略。为了帮助读者更好地学习WEEX,本节将对React Native、Weex和Flutter等主流的跨平台方案进行简单的介绍和对比。React NativeReact Native (简称RN)是Facebook于2015年4月开源的跨平台移动应用开发框架,是Facebook早先开源的React框架在原生移动应用平台的衍生产物,目前主要支持iOS和安卓两大平台。RN使用Javascript语言来开发移动应用,但UI渲染、网络请求等均由原生端实现。具体来说,开发者编写的Javascript代码,通过中间层转化为原生控件后再执行,因此熟悉Web前端开发的技术人员只需很少的学习就可以进入移动应用开发领域,并可以在不牺牲用户体验的前提下提高开发效率。作为一个跨平台技术框架,RN从上到下可以分为Javascript层、C++层和Native层。其中,C++层主要用于实现动态连结库(.so),作为中间适配层桥接,实现js端与原生端的双向通信交互,如下图所示是RN在Android平台上的通信原理图。在RN的三层架构中,最核心的就是中间的C++层,C++层最核心的功能就是封装JavaScriptCore,用于执行对js的解析。同时,原生端提供的各种Native Module(如网络请求,ViewGroup控件模块)和JS端提供的各种JS Module(如JS EventEmiter模块)都会在C++实现的so文件中保存起来,最终通过C++层中的保存的映射实现两端的交互。在RN开发过程中,大多数情况下开发人员并不需要需要了解RN框架的具体细节,只需要专注JS端的逻辑代码实现即可。但是需要注意的是,由于js代码是运行在独立的JS线程中,所以在js中不能处理耗时的操作,如fetch、图片加载和数据持久化等操作。最终,JS代码会被打包成一个bundle文件并自动添加到应用程序的资源目录下,而应用程序最终加载的也是打包后的bundle文件。RN的打包脚本位于“/node_modules/react-native/local-cli”目录下,打包后通过metro模块压缩成bundle文件,而bundle文件只包含打包js的代码,并不包含图片、多媒体等静态资源,而打包后的静态资源会是被拷贝到对应的平台资源文件夹中。总的来说,RN使用Javascript来编写应用程序,然后调用原生组件执行页面渲染操作,在提高了开发效率的同时又保留了Native的用户体验。并且,伴随着Facebook重构RN工作的完成,RN也将变得更快、更轻量、性能更好。Weex作为一套前端跨平台技术框架,Weex建立了一套源码转换以及Native与Js通信的机制。Weex表面上是一个客户端框架,但实际上它串联起了从本地开发、云端部署到分发的整个链路。具体来说,在开发阶段编写一个.we文件,然后使用Weex提供的weex-toolkit转换工具将.we文件转换为JS bundle,并将生成的JS bundle上传部署到云端,最后通过网络请求或预下发的方式加载至用户的移动应用客户端。当集成了Weex SDK的客户端接收到JS bundle文件后,调用本地的JavaScript引擎执行环境执行相应的JS bundle,并将执行过程中产生的各种命令发送到native端进行界面渲染、数据存储、网络通信以及用户交互响应。由上图可知,Weex框架中最核心的部分就是JavaScript Runtime。具体来说,当需要执行渲染操作时,在iOS环境下选择基于JavaScriptCore内核的iOS系统提供的JSContext,在Android环境下使用基于JavaScriptCore内核的JavaScript引擎。当JS bundle从服务器下载完成之后,Weex的Android、iOS和H5会运行一个JavaScript引擎来执行JS bundle,同时向各终端的渲染层发送渲染指令,并调度客户端的渲染引擎实现视图渲染、事件绑定和处理用户交互等操作。由于Android、iOS和H5等终端最终使用的是native渲染引擎,也就是说使用同一套代码在不同终端上展示的样式是相同的,并且Weex使用native引擎渲染的是native组件,所以在性能上比传统的WebView方案要好很多。当然,尽管Weex已经提供了开发者所需要的最常用的组件和模块,但面对丰富多样的移动应用研发需求,这些常用基础组件还是远远不能满足开发的需要,因此Weex提供了灵活自由的扩展能力,开发者可以根据自身的情况定制属于自己客户端的组件和模块,从而丰富Weex生态。FlutterFlutter是Google开源的移动跨平台框架,其历史最早可以追溯到2015年的Sky项目,该项目可以同时运行在Android、iOS和fuchsia等包含Dart虚拟机的平台上,并且性能无限接近原生。相较于RN和Weex使用Javascript作为编程语言与使用平台自身引擎渲染界面不同,Flutter直接选择2D绘图引擎库skia来渲染界面。如上图所示,Flutter框架主要由Framework和Engine层组成,而我们基于Framework开发App最终会运行在Engine上。其中,Engine是Flutter提供的独立虚拟机,正是由于它的存在Flutter程序才能运行在不同的平台上,实现跨平台运行的能力。与RN和Weex使用原生控件渲染界面不同,Flutter并不需要使用原生控件来渲染界面,而是使用Engine来绘制Widget(Flutter显示单元),并且Dart代码会通过AOT编译为平台的原生代码,实现与平台的直接通信,不需要JS引擎的桥接,也不需要原生平台的Dalvik虚拟机,如图1-5所示。同时,Flutter的Widget采用现代响应式框架来构建,而Widget是不可变的,仅支持一帧,并且每一帧上的内容不能直接更新,需要通过Widget的状态来间接更新。在Flutter中,无状态和有状态Widget的核心特性是相同的,视图的每一帧Flutter都会重新构建,通过State对象Flutter就可以跨帧存储状态数据并恢复它。总的来说,Flutter是目前跨平台开发中最好的方案,它以一套代码即可生成Android和iOS平台两种应用,很大程度上减少了App开发和维护的成本,同时Dart语言强大的性能表现和丰富的特性,也使得跨平台开发变得更加便利。而不足的是,Flutter还处于Alpha阶段,许多功能还不是特别完善,而全新的Dart语言也带来了学习上的成本,如果想要完全替代Android和iOS开发还有比较长的路要走。PWAPWA,全称Progressive Web App,是Google在2015年提出渐进式的网页技术。PWA结合了一系列的现代Web技术,并使用多种技术来增强Web App的功能,最终可以让网页应用呈现和原生应用相似的体验。相比于传统的网页技术,渐进式Web技术是可以横跨Web技术及Native APP开发的技术解决方案,具有可靠、快速且可参与等诸多特点。具体来说,当用户从手机主屏幕启动时,不用考虑网络的状态就可以立刻加载出PWA。并且,相比传统的网页加载速度,PWA的加载速度是非常快的,因为PWA使用了Service Worker 等先进技术。除此之外,PWA还可以被添加在用户的主屏幕上,不用从应用商店进行下载即可通过网络应用程序Manifest file提供类似于APP的使用体验。作为一种全新Web技术方案,PWA的正常工作需要一些重要的技术组件,它们协同工作并为传统的Web应用程序注入活力,如图1-8所示。其中,Service Worker表示离线缓存文件,其本质是Web应用程序与浏览器之间的代理服务器,可以在网络可用时作为浏览器和网络间的代理,也可以在离线或者网络极差的环境下使用离线的缓冲文件。Manifest则是W3C一个技术规范,它定义了基于JSON的清单,为开发人员提供一个放置与Web应用程序关联的元数据的集中地点。Manifest是PWA 开发中的重要一环,它为开发人员控制应用程序提供了可能。目前,渐进式Web应用还处于起步阶段,使用的厂商也是诸如Twitter、淘宝、微博等大平台。不过,PWA作为Google主推的一项技术标准,Edge、Safari和FireFox等主流浏览器也都开始支持渐进式Web应用。因此,可以预见的是,PWA必将成为继移动之后的又一革命性技术方案。对比在当前诸多的跨平台方案中,RN、Weex和Flutter无疑是最优秀的。而从不同的细节来看,三大跨平台框架又有各自的优点和缺点,可以通过表1-1来查看。对比类型React NativeWeexFlutter支持平台Android/IOSAndroid/IOS/WebAndroid/IOS实现技术JavaScriptJavaScript原生编码/渲染引擎JS V8JSCoreFlutter Engine编程语言ReactVueDartbundle包大小单一、较大较小、多页面不需要框架程度较重较轻重社区活跃、FB维护不活跃活跃如上表所示,RN、Weex采用的技术方案大体相同,它们都使用JavaScript作为编程语言,然后通过中间层转换为原生的组件后再利用Native渲染引擎执行渲染操作。而Flutter直接使用skia来渲染视图,而Flutter Widget则使用现代响应式框架来构建,和平台没有直接的关系。就目前跨平台技术来看,JavaScript在跨平台开发中可谓占据半壁江山,大有“一统天下”的趋势。从性能方面来说,Flutter的性能理论上是最好的,RN和Weex次之,并且都好于传统的WebView方案。但从目前的实际应用来看却并没有太大的差距,特别是和0.5.0版本以上的RN对比性能体验上差异并不明显。而从社群和社区的活跃来看,RN和Flutter无疑是最活跃的,RN经过4年多的发展已经成长为跨平台开发的实际领导者,并拥有各类丰富的第三方库和开发群体。Flutter作为最近才火起来的跨平台技术方案,不过目前还处在beta阶段,商用的实例也很少,不过应该看到google的号召力一直是很强,未来究竟如何发展让我们拭目以待。示例eros-yanxuan简介eros-yanxuan 是基于 eros 开发的Weex项目,部分页面参考了项目网易严选 weex 版本,欢迎star或fork。eros 文档eros github运行确保你本地已经集成了 eros 开发所需的环境。clone 项目到本地:$ git clone https://github.com/xiangzhihong/eros-yanxuan.git进入目录,下载前端所需的依赖:$ cd eros-yanxuan$ npm installiOS SDK打开platforms目录下的WeexEros项目,在WeexEros中使用pod添加依赖。$ cd platforms/ios/WeexEros$ pod update // 下载 iOS 依赖$ open WeexEros.xcworkspace // 自动打开项目选中模拟器,点击绿色箭头运行 app 即可。Android对于Android工程来说,使用Android Studio打开platforms目录下的WeexFrameworkWrapper的Android工程,然后使用install.sh安装Android工程的需要依赖包nexus和wxframework。具体可以参考自行导入项目,便可运行起来。运行项目根目录下运行 eros dev关闭调试,拦截器,打开热更新重新 build app效果Question运行过程中出现问题在以下地址解决方法,如果没有找到,请加群:515980159eros issueeros Q&A

January 22, 2019 · 1 min · jiezi

WeexBox 1.2.0 新增 Lottie 动画,妈妈再也不用担心我加班写动画了!

背景weex官方提供了animation模块可以用来在组件上执行动画,但是它的功能有限还容易造成卡顿。所以WeexBox从一开始就支持了BindingX,丰富的API和流畅的性能可以支撑复杂的动画。可是这样就满足了吗?致力于解放开发的WeexBox,怎么忍心看着你们写大坨大坨的动画代码呢,如果可以不写代码,那就太好了~LottieLottie是Airbnb开源的动画库。它通过AE做成动画导出JSON文件,然后前端使用Lottie直接加载JSON文件生成动画,不需要前端进行复杂的绘制等操作。而且它还具有占用内存少,加载流畅等特点。N多现成可用的优秀动画在这里WeexBox中使用Lottie因为太简单,我就直接贴代码了。<template> <div class=“wrap”> <wb-lottie class=“happyBirthday” :sourceJson=sourceJson :loop=loop ref=“lottie”></wb-lottie> <text class=“title”>播放动画</text> <div class=“button” @click=“play”> <text class=“button-text”>播放</text> </div> <div class=“empty”></div> <text class=“title”>暂停动画</text> <div class=“button” @click=“pause”> <text class=“button-text”>暂停</text> </div> <div class=“empty”></div> <text class=“title”>停止动画</text> <div class=“button” @click=“stop”> <text class=“button-text”>停止</text> </div> <div class=“empty”></div> </div></template><script>// 这个就是设计师给你的json文件const happyBirthday = require(’./happyBirthday.json’)export default { components: { }, data() { return { sourceJson: happyBirthday, loop: false, } }, created() { }, methods: { // 播放动画 play() { this.$refs.lottie.play((result) => { console.log(JSON.stringify(result)) }) }, // 暂停动画 pause() { this.$refs.lottie.pause() }, // 停止动画 stop() { this.$refs.lottie.stop() }, },}</script><style lang=“scss” scoped>@import ‘../../style/global’;.happyBirthday { width: 750px; height: 750px;}</style>以上只演示了部分功能,详细文档在此总结我们终于找到了能让设计师、产品都对动画满意的方法,那就是设计师设计好了直接导出动画啊哈哈,妈妈再也不用担心我加班写动画了! ...

January 19, 2019 · 1 min · jiezi

weex页面上划动,固定header

注:请在手机上扫描预览效果(支持ios、android)<template> <scroller> <header class=“search”> <text class=“text”>sdasxxxxxxxx</text> </header> <list> <template v-for="(index, i) in lists"> <header :key="${index}-header" v-if=“i%2===0 && i != 0”> <text class=“banner”>{{index}} HEADER</text> </header> <cell :key=“index”> <div class=“panel”> <text class=“text”>{{index}}</text> </div> </cell> </template> </list> </scroller></template><script> export default { data () { return { lists: [ ‘First’, ‘Second’, ‘Third’, ‘Fourth’, ‘Fifth’, ‘Sixth’, ‘Seventh’, ‘Eighth’ ] } } }</script><style scoped> .search{ background-color: red; opacity: .1; background-color: rgba(0,0,0,0.6) } .search .text{ background-color: rgba(0,0,0,0.6) } .banner { width: 750px; padding: 25px; font-size: 60px; text-align: center; font-weight: bold; color: #41B883; background-color: rgb(162, 217, 192); } .panel { width: 600px; height: 300px; margin-left: 75px; margin-top: 35px; margin-bottom: 35px; flex-direction: column; justify-content: center; border-width: 2px; border-style: solid; border-color: rgb(162, 217, 192); background-color: rgba(162, 217, 192, 0.2); } .text { font-size: 50px; text-align: center; color: #41B883; }</style> ...

January 17, 2019 · 1 min · jiezi

Weex开发之地图篇

在移动应用开发中,地图是一个很重要的工具,基于地图的定位、导航等特点衍生出了很多著名的移动应用。在Weex开发中,如果要使用定位、导航和坐标计算等常见的地图功能,可以使用weex-amap插件。weex-amap是高德针对Weex开发的一款地图插件,在Eros开发中,Eros对weex-amap进行二次封装,以便让开发者更集成地图功能。和其他的插件一样,集成此插件需要在原生平台中进行集成。本文介绍的是如何在iOS中集成weex-amap,以及它的一些核心功能。本文将要介绍的内容如下:1.高德地图开发准备工作1.1 iOS高德地图开发流程简单介绍1.2 android高德地图开发流程简单介绍1.3 web高德地图开发流程简单介绍2. weex-iOS地图组件扩展方式介绍2.1 dwd-weex-amap2.2 dwd-weex-amap-marker2.3 dwd-weex-amap-info-window2.4 dwd-weex-amap-circle2.5 dwd-weex-amap-polygon2.5 dwd-weex-amap-polyline3.weex-android地图组件扩展方式介绍3.1 dwd-weex-amap3.2 dwd-weex-amap-marker3.3 dwd-weex-amap-info-window3.4 dwd-weex-amap-circle3.5 dwd-weex-amap-polygon3.6 dwd-weex-amap-polyline4.weex-html5地图组件扩展方式介绍4.1 dwd-weex-amap4.2 dwd-weex-amap-marker4.3 dwd-weex-amap-info-window4.4 dwd-weex-amap-circle4.5 dwd-weex-amap-polygon4.6 dwd-weex-amap-polyline5.获取地图数据(例如骑行路径规划)5.1 weex-iOS地图骑行路径规划5.2 weex-android地图骑行路径规划5.3 weex-web地图骑行路径规划准备工作1.1 开发流程简绍1.使用 CocoaPods 安装AMapSearch,AMap3DMap SDK 2.前往高德开放平台控制台申请 iOS Key 3.配置高德Key至AppDelegate.m文件1.2 android高德地图开发流程简绍1.使用 CocoaPods 安装AMapSearch,AMap3DMap SDK 2.前往高德开放平台控制台申请 android Key 3.AndroidManifest.xml的application标签中配置Key 4.AndroidManifest.xml中配置权限1.3 HTML5高德地图开发流程简绍1.前往高德开放平台控制台申请 jsAPI Key 2.可通过CDN同步加载方式或使用require异步方式来加载key 参考:高德地图开发文档,vue-amap开发文档weex-iOS地图组件扩展2.1 weex-amap地图展示是地图最基本的功能,其常见的效果如下:如有要在iOS中自定义weex-amap可以从以下几个步骤完成:1.新建DMapViewComponent类继承WXComponent; 2.在DMapViewComponent实现文件中实现MAMapViewDelegate代理; 3. 重写DMapViewComponent的loadView方法加载地图视图并设置自身为代理对象; 4.在DMapViewComponent的初始化函数viewDidLoad中做一些准备工作; 5.在DMapViewComponent的initWithRef方法中实现属性绑定; 6.通过fireEvent添加适当时机可以触发的事件; 7.重写insertSubview方法来添加子组建包括覆盖物,线,圆等等。下面是部分的业务实现代码:@implementation DMapViewComponent…// 属性绑定- (instancetype)initWithRef:(NSString )ref type:(NSString)type styles:(nullable NSDictionary *)styles attributes:(nullable NSDictionary *)attributes events:(nullable NSArray *)events weexInstance:(WXSDKInstance *)weexInstance{ self = [super initWithRef:ref type:type styles:styles attributes:attributes events:events weexInstance:weexInstance]; if (self) { // 中心点 NSArray *center = [attributes map_safeObjectForKey:@“center”]; _zoomLevel = [[attributes map_safeObjectForKey:@“zoom”] floatValue]; // 是否允许显示指南针 _compass = [[attributes map_safeObjectForKey:@“compass”] boolValue]; // sdkKey if ([attributes map_safeObjectForKey:@“sdkKey”]) { [self setAPIKey:[attributes[@“sdkKey”] objectForKey:@“ios”] ? : @""]; }… } return self;}// 重写DMapViewComponent的loadView方法加载地图视图并设置自身为代理对象 - (UIView *) loadView{ UIWindow *window = [UIApplication sharedApplication].keyWindow; CGSize windowSize = window.rootViewController.view.frame.size; self.mapView = [[MAMapView alloc] initWithFrame:CGRectMake(0, 0, windowSize.width, windowSize.height)]; self.mapView.showsUserLocation = _showGeolocation; self.mapView.delegate = self; self.mapView.customMapStyleEnabled = YES; [self.mapView setCustomMapStyleWithWebData:[self getMapData]]; return self.mapView;}// 设置地图样式- (NSData *)getMapData{ NSString *path = [NSString stringWithFormat:@"%@/gaodeMapStyle.data", [NSBundle mainBundle].bundlePath]; NSData *data = [NSData dataWithContentsOfFile:path]; return data;}- (void)viewDidLoad{ [super viewDidLoad]; self.mapView.showsScale = _showScale; self.mapView.showsCompass = _compass; [self.mapView setCenterCoordinate:_centerCoordinate]; [self.mapView setZoomLevel:_zoomLevel];}// 添加覆盖物- (void)insertSubview:(WXComponent *)subcomponent atIndex:(NSInteger)index{ if ([subcomponent isKindOfClass:[DMapRenderer class]]) { DMapRenderer *overlayRenderer = (DMapRenderer *)subcomponent; [self addOverlay:overlayRenderer]; }else if ([subcomponent isKindOfClass:[DMapViewMarkerComponent class]]) { [self addMarker:(DMapViewMarkerComponent *)subcomponent]; }}// 更新属性- (void)updateAttributes:(NSDictionary *)attributes{… if (attributes[@“zoom”]) { [self setZoomLevel:[attributes[@“zoom”] floatValue]]; } …}#pragma mark - component interface- (void)setAPIKey:(NSString *)appKey{ [AMapServices sharedServices].apiKey = appKey;}- (void)setZoomLevel:(CGFloat)zoom{ [self.mapView setZoomLevel:zoom animated:YES];}#pragma mark - publish method- (NSDictionary *)getUserLocation{ if(self.mapView.userLocation.updating && self.mapView.userLocation.location) { NSArray *coordinate = @[[NSNumber numberWithDouble:self.mapView.userLocation.location.coordinate.longitude],[NSNumber numberWithDouble:self.mapView.userLocation.location.coordinate.latitude]]; NSDictionary userDic = @{@“result”:@“success”,@“data”:@{@“position”:coordinate,@“title”:@""}}; return userDic; } return @{@“resuldt”:@“false”,@“data”:@""};}#pragma mark - mapview delegate/! @brief 根据anntation生成对应的View /- (MAAnnotationView)mapView:(MAMapView *)mapView viewForAnnotation:(id <MAAnnotation>)annotation{ if ([annotation isKindOfClass:[MAPointAnnotation class]]) { MAPointAnnotation pointAnnotation = (MAPointAnnotation )annotation; if ([pointAnnotation.component isKindOfClass:[WXMapInfoWindowComponent class]]) { return [self _generateCustomInfoWindow:mapView viewForAnnotation:pointAnnotation]; }else { return [self _generateAnnotationView:mapView viewForAnnotation:pointAnnotation]; } } return nil;}/ * @brief 当选中一个annotation views时,调用此接口 * @param mapView 地图View * @param view 选中的annotation views */- (void)mapView:(MAMapView *)mapView didSelectAnnotationView:(MAAnnotationView *)view{ MAPointAnnotation *annotation = view.annotation; for (WXComponent *component in self.subcomponents) { if ([component isKindOfClass:[WXMapViewMarkerComponent class]] && [component.ref isEqualToString:annotation.component.ref]) { WXMapViewMarkerComponent marker = (WXMapViewMarkerComponent )component; if (marker.clickEvent) { [marker fireEvent:marker.clickEvent params:[NSDictionary dictionary]]; } } }}/ * @brief 当取消选中一个annotation views时,调用此接口 * @param mapView 地图View * @param view 取消选中的annotation views */- (void)mapView:(MAMapView )mapView didDeselectAnnotationView:(MAAnnotationView )view{}/ * @brief 地图移动结束后调用此接口 * @param mapView 地图view * @param wasUserAction 标识是否是用户动作 */- (void)mapView:(MAMapView *)mapView mapDidMoveByUser:(BOOL)wasUserAction{ if (_isDragend) { [self fireEvent:@“dragend” params:[NSDictionary dictionary]]; }}/**设置地图缩放级别 */- (void)setMapViewRegion:(NSMutableArray *)poiArray animated:(BOOL)animated { NSMutableArray *arrays = [NSMutableArray array]; for (MAPointAnnotation *anot in self.mapView.annotations) { CLLocationCoordinate2D coordinate = anot.coordinate; NSDictionary *poidic = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:coordinate.latitude * 1000000], @“lat”, [NSNumber numberWithInt:coordinate.longitude * 1000000], @“lng”, nil]; [arrays addObject:poidic]; } MACoordinateRegion region = [self getCoordinateMapSpan:arrays]; [self.mapView setRegion:region animated:animated];}/**配置地图region */- (MACoordinateRegion)getCoordinateMapSpan:(NSMutableArray *)knightArray { MACoordinateRegion region; MACoordinateSpan span; CLLocationDegrees maxLat = -90; CLLocationDegrees maxLon = -180; CLLocationDegrees minLat = 90; CLLocationDegrees minLon = 180; if (knightArray && knightArray.count > 1) { for (int i = 0; i < knightArray.count; i++) { NSDictionary *knightDictionary = [knightArray objectAtIndex:i]; float lat = [[knightDictionary objectForKey:@“lat”] floatValue] / 1000000; float lng = [[knightDictionary objectForKey:@“lng”] floatValue] / 1000000; if(lat > maxLat) maxLat = lat; if(lat < minLat) minLat = lat; if(lng > maxLon) maxLon = lng; if(lng < minLon) minLon = lng; } span.latitudeDelta = (maxLat - minLat) * 2 + 0.005; span.longitudeDelta = (maxLon - minLon) * 2 + 0.005; region.center.latitude = (maxLat + minLat) / 2; region.center.longitude = (maxLon + minLon) / 2; region.span = span; } else { NSDictionary *knightDictionary = [knightArray objectAtIndex:0]; span.latitudeDelta = 0.01; span.longitudeDelta = 0.01; float lat = [[knightDictionary objectForKey:@“lat”] floatValue] / 1000000; float lng = [[knightDictionary objectForKey:@“lng”] floatValue] / 1000000; if (lat !=0 && lng != 0) { region.center.longitude = lng; region.center.latitude = lat; } else { region.center = [[ShopLocateManager shared] getLocationCoordinate]; } region.span = span; } return region;}…@end2.2 weex-amap-markermarker主要用于实现锚点,其效果如下:要在Weex中自定义锚点,需要遵循以下几步:新建DMapViewMarkerComponent类继承WXComponent;在DMapViewComponent中使用mapview的addAnnotation方法添加DMapViewMarkerComponent组件;在DMapViewComponent重写insertSubview方法来添加子组建覆盖物。部分实现代码如下:- (instancetype)initWithRef:(NSString )ref type:(NSString)type styles:(nullable NSDictionary *)styles attributes:(nullable NSDictionary *)attributes events:(nullable NSArray *)events weexInstance:(WXSDKInstance *)weexInstance{ self = [super initWithRef:ref type:type styles:styles attributes:attributes events:events weexInstance:weexInstance]; if (self) { if ([events containsObject:@“click”]) { _clickEvent = @“click”; } NSArray *offset = attributes[@“offset”]; if ([WXConvert isValidatedArray:offset]) { _offset = CGPointMake([WXConvert CGFloat:offset[0]], [WXConvert CGFloat:offset[1]]);//[WXConvert sizeToWXPixelType:attributes[@“offset”] withInstance:self.weexInstance]; } if (styles[@“zIndex”]) { _zIndex = [styles[@“zIndex”] integerValue]; } _hideCallout = [[attributes map_safeObjectForKey:@“hideCallout”] boolValue]; NSArray *position = [attributes map_safeObjectForKey:@“position”]; if ([WXConvert isValidatedArray:position]) { _location = [attributes map_safeObjectForKey:@“position”]; } _title = [attributes map_safeObjectForKey:@“title”]; _icon = [attributes map_safeObjectForKey:@“icon”]; } return self;}- (void)updateAttributes:(NSDictionary *)attributes{ DMapViewComponent *mapComponent = (DMapViewComponent *)self.supercomponent; if (attributes[@“title”]) { _title = attributes[@“title”]; [mapComponent updateTitleMarker:self]; } if ([attributes map_safeObjectForKey:@“icon”]) { _icon = attributes[@“icon”]; [mapComponent updateIconMarker:self]; } NSArray *position = [attributes map_safeObjectForKey:@“position”]; if ([WXConvert isValidatedArray:position]) { _location = position; [mapComponent updateLocationMarker:self]; }}weex-amap-info-windowweex-amap-info-window组件主要用于显示地图信息,如地图的图片模式,其效果如下:要自定义窗体组件,需要用到以下几个步骤:新建DMapInfoWindowComponent类继承WXComponent;在DMapViewComponent中使用mapview的addAnnotation方法添加DMapInfoWindowComponent组件;在DMapViewComponent重写insertSubview方法来添加子组建信息窗体。部分实现代码如下:- (instancetype)initWithRef:(NSString )ref type:(NSString)type styles:(nullable NSDictionary *)styles attributes:(nullable NSDictionary *)attributes events:(nullable NSArray *)events weexInstance:(WXSDKInstance *)weexInstance{ self = [super initWithRef:ref type:type styles:styles attributes:attributes events:events weexInstance:weexInstance]; if (self) { if (attributes[@“open”]) { _isOpen = [attributes[@“open”] boolValue]; } } return self;}- (UIView *) loadView{ return [[DMapInfoWindow alloc] initWithAnnotation:_annotation reuseIdentifier:_identifier];}- (void)insertSubview:(WXComponent *)subcomponent atIndex:(NSInteger)index{}- (void)updateAttributes:(NSDictionary *)attributes{ [super updateAttributes:attributes]; if (attributes[@“open”]) { _isOpen = [attributes[@“open”] boolValue]; if (_isOpen) { [self _addSubView]; }else { [self _removeViewFromSuperView]; } }}#pragma mark - private method 1. (void)_addSubView{ [self _removeViewFromSuperView]; [(DMapViewComponent *)self.supercomponent addMarker:self];} 2. (void)_removeViewFromSuperView{ [(DMapViewComponent *)self.supercomponent removeMarker:self];}2.4 weex-amap-circleweex-amap-circle组件主要用于实现画圈功能,如地图范围,其效果如下图所示:新建DMapCircleComponent类继承WXComponent;在DMapViewComponent中使用mapview的addOverlay方法添加DMapCircleComponent组件;在DMapViewComponent重写insertSubview方法来添加子组建圆。下面是部分实现逻辑:- (instancetype)initWithRef:(NSString )ref type:(NSString)type styles:(nullable NSDictionary *)styles attributes:(nullable NSDictionary *)attributes events:(nullable NSArray *)events weexInstance:(WXSDKInstance *)weexInstance{ self = [super initWithRef:ref type:type styles:styles attributes:attributes events:events weexInstance:weexInstance]; if (self) { NSArray *centerArray = [attributes map_safeObjectForKey:@“center”]; if ([WXConvert isValidatedArray:centerArray]) { _center = centerArray; } _radius = [[attributes map_safeObjectForKey:@“radius”] doubleValue]; } return self;}- (void)updateAttributes:(NSDictionary *)attributes{ NSArray *centerArray = [attributes map_safeObjectForKey:@“center”]; DMapViewComponent *parentComponent = (DMapViewComponent *)self.supercomponent; if ([WXConvert isValidatedArray:centerArray]) { _center = centerArray; [parentComponent removeOverlay:self]; [parentComponent addOverlay:self]; }else if ([[attributes map_safeObjectForKey:@“radius”] doubleValue] >= 0) { _radius = [[attributes map_safeObjectForKey:@“radius”] doubleValue]; [parentComponent removeOverlay:self]; [parentComponent addOverlay:self]; }else { [super updateAttributes:attributes]; }}2.5 weex-amap-polygonweex-amap-polygon主要用于绘制多边形,其效果如下图:要自定义weex-amap-polygon,可以从以下步骤着手:新建DMapPolygonComponent类继承WXComponent;在DMapViewComponent中使用mapview的addOverlay方法添加DMapPolygonComponent组件;在DMapViewComponent重写insertSubview方法来添加子组建多边形。部分实现代码如下:- (instancetype)initWithRef:(NSString )ref type:(NSString)type styles:(nullable NSDictionary *)styles attributes:(nullable NSDictionary *)attributes events:(nullable NSArray *)events weexInstance:(WXSDKInstance *)weexInstance{ self = [super initWithRef:ref type:type styles:styles attributes:attributes events:events weexInstance:weexInstance]; if (self) { _fillColor = [attributes map_safeObjectForKey:@“fillColor”]; _fillOpacity = [attributes map_safeObjectForKey:@“fillOpacity”]; } return self;}- (void)updateAttributes:(NSDictionary *)attributes{ if ([attributes map_safeObjectForKey:@“fillColor”]) { _fillColor = [attributes map_safeObjectForKey:@“fillColor”]; }else if ([attributes map_safeObjectForKey:@“fillOpacity”]) { _fillOpacity = [attributes map_safeObjectForKey:@“fillOpacity”]; }else { [super updateAttributes:attributes]; }}2.6 weex-amap-polylineweex-amap-polyline组件主要用于在地图上实现划线操作,其最终效果如下图:在iOS中,自定义直接需要从以下几步着手:新建DMapPolylineComponent类继承WXComponent;在DMapViewComponent中使用mapview的addOverlay方法添加DMapPolylineComponent组件;在DMapViewComponent重写insertSubview方法来添加子组建折线。@implementation DMapPolylineComponent- (instancetype)initWithRef:(NSString )ref type:(NSString)type styles:(nullable NSDictionary *)styles attributes:(nullable NSDictionary *)attributes events:(nullable NSArray *)events weexInstance:(WXSDKInstance *)weexInstance{ self = [super initWithRef:ref type:type styles:styles attributes:attributes events:events weexInstance:weexInstance]; if (self) { NSArray * pathArray = [attributes map_safeObjectForKey:@“path”]; if ([WXConvert isValidatedArray:pathArray]) { _path = pathArray; } _strokeColor = [attributes map_safeObjectForKey:@“strokeColor”]; _strokeWidth = [[attributes map_safeObjectForKey:@“strokeWidth”] doubleValue]; _strokeOpacity = [[attributes map_safeObjectForKey:@“strokeOpacity”] doubleValue]; _strokeStyle = [attributes map_safeObjectForKey:@“strokeStyle”]; } _viewLoaded = NO; return self;}- (void)updateAttributes:(NSDictionary *)attributes{ NSArray * pathArray = [attributes map_safeObjectForKey:@“path”]; DMapViewComponent *parentComponent = (DMapViewComponent *)self.supercomponent; if (pathArray) { if ([WXConvert isValidatedArray:pathArray]) { _path = pathArray; } [parentComponent removeOverlay:self]; [parentComponent addOverlay:self]; return; }else if ([attributes map_safeObjectForKey:@“strokeColor”]) { _strokeColor = [attributes map_safeObjectForKey:@“strokeColor”]; }else if ([[attributes map_safeObjectForKey:@“strokeWidth”] doubleValue] >= 0) { _strokeWidth = [[attributes map_safeObjectForKey:@“strokeWidth”] doubleValue]; }else if ([[attributes map_safeObjectForKey:@“strokeOpacity”] doubleValue] >= 0) { _strokeOpacity = [[attributes map_safeObjectForKey:@“strokeOpacity”] doubleValue]; }else if ([attributes map_safeObjectForKey:@“strokeStyle”]) { _strokeStyle = [attributes map_safeObjectForKey:@“strokeStyle”]; } [parentComponent updateOverlayAttributes:self];}@end地图组件扩展当然,我们也可以不使用weex-amap,而是直接使用高德地图进行扩展。3.1 weex-amap例如,我们自己扩展一个基于原生高德SDK生成的weex-amap组件。要实现这么一个地图显示的功能,实现的步骤如下:新建DMapViewComponent类继承WXVContainer实现LocationSource;使用initComponentHostView(context)初始化;在DMapViewComponent实现文件中实现初始化map对象initMap,设置map的key;@WXComponentProp注解实现属性绑定;通过fireEvent添加适当时机可以触发的事件;设置mapview的setInfoWindowAdapter,addPolyline,addPolygon,addCircle,addMarker等方式来实现覆盖物,,线,圆等等。实现代码可以参考下面的代码: @Override protected FrameLayout initComponentHostView(@NonNull Context context) { mapContainer = new FrameLayout(context) { @Override public boolean onInterceptTouchEvent(MotionEvent ev) { // 解决与Scroller的滑动冲突 if (ev.getAction() == MotionEvent.ACTION_UP) { requestDisallowInterceptTouchEvent(false); } else { requestDisallowInterceptTouchEvent(true); } return false; } }; mapContainer.setBackgroundColor(fakeBackgroundColor); if (context instanceof Activity) { mActivity = (Activity) context; } return mapContainer; } @Override protected void setHostLayoutParams(FrameLayout host, int width, int height, int left, int right, int top, int bottom) { super.setHostLayoutParams(host, width, height, left, right, top, bottom); if (!isMapLoaded.get() && !isInited.get()) { isInited.set(true); mapContainer.postDelayed(new Runnable() { @Override public void run() { mMapView = new TextureMapView(getContext()); mapContainer.addView(mMapView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); WXLogUtils.e(TAG, “Create MapView " + mMapView.toString()); initMap(); } }, 0); } } private void initMap() { mMapView.onCreate(null); isMapLoaded.set(false); if (mAMap == null) { mAMap = mMapView.getMap(); mAMap.setInfoWindowAdapter(new InfoWindowAdapter(this)); mAMap.setOnMapLoadedListener(new AMap.OnMapLoadedListener() { @Override public void onMapLoaded() { WXLogUtils.e(TAG, “Map loaded”); isMapLoaded.set(true); mZoomLevel = mAMap.getCameraPosition().zoom; mMapView.postDelayed(new Runnable() { @Override public void run() { execPaddingTasks(); } }, 16); } }); // 绑定 Marker 被点击事件 mAMap.setOnMarkerClickListener(new AMap.OnMarkerClickListener() { // marker 对象被点击时回调的接口 // 返回 true 则表示接口已响应事件,否则返回false @Override public boolean onMarkerClick(Marker marker) { if (marker != null) { for (int i = 0; i < getChildCount(); i++) { if (getChild(i) instanceof DMapMarkerComponent) { DMapMarkerComponent child = (DMapMarkerComponent) getChild(i); if (child.getMarker() != null && child.getMarker().getId() == marker.getId()) { child.onClick(); } } } } return false; } }); mAMap.setOnCameraChangeListener(new AMap.OnCameraChangeListener() { private boolean mZoomChanged; @Override public void onCameraChange(CameraPosition cameraPosition) { mZoomChanged = mZoomLevel != cameraPosition.zoom; mZoomLevel = cameraPosition.zoom; } @Override public void onCameraChangeFinish(CameraPosition cameraPosition) { if (mZoomChanged) { float scale = mAMap.getScalePerPixel(); float scaleInWeex = scale / WXViewUtils.getWeexPxByReal(scale); VisibleRegion visibleRegion = mAMap.getProjection().getVisibleRegion(); WXLogUtils.d(TAG, “Visible region: " + visibleRegion.toString()); Map<String, Object> region = new HashMap<>(); region.put(“northeast”, convertLatLng(visibleRegion.latLngBounds.northeast)); region.put(“southwest”, convertLatLng(visibleRegion.latLngBounds.southwest)); Map<String, Object> data = new HashMap<>(); data.put(“targetCoordinate”, cameraPosition.target.toString()); data.put(“zoom”, cameraPosition.zoom); data.put(“tilt”, cameraPosition.tilt); data.put(“bearing”, cameraPosition.bearing); data.put(“isAbroad”, cameraPosition.isAbroad); data.put(“scalePerPixel”, scaleInWeex); data.put(“visibleRegion”, region); getInstance().fireEvent(getRef(), WeexConstant.EVENT.ZOOM_CHANGE, data); } } }); mAMap.setOnMapTouchListener(new AMap.OnMapTouchListener() { boolean dragged = false; @Override public void onTouch(MotionEvent motionEvent) { switch (motionEvent.getAction()) { case MotionEvent.ACTION_MOVE: dragged = true; break; case MotionEvent.ACTION_UP: if (dragged) getInstance().fireEvent(getRef(), WeexConstant.EVENT.DRAG_CHANGE); dragged = false; break; } } }); setUpMap(); } }}3.2 weex-amap-info-window当然,我们也可以使用它实现weex-amap-info-window功能,虽然weex-amap-info-window已经被内置到weex-amap中。是的的思路如下:新建DMapViewMarkerComponent类继承WXComponent;在DMapViewComponent中使用mapview的addMarker方法添加DMapViewMarkerComponent组件 。 在DMapViewComponent中使用mapview的addMarker方法添加DMapViewMarkerComponent组件 。private static class InfoWindowAdapter implements AMap.InfoWindowAdapter { private DMapViewComponent mWXMapViewComponent; InfoWindowAdapter(DMapViewComponent wxMapViewComponent) { mWXMapViewComponent = wxMapViewComponent; } @Override public View getInfoWindow(Marker marker) { return render(marker); } @Override public View getInfoContents(Marker marker) { return null;// return render(marker); } private View render(Marker marker) { WXMapInfoWindowComponent wxMapInfoWindowComponent = mWXMapViewComponent.mInfoWindowHashMap.get(marker.getId()); if (wxMapInfoWindowComponent != null) { WXFrameLayout host = wxMapInfoWindowComponent.getHostView();// WXFrameLayout content = (WXFrameLayout) host.getChildAt(0); host.getLayoutParams().width = ViewGroup.LayoutParams.WRAP_CONTENT; host.getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT; WXLogUtils.d(TAG, “Info size: " + host.getMeasuredWidth() + “, " + host.getMeasuredHeight()); return host; } else { WXLogUtils.e(TAG, “WXMapInfoWindowComponent with marker id " + marker.getId() + " not found”); } return null; } }html5地图组件扩展当然,我们可以使用对html5的amap进行扩展,例如扩展weex-amap。4.1 weex-amap示例代码如下:<template> <div class=“amap-page-container”> <el-amap ref=“map” vid=“amapDemo” :amap-manager=“amapManager” :center=“center” :zoom=“zoom” :plugin=“plugin” :events=“events” class=“amap-demo”> </el-amap> <div class=“toolbar”> <button @click=“getMap()">get map</button> </div> </div> </template> <style> .amap-demo { height: 300px; } </style> <script> // NPM 方式 // import { AMapManager } from ‘vue-amap’; // CDN 方式 let amapManager = new VueAMap.AMapManager(); module.exports = { data: function() { return { amapManager, zoom: 12, center: [121.59996, 31.197646], events: { init: (o) => { console.log(o.getCenter()) console.log(this.$refs.map.$$getInstance()) o.getCity(result => { console.log(result) }) }, ‘moveend’: () => { }, ‘zoomchange’: () => { }, ‘click’: (e) => { alert(‘map clicked’); } }, plugin: [‘ToolBar’, { pName: ‘MapType’, defaultType: 0, events: { init(o) { console.log(o); } } }] }; }, methods: { getMap() { // amap vue component console.log(amapManager._componentMap); // gaode map instance console.log(amapManager._map); } } };</script> 4.2 weex-amap-marker实现代码如下:<template> <div class=“amap-page-container”> <el-amap vid=“amapDemo” :zoom=“zoom” :center=“center” class=“amap-demo”> <el-amap-marker vid=“component-marker” :position=“componentMarker.position” :content-render=“componentMarker.contentRender” ></el-amap-marker> <el-amap-marker v-for="(marker, index) in markers” :position=“marker.position” :events=“marker.events” :visible=“marker.visible” :draggable=“marker.draggable” :vid=“index”></el-amap-marker> </el-amap> <div class=“toolbar”> <button type=“button” name=“button” v-on:click=“toggleVisible”>toggle first marker</button> <button type=“button” name=“button” v-on:click=“changePosition”>change position</button> <button type=“button” name=“button” v-on:click=“chnageDraggle”>change draggle</button> <button type=“button” name=“button” v-on:click=“addMarker”>add marker</button> <button type=“button” name=“button” v-on:click=“removeMarker”>remove marker</button> </div> </div> </template> <style> .amap-demo { height: 300px; } </style> <script> const exampleComponents = { props: [’text’], template: &lt;div&gt;text from parent: {{text}}&lt;/div&gt; } module.exports = { name: ‘amap-page’, data() { return { count: 1, slotStyle: { padding: ‘2px 8px’, background: ‘#eee’, color: ‘#333’, border: ‘1px solid #aaa’ }, zoom: 14, center: [121.5273285, 31.21515044], markers: [ { position: [121.5273285, 31.21515044], events: { click: () => { alert(‘click marker’); }, dragend: (e) => { console.log(’—event—: dragend’) this.markers[0].position = [e.lnglat.lng, e.lnglat.lat]; } }, visible: true, draggable: false, template: ‘<span>1</span>’, } ], renderMarker: { position: [121.5273285, 31.21715058], contentRender: (h, instance) => { // if use jsx you can write in this // return <div style={{background: ‘#80cbc4’, whiteSpace: ’nowrap’, border: ‘solid #ddd 1px’, color: ‘#f00’}} onClick={() => …}>marker inner text</div> return h( ‘div’, { style: {background: ‘#80cbc4’, whiteSpace: ’nowrap’, border: ‘solid #ddd 1px’, color: ‘#f00’}, on: { click: () => { const position = this.renderMarker.position; this.renderMarker.position = [position[0] + 0.002, position[1] - 0.002]; } } }, [‘marker inner text’] ) } }, componentMarker: { position: [121.5273285, 31.21315058], contentRender: (h, instance) => h(exampleComponents,{style: {backgroundColor: ‘#fff’}, props: {text: ‘father is here’}}, [‘xxxxxxx’]) }, slotMarker: { position: [121.5073285, 31.21715058] } }; }, methods: { onClick() { this.count += 1; }, changePosition() { let position = this.markers[0].position; this.markers[0].position = [position[0] + 0.002, position[1] - 0.002]; }, chnageDraggle() { let draggable = this.markers[0].draggable; this.markers[0].draggable = !draggable; }, toggleVisible() { let visableVar = this.markers[0].visible; this.markers[0].visible = !visableVar; }, addMarker() { let marker = { position: [121.5273285 + (Math.random() - 0.5) * 0.02, 31.21515044 + (Math.random() - 0.5) * 0.02] }; this.markers.push(marker); }, removeMarker() { if (!this.markers.length) return; this.markers.splice(this.markers.length - 1, 1); } } };</script> 4.3 weex-amap-info-window<template> <div class=“amap-page-container”> <el-amap vid=“amap” :zoom=“zoom” :center=“center” class=“amap-demo”> <el-amap-info-window :position=“currentWindow.position” :content=“currentWindow.content” :visible=“currentWindow.visible” :events=“currentWindow.events”> </el-amap-info-window> </el-amap> <button @click=“switchWindow(0)">Show First Window</button> <button @click=“switchWindow(1)">Show Second Window</button> </div> </template> <style> .amap-demo { height: 300px; } </style> <script> module.exports = { data () { return { zoom: 14, center: [121.5273285, 31.21515044], windows: [ { position: [121.5273285, 31.21515044], content: ‘Hi! I am here!’, visible: true, events: { close() { console.log(‘close infowindow1’); } } }, { position: [121.5375285, 31.21515044], content: ‘Hi! I am here too!’, visible: true, events: { close() { console.log(‘close infowindow2’); } } } ], slotWindow: { position: [121.5163285, 31.21515044] }, currentWindow: { position: [0, 0], content: ‘’, events: {}, visible: false } } }, mounted() { this.currentWindow = this.windows[0]; }, methods: { switchWindow(tab) { this.currentWindow.visible = false; this.$nextTick(() => { this.currentWindow = this.windows[tab]; this.currentWindow.visible = true; }); } } };</script> API当然,除了组件之外,我们还可以使用weex-amap的API来直接操作地图。5.1 骑行路径Android实现- (void)searchRidingRouteFromLat:(int)fromLat fromLng:(int)fromLng toLat:(int)toLat toLng:(int)toLng { AMapRidingRouteSearchRequest *request = [[AMapRidingRouteSearchRequest alloc] init]; request.origin = [AMapGeoPoint locationWithLatitude:INT_2_FLOAT(fromLat) / 1000000 longitude:INT_2_FLOAT(fromLng) / 1000000]; request.destination = [AMapGeoPoint locationWithLatitude:INT_2_FLOAT(toLat) / 1000000 longitude:INT_2_FLOAT(toLng) / 1000000]; //发起路径搜索 [self.aMapSearch AMapRidingRouteSearch:request];}- (void)onRouteSearchDone:(AMapRouteSearchBaseRequest *)request response:(AMapRouteSearchResponse *)response { if(response.route == nil) { return; } //通过AMapNavigationSearchResponse对象处理搜索结果 AMapRoute *route = response.route; if (route.paths.count > 0) { AMapPath *amapPath = route.paths[0]; NSArray *coordArray = amapPath.steps; NSMutableArray *mArray = [NSMutableArray array]; NSArray *start = @[[NSString stringWithFormat:@"%f”, request.origin.longitude], [NSString stringWithFormat:@"%f”, request.origin.latitude]]; [mArray insertObject:start atIndex:0]; for (AMapStep *step in coordArray) { NSString *polistring = step.polyline; NSArray *array = [polistring componentsSeparatedByString:@”;”]; for (NSString *str in array) { NSArray *loc =[str componentsSeparatedByString:@”,”]; [mArray addObject:loc]; } } NSArray end = @[[NSString stringWithFormat:@"%f", request.destination.longitude], [NSString stringWithFormat:@"%f", request.destination.latitude]]; [mArray insertObject:end atIndex:mArray.count]; [[DMessageChannelManager shared] postMessage:@“mapLines” andData:@{@“result”: @“success”, @“data”: @{@“mapLines”:mArray}}]; }}- (void)AMapSearchRequest:(id)request didFailWithError:(NSError )error{ NSLog(@“Error: %@”, error);}@end5.2 骑行路径iOS实现 private RouteTask.OnRouteCalculateListener calculateListener = new RouteTask.OnRouteCalculateListener() { @Override public void onRouteCalculate(RideRouteResult result, int code) { HashMap<String, Object> res = new HashMap<>(2); if (code == 1000 && result != null) { Map<String, Object> data = new HashMap<>(1); data.put(“mapLines”, getLatLngList(result.getPaths().get(0), routeTask.getStartPoint(), routeTask.getEndPoint())); res.put(“result”, “success”); res.put(“data”, data); } else { res.put(“result”, “fail”); } String dataJson = new Gson().toJson(res); NotifyDataManager.getInstance().postMessage(“mapLines”, dataJson); WXLogUtils.d(“RideRouteResult Json: " + dataJson); } }; routeTask.addRouteCalculateListener(calculateListener); / * 通过首尾经纬度计算走路规划路径中所有经纬度 * * @param fromLat * @param fromLng * @param toLat * @param toLng */ @JSMethod public void searchRidingRouteFromLat(float fromLat, float fromLng, float toLat, float toLng) { LocationEntity fromLe = new LocationEntity(); fromLe.lat = fromLat / 1e6; fromLe.lng = fromLng / 1e6; LocationEntity toLe = new LocationEntity(); toLe.lat = toLat / 1e6; toLe.lng = toLng / 1e6; if (routeTask == null) { routeTask = RouteTask.getInstance(mWXSDKInstance.getContext()); routeTask.addRouteCalculateListener(calculateListener); } routeTask.search(fromLe, toLe); }5.3 骑行路径Web实现 let stream = weex.requireModule(‘stream’) stream.fetch({ timeout:20000, method: ‘GET’, url: ‘https://restapi.amap.com/v4/direction/bicycling?key=87453539f02a65cd6585210fa2e64dc9&origin='+fromLng/1000000+','+fromLat/1000000+'&destination='+toLng/1000000+','+toLat/1000000, }, (response) => { if (response.status == 200) { let apiData = JSON.parse(response.data) if(apiData.data){ var polyline= new Array(); polyline[0] = apiData.data.origin.split(”,"); var polylineList = apiData.data.paths[‘0’].steps[0].polyline.split(";"); for(var i=0;i<polylineList.length;i++) { var polylinePoint = polylineList[i].split(","); polyline.push(polylinePoint); } polyline.push(apiData.data.destination.split(",")); //字符分割 callback({“result”:“success”,“data”: {“mapLines”:polyline}}); } } }, () => {}) ...

January 1, 2019 · 12 min · jiezi

WeexBox 快速上手

概述Weex是由阿里巴巴研发的一套移动跨平台技术框架,研发的初衷是为了解决移动开发过程中频繁发版和多端研发的问题。使用Weex提供的跨平台技术,开发者可以很方便的使用Web技术来构建高性能、可扩展的Native级别的性能体验,并支持在Android、iOS、YunOS和Web等多平台上进行部署。具体的说,当在项目中集成WeexSDK之后,就可以使用JavaScript和现代流行的前端框架来开发移动应用。同时,Weex框架的结构是解耦的,渲染引擎与语法层是分开的,也不依赖任何特定的前端框架,目前主要支持Vue.js和Rax两个前端框架。这样一来,甚至可以使用其他前端框架来驱动Weex,打造三端一致的Native应用。WeexBox是Weex的脚手架开发框架,和著名的WeexEros和WeexPlus的作用一样。相比Weex,WeexBox具有如下的特点:零配置,开箱即用的项目,自带最佳实践;无需安装 weex-toolkit;比 weex-debugger、weex-builder 更快的构建速度;支持 sass、es6、file-loader、uglify、eslint等;可通过审核的热更新,静默模式和强制模式随意切换;N 多实用的 Module 扩展;快速上手支持的系统Android 5.0 (API 21)+iOS 10.0+开发环境Node:Nodejs 8.0+AndroidStudio(仅限Android):AndroidStudio 3.0+Xcode(仅限iOS):Xcode 10.0+CocoaPods(仅限iOS):CocoaPods 1.5.0+创建项目借助weexbox提供的cli工具,我们可以快速的初始化工程项目。# 安装cnpm i -g @weexbox/cli# 新建一个weex工程weexbox create projectName# 进入工程cd projectName# 安装依赖cnpm i初始化的项目里已经内置了 @weexbox/debugger工具,它负责调试功能。调试注意:确保电脑与手机处于同一网段。调试真机JSbundle调试app时,需要在weex项目中运行如下命令:npm run debug此时会自动打开web,打开app的调试扫码工具扫二维码使pc与移动终端建立连接,当你看到类似以下这张图,就表示连接成功了。调试开发页面如果要单独调试某个页面,WeexBox也是支持的。npm run debug [vue/weex页面的路径]打开app的调试扫码工具,扫二维码使pc与移动终端建立连接。此时右上角有另外一个二维码,点开并扫描这个二维码即可将这个JSbundle页面载入真机渲染成原生页面。打包同时,WeexBox初始化的项目里已经内置了 @weexbox/builder,它负责打包功能。#编译开发环境npm run develop# 编译测试环境npm run test# 编译准生产环境npm run preRelease#编译生成环境npm run release项目结构.├── config // 配置文件夹│ ├── update-config.json // 热更新的配置文件│ └── weexbox-config.js // 图片资源的配置文件├── deploy // 输出文件夹├── platforms // 原生文件夹│ ├── android // Android工程│ └── ios // iOS工程├── src // vue源码文件夹│ └── module // 模块文件夹│ └── page // 页面文件夹│ ├── App.vue // vue源码│ └── index.js // 入口文件└── static // 图片资源文件夹使用npm i命令安装依赖后,项目的结构如上。项目同时也搭建了app 的基础架构:在工程 platforms 文件夹中,会看到两个文件夹 android 、ios,Android 端使用 Android Studio 开发工具,导入 platforms/android 文件夹,构建打包生成项目的apk;iOS 端使用 Xcode 开发工具,导入 platforms/ios 文件夹,构建打包生成项目的ipa。iOS集成WeexBox集成SDK修改Podfile文件,添加WeexBox依赖:source ‘https://github.com/cocoapods/specs.git'platform :ios, ‘10.0’inhibit_all_warnings!use_modular_headers!target ‘WeexBoxExample’ do pod ‘WeexBox’ end初始化在 AppDelegate.swift 中添加如下代码:func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // 初始化 WeexBox WeexBoxEngine.setup() // 开启调试 WeexBoxEngine.isDebug = true window = UIWindow(frame: UIScreen.main.bounds) window?.backgroundColor = .white // 使用 WBNavigationController 作为导航基类 window?.rootViewController = WBNavigationController(rootViewController: LaunchController()) window?.makeKeyAndVisible() return true}事件通知WeexBox 提供了原生与weex互相通知的能力,可以用作原生之间的通知。主要借助Event来实现:// 注册事件Event.register()// 发送事件Event.emit()// 注销事件Event.unregister()// 注销所有事件Event.unregisterAll()网络Network 类封装了Alamofire,原生和weex的网络请求都会走这里。// 网络请求域名Network.server = 你的网络请求域名// 发起网络请求。如果url已经包含了域名,会忽略你上面的设置Network.request(url)说明:原生也可以直接使用Alamofire,如果是这样,建议使用Network提供的sessionManager。大多数情况下app会有自己的网络封装,可以参考weexbox来实现自己的network module。热更新// 配置热更新地址UpdateManager.serverUrl = hotdeployUrl// 是否需要强制更新UpdateManager.forceUpdate = true// 执行热更新UpdateManager.update { (state, progress, error, url) in switch state { case .Unzip: // 解压 case .DownloadFile: // 下载 case .UpdateSuccess: // 更新成功,可以进入APP // 如果开启了强制更新,会等到下载完成才会进入这里。否则就是静默更新,解压成功就会进入 }}路由路由提供页面间的跳转功能。前端的路由可以参考:vue-router。注册路由Router.register()说明:WeexBox 默认注册了weex和web,你可以在app初始化的时候重新注册,用你自己的VC覆盖它们。路由实例的属性// 页面名称public var name: String = “”// 下一个weex/web的路径public var url: String?// 页面出现方式:push, presentpublic var type: String = Router.typePush// 是否隐藏导航栏public var navBarHidden: Bool = false// 需要传到下一个页面的数据public var params: Dictionary<String, Any>?// 打开页面的同时关闭页面public var closeFrom: Int?// 关闭页面的方向,默认和堆栈方向一致public var closeFromBottomToTop = true// 关闭页面的个数public var closeCount: Int?打开页面var router = Router()// 原生页面router.name = “你注册路由时的页面名称”// weex页面router.name = Router.nameWeexrouter.url = “module/page.js”// web页面router.name = Router.nameWebrouter.url = “https://aygtech.github.io/weexbox"router.open()关闭页面var router = Router()router.close()Android 集成 WeexBoxAndroid SDK使用Kotlin开发,并且100%兼容Java。 对于有追求的团队而言,强烈建议使用Kotlin来开发,开发速度和稳健度都会大幅提升!初始化在Android的 Application 中初始化WeexBox SDK。override fun onCreate() { super.onCreate() // 初始化 WeexBox WeexBoxEngine.setup(this, null) // 开启调试 WeexBoxEngine.isDebug = true}事件通知WeexBox 提供了原生与weex互相通知的能力,你可以将它用作原生之间的通知,不管是weex界面还是原生界面,只要注册了事件,都能接收到。通过 Event 类,你可以在weex发送事件与注册事件:// 注册事件Event.register()// 发送事件Event.emit()// 注销事件Event.unregister()// 注销所有事件Event.unregisterAll()也可以 在原生代码中发送事件与注册事件。// 注册事件Event.register(this,“YourEventName”) { //this为Activity或者Fragment //var value = it!![“key”] it为发送事件传过来的Map<String,Any>,可不传}// 发送事件Map<String, Object> map = new HashMap<>()map.put(“key”, Object)Event.emit(“YourEventName”, map)//map可为null// 注销事件Event.unregister(this, “YourEventName”) //this为Activity或者Fragment// 注销所有事件Event.unregisterAll(this)网络WeexBox的网络使用的是Retrofit的二次封装。原生和weex的网络请求都会走这里。// 网络请求域名Network.server = 你的网络请求域名// 发起网络请求。如果url已经包含了域名,会忽略你上面的设置Network.request(url)热更新// 配置热更新地址UpdateManager.serverUrl = hotdeployUrl// 是否需要强制更新UpdateManager.forceUpdate = true// 执行热更新UpdateManager.update { state, progress, error, url -> when (updateState) { UpdateManager.UpdateState.Unzip -> // 解压 UpdateManager.UpdateState.DownloadFile -> // 下载 UpdateManager.UpdateState.UpdateSuccess -> { // 更新成功,可以进入APP // 如果开启了强制更新,会等到下载完成才会进入这里。否则就是静默更新,解压成功就会进入 … // 还有各种状态码,参见下面表格,可以处理热更新各种情况,如热更新失败提示用户重启 } }}UpdateManager返回的状态有:状态码描述Unzip解压文件UnzipError解压文件出错UnzipSuccess解压文件成功GetServer获取服务器路径GetServerError获取服务器路径出错DownloadConfig下载配置文件DownloadConfigError下载配置文件出错DownloadConfigSuccess下载配置文件成功DownloadMd5下载md5文件DownloadMd5Error下载Md5出错DownloadMd5Success下载md5文件成功DownloadFile下载文件DownloadFileError下载文件出错DownloadFileSuccess下载文件成功UpdateSuccess更新成功路由注册路由Router.register()说明:WeexBox 默认注册了weex和web,你可以在app初始化的时候重新注册,用你自己的VC覆盖它们。路由实例的属性// 页面名称public var name: String = “”// 下一个weex/web的路径public var url: String?// 页面出现方式:push, presentpublic var type: String = Router.typePush// 是否隐藏导航栏public var navBarHidden: Bool = false// 需要传到下一个页面的数据public var params: Dictionary<String, Any>?// 打开页面的同时关闭页面public var closeFrom: Int?// 关闭页面的方向,默认和堆栈方向一致public var closeFromBottomToTop = true// 关闭页面的个数public var closeCount: Int?打开页面var router = Router()// 原生页面router.name = “你注册路由时的页面名称”// weex页面router.name = Router.nameWeexrouter.url = “module/page.js”// web页面router.name = Router.nameWebrouter.url = “https://aygtech.github.io/weexbox"router.open()关闭页面var router = Router()router.close()静态资源图片加载weexbox 支持 3 种图片加载方式:网络加载网络加载图片时,src 以http开头,例如:<image src=“https://aygtech.github.io/weexbox/logo.png"></image>从 APP bundle 中加载如果从bundle中加载图片,src 以bundle://开头,例如:<image src=“bundle://image.png”></image>从 APP 文件中加载如果src 不以上面两种方式开头,还可以从文件中加载,例如:// iOS<image src=“file://var/mobile/Media/DCIM/100APPLE/IMG_0171.PNG”></image>// Android<image src="/storage/emulated/0/DCIM/Camera/IMG_20180917_145836.jpg”></image>modalWeexBox内置了一些模块,但是这些模块相比其他的,如WeexEros和WeexPlus来说是明显偏少的。为此,你可以使用Weex提供的扩展机制来扩展自己的modal,相关内容可以参考:Weex快速上手除了常见的:alert、confirm外,还延伸了一些更频繁使用的api,eg:actionSheet(操作表弹框)、showLoading(显示菊花)等,更加常态化、大众化以及多元化。实现的示例代码如下:# 引用const modal = weex.requireModule(‘wb-modal’)# 警告弹框modal.alert({ title: ‘标题’, message: ‘弹窗内容’, okTitle: ‘确定’}, (result) => {})// callback参数result: { status: 0}如果要打开外部的Module,需要使用wb-external。例如:# 引用const external = weex.requireModule(‘wb-external’)# 调用摄像头拍照,实现图片裁剪上传external.openCamera({ // 能否剪裁 enableCrop: true, // 是否矩形剪裁,true为圆形剪裁 isCircle: true, // 宽度 width: 100, // 高度 height:100}, (result) => {})// callback参数result: { status: 0, error: ‘’, data: { // 图片的存储路径 url: ‘/docment/123.png’更多的模块可以参考:传送门目前,大前端开发的趋势越来越明显,与Weex同一技术系的RN早已声名远播,Weex作为后期之秀,目前还在不断的追赶和优化中虽然有各种bug被人诟病,但是,哪个优秀的技术发展没有经历这样的过程呢。“不经一番寒彻骨,怎得梅花扑鼻香”,相信通过大家的无私奉献,Weex社区也会变得越来越好。 本人正在完成《Weex跨平台开发实战》一书,有任何好的建议的可以留言,也欢迎大家踊跃提意见。(群:515980159) ...

December 21, 2018 · 3 min · jiezi

【开源】合摩 WeexBox 正式发布

WeexBox一套简洁高效的APP混合开发解决方案写在开头一提到 Weex,相信下面已经有一群小伙伴在哀嚎了,是的,大多数开发者对 Weex 的感情是既爱又恨的。Weex 是优秀的跨平台框架,「Write Once, Run Everywhere」,但坑也多的不要不要的,特别对于刚开始尝试 Weex 开发的团队来说,各种坑和不友好把许多人劝退了。首先聊聊我们为什么选择weex,在我们做技术选型时,综合考虑了目前市面上比较流行的RN、Weex,最终我们选择了 Weex。在决定使用哪种技术前,我们也在不断的问自己,为什么,为什么,为什么?在综合考虑各种因素后,我们技术团队决定使用weex。决定使用哪种技术,主要看其优缺点:其优点来验证,是否符合我们的业务场景;其缺点来判断,是否限制我们的业务场景,是否有方案避开这种限制。使用weex的优点:Weex 使用同一套代码来构建 Android、iOS 和 Web 应用。Weex 能用 Vue 的前端框架,贴近我们的技术栈Weex 比 RN 更轻量,体积小巧,可以分包,每个页面一个实例,性能更好学习成本低,上手快有良好的扩展性,比较好扩展新的 Component 和 Module同时,它也因资料少,社区活跃度不够,相对的坑较多,被大家一直诟病。我们收集了大家在开发中遇见的问题,针对这些问题,我们开发了WeexBox框架,并致力于:扩展 weex 的能力把最佳实践带入进来,提供大前端正确拥抱的姿势开发一些实用工具,带来更棒的开发体验填掉 weex 的坑最终,开发者能够专注写bug了~~~WeexBox 的特色零配置,开箱即用的项目,自带最佳实践无需安装 weex-toolkit(有多少汪在这一步安装不上、运行报错的。来,举个爪)比 weex-debugger、weex-builder 更快的构建速度支持 sass、es6、file-loader、uglify、eslint等可通过审核的热更新,静默模式和强制模式随意切换N 多实用的 Module 扩展合摩大前端团队实现了app开发从0到1,9天上线的壮举!可见 WeexBox 能够带来开发效率的巨大提升。快速上手@weexbox/cli 帮助你快速初始化工程项目。# 安装cnpm i -g @weexbox/cli# 新建一个weex工程weexbox create projectName# 进入工程cd projectName# 安装依赖cnpm i# 随后,可以愉快的写bug了…项目结构.├── config // 配置文件夹│ ├── update-config.json // 热更新的配置文件│ └── weexbox-config.js // 图片资源的配置文件├── deploy // 输出文件夹├── platforms // 原生文件夹│ ├── android // Android工程│ └── ios // iOS工程├── src // vue源码文件夹│ └── module // 模块文件夹│ ── page // 页面文件夹│ ├── App.vue // vue源码│ └── index.js // 入口文件├── static // 图片资源文件夹└── package.json // 配置文件安装依赖后,项目的结构如上,同时也搭建了app 的基础架构;在工程 platforms 文件夹中,会看到两个文件夹 android 、ios,Android 端使用 Android Studio 开发工具,导入 platforms/android 文件夹,构建打包生成项目的apk;iOS 端使用 Xcode 开发工具,导入 platforms/ios 文件夹,构建打包生成项目的ipa;随后,在src下建立业务模块,就可以愉快的开发了~这时你可能又有疑问了?本地书写的代码,如何能及时的展示在app界面上呢,不可能要每次打包吧,这样的话,也太LOW了!对的,不需要,这时你需要进入 Debug 调试 中了。Debug 调试Tips: 确保电脑与手机处于同一网段.1、调试打包在真机上的代码npm run debug打开app的调试扫码工具,扫二维码使pc与移动终端建立连接2、调试正在开发的页面npm run debug [vue/weex页面的路径]打开app的调试扫码工具,扫二维码使pc与移动终端建立连接更多详细的 debug 步骤请查看,WeexBox 开发指南中的 Debug 调试WeexBox 也提供了很多常见的模块。如何使用呢?1、丰富的 modal 模块modal 模块,除了常见的:alert、confirm外,还延伸了一些更频繁使用的api,eg:actionSheet(操作表弹框)、showLoading(显示菊花)等,更加常态化、大众化以及多元化。# 引用const modal = weex.requireModule(‘wb-modal’)# 警告弹框modal.alert({ title: ‘标题’, message: ‘弹窗内容’, okTitle: ‘确定’}, (result) => {})// callback参数result: { status: 0}效果图:# 引用const modal = weex.requireModule(‘wb-modal’)# 操作表弹窗,配合 wb-external 可调取摄像头及相册modal.actionSheet({ title: ‘标题’, message: ‘弹窗内容’, actions: [{ // 按钮类型’danger’, ‘cancel’, ’normal’。默认normal type: ‘danger’, // 按钮的标题 title: ‘删除’ }]}, (result) => {})// callback参数result: { // 取消按钮-1,其他0 status: 0, data: { // 按钮的索引 index: 0 }}效果图:等等功能,更多常见的modal api,详情请查看 传送门2、 打开外部功能 Module# 引用const external = weex.requireModule(‘wb-external’)# 调用摄像头拍照,实现图片裁剪上传external.openCamera({ // 能否剪裁 enableCrop: true, // 是否矩形剪裁,true为圆形剪裁 isCircle: true, // 宽度 width: 100, // 高度 height:100}, (result) => {})// callback参数result: { status: 0, error: ‘’, data: { // 图片的存储路径 url: ‘/docment/123.png’ }}等等功能,更多常见的external api,详情请查看 传送门写在最后目前 weex 官方也在不断的更新,虽然有各种bug被人诟病,但是,哪个优秀的技术发展没有经历这样的过程呢,“不经一番寒彻骨,怎得梅花扑鼻香”,技术本身就没有对错,只有我们调整好自己的心态,挖掘底层事物,垫好自己的基石,让技术更好的为业务服务。项目团队通过大量的业务实践和积累以后,总结归纳出这套基于 weex 的技术方案 WeexBox 并开源,解决大家在使用 weex 所遇见的各种坑,同时对官方的 Module 进行拓展、延伸,提供了更加丰富的模块,解决实际业务场景中的问题。目前团队使用 WeexBox 已研发了好几款APP,它能满足及支撑我们上百个页面的业务场景,让我们的开发效率大大提升,使我们的技术栈更加完善。在使用中有任何问题,欢迎给我们issue,有任何想法也欢迎PR。最后希望我们的方案能帮助开发中的你。官网地址:https://aygtech.github.io/wee…github: https://github.com/aygtech/ay…附上一份完整功能列表 ...

December 20, 2018 · 2 min · jiezi

Weex系列(4) —— 老生常谈的三端统一

目录Weex系列(序) —— 总要知道原生的一点东东(iOS)Weex系列(序) —— 总要知道原生的一点东东(Android)Weex系列(1) —— Hello World项目Weex系列(2) —— 页面跳转和通信Weex系列(3) —— 单页面还是多页面Weex系列(4) —— 老生常谈的三端统一[Weex系列(5) —— 封装原生组件和模块][Weex系列(6) —— css相关小结][Weex系列(7) —— web组件和webview][Weex系列(8) —— 是时候简析一下流程原理了][Weex系列(9) —— 踩坑填坑的集锦][Weex系列(10) —— 先这么多吧想到再写。。。]哎,手动捂脸,真的是好忙的两周,拔了一颗智齿、做了一个三端的投票活动、参加了微信马拉松比赛。还好都坚持过来了,我怎么这么优秀,还是手动doge一下吧。上面提到了一个三端投票活动,之前还想着怎么写这篇文章,做了这个活动后,感觉有千言万语。。场景附上我们的活动链接 https://tousu.sina.cn/activit… ,欢迎为自己喜欢的爱豆打call哦。APP端,欢迎搜索 黑猫投诉 或 新浪众测 呦,点击banner都可以双倍投票呢。对,这两个app都是基于weex做的。打开活动页,可以看到就三个页面,首页、明星详情页、明星列表页。刚看到这仨页面的时候,我就想着可以用路由,做成三端统一。配置看过我前一篇的文章,就知道我们的app都是多页面的,webpack只会打包成多个js,按照我上面的思路,这个时候需要修改配置,做过vue大型项目的应该遇到过吧,我之前是没有弄过,花了半天时间,参考的是已有的app多页面配置,和新建的只有单页面项目的配置,终于修改好了配置文件。(这里的单页多页可以参考我的前一篇文章)。然后就把静态的三个页面切好了,在app端和web样式基本都是正常的。如果你用的是最近的weex脚手架,web的index.html里面需要引入dist目录里面对应的index.web.js和vendor.web.js,而不是网上weex-hackernews-master项目里面引的weex-vue-render等js。(不然是不能单独运行的)vendor.web.js里面兼容了我们使用的weex组件和模块,有兴趣的可以去研究一下。开始其实还挺顺利的,但是中间遇到了很多问题,主要列出以下几点吧封装的模块和组件刚开始拿到项目的时候,想的还是少了。weex只是处理了他支持的组件和模块,所以我们自己封装的就需要自己做兼容了/(ㄒoㄒ)/。这里要说的一点是weex-ui也是处理过了,比如wxc-slider-bar三端基本无差异。比如我们这边的登录模块,h5是一套登录组件,app里面是微博的登录模块。由此还牵扯的有相关的请求方法、后端接口处理等。样式这部分真的三端基本是高度统一的,部分微调一下就可以了,也正是这样,我们后续才能迅速解决h5和pc。总结上面模块那部分由于涉及项目,我是简单几笔带过,其实这块真的是挺麻烦的,祝大家顺利吧。这次我们是有pc、h5、两个app的两端,其实是6端,时间也是挺紧的,所以最后基本还是h5、pc维护一套,app再维护一套。终于不是谈谈三端统一了,也是真的体验了一次,虽然最后有点出入,但是下次基本是没问题了(doge)。想用但还没有去实践过的,真的可以去试试了。最后欢迎评论交流学习啊,如果喜欢就请点个赞

December 19, 2018 · 1 min · jiezi

Weex 快速上手

“工欲善其事,必先利其器”,学习Weex之前需要先搭建好本地的开发环境,如果只是想简单的体验下Weex的魅力,可以使用Weex提供的dotWe在线运行环境,地址为“http://dotwe.org/vue”。安装依赖Weex官方提供weex-toolkit的脚手架工具来辅助开发和调试。不过需要先安装Node.js和Weex Cli相关的环境。安装Node.js安装Node.js有多种方式,最简单的方式是直接从Node.js官网下载可执行安装程序直接安装即可。如果是Mac环境,还可以使用Homebrew进行安装,安装命令如下:brew install node安装完成之后,可以使用下面的命令来检测是否安装成功。$ node -vv6.11.3$ npm -v3.10.10通常,安装Node.js软件包后,npm包管理工具也会随之安装。因此,接下来可以直接使用npm来安装weex-toolkit工具。npm install -g weex-toolkit如果要升级weex-toolkit,则可以使用下面的命令:weex update weex-devtool@latest //@后标注版本后,latest表示最新版本如果是国内开发者,还可以使用淘宝的npm镜像来安装weex-toolkit,涉及到的安装命令如下:npm install -g cnpm –registry=https://registry.npm.taobao.orgcnpm install -g weex-toolkit安装结束后,可以直接使用weex命令来验证是否安装成功,如果安装成功会显示weex命令行工具各参数,如下图所示。安装weexpackweexpack是weex新一代的工程开发套件,是基于weex快速搭建应用原型的利器。使用weexpack可以快速的帮助开发者通过命令行创建weex工程和插件工程,添加相应平台的weex 应用模版,weexpack还支持快速打包weex 应用并安装到手机运行,并创建weex插件模版并发布插件到weex应用市场。安装weexpack的命令如下:npm install -g weexpack安装Weektoolsweex-toolkit是官方提供的一个脚手架命令行工具,可以使用它进行Weex 项目的创建、调试以及打包等操作。weex-toolkit从1.0.1版本之后才开始支持初始化Vue项目,所以使用时请注意weex-toolki的版本。weex-toolkit的安装命令如下:npm install -g weex-toolkit如果使用上面的命令安装,使用的是从https://registry.npmjs.org获…,所以对于国内用户来说,最好选择从阿里的镜像去下载,安装时需要执行如下的一些命令。npm install -g cnpm –registry=https://registry.npm.taobao.org //淘宝镜像npm install -g weex-toolkit安装完成之后,可以使用weex -v或者weex命令来验证是否安装成功。安装Android环境如果需要支持Android平台则需要配置Android开发环境:安装Java相关环境,安装Android Studio及Android SDK。在安装编译Android项目时需要保证Android build-tool的版本为23.0.2以上。安装iOS环境如果需要支持iOS平台则需要配置iOS开发环境:安装Xcode、cocoaPods及其相关环境。其中,Xcode是Apple 公司提供的集成开发工具,可以使用它开发macOS和iOS应用程序,而CocoaPods则是负责iOS项目管理的第三方开源库的工具,合理的使用CocoaPods可以提高程序的开发效率。创建项目接下来,使用Weex提供的create命令初始化一个Weex项目。weex create weexdemo执行完上述命令后,在工程weexdemo目录下就会创建一个使用Weex或Vue模板创建的项目,工程目录结构如下。WeexProject ├── README.md //项目便签├── android.config.json //Android工程配置├── configs //weex配置 ├── ios.config.json //ios工程配置├── package-lock.json ├── package.json //项目npm包管理├── platforms //平台模版目录 │ ├── ios //ios原生平台目录│ └── android //android原生平台目录├── plugins //插件下载目录 │ └── README.md ├── src //业务源码目录│ └── index.we ├── tools //工具目录│ └── webpack.config.plugin.js ├── web //web平台目录│ ├── index.html └── webpack.config.js // webpack模块打包配置目录不过,通过create命令创建的weex工程默认是不包含iOS和Android原生工程模版的。如果需要添加原生工程模板,可以切换到appName目录后再安装依赖,模版默认会被安装到工程的platforms目录下,官方提供的模版默认支持weex bundle调试和插件机制。安装命令如下:weexpack platform add ios //安装ios模板weexpack platform add android //安装android模板安装模版完成之后,会在工程目录下增加如下的模版目录。目录结构如下:├── platforms │ ├── ios│ └── android当需要用到混合开发的时候,就可以使用原生开发环境打开Android或iOS项目进行原生功能的开发。开发与运行使用create命令创建weex项目时,weex-toolkit工具已经为我们生成了标准项目结构。此时,打开初始化的Weex项目并定位到“/src/index.vue”,该页面是Weex的默认首页。代码内容如下<template> <div class=“wrapper”> <image :src=“logo” class=“logo” /> <text class=“greeting”>The environment is ready!</text> <HelloWorld/> </div></template><script>import HelloWorld from ‘./components/HelloWorld.vue’export default { name: ‘App’, components: { HelloWorld }, data () { return { logo: ‘https://gw.alicdn.com/tfs/TB1yopEdgoQMeJjy1XaXXcSsFXa-640-302.png' } }}</script>运行weex项目前,需要先使用命令“npm install”来安装项目的依赖,可以在package.json文件中查看与项目相关的依赖。然后,在项目的根目录下使用命令“npm run dev & npm run serve”开启watch模式和静态服务器。打开浏览器,输入“http://localhost:8081/index.html”即可开启一个预览页面,使用用Weex 提供的playground app扫描生成的二维码即可在在手机上看到页面在手机上的渲染效果,如下图所示。如果需要在模拟器或真机设备上运行项目,可以使用下面的命令来启动应用程序,并且在运行客户端命令前请先启动服务端服务。weex run ios //在ios设备上运行weex run android //Android设备上运行需要注意的是,在运行启动命令前,请确保Android、iOS所需要的原生运行环境配置正确,并且需要先启动模拟器或连接上移动设备再运行启动命令。执行启动命令后,如果项目编译过程没有出现任何的错误提示,系统会在运行时要求用户选择一个安装的设备,如下图所示。如果没有任何错误,将会看到如系下图所示的运行效果。 ...

November 30, 2018 · 1 min · jiezi

Weex系列(3) —— 单页面还是多页面

目录Weex系列(序) —— 总要知道原生的一点东东(iOS)Weex系列(序) —— 总要知道原生的一点东东(Android)Weex系列(1) —— Hello World项目Weex系列(2) —— 页面跳转和通信Weex系列(3) —— 单页面还是多页面[Weex系列(4) —— 老生常谈的三端统一][Weex系列(5) —— 封装原生组件和模块][Weex系列(6) —— css相关小结][Weex系列(7) —— web组件和webview][Weex系列(8) —— 是时候简析一下流程原理了][Weex系列(9) —— 踩坑填坑的集锦][Weex系列(10) —— 先这么多吧想到再写。。。]时间总是过得那么快,一周又过去了。天越来越冷了,感觉跟要冬眠似的,越来越懒得动脑了,哈哈哈,下面开始进入我们的主题吧。单页面应用单页面应用(single page web application,SPA),大家应该很熟悉了,现在好多页面都采用的是这种模式,优缺点网上一搜一箩筐,支持的框架也有很多,react全家桶、vue全家桶等。Weex的上层语言有vue,所以我们是不是也可以用vue全家桶来打造一个App,官网的回答是可以的。用weex脚手架初始化项目,选项vue-router后面竟然跟了一个(not recommended)不推荐的。demo如下图,这个例子很简单,就不上传代码了,其实官网有一个很典型的例子weex-hackernews(https://github.com/weexteam/w…,用了vuex和vue-router,感觉入了weex这个坑的(doge),应该都看过研究过这个例子吧。官网有一个 使用 Vuex 和 vue-router ,大家也可以点进去看一下。然后我们来简单分析一下吧一个bundlejs上面的例子,虽然有三个tab,还有一个page3,感觉好多页面的样子,像web一样,最后打包只有一个js,是不是感觉到一丝不对的气息,是啊,这么一个大的app就这么一个js。1、首次打开白屏时间长2、不能按需加载对应页面js3、整个app使用相同的执行环境,隐患很多等一般app都是越做越大,越做越复杂,想想是不是有点可怕呢。所以官网也是引导我们集成Weex到已有的app。多页面应用其实原生app本就是多页面的场景,好比浏览器可以开很多窗口,上面那个例子就只是在一个窗口里来回折腾。说了这么多,那上面那个例子的底部tab1、2、3怎么实现呢,对,这就是多页面的成本,应该有好多跟我们一样,完全用Weex开发出一个从无到有的app,考虑了很多,底部这块我们还是决定用原生去做,这块我们是找了原生开发同学去做了一些支持的,这块据说是原生开发很基础很基础的一部分,大概半天就能搞定,可是后续的扩展性、性能优化、延展性等就好说多了,下面仅提供我们这边的一个思路。iOS: UITabBarController + UIViewController 把tab1、2、3.js的路径分别赋值给UIViewController,之前也有分析过WXDemoViewController大家可以去看看。UIViewController * weexVC = [[WXDemoViewController alloc] init];((WXDemoViewController *)weexVC).url = url;Android: 这个用的是Fragment,网上搜weex Fragment,会出来好多有参考价值的文章,大家可以去了解一下,我就不截图了,怕有版权之类的。navigator感觉这个词在我前面的文章里也是多次出现过了。是啊,底部tab1对应tab1.js渲染完页面,怎么进去到相应的page.js呢,就是我上一篇讲的了,用的基本就是navigator了,而且在page.js对应的页面,我们也是可以使用vue-router的。这个当然是用原生的模块组件封装的,有兴趣的可以看看WXNavigatorModule.m这个文件,所以页面的进退、切换等效果也都是极佳的,个人感觉完全超过单页面应用。小结读完文章的不难发现,我的观点就是偏向于多页面应用。各有所需,大家完全可以根据自己的场景来选择,如果你的app页面不多、轻量等,完全也是可以用单页面模式的。最后如果大家有一点点喜欢,对你有一点点的帮助,欢迎点赞收藏啊。

November 22, 2018 · 1 min · jiezi

Weex系列(2) —— 页面跳转和通信

Hello World项目之后就在想着这个系列接下来该怎么写,那就先简单拟个目录吧,一方面督促自己能坚持下去,一方面如果大家有兴趣的话,也请多多关注我的专栏,顺手点个赞啊~~目录Weex系列(序) —— 总要知道原生的一点东东(iOS)Weex系列(序) —— 总要知道原生的一点东东(Android)Weex系列(1) —— Hello World项目Weex系列(2) —— 页面跳转和通信[Weex系列(3) —— 单页面还是多页面][Weex系列(4) —— 老生常谈的三端统一][Weex系列(5) —— 封装原生组件和模块][Weex系列(6) —— css相关小结][Weex系列(7) —— web组件和webview][Weex系列(8) —— 是时候简析一下流程原理了][Weex系列(9) —— 踩坑填坑的集锦][Weex系列(10) —— 先这么多吧想到在写。。。]大致就是这个顺序吧,可能会微调,那下面就开始这一章吧。入口标题上加了官网怎么集成Weex到已有应用的链接,里面提到了很重要的入口方法。iOSNSURL *URL = [self testURL: [self.url absoluteString]];NSString *randomURL = [NSString stringWithFormat:@"%@%@random=%d",URL.absoluteString,URL.query?@"&":@"?",arc4random()];[_instance renderWithURL:[NSURL URLWithString:randomURL] options:@{@“bundleUrl”:URL.absoluteString} data:nil];这是前一篇用weex脚手架初始化的helloworld项目,在WXDemoViewController.m的render方法里面可以看到这段代码。然后重点来了,weex.config.bundleUrl的值:1、取得是上面options的bundleUrl值2、如果这个值不填,取得就是我们赋给renderWithURL的url地址。所以如果我们加载服务器上的一个页面js,然后这个页面又想跳回到本地的一个页面js,那么在服务器页面取bundleUrl的时候取得就是http的一个地址,是取不到我们想要跳到本地页面js绝对前缀地址的,有点绕,最后就讲讲我们App的思路吧。我做的两个App页面全部都是用vue写的,所以首页、tab页肯定的页面js肯定是的打在包里面的,我们也有做过拉新的活动页,这个页面就可以放在服务器上,支持热更新啊,就遇到了上面的跳转问题,我是全局取了一个bundlejs的绝对地址,在服务器上的页面也就是我们的拉新活动页面里面直接用这个地址就跳回到本地的页面了- (void)renderWithURL:(NSURL *)url options:(NSDictionary )options data:(id)data{ if (!url) { WXLogError(@“Url must be passed if you use renderWithURL”); return; } self.needValidate = [[WXHandlerFactory handlerForProtocol:@protocol(WXValidateProtocol)] needValidate:url]; WXResourceRequest request = [WXResourceRequest requestWithURL:url resourceType:WXResourceTypeMainBundle referrer:@"" cachePolicy:NSURLRequestUseProtocolCachePolicy]; [self _renderWithRequest:request options:options data:data]; [WXTracingManager startTracingWithInstanceId:self.instanceId ref:nil className:nil name:WXTNetworkHanding phase:WXTracingBegin functionName:@“renderWithURL” options:@{@“bundleUrl”:url?[url absoluteString]:@"",@“threadName”:WXTMainThread}];}Android可以在WXSDKInstance.java里面可以看到render(pageName,template,options,jsonInitData,flag);renderByUrl(pageName,url,options,jsonInitData,flag);安卓和iOS基本类似,但是这儿有两个方法,官网的文档是render,这一个render害死人啊,不过用weex脚手架初始化的项目用的是renderByUrl,所以如果官网直接用脚手架开发的,躲过一劫啊。可以看到这两个方法就差了第二个参数,下面是官网的代码,用render方法的时候,第二个参数里面得用WXFileUtils.loadFileContent这个方法,而且如果option不填的话bundleUrl是取不到js地址?后面我们加的参数的/ * WXSample 可以替换成自定义的字符串,针对埋点有效。 * template 是.we transform 后的 js文件。 * option 可以为空,或者通过option传入 js需要的参数。例如bundle js的地址等。 * jsonInitData 可以为空。 * width 为-1 默认全屏,可以自己定制。 * height =-1 默认全屏,可以自己定制。 */mWXSDKInstance.render(“WXSample”,WXFileUtils.loadFileContent(“hello.js”, this), null, null, -1, -1, WXRenderStrategy.APPEND_ASYNC);下面就来说说几种常见的跳转吧Native -> Weex也就是用开头我们提到的两个方法,然后把我们的bundlejs地址传入就可以打开Weex页面了Weex -> Native这个我这边用的很少,大概思路就是,拦截处理,iOS用Scheme、[[UIApplication sharedApplication] openURL:weburl];吧,Android用intent-filter吧。Weex -> Weex大家在我的helloworld那篇bundlejs小节里面有一个路径截图,可以对比代码参考一下,下面我也把iOS和Android的bundlejs路径图截出来了。const device = weex.config.env;getBaseUrl(url) { if (device.platform === ‘iOS’) { nativeBase = url.substring(0, bundleUrl.lastIndexOf(’/’) + 1); } else { nativeBase = ‘file://assets/dist/’; }}Weex页面A:Weex页面B的地址BUrl=getBaseUrl(weex.config.bundleUrl)+B.jsnavigator.push({url:BUrl}, function(e) {});webview -> Weex这也是比较常遇到的一个跳转,大概思路和Weex跳原生类似,也是一个拦截处理,这一块就放在后面Weex系列(7) —— web组件和webview这个章节讲吧。页面通信这块用的比较多的大概有三种。bundlejs路径地址传参就如标题描述的一样,weex.config.bundleUrl拿到类似A.js?a=1&b=2,和解析网页地址一样,拿到A传给B的a、b后面的值。BroadcastChannel我们在B页面操作完成之后,navigator.pop()之后回到A页面,希望A页面的button状态改变,因为页面是栈式操作,A页面不会自动刷新,可以用Weex提供的这个BroadcastChannel通道来解决,具体操作,大家点击标题就可以了解了。storage感觉这个大家应该非常熟悉,对,Weex也提供给我们了,我们可以愉快的在页面上使用了。globalevent最后还是提一下这个globalEvent,Weex和原生通过这个可以通信,这个我们用到的还是挺多的,大家也可以去官网了解一下呦。就这么多吧,欢迎大家关注我的专栏啊,如果有一点点喜欢,也请点个赞啊~ ...

November 14, 2018 · 1 min · jiezi