weex自定义组件的探索

赵小温

weex学习一段时间后,开始针对点我达骑手App上相应功能做一些尝试,比如登录、侧边栏、地图、定位、浮层等,其中侧边栏的实现过程比较曲折,参考了官网和源码部分,涉及到数据绑定、手势、Component扩展、Module扩展等知识,本文将通过侧边栏的实现过程来介绍一下weex的基础布局和android如何提供具有交互效果的自定义组件,涉及到了自定义组件的全部知识。

一、js方式

首先想到的是使用weex提供的手势来实现,即使用js的方式。 weex提供四种手势类型:Touch、Pan、Swipe、LongPress,这里采用Pan来实现滑动监听。
panstart:将在触摸到触摸面上时触发
panmove:将在触摸点在触摸面移动时被触发
panend:将在从触摸面离开时被触发
可以使用手势回调的数据changedTouches中screenX(触摸点相对于屏幕左侧边缘的X轴坐标)计算滑动距离,实现侧边栏的伸缩控制。

计算方法:

onTuoucMove(event){ if(this.tempX !== 0){ var d = this.tempX - event.changedTouches[0].screenX; if(this.leftX - d>0){ this.leftX = 0; }else if(this.leftX -d<-550){ this.leftX = -550; }else{ this.leftX = this.leftX - d; } } this.tempX = event.changedTouches[0].screenX; }

550:侧边栏的宽度;
leftX:侧边栏缩进的宽度;
滑动过程中触发panmove事件,panmove事件调用onTuoucMove方法计算侧边栏的缩进宽度。

布局代码如下:

<template> <div class="wrapper" :style="{height:screenHeight}"> <div ref="main" class="main-div" :style="{height:screenHeight}"> <text class="title">点我达主页</text> </div> <div ref="left" class="left-div" :style="{left:leftX, height:screenHeight}" @panstart="onTuoucStart" @panmove="onTuoucMove"> <text class="left-title" @click="panClick">点我达侧边栏</text> </div> </div> </template>
通过v-bind命令将计算得出的leftX绑定给侧边栏div的left属性,代码-> :style="{left:leftX, height:screenHeight}",':'是v-bind的简写。

测试结果:IOS、Android都可以实现侧边栏功能,但Android平台上卡顿现象比较严重。 alt 二、自定义Component

查看weex的源码会发现,weex的div等控件是使用android系统的FrameLayout等控件封装的,如果weex自身提供的组件不能满足开发需求,完全可以通过自定义组件来实现,并且android上对侧边栏的实现非常简单。由此就有了一个新的侧边栏方案,通过自定义Component。

weex手册上提到android的扩展:
1.Component 扩展 实现特别功能的 Native 控件。例如:RichTextview,RefreshListview 等。
2.Component 扩展类必须集成 WXComponent。

android侧边栏的实现:
(一).android.support.v4.widget.DrawerLayout和FrameLayout的组合来实现

<android.support.v4.widget.DrawerLayout android:id="@+id/dwd_drawer_layout" android:layout_width="match_parent" android:layout_height="match_parent"> <FrameLayout android:id="@+id/content_frame" android:layout_width="fill_parent" android:layout_height="fill_parent"> </FrameLayout> <FrameLayout android:id="@+id/left_drawer" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_gravity="start"> </FrameLayout> </android.support.v4.widget.DrawerLayout>

dwd_drawer_layout提供滑动控制,contentframe提供主页内容,leftdrawer提供侧边栏内容,
由此猜想我们只要将DrawerLayout和FrameLayout封装成weex的组件即可。

weex手册关于“Component 扩展类必须集成 WXComponent”的说法不够严谨或者说不够全面吧,在源码中发现组件text、image继承的是WXComponent,而div、list继承的是WXVContainer,通过试验证明继承WXComponent的FrameLayout无法提供容器功能(其内的子组件无法显示),而继承WXVContainer后子组件内容即可显示出来。
备注:WXVContainer继承WXComponent

WXDrawerLayout组件的实现

android.support.v4.widget.DrawerLayout的功能:
1.提供DrawerLayout给自定义组件
@Override protected android.support.v4.widget.DrawerLayout initComponentHostView(@NonNull Context context) { android.support.v4.widget.DrawerLayout view = new android.support.v4.widget.DrawerLayout(context); setListener(view); return view; }

initComponentHostView是WXComponent的方法,在该方法里可以create一个view,然后将view返回给自定义组件

2.设置滑动事件监听,并将事件传递给weex

private void setListener( android.support.v4.widget.DrawerLayout view){
view.setDrawerListener(new DrawerLayout.DrawerListener() { @Override public void onDrawerSlide(View drawerView, float slideOffset) { fireEvent("onDrawerSlide"); } @Override public void onDrawerOpened(View drawerView) { fireEvent("onDrawerOpened"); } @Override public void onDrawerClosed(View drawerView) { fireEvent("onDrawerClosed"); } @Override public void onDrawerStateChanged(int newState) { fireEvent("onDrawerStateChanged"); } }); }

设置DrawerLayout的监听,并且通过fireEvent将监听事件传递到weex,fireEvent是weex手册上“Native 和 JS 通信”提到的事件通信,WXComponent做了进一步的封装,可以只提供事件名称,详情可以查看WXComponent源码。

fireEvent("onDrawerClosed")这里“ onDrawerClosed”是事件的名称,weex上可以通过onDrawerClosed方法得到事件通知
<drawerlayout class="drawerlayout" @onDrawerOpened="onDrawerOpened" @onDrawerClosed="onDrawerClosed"></drawerlayout >

3.设置属性

@WXComponentProp(name = "layoutgravity") public void setLayoutGravity(String gravity){ DrawerLayout view = ((DrawerLayout)getHostView()); FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) view.getLayoutParams(); params.width = DrawerLayout.LayoutParams.MATCH_PARENT; params.height = DrawerLayout.LayoutParams.MATCH_PARENT; view.setLayoutParams(params); }

通过注解WXComponentProp可以提供设置属性的方法

4.提供侧边栏打开/收起方法
打开侧边栏: gravity,0-左侧侧边栏,1-右侧侧边栏
@JSMethod(uiThread = true) public void openDrawer(int gravity){ DrawerLayout view = ((DrawerLayout)getHostView()); if(gravity==0 && !view.isDrawerOpen(Gravity.LEFT)){ view.openDrawer(Gravity.LEFT); }else if(gravity==1 && !view.isDrawerOpen(Gravity.RIGHT)){ view.openDrawer(Gravity.RIGHT); } }

收起侧边栏: gravity,0-左侧侧边栏,1-右侧侧边栏
@JSMethod(uiThread = true) public void closeDrawer(int gravity){ DrawerLayout view = ((DrawerLayout)getHostView()); if(gravity==0 && view.isDrawerOpen(Gravity.LEFT)){ view.closeDrawer(Gravity.LEFT); }else if(gravity==1 && view.isDrawerOpen(Gravity.RIGHT)){ view.closeDrawer(Gravity.RIGHT); } }

注解JSMethod是weex手册在“Module 扩展”中提到的可以提供方法给weex调用

总结:通过fireEvent将事件通知给weex,weex可以通过JSMethod调用native的方法,实现了weex和native的双向沟通。

WXDrawerLeft的实现

WXDrawerLeft的实现和WXDrawerLayout类似,唯一不同的是设置属性时,做了一个左侧边栏和右侧边栏的区分
/**左侧侧边栏 */ public static String GravityStart = "left"; /**右侧侧边栏 */ public static String GravityEnd = "right";

@WXComponentProp(name = "layoutgravity") public void setLayoutGravity(String gravity){ FrameLayout view = ((FrameLayout)getHostView()); DrawerLayout.LayoutParams params = (DrawerLayout.LayoutParams) view.getLayoutParams(); params.height = FrameLayout.LayoutParams.MATCH_PARENT; if(TextUtils.equals(gravity,GravityStart)){ params.gravity = Gravity.START; params.topMargin = 0; params.width = FrameLayout.LayoutParams.WRAP_CONTENT; }else if(TextUtils.equals(gravity,GravityEnd)){ params.gravity = Gravity.END; params.topMargin = 0; params.width = FrameLayout.LayoutParams.WRAP_CONTENT; }else{ params.width = FrameLayout.LayoutParams.MATCH_PARENT; } view.setLayoutParams(params); }

weex上组件的使用方法

1.注册组件
在native上通过WXSDKEngine注册组件 WXSDKEngine.registerComponent("drawerlayout",WXDrawerLayout.class); WXSDKEngine.registerComponent("drawerleft",WXDrawerLeft.class);

2.weex的布局
drawerleft是侧边栏布局
<drawerlayout ref="drawerlayout" class="drawerlayout" @onDrawerOpened="onDrawerOpened" @onDrawerClosed="onDrawerClosed"> <div class="drawermain"> <div class="wrapper"> </div> </div> <drawerleft class="drawerleft"> <scroller> </scroller> </drawerleft> </drawerlayout>

ref="drawerlayout",给组件注册引用信息;
@onDrawerOpened="onDrawerOpened",监听onDrawerOpened事件,并通过js方法做相应处理;

3.weex调用组件的方法
在weex上通过openDrawer方法,可以打开侧边栏 openDrawer(){ this.$refs.drawerlayout.openDrawer(0); }

通过vm.$refs.drawerlayout拿到组件的引用,同时可以调用组件提供的方法。

自定义Component方式实现的侧边栏,控制和显示效果和native上效果几乎没有差别,并且还保留了阴影效果。

alt