weex-nuke
Version:
基于 Rax 、Weex 的高性能组件体系 ~~
379 lines (372 loc) • 11.5 kB
JSX
/** @jsx createElement */
;
import { createElement, Component, findDOMNode, PropTypes } from 'rax';
import View from 'nuke-view';
import { Detection } from 'nuke-ep-utils';
import { connectStyle } from 'nuke-theme-provider';
import Emitter from '../util/emitter';
import stylesProvider from '../styles/index';
import Nav from './nav';
// 在weex及非android4.4以下的机器中才使用ep绑定
let expressionBinding = {};
try {
expressionBinding = require('@weex-module/expressionBinding');
} catch (e) { }
class Tabbar extends Component {
constructor(props) {
super(props);
this.childrenElements = []; // 存放tab主体element
this.tabMap = {}; // 用于记录哪个tab是被激活过的状态
this.tabPage = []; // 存放tab节点
this.scrolling = false;
this.maxIndex = (props.children && props.children.length - 1) || 0; // tab的子节点只能是tab.item
this.state = {
curIndex:
props.activeKey &&
props.activeKey >= 0 &&
props.activeKey <= this.maxIndex
? props.active
: 0,
transform: 0,
prevIndex: null,
};
if (
typeof props.activeKey === 'number' &&
props.activeKey >= 0 &&
props.activeKey <= this.maxIndex
) {
this.tabMap[props.activeKey] = true;
}
}
componentWillMount() {
Emitter.on('scroll', (data) => {
this.scrolling = true;
if (this.scrollTimer) {
clearTimeout(this.scrollTimer);
}
this.scrollTimer = setTimeout(() => {
this.scrolling = false;
}, 300);
});
}
componentDidMount() {
if (Detection.epEnable && Detection.iOS && this.props.epEnable) {
setTimeout(() => {
const listElement = findDOMNode(this.refs.listSlider);
if (listElement && listElement.ref) {
expressionBinding.enableBinding(listElement.ref, 'pan');
}
}, 500);
}
// hack for android
if (Detection.epEnable && Detection.Android) {
Emitter.on('slider', (params) => {
this.bindExp(params.element);
});
}
}
/**
* ep绑定方法
*/
bindExp = (element) => {
if (!this.isMoving && element && element.ref && Detection.epEnable) {
this.startTime = Date.now();
const { dataSource, navFocusStyle } = this.props;
const listElement = findDOMNode(this.refs.listSlider);
const index = this.state.curIndex;
const tabFocusElement = findDOMNode(
this.refs.nav.wrappedInstance.refs.tabFocus
);
// 绑定滚动只取第一个 itemWidthList的宽度
const tabItemWidth =
dataSource[0] && dataSource[0].style && dataSource[0].style.width;
const dpr = 1;
const tabLeft = index * tabItemWidth / dpr;
if (navFocusStyle && navFocusStyle.left) {
// tabLeft += navFocusStyle.left;
}
const sliderLeft = index * 750 / dpr;
/*
ep表达式备注
左边界 min(x, x/3)
右边界 max(x, x/3) - ${dist}
中间 x - dist
*/
const args = [
{
element: tabFocusElement.ref,
property: 'transform.translateX',
expression: `{\"type\":\"-\",\"children\":[{\"type\":\"NumericLiteral\",\"value\":${tabLeft}},{\"type\":\"/\",\"children\":[{\"type\":\"Identifier\",\"value\":\"x\"},{\"type\":\"NumericLiteral\",\"value\":5}]}]}`,
},
];
if (index == 0) {
// 第一帧 min(x, x/3)
const ep2 =
'{"type":"CallExpression","children":[{"type":"Identifier","value":"min"},{"type":"Arguments","children":[{"type":"Identifier","value":"x"},{"type":"/","children":[{"type":"Identifier","value":"x"},{"type":"NumericLiteral","value":3}]}]}]}';
args.push({
element: listElement.ref,
property: 'transform.translateX',
expression: ep2,
});
} else if (index == dataSource.length - 1) {
// 最后一祯 `max(x, x/3) - ${sliderLeft}`
const ep2 = `{\"type\":\"-\",\"children\":[{\"type\":\"CallExpression\",\"children\":[{\"type\":\"Identifier\",\"value\":\"max\"},{\"type\":\"Arguments\",\"children\":[{\"type\":\"Identifier\",\"value\":\"x\"},{\"type\":\"/\",\"children\":[{\"type\":\"Identifier\",\"value\":\"x\"},{\"type\":\"NumericLiteral\",\"value\":3}]}]}]},{\"type\":\"NumericLiteral\",\"value\":${sliderLeft}}]}`;
args.push({
element: listElement.ref,
property: 'transform.translateX',
expression: ep2,
});
} else {
// `x - ${sliderLeft}`
const ep2 = `{\"type\":\"-\",\"children\":[{\"type\":\"Identifier\",\"value\":\"x\"},{\"type\":\"NumericLiteral\",\"value\":${sliderLeft}}]}`;
args.push({
element: listElement.ref,
property: 'transform.translateX',
expression: ep2,
});
}
// console.log('enableBinding');
// expressionBinding.enableBinding(element.ref, 'pan');
// console.log('createBinding');
expressionBinding.createBinding(
element.ref,
'pan',
'',
args,
this.panEnd
);
}
};
/**
* 横向滑动事件
*/
onHorizontalPan = (e) => {
if (e.state === 'start' && !this.scrolling) {
const listElement = findDOMNode(this.refs.listSlider);
this.bindExp(listElement);
}
};
/**
* 横向滑动停止
*/
panEnd = (e) => {
console.log('[debug]:panEnd');
if (e.state === 'end') {
const duration = Date.now() - this.startTime;
const dist = e.deltaX;
const { curIndex } = this.state;
const panDist = this.props.panDist ? this.props.panDist : 375;
let newIndex = curIndex;
if (
Math.abs(dist) > panDist ||
(Math.abs(dist) / duration > 0.5 && duration < 200)
) {
if (dist > 0) {
newIndex--;
} else {
newIndex++;
}
}
if (newIndex < 0) {
newIndex = 0;
} else if (newIndex > this.maxIndex) {
newIndex = this.maxIndex;
}
if (Detection.iOS) {
setTimeout(() => {
this.slideTo(newIndex, 'pan');
}, 50);
} else {
this.slideTo(newIndex, 'pan');
}
}
};
/**
* tab切换前的回调函数
* @param index 即将滚动去的位置
* @param type 触发滚动的事件类型
*/
slideTo = (index, type) => {
const time = 300;
if (this.isMoving === true) return;
this.isMoving = true;
const { onChange, beforeSlide, forbidNavScroll } = this.props;
const prevIndex = this.state.curIndex;
const moveEndCallback = () => {
if (typeof onChange === 'function') {
onChange(index, prevIndex, type);
}
this.setState({
curIndex: index,
prevIndex,
});
this.isMoving = false;
};
const navRef = this.refs.nav && this.refs.nav.wrappedInstance;
if (typeof beforeSlide === 'function') {
if (!beforeSlide(index, type)) return;
}
// 选中状态滚动,当只有两个item,无需scroll的时候需要禁止滚动
if (!forbidNavScroll) {
navRef.scrollTo(index);
}
navRef.focusMove(index);
navRef.setState({
curIndex: index,
});
if (Detection.epEnable) {
// 高版本系统的手机才使用transform动画
const animation = require('@weex-module/animation');
const sliderElement = this.refs.listSlider;
const dist = 750 * index;
animation.transition(
findDOMNode(sliderElement),
{
styles: {
transform: `translateX(-${dist}px)`,
},
delay: 0,
duration: time,
timingFunction: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)', // ['cubic-bezier(0.25, 0.46, 0.45, 0.94)', 'cubic-bezier(0,0,0.25,1)']
},
() => { }
);
}
moveEndCallback();
};
render() {
const { curIndex, prevIndex } = this.state;
const {
themeStyle,
children,
style,
navTop,
dataSource,
renderNavItem,
navStyle,
renderLoading,
navFocusStyle,
forceRender, // ignore SCU just forceRender
navContentStyle,
} = this.props;
const { slider } = themeStyle;
if (!children.length) {
return null;
}
if (navTop) {
slider.top = navStyle && navStyle.height;
navStyle.top = 0;
delete navStyle.bottom;
} else {
slider.bottom = navStyle && navStyle.height;
navStyle.bottom = 0;
delete navStyle.top;
}
// 根据子节点计算tab偏移
const sliderStyle = [slider];
if (Detection.epEnable) {
sliderStyle.push({
width: 750 * children.length,
});
if (Detection.iOS) {
sliderStyle.push({
transform: `translateX(-${750 * this.state.curIndex})`,
});
}
if (this.tabMap[curIndex]) {
this.tabMap[curIndex]++;
} else {
this.tabMap[curIndex] = 1;
}
}
let formatChildren = [];
if (Detection.epEnable) {
formatChildren = children.map((child, index) => {
const length = children.length;
const next = circleIndex(this.state.curIndex + 1, length);
const prev = circleIndex(this.state.curIndex - 1, length);
const shouldRender = this.tabMap[index] === 1;
const eachTabStyle = [themeStyle.eachTab, { left: 750 * index }];
if (this.tabMap[index] && curIndex === index) {
this.tabPage[curIndex] = (
<View style={eachTabStyle} key={index} shouldRender={shouldRender}>
{child}
</View>
);
}
if (this.tabMap[index]) {
return this.tabPage[index];
}
if (
typeof renderLoading === 'function' &&
(index === next || index === prev)
) {
return (
<View style={eachTabStyle} key={index}>
{renderLoading(index)}
</View>
);
}
return null;
});
} else {
formatChildren = children.map((child, index) => {
if (index === curIndex) {
return child;
}
return null;
});
}
const childrenElements = [
<View
style={sliderStyle}
ref="listSlider"
onHorizontalPan={
Detection.epEnable && Detection.iOS && this.props.epEnable
? this.onHorizontalPan
: null
}
>
{formatChildren}
</View>,
];
const headerStyle = navTop
? {
...navStyle,
top: 0,
positon: 'relative',
}
: navStyle;
const NavWithProps = (
<View style={[themeStyle.header, headerStyle]}>
<Nav
ref="nav"
dataSource={dataSource}
renderNavItem={renderNavItem}
slideTo={(...args) => this.slideTo(...args)}
style={navStyle}
contentStyle={navContentStyle}
navFocusStyle={navFocusStyle}
forceRender={forceRender}
/>
</View>
);
navTop
? childrenElements.unshift(NavWithProps)
: childrenElements.push(NavWithProps);
return (
<View style={[themeStyle.wrapContainer, style]}>{childrenElements}</View>
);
}
}
function circleIndex(i, len) {
return (len + i % len) % len;
}
// Tabbar.propTypes = {
// };
Tabbar.defaultProps = {
epEnable: false,
navTop: true,
};
Tabbar.displayName = 'Ep-Tabbar';
export default connectStyle(stylesProvider)(Tabbar);