Weex是阿里开源的类React Native技术,其实按知乎上的讨论基本可以说是整合Vue.js+React Native造的轮子(如何看待阿里无线前端发布的Weex?)。Weex是一款轻量级的移动端跨平台动态性技术解决方案,主要致力于使用Web方式开发出Native性能的App。Weex学习成本较RN相对较低, 可以说就是使用简易版的HTML/CSS/JavaScript以及自定义的一些组件和规则开发.we文件,完成界面控件布局、样式、数据绑定以及简单的事件绑定等。Weex提供了node.js的小工具可以将.we文件转换成js bundle 文件,客户端引入了weex sdk就可以解析js bundle文件,最终完成页面渲染。接下来在简单尝试下weex。
weex配置
.we文件开发其实任何编辑器都可以,不过需要使用Node安装Weex提供的.we文件转换工具,该工具可以将.we文件转换成weex sdk能够识别和解析的js bundle文件。
npm install -g weex-toolkit
主要介绍下Android端配置:
1、首先引入weex sdk,gradle添加weex依赖
compile 'com.taobao.android:weex_sdk:0.5.1@aar'
2、确保声明了网络权限
<uses-permission android:name="android.permission.INTERNET" />
3、配置ImageView加载网络图片形式
weex需要我们手动配置网络图片加载,否则imageview将无法正常工作。通常我们可以使用第三方图片加载库,这里我引入了Picasso来帮助我们加载网络图片。配置时机可以放在Application初始化中,这样全局有效。
private void initWeex(){
InitConfig.Builder configBuilder = new InitConfig.Builder().setImgAdapter(new IWXImgLoaderAdapter() {
@Override
public void setImage(String url, ImageView view, WXImageQuality quality, WXImageStrategy strategy) {
Picasso.with(getApplicationContext()).load(url).into(view);
}
});
WXSDKEngine.initialize(this, configBuilder.build());
}
通过以上配置,客户端基本的weex环境也就配置好了。
.we文件开发
.we文件主要包括三大部分,<template></template>
声明组件,<style></style>
定义组件样式,<script></script>
声明组件data、events,业务逻辑处理等,基本类似web端开发。template中可以使用{{}}进行data binding,将script中的data和events绑定到相应的组件中。具体语法不再介绍,下面是一个类似ViewPager的自动轮播banner例子。
<template>
<div style="flex-direction: column;">
<slider class="slider" interval="2000" auto-play="true">
<div class="slider-pages" repeat="{{headline}}" onclick="openUrl(headline[$index].url)">
<image class="image" src="{{image}}"></image>
<text class="title">{{title}}</text>
</div>
<indicator class="indicator" if="shouldShowIndicators()"></indicator>
</slider>
</div>
</template>
<style>
.image {
width: 750;
height: 260;
}
.title {
margin-top: 20;
margin-bottom: 20;
text-align: left;
flex: 1;
color: black;
font-size: 35;
}
.slider {
width: 750;
height: 450;
}
.slider-pages {
padding-top: 30;
flex-direction: column;
width: 750;
height: 400;
}
.indicator {
height: 20;
width: 750;
position:absolute;
left: 1;
bottom: 1;
item-color: grey;
item-selectedColor: orange;
item-size: 20;
}
</style>
<script>
var weexModule = require('@weex-module/weexModule');
module.exports = {
data: {
headline:[]
},
methods: {
openUrl: function (url) {
weexModule.startActivity(url, function(err){
console.log(err);
});
},
shouldShowIndicators: function(){
return this.headline.length > 1;
}
}
}
</script>
style默认屏幕宽度为750px,所以如果组件宽度为整屏宽度,直接定义为750即可。绑定onclick事件,其实只是属性设置并不是方法调用,如果不带对应方法不带参数直接使用方法名即可,但是其他地方如果进行方法调用,必须得加(),表示方法的调用,例如if="shouldShowIndicators()"
。
自定义Module
在script中我们引用了自定义的Module,负责与Native端通信,处理具体的业务逻辑。要引用自定义Module需要事先使用WXSDKEngine的registerModule方法进行注册,可以在Application启动时注册一些通用的Module,也可以在需要使用时再去注册一些具体业务逻辑Module。
try {
WXSDKEngine.registerModule("weexModule", WeexModule.class);
} catch (WXException e) {
e.printStackTrace();
}
自定义Module时,方法访问权限必须声明为public,并且必须使用@WXModuleAnno注解标识。这里,weexModule是个简单的负责Activity跳转的Module,并且回调了Activity启动结果。对应的WeexModule代码如下:
public class WeexModule extends WXModule {
@WXModuleAnno
public void startActivity(String url, String cb){
Log.d("weex", "========" + url);
boolean error = false;
try {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
mWXSDKInstance.getContext().startActivity(intent);
} catch (ActivityNotFoundException e) {
error = true;
}
Map<String, Object> result = new HashMap<>(1);
result.put("error", error);
WXBridgeManager.getInstance().callback(mWXSDKInstance.getInstanceId(), cb, result);
}
}
自定义Component
weex目前只支持一些常用的组件,如果有需要,只要遵循weex规范,我们完全可以自定义组件。在配置weex客户端环境时,我们使用了第三方图片请求库Picasso使得imageview能够直接加载url。其实公司项目里已经有了强大的自造轮子——NetworkImageView,我们并不想引用其他库增加App size,但是template中无法直接使用Native组件,我们需要自定义Component进行简单的包装。例如:
@Component(lazyload = false)
public class NetworkImageViewComponent extends WXImage {
public WeexComponent(WXSDKInstance instance, WXDomObject dom, WXVContainer parent, boolean isLazy) {
super(instance, dom, parent, isLazy);
}
@Override
protected void initView() {
if (mContext != null) {
mHost = new NetworkImageView(mContext); // 替换原生组件
((NetworkImageView) mHost).setScaleType(ImageView.ScaleType.CENTER_CROP);
}
}
@Override
public View getView() {
return super.getView();
}
@WXComponentProp(name = "url")
public void setImageUrl(String url) {
((NetworkImageView) mHost).setImage(url);
}
}
如上代码所示,通常自定义Component只需要重写initView方法,替换mHost为所需的原生组件即可。使用@WXComponentProp注解可以为组件添加自定义属性,在template中声明组件时设置属性就可以调用对应方法。最后和Module类似,自定义Component也需要向weex注册,可以在Application启动时注册通用Component,也可以在需要时注册业务耦合较大的Component。
try {
WXSDKEngine.registerComponent("myimageview", NetworkImageViewComponent.class);
} catch (WXException e) {
e.printStackTrace();
}
经过以上操作我们已经可以在template中使用我们自定义的MyImageView了。简单修改下.we文件:
<template>
<div style="flex-direction: column;">
<slider class="slider" interval="2000" auto-play="true">
<div class="slider-pages" repeat="{{headline}}" onclick="goWeexSite(headline[$index].url)">
<MyImageView class="image" url="{{image}}"></MyImageView>
<text class="title">{{title}}</text>
</div>
<indicator class="indicator" if="shouldShowIndicators()"></indicator>
</slider>
</div>
</template>
这里值得注意的是,.we文件中的MyImageView在通过weex自动转换工具转换成的js bundle文件中type被标识为"myimageview",也就是说会转换为全小写,因此在注册Component时应该尽量使用小写key。当然我们也可以手动修改js bundle文件,不过自定义组件多了会比较繁琐。
Native端渲染
客户端渲染工作主要包括解析js bundle文件还原Native端组件,至于数据请求可以直接在Native端发送网络请求,也可以在script中通过js调用weex内置的网络请求Module——WXStreamModule的sendHttp方法进行网络请求,请求到的数据会通过WXBridgeManager回调给js端。测试时为了方便,直接在Native端进行网络请求,然后将请求到的数据塞给weex,weex进行render渲染,渲染成功后更新listview,将最终得到的native view塞进listview。调用weex渲染的主要代码如下:
JSONObject json = new JSONObject();
json.put("headline", array); //key和js端对应,相当于将数据塞进.we文件的data中
String template = WXFileUtils.loadFileContent("weex/index.js", getContext());
if (weexLayout == null) {
wxsdkInstance.render("headline", template, null, json.toString(), -1, -2, WXRenderStrategy.APPEND_ASYNC);
} else {
wxsdkInstance.refreshInstance(json.toString());
}
测试时直接使用的客户端本地的由.we文件转换来的js bundle文件,生产环境通常应该从服务端拉取。需要注意的是,一个WXSDKInstance实例只负责一次页面渲染,如果想重新加载模版渲染页面,需要将WXSDKInstance实例destroy再重新new一个,如果后期只是更新数据直接refresh即可。另外,使用WXSDKInstance.registerRenderListener可以注册对渲染结果的监听。
@Override
public void onViewCreated(WXSDKInstance instance, View view) {
weexLayout = view; // 首次渲染成功拿到native view
}
@Override
public void onRenderSuccess(WXSDKInstance instance, int width, int height) {
adapter.notifyDataSetChanged(); // 更新listview
}
@Override
public void onRefreshSuccess(WXSDKInstance instance, int width, int height) {
adapter.notifyDataSetChanged(); // 更新listview
}
@Override
public void onException(WXSDKInstance instance, String errCode, String msg) {
Log.d("weex", "=======" + msg);
}
onViewCreated会在首次渲染成功后回调,拿到了native view,剩下的一切都非常熟悉了,放到你期望的容器中显示即可。