UNPKG

weex-nuke

Version:

基于 Rax 、Weex 的高性能组件体系 ~~

379 lines (372 loc) 11.5 kB
/** @jsx createElement */ 'use strict'; 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);