Weex实战开发

作者: 入夜_渐微凉 | 来源:发表于2018-08-09 16:36 被阅读41次

    俗话说光说不练假把式。那就先来感受下Weex的魅力:

    效果图.png
    目前项目已上传至GitHub上,需要的可自行前去下载:
    原生项目(Xcode)
    Weex项目(WebStorm)

    一.目的

            Weex虽然从阿里爸爸把它生出来也有两年时间了,但我想对于广大开发者来说,可能对它的了解少之又少。对于这种新鲜的事物,我们总是要持有敬畏的态度的,因为当你在进一步了解它的时候,你会发现它有无穷的魅力在吸引你。我当初就是被它的魅力深深吸引,想更深入的了解,但我在度娘那里没有找到多少真正的项目实战,即使有也是比较笼统的讲了一下大概,没有详细的介绍。因此我写此篇文章的目的是帮助那些刚刚开始接触Weex又急于想找个项目练手的新手玩家们,我会以一个初学者的角度,尽可能的讲解项目的每个细节。

    二.声明

            对于小白:如果在这之前你没听说过Weex,那很好,这篇文章你可以读一读。啥?没兴趣?那我可以把Weex的广告语透露给你,Write Once, Run Everywhere。点我

            对于新手:如果你是刚刚开始接触并且跃跃欲试的新手玩家,正想找个真实项目练练手。那更好,这篇文章对你在合适不过了。跟我一起,边学边练。我会一步一步的介绍整个项目的流程。

            对于大佬:如果您是Weex大佬,那更好了。不要走,留下联系方式,小弟我有点问题想跟您请教请教。

    学习门槛:

    1.Vue:Vue语言基础

    2.ES6:ECMAScript 6 入门

    3.iOS或者安卓开发语言和编辑器基本使用

            还有一点我觉得有必要先声明一下,由于本人的文字能力有限,有些地方语言表达可能不太清楚,文章排版可能不太清晰,但是这又有什么关系呢,再大的困难也挡不住大家的热情啊!由于内容很长而本人时间有限,只能在工作之余写一些东西,所以打算不定时更新,不便之处还请谅解。好了废话不多说了,开始入正题吧。。。


    三.那就开始吧

    开发环境:macOS 10.13.4

    开发工具:WebStorm 2018.1 Xcode 9.3

            本项目采用的是集成的方式,即将 Weex 集成到已有的应用。为什么呢?原因有两点,一:Weex TabBar(标签栏)和NavigationBar(导航栏)不太好用,需要用到第三方的组件,我想与其用第三方的不如直接用原生代码写了。二:本人认为项目中还是需要用原生的代码的,毕竟像Weex这种新生事物,很多地方有待完善,完全依赖未免在有些地方会存在力不从心,所以为了增强代码可控性,我建议使用集成的方式。(个人看法,不喜勿喷)

    新建原生工程:

    1.新建一个Xcode工程,建立目录结构如下:(里面的类文件暂时先不要建,之后会慢慢的一一说明) 原生项目结构

    2.通过cocoaPods向项目中导入最新版本的WeexSDK,在 Podfile 文件中添加如下内容:(至于cocoaPods怎么使用就在这就不多赘述了,不会的可以去问问度娘)

    target 'MeiTuan' do
      # Uncomment the next line if you're using Swift or would like to use dynamic frameworks
      # use_frameworks!
    
      # Pods for MeiTuan
      pod 'WeexSDK'
    
      target 'MeiTuanTests' do
        inherit! :search_paths
        # Pods for testing
      end
      target 'MeiTuanUITests' do
        inherit! :search_paths
        # Pods for testing
      end
    end
    

            打开命令行,切换到你已有项目 Podfile 这个文件存在的目录,执行 pod install,没有出现任何错误表示已经完成环境配置。至此,原生的工程就算大功告成了。

    新建Weex工程:

    1.新建一个Weex工程,建立目录结构如下:(里面的类文件暂时先不要建,之后会慢慢的一一说明)

    Weex项目结构.png
    啥?你告诉我你不会!那不可能吧,既然都来实战了,我默认你会这些基本的操作了啊。哪有都上阵打仗了不会用枪的道理。。。(不会请看这里

    2.下一步就是进入刚刚创建的文件夹,并且安装依赖,然后执行 npm start:

    cd your`s project file
    npm install
    npm start

            然后工具会启动一个本地的 web 服务,监听 8081 端口。

    3.使用WebStorm打开Weex项目(Weex编译器有很多比如还有Sublime等等,使用哪个看个人爱好了)。在WebStorm里面打开2个终端,依次执行npm run build,npm run serve两条命令。 npm run build.png npm run serve.png         然后在项目下会自动生成一个叫dist文件夹,里面的index.js文件就是我们需要放到服务器上的。当执行完npm run serve命令后,浏览器会自动打开一个窗口,名叫Weex Preview,可以动态查看页面在Web 下的渲染效果。 源代码在 src/ 目录中,你可以像一个普通的 Vue.js 项目一样来开发。
    Weex Preview.png

            当你出现这种页面的时候,那么恭喜你,你的Weex工程算是新建好了。


    开始写代码

    1.打开原生工程
    (1)创建GlobalDefine文件,里面加1条宏,其值就是Weex工程中index.js文件的路径。这样做的好处就是当我们在Weex项目中修改好代码之后,原生项目只需要重新加载一次js文件就可以同步看到修改之后的效果,不需要每次都拷贝过来,然后在build一次原生项目。

    #define HomeJS @"/Users/peter/Desktop/weexCode/weexDemo/dist/index.js"
    

    (2)创建.pch文件,为以后类文件引用做准备。

    #import <WeexSDK/WeexSDK.h>
    #import "GlobalDefine.h"
    

    (3)在info.plist中添加Allow Arbitrary Loads并设置值为YES(不会就点我)。如果不设置会无法进行http请求哦,当然也加载不了网络图片咯。
    (4)由于weexSDK 目前没有提供图片下载的能力,在WXImgLoaderProtocol 定义了一些获取图片的接口, image 组件正是通过 WXImgLoaderProtocol 获得并展示图片,我们可以实现该 protocol 中的接口方法,这样 image 标签才能正常展示图片。这就需要我们自定义handler并注册了。在WeexCustom目录下创建WXImgLoaderDefaultImpl类,实现WXImgLoaderProtocol协议里面的方法。

    @implementation WXImgLoaderDefaultImpl
    #pragma mark -
    #pragma mark WXImgLoaderProtocol
    - (id<WXImageOperationProtocol>)downloadImageWithURL:(NSString *)url imageFrame:(CGRect)imageFrame userInfo:(NSDictionary *)userInfo completed:(void(^)(UIImage *image,  NSError *error, BOOL finished))completedBlock
    {
        if ([url hasPrefix:@"//"]) {
            url = [@"http:" stringByAppendingString:url];
        }
        return (id<WXImageOperationProtocol>)[[[SDWebImageManager sharedManager] imageDownloader]downloadImageWithURL:[NSURL URLWithString:url] options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
        } completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished) {
            if (completedBlock) {
                completedBlock(image, error, finished);
            }
        }];
    }
    @end
    

    (5)在NativeFile下面创建四个Controller分别对应底部四个标签栏。并且自定义TabBarController文件继承系统的UITabBarController作为项目的根控制器。

    @implementation TabBarController
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.view.backgroundColor = [UIColor whiteColor] ;
        [self setViewControllers:self.allControllers animated:NO];
    }
    - (NSArray *)allControllers{
        if (_allControllers == nil) {
            HomeViewController *home = [[HomeViewController alloc] init] ;
            ShopViewController *shop = [[ShopViewController alloc] init] ;
            MineViewController *mine = [[MineViewController alloc] init] ;
            MoreViewController *more = [[MoreViewController alloc] init] ;
            
            NSArray *array = @[[self navWithRoot:home title:@"首页" image:@"icon_tabbar_homepage" selectedImage:@"icon_tabbar_homepage_selected"],
                              [self navWithRoot:shop title:@"商家" image:@"icon_tabbar_merchant_normal" selectedImage:@"icon_tabbar_merchant_selected"],
                              [self navWithRoot:mine title:@"我的" image:@"icon_tabbar_mine" selectedImage:@"icon_tabbar_mine_selected"],
                              [self navWithRoot:more title:@"更多" image:@"icon_tabbar_misc" selectedImage:@"icon_tabbar_misc_selected"]];
            _allControllers = [[NSArray alloc] initWithArray:array];
        }
        return _allControllers;
    }
    - (UINavigationController *)navWithRoot:(UIViewController *)vc title:(NSString *)title image:(NSString *)image selectedImage:(NSString *)selectedImage {
        UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc];
        UIImage *imageNormal = [UIImage imageNamed:image];
        UIImage *imageSelected = [UIImage imageNamed:selectedImage];
        UITabBarItem *tabBarItem = [[UITabBarItem alloc] initWithTitle:title image:[[imageNormal bp_scaleWithSize:CGSizeMake(30, 30)] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal] selectedImage:[[imageSelected bp_scaleWithSize:CGSizeMake(30, 30)] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]];
        tabBarItem.titlePositionAdjustment = UIOffsetMake(0, -2) ;
        [tabBarItem setTitleTextAttributes:[NSDictionary dictionaryWithObject:[UIColor orangeColor] forKey:NSForegroundColorAttributeName] forState:UIControlStateSelected] ;
        nav.tabBarItem = tabBarItem ;
        return nav;
    }
    @end
    

    (6)在WeexConfig目录下创建WeexSDKManager类,用来对WeexSDK的初始化,以及相关自定义组件的注册都可以放在该类里面(我们前面自定义的图片下载WXImgLoaderDefaultImpl就放在这里面注册)。

    + (void)setup;
    {
        [self initWeexSDK];
        [self loadCustomContain];
    }
    
    + (void)initWeexSDK
    {
        [WXAppConfiguration setAppGroup:@"AliApp"];
        [WXAppConfiguration setAppName:@"WeexDemo"];
        [WXAppConfiguration setAppVersion:@"1.8.3"];
        [WXAppConfiguration setExternalUserAgent:@"ExternalUA"];
        [WXSDKEngine initSDKEnvironment];
    #ifdef DEBUG
        [WXLog setLogLevel:WXLogLevelLog];
    #endif
        //自定义组件的注册
        [WXSDKEngine registerHandler:[WXImgLoaderDefaultImpl new] withProtocol:@protocol(WXImgLoaderProtocol)];
        [WXSDKEngine registerModule:@"HomeViewController" withClass:NSClassFromString(@"HomeViewController")];
        [WXSDKEngine registerComponent:@"PeterSwitch" withClass:NSClassFromString(@"PeterSwitch")];
    }
    + (void)loadCustomContain
    {
        [[UIApplication sharedApplication] delegate].window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
        [[UIApplication sharedApplication] delegate].window.window.backgroundColor = [UIColor whiteColor];
        TabBarController *demo = [[TabBarController alloc] init];
        [[UIApplication sharedApplication] delegate].window.rootViewController = demo;
        [[[UIApplication sharedApplication] delegate].window makeKeyAndVisible];
    }
    

    (7)WeexSDKManager对外提供setup的类方法,在AppDelegate的didFinishLaunchingWithOptions方法里面调用。

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        // Override point for customization after application launch.
        [WeexSDKManager setup];
        return YES;
    }
    

    (8)万事俱备,接下来需要我们把WeexSDK用起来啊,这就是使用SDK将打包生成的js文件解析成各平台原生组件的过程。进入HomeViewController(首页),代码如下。

    @interface HomeViewController ()
    @property (nonatomic, strong) WXSDKInstance *instance;
    @property (nonatomic, strong) UIView *weexView;
    @end
    @implementation HomeViewController
    WX_EXPORT_METHOD(@selector(weexRender))
    WX_EXPORT_METHOD(@selector(iosRender))
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view.
        //隐藏系统导航栏
        [self.navigationController setNavigationBarHidden:YES animated:NO];
        [self iosRender];
    }
    - (void)iosRender
    {
        CGFloat width = self.view.frame.size.width;
        CGFloat height = self.view.frame.size.height;
        [_instance destroyInstance];
        _instance = [[WXSDKInstance alloc] init];
        _instance.viewController = self;
        _instance.frame = CGRectMake(0, 0, width, height-49);
    
        __weak typeof(self) weakSelf = self;
        _instance.onCreate = ^(UIView *view) {
            [weakSelf.weexView removeFromSuperview];
            weakSelf.weexView = view;
            [weakSelf.view addSubview:weakSelf.weexView];
            UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, weakSelf.weexView);
        };
        _instance.onFailed = ^(NSError *error) {
            WXLogDebug(@"%@", @"Render onFailed...");
        };
        _instance.renderFinish = ^(UIView *view) {
            WXLogDebug(@"%@", @"Render Finish...");
        };
        _instance.updateFinish = ^(UIView *view) {
            WXLogDebug(@"%@", @"Update Finish...");
        };
        //这里的HomeJS就是全局的宏定义
        NSURL *URL = [NSURL fileURLWithPath:HomeJS];
        [_instance renderWithURL:URL options:@{@"bundleUrl":URL.absoluteString} data:nil];
    }
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        //只要点击屏幕就会调用这个方法,重新解析一次js文件,这样做的好处就是不需要重新build项目就能刷新js
        [self iosRender];
    }
    - (UIStatusBarStyle)preferredStatusBarStyle
    {   //修改顶部状态栏(电池栏)颜色为白色
        return UIStatusBarStyleLightContent;
    }
    - (void)dealloc
    {   //控制器销毁的时候要做相应处理
        [_instance destroyInstance];
    }
    @end
    
    (9)这些完成之后build一下原生项目,之后自动启动Xcode自带模拟器,神奇的一幕出现了。哇!成就感爆棚有木有,之前的一切努力都是值得的,这就是前端开发的魅力所在。 start.png

            至此我们原生部分代码就可以告一段落了,之后在写“商家”、“我的”和“更多”的时候还需要再回来,接下里我们大部分工作都会在Weex项目中完成。
    2.打开Weex项目
    (1)在Home目录下创建Home.vue文件,用来写首页。
    (2)进入index.vue,将Home.vue引入进来。

    <template>
        <home></home>
    </template>
    <script>
        //用这种方式引入vue组件
        import home from '../src/MeiTuan/Home/Home';
        export default {
            name: 'App',
            data () {
                return {
                }
            },
            //在components里面声明然后才能使用
            components:{
                home
            }
        }
    </script>
    //scoped-以表示它的样式作用于当下的模块,很好的实现了样式私有化的目的
    <style scoped>
    </style>
    

    (3)新建globalDefine.js文件,放一些全局的变量

    exports.apiUrl = {
        resUrl:'http://192.168.0.225:8081/images/'
    }
    

    首页

    1.顶部导航栏:

    <template>
        <!--Weex的template里面有且只能有一个div标签作为跟标签-->
        <div class="container">
            <!--导航栏-->
            <div class="navgationContainer">
                <div class="navigation">
                    <!--地理位置-->
                    <div class="locationContainer">
                        <text style="color: white">上海△</text>
                    </div>
                    <!--搜索框-->
                    <div class="search">
                        <!--图标-->
                        <image :src="searchIcon" style="width: 44px;height: 44px;margin-left: 10px"></image>
                        <input  style="margin-right: 10px;margin-left: 10px;font-size: 30px;flex: 1" placeholder="输入商家、品类、商圈"/>
                        <image :src="scanIcon" style="width: 44px;height: 44px;margin-right: 10px"></image>
                    </div>
                    <div style="flex-direction: row; flex: 0.3;justify-content: center;align-items: center">
                        <!--地图-->
                        <image :src="mapIcon" style="width: 44px;height: 44px;margin-right: 5px"></image>
                        <text style="color: white">地图</text>
                    </div>
                </div>
            </div>
        </div>
    </template>
    <script>
        //图片地址采用基础地址加名称的方式拼接
        var globalDefine = require('../../globalDefine');
        export default {
            data(){
                return{
                    searchIcon:globalDefine.apiUrl.resUrl + 'search.png',
                    scanIcon:globalDefine.apiUrl.resUrl + 'scan.png',
                    mapIcon:globalDefine.apiUrl.resUrl + 'map.png',
                }
            }
        }
    </script>
    <style scoped>
        .navgationContainer{
            height: 128px;
            background-color: rgba(255,96,0,1.0);
        }
        .navigation{
            flex-direction: row;
            height: 88px;
            margin-top: 40px;
            align-items: center;
        }
        .search{
            flex: 1;
            flex-direction: row;
            background-color: white;
            justify-content: space-between;
            align-items: center;
            margin-left: 20px;
            margin-right: 20px;
            border-radius: 8px;
            height: 60px;
        }
    </style>
    

    代码注解:
    1.为了简化页面设计和实现, 屏幕的宽度统一为750像素,不同屏幕会按照比例转化为这一尺寸。
    2.标准CSS支持很多样式选择器, 但Weex目前只支持单个类的选择器。
    3.标准CSS支持很多的长度单位,Weex目前只支持像素,并且px在样式中可以忽略不写, 直接使用对应的数值。
    4.标准CSS包含了非常多的样式属性,但Weex只支持了其中的一部分,包括盒模型,flexbox,position等布局属性。以及font-size,color等样式。
    5.v-bind动态绑定指令,默认情况下标签自带属性的值是固定的,在为了能够动态的给这些属性添加值,可以使用v-bind:你要动态变化的值="表达式"。
    6.v-bind用于绑定属性和数据 ,其缩写为“ : ” 也就是v-bind:src === :src。
    7.项目中图片地址均采用基础地址+名称的方式拼接,如果出现图片加载不出来的情况可以在globalDefine.js将resUrl更换成自己本机的ip地址即可。(至于Weex的图片导入方式我建议看一下这篇文章:Weex导入图片)
    8.你或许会问为什么我把样式直接写在行内了,个人习惯而已,我喜欢把样式代码比较少的或者不是公共样式采用内联样式,其他的用页内样式。不然一个标签一个class得把我累死。

            当我们执行了npm run serve命令之后,我们每一次改变都会自动在Weex Preview渲染,相应的我们点击iOS模拟器重新加载index.js文件会得到最新的页面渲染效果。


    首页导航栏.png

    2.导航栏做好了接下来就是正文的列表页,整个列表用一个scroller组件包装,里面的每一个cell分开来写,这样可以减轻首页的代码量。

    顶部分页视图


    首页顶部视图.png
    <template>
        <div class="tab" style="background-color: white;flex: 1;height: 380px">
            <slider class="slider" auto-play="true" interval="3000" @change="onchange">
                <div style="width: 750px">
                    <div v-for="(v,i) in items2" style="flex-direction: row;margin-top: 36px;width: 750px">
                        <div v-for="(item,k) in v" style="flex: 1;justify-content: center;align-items: center">
                            <image :src="item.icon" style="width: 88px;height: 88px"></image>
                            <text style="font-size: 30px">{{item.name}}</text>
                        </div>
                    </div>
                </div>
                <div style="width: 750px">
                    <div v-for="(v,i) in items3" style="flex-direction: row;margin-top: 36px;width: 750px">
                        <div v-for="(item,k) in v" style="flex: 1;justify-content: center;align-items: center">
                            <image :src="item.icon" style="width: 88px;height: 88px"></image>
                            <text style="font-size: 30px">{{item.name}}</text>
                        </div>
                    </div>
                </div>
                <indicator class="indicatorClass"></indicator>
            </slider>
        </div>
    </template>
    <script>
            methods: {
                onchange (event) {
                    console.log('changed:', event.index)
                }
            }
        }
    </script>
    

    代码注解:
    1.由于篇幅的原因我就不把所有代码都截上来了,这里只选取相对重要的部分,需要的童鞋请前去下载完整项目。
    2.为什么用slider而不用scroller?slider组件用于在一个页面中展示多个图片,在前端,这种效果被称为轮播图。它支持任意类型的Weex组件作为其子组件,而且它有一个专属子组件—indicator用于显示轮播图指示器效果,这个indicator必须充当slider组件的子组件使用才有效果。
    3.@change="onchange",slider的事件,当轮播索引改变时,触发该事件。
    4.<div v-for="(v,i) in items">
            <div v-for="(item,k) in v" >
            </div>
       </div>
        循环创建每一个item,注意v-for语句的写法。如果只是一重循环直接v-for="item in items"就可以了,其中item就是items里面的每一个元素,在其子组件中可以直接使用item赋值。
    5.indicator作为子组件之间写在slider里面就可以了,他会自动随着slider的滑动而改变指示器。
    6.text组件只能包含文本值,你可以使用 {{}} 标记插入变量值作为文本内容。不支持子组件。

    首页中间视图


    首页中间的view.png
    <template>
        <div class="container">
            <!--左边view-->
            <div class="leftView">
                <image :src="leftViewTopImage" style="width: 240px;height: 60px;margin-top: 40px"></image>
                <image :src="leftViewMiddleImage" style="width: 240px;height: 120px"></image>
                <text style="color: darkgray;font-size: 34px">探路组碳烤鱼</text>
                <div style="flex-direction: row">
                    <text style="color: cyan;font-size: 28px">¥9.5</text>
                    <text style="color: darkorange;background-color: khaki;font-size: 28px">再减3元</text>
                </div>
            </div>
    
            <!--右边view-->
            <div class="rightView">
                <div class="rightViewTopView">
                    <div>
                        <text style="color: darkorange;font-size: 32px;margin-left: 20px">天天特价</text>
                        <text style="color: #717171;font-size: 32px;margin-left: 20px;margin-top: 10px">特惠不打烊</text>
                    </div>
                    <image :src="rightViewTopImage" style="width: 150px;height: 120px;"></image>
                </div>
                <div class="rightViewBottomView">
                    <div>
                        <text style="color: crimson;font-size: 32px;margin-left: 20px">一元吃</text>
                        <text style="color: #717171;font-size: 32px;margin-left: 20px;margin-top: 10px">一元吃美食</text>
                    </div>
                    <image :src="rightViewBtttomImage" style="width: 150px;height: 120px"></image>
                </div>
            </div>
        </div>
    </template>
    

    代码注解:
    此处没啥好说的,常规UI布局,注意Flexbox布局技巧。

    首页促销视图


    首页促销视图.png

    如需转载,请注明出处,谢谢~~~

    相关文章

      网友评论

      • xing222333:ios源码 跑不起来
      • louis的小仓库:你好,最近打算学习APP开发,在react native和weex间纠结,我对vue比较熟悉,想选weex,但是到处都说weex坑太多,前辈能不能给点建议,表示react native的写法很不适应啊
      • ca865df9a39b:想问一下,weex的storage模块,getItem的值怎么绑定在data中
        ca865df9a39b:@入夜_渐微凉 我只能在getItem这个函数内获得我存储的值,出了这个函数就获取不到了
        入夜_渐微凉:@陈小池池 weex的官方文档了写的很清楚啊:wink: http://weex.apache.org/cn/references/modules/storage.html
      • TGTLo:hi,页面跳转该怎么写?
        TGTLo:@入夜_渐微凉 恩,文档里看到了navigator了。
        入夜_渐微凉:@TGTLo 兄弟,页面跳转的话有很多种方式,比如可以用weex自带的navigator模块,这个可以实现weex页面之间的跳转。还可以借助原生的容器vc,用拓展的方式来实现weex到原生页面的之间跳转。这些我以后都会在文章里加上的哦。

      本文标题:Weex实战开发

      本文链接:https://www.haomeiwen.com/subject/kldcbftx.html