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,剩下的一切都非常熟悉了,放到你期望的容器中显示即可。

Logo

前往低代码交流专区

更多推荐