自定义Weex组件——Weex的学习之路(八)
在四月份和五月份的时候我用业余时间来学习weex,在这期间一直在看文档写demo,每一个组件都自己写demo运行一遍。我本人是做Android开发的,对JS,CSS和Html有一定的了解,所以学习weex不是很难。然后我把自己所学的主要经历和过程用博客记录下来,其目的是想巩固自己的学习知识,还有就是希望能给weex学习的小伙伴一点帮助。最近有一部分小伙伴私信我说,问我weex好学吗、weex学了之后有用吗、weex现在是不是没有人维护了,还有weex和flutter哪个更好?针对这些问题,我后续会专门用一篇博客来统一回答的。不过我还是想说的就是好不好学、有没有用,只有等你学了以后才会知道!
到了这篇博客,我目前weex所有的组件和模块都使用过了,那么我发现,有一些weex原生组件最终在安卓和苹果终端上显示和交互的交过不一样,有一些UI效果内置组件达不到的时候,就需要自定义组件了。自定义组件主要分为两大步骤:
1.重写内置组件
前面几篇博客写的都是比较基础的,那么从这篇博客开始,复杂度就会高一些的。就拿TabPage来说吧,他原生是长这个样子的
TabPage原生样式我们在实际项目中样式也不定和这个相同,所以这个时候我们就需要将源码拷贝下来,根据自己的需求改动代码。还是以TabPage为例,我这里的需求是顶部的tab只有两个,并且这两个tab不是按权重等分的,而是相对来说比较靠近的,并且在下方还要有一条分割线。那么我将TabPage的源码从GitHub上拷贝下来。拷贝地址是:TabPage源码地址
经过改动后的代码:
<template>
<div class="wxc-tab-page"
:style="{ height: (tabPageHeight)+'px', backgroundColor:wrapBgColor }">
<scroller class="tab-title-list"
ref="tab-title-list"
:show-scrollbar="false"
scroll-direction="horizontal"
:data-spm="spmC"
:style="{
backgroundColor: tabStyles.bgColor,
height: (tabStyles.height)+'px',
leftOffset:'100px',
rightOffset: '100px'
}">
<div class="title-item"
v-for="(v,index) in tabTitles"
:key="index"
:ref="'wxc-tab-title-'+index"
@click="setPage(index,v.url, clickAnimation)"
:style="{
width: tabStyles.width +'px',
height: tabStyles.height +'px',
backgroundColor: currentPage === index ? tabStyles.activeBgColor : tabStyles.bgColor,
borderBottomWidth: tabStyles.normalBottomHeight,
borderBottomColor: tabStyles.normalBottomColor
}"
:accessible="true"
:aria-label="`${v.title?v.title:'标签'+index}`">
<image :src="currentPage === index ? v.activeIcon : v.icon"
v-if="titleType === 'icon' && !titleUseSlot"
:style="{ width: tabStyles.iconWidth + 'px', height:tabStyles.iconHeight+'px'}"/>
<text class="icon-font"
v-if="titleType === 'iconFont' && v.codePoint && !titleUseSlot"
:style="{fontFamily: 'wxcIconFont',fontSize: tabStyles.iconFontSize+'px', color: currentPage === index ? tabStyles.activeIconFontColor : tabStyles.iconFontColor}">{{v.codePoint}}</text>
<text
v-if="!titleUseSlot"
:style="{ fontSize: tabStyles.fontSize+'px', fontWeight: (currentPage === index && tabStyles.isActiveTitleBold)? 'bold' : 'normal', color: currentPage === index ? tabStyles.activeTitleColor : tabStyles.titleColor, paddingLeft:(tabStyles.textPaddingLeft?tabStyles.textPaddingLeft:10)+'px', paddingRight:(tabStyles.textPaddingRight?tabStyles.textPaddingRight:10)+'px'}"
class="tab-text">{{v.title}}</text>
<div class="border-bottom"
v-if="tabStyles.hasActiveBottom && !titleUseSlot"
:style="{ width: tabStyles.activeBottomWidth+'px', left: (tabStyles.width-tabStyles.activeBottomWidth)/2+'px', height: tabStyles.activeBottomHeight+'px', backgroundColor: currentPage === index ? tabStyles.activeBottomColor : 'transparent' }"></div>
<slot :name="`tab-title-${index}`" v-if="titleUseSlot"></slot>
</div>
<div v-if="tabStyles.hasRightIcon"
class="rightIcon"
:style="{
top: rightIconStyle.top,
right: rightIconStyle.right,
paddingLeft: rightIconStyle.paddingLeft,
paddingRight: rightIconStyle.paddingRight
}">
<slot name="rightIcon"></slot>
</div>
</scroller>
<div class="tab-page-wrap"
ref="tab-page-wrap"
@horizontalpan="startHandler"
:style="{ height: (tabPageHeight-tabStyles.height)+'px' }">
<div ref="tab-container"
class="tab-container">
<slot></slot>
</div>
<div class="line"></div>
</div>
</div>
</template>
<style scoped>
.wxc-tab-page {
width: 750px;
}
.tab-title-list {
flex-direction: row;
justify-content: center;
align-items: center;
}
.title-item {
justify-content: center;
align-items: center;
border-bottom-style: solid;
}
.border-bottom {
position: absolute;
bottom: 0;
}
.tab-page-wrap {
width: 750px;
overflow: hidden;
}
.tab-container {
flex: 1;
flex-direction: row;
position: absolute;
}
.tab-text {
lines: 1;
text-overflow: ellipsis;
}
.rightIcon {
position: fixed;
background-color: #ffffff;
box-shadow: -50px 0 20px #ffffff;
}
.line {
background-color: #ebebeb;
width: 750px;
height: 2px;
}
</style>
<script>
const dom = weex.requireModule("dom");
const animation = weex.requireModule("animation");
const swipeBack = weex.requireModule("swipeBack");
import { Utils, BindEnv } from "weex-ui";
// import BindEnv from 'weex-ui';
import Binding from "weex-bindingx/lib/index.weex.js";
export default {
props: {
tabTitles: {
type: Array,
default: () => []
},
panDist: {
type: Number,
default: 200
},
spmC: {
type: [String, Number],
default: ""
},
titleUseSlot: {
type: Boolean,
default: false
},
tabStyles: {
type: Object,
default: () => ({
bgColor: "#FFFFFF",
titleColor: "#666666",
activeTitleColor: "#3D3D3D",
activeBgColor: "#FFFFFF",
isActiveTitleBold: true,
iconWidth: 70,
iconHeight: 70,
width: 160,
height: 120,
fontSize: 24,
hasActiveBottom: true,
activeBottomColor: "#FFC900",
activeBottomWidth: 120,
activeBottomHeight: 6,
textPaddingLeft: 10,
textPaddingRight: 10,
leftOffset: 0,
rightOffset: 0,
normalBottomColor: "#F2F2F2",
normalBottomHeight: 0,
hasRightIcon: false
})
},
titleType: {
type: String,
default: "icon"
},
tabPageHeight: {
type: [String, Number],
default: 1334
},
needSlider: {
type: Boolean,
default: true
},
isTabView: {
type: Boolean,
default: true
},
duration: {
type: [Number, String],
default: 300
},
timingFunction: {
type: String,
default: "cubic-bezier(0.25, 0.46, 0.45, 0.94)"
},
wrapBgColor: {
type: String,
default: "#f2f3f4"
},
clickAnimation: {
type: Boolean,
default: true
},
rightIconStyle: {
type: Object,
default: () => ({
top: 0,
right: 0,
paddingLeft: 20,
paddingRight: 20
})
}
},
data: () => ({
currentPage: 0,
gesToken: 0,
isMoving: false,
startTime: 0,
deltaX: 0,
translateX: 0
}),
created() {
const { titleType, tabStyles } = this;
if (titleType === "iconFont" && tabStyles.iconFontUrl) {
dom.addRule("fontFace", {
fontFamily: "wxcIconFont",
src: `url(${tabStyles.iconFontUrl})`
});
}
},
mounted() {
if (swipeBack && swipeBack.forbidSwipeBack) {
swipeBack.forbidSwipeBack(true);
}
if (BindEnv.supportsEBForIos() && this.isTabView && this.needSlider) {
const tabPageEl = this.$refs["tab-page-wrap"];
Binding.prepare &&
Binding.prepare({
anchor: tabPageEl.ref,
eventType: "pan"
});
}
},
methods: {
next() {
let page = this.currentPage;
if (page < this.tabTitles.length - 1) {
page++;
}
this.setPage(page);
},
prev() {
let page = this.currentPage;
if (page > 0) {
page--;
}
this.setPage(page);
},
startHandler() {
if (BindEnv.supportsEBForIos() && this.isTabView && this.needSlider) {
this.bindExp(this.$refs["tab-page-wrap"]);
}
},
bindExp(element) {
if (element && element.ref) {
if (this.isMoving && this.gesToken !== 0) {
Binding.unbind({
eventType: "pan",
token: this.gesToken
});
this.gesToken = 0;
return;
}
const tabElement = this.$refs["tab-container"];
const { currentPage, panDist } = this;
const dist = currentPage * 750;
// x-dist
const props = [
{
element: tabElement.ref,
property: "transform.translateX",
expression: `x-${dist}`
}
];
const gesTokenObj = Binding.bind(
{
anchor: element.ref,
eventType: "pan",
props
},
e => {
const { deltaX, state } = e;
if (state === "end") {
if (deltaX < -panDist) {
this.next();
} else if (deltaX > panDist) {
this.prev();
} else {
this.setPage(currentPage);
}
}
}
);
this.gesToken = gesTokenObj.token;
}
},
setPage(page, url = null, animated = true) {
if (!this.isTabView) {
this.jumpOut(url);
return;
}
if (this.isMoving === true) {
return;
}
this.isMoving = true;
const previousPage = this.currentPage;
const currentTabEl = this.$refs[`wxc-tab-title-${page}`][0];
const { width } = this.tabStyles;
const appearNum = parseInt(750 / width);
const tabsNum = this.tabTitles.length;
const offset = page > appearNum ? -(750 - width) / 2 : -width * 2;
if (appearNum < tabsNum) {
(previousPage > appearNum || page > 1) &&
dom.scrollToElement(currentTabEl, {
offset,
animated
});
page <= 1 &&
previousPage > page &&
dom.scrollToElement(currentTabEl, {
offset: -width * page,
animated
});
}
this.isMoving = false;
this.currentPage = page;
this._animateTransformX(page, animated);
this.$emit("wxcTabPageCurrentTabSelected", { page });
},
jumpOut(url) {
url && Utils.goToH5Page(url);
},
_animateTransformX(page, animated) {
const { duration, timingFunction } = this;
const computedDur = animated ? duration : 0.00001;
const containerEl = this.$refs[`tab-container`];
const dist = page * 750;
animation.transition(
containerEl,
{
styles: {
transform: `translateX(${-dist}px)`
},
duration: computedDur,
timingFunction,
delay: 0
},
() => {}
);
}
}
};
</script>
其中顶部tab的数据源改动后代码 :
/**
* Created by Tw93 on 2016/11/4.
*/
export default {
tabTitles: [
{
title: '出借中',
icon: 'https://gw.alicdn.com/tfs/TB1MWXdSpXXXXcmXXXXXXXXXXXX-72-72.png',
activeIcon: 'https://gw.alicdn.com/tfs/TB1kCk2SXXXXXXFXFXXXXXXXXXX-72-72.png',
},
{
title: '已完成',
icon: 'https://gw.alicdn.com/tfs/TB1ARoKSXXXXXc9XVXXXXXXXXXX-72-72.png',
activeIcon: 'https://gw.alicdn.com/tfs/TB19Z72SXXXXXamXFXXXXXXXXXX-72-72.png'
}
],
tabStyles: {
bgColor: '#FFFFFF',
titleColor: '#666666',
activeTitleColor: '#3D3D3D',
activeBgColor: '#FFFFFF',
isActiveTitleBold: true,
iconWidth: 70,
iconHeight: 70,
width: 280,
height: 100,
fontSize: 36,
hasActiveBottom: true,
activeBottomColor: '#FFC900',
activeBottomHeight: 6,
activeBottomWidth: 120,
textPaddingLeft: 10,
textPaddingRight: 10,
normalBottomColor: '#fb4343',
normalBottomHeight: 0,
hasRightIcon: true,
rightOffset: 0,
},
// 使用 iconfont 模式的tab title配置
tabIconFontTitles: [
{
title: '首页',
codePoint: '\ue623'
},
{
title: '特别推荐',
codePoint: '\ue608'
},
{
title: '消息中心',
codePoint: '\ue752',
badge: 5
},
{
title: '我的主页',
codePoint: '\ue601',
dot: true
}
],
tabIconFontStyles: {
bgColor: '#FFFFFF',
titleColor: '#666666',
activeTitleColor: '#3D3D3D',
activeBgColor: '#FFFFFF',
isActiveTitleBold: true,
width: 160,
height: 120,
fontSize: 24,
textPaddingLeft: 10,
textPaddingRight: 10,
iconFontSize: 50,
iconFontColor: '#333333',
iconFontMarginBottom: 8,
activeIconFontColor: 'red',
iconFontUrl: '//at.alicdn.com/t/font_501019_mauqv15evc1pp66r.ttf'
}
}
2.在页面中引用
上面我们将源码修改好了,那么接下来就是在weex页面中编写了。引用自定义的组件很关键。
1)在<script>标签中导入自定义的组件
import tabTitleList from "@/utils/tabTitleList";
import ZengTabPage from "@/components/zeng-tab-page";
注意:这里一定要注意自定义组件的命名规则
2)在<template>标签中使用
<zeng-tab-page ref="wxc-tab-page"
:tab-titles="tabTitles"
:tab-styles="tabStyles"
title-type="text"
:tab-page-height="tabPageHeight"
@wxcTabPageCurrentTabSelected="wxcTabPageCurrentTabSelected">
<list v-for="(v,index) in tabList"
:key="index"
class="item-container"
:style="{ height: (tabPageHeight - tabStyles.height) + 'px' }">
<!-- <cell class="border-cell"></cell> -->
<cell v-for="(demo,key) in v"
class="cell"
:key="key">
<!-- url="https://h5.m.taobao.com/trip/ticket/detail/index.html?scenicId=2675" -->
<wxc-pan-item :ext-id="'1-' + (v) + '-' + (key)"
@wxcPanItemClicked="wxcPanItemClicked(demo,key)"
@wxcPanItemPan="wxcPanItemPan">
<div class="content">
<text>{{demo.productName}}</text>
</div>
</wxc-pan-item>
</cell>
</list>
</zeng-tab-page>
说明:自定义组件在源码的基础上改动的话,原来自带的属性,比如:tab-titles="tabTitles"、:tab-styles="tabStyles"、title-type="text"这些属性是不会变的。
最后我们将项目进行编译运行如图:
自定义TabPage样式由于代码较多,就不把全部代码贴出来,在开发中遇到坑的同学可以评论中讨论。生命不止,学习不停!