react-native-customer-stack-tabview
Version:
react-native-customer-stack-tabview
916 lines (859 loc) • 29.4 kB
JavaScript
import React from 'react';
import {
StyleSheet,
Text,
View,
SectionList,
RefreshControl,
TouchableOpacity,
Animated,
Dimensions,
ScrollView,
} from 'react-native';
import PropTypes from 'prop-types';
import Carousel from 'react-native-snap-carousel';
import HocComponent from './HocComponent';
import _throttle from 'lodash.throttle';
import {
initScreen,
triggerOnce,
refreshMap,
onRefresh,
triggerRefresh,
onEndReached,
triggerEndReached,
} from './useRefreshEndReached';
const deviceWidth = Dimensions.get('window').width;
const AnimatedSectionList = Animated.createAnimatedComponent(SectionList);
const AnimatedCarousel = Animated.createAnimatedComponent(Carousel);
const CONSOLE_LEVEL = {
LOG: 'log',
INFO: 'info',
WARN: 'warn',
ERROR: 'error',
};
export default class ScrollableTabView extends React.Component {
static propTypes = {
stacks: PropTypes.array.isRequired,
firstIndex: PropTypes.number,
mappingProps: PropTypes.object,
header: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
stickyHeader: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
badges: PropTypes.array,
tabsStyle: PropTypes.object,
tabWrapStyle: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
tabInnerStyle: PropTypes.object,
tabActiveOpacity: PropTypes.number,
tabStyle: PropTypes.object,
tabsEnableAnimated: PropTypes.bool,
tabsEnableAnimatedUnderlineWidth: PropTypes.number,
useScrollStyle: PropTypes.object,
textStyle: PropTypes.object,
textActiveStyle: PropTypes.object,
tabUnderlineStyle: PropTypes.object,
syncToSticky: PropTypes.bool,
onEndReachedThreshold: PropTypes.number,
onBeforeRefresh: PropTypes.func,
onBeforeEndReached: PropTypes.func,
onTabviewChanged: PropTypes.func,
oneTabHidden: PropTypes.bool,
enableCachePage: PropTypes.bool,
carouselProps: PropTypes.object,
sectionListProps: PropTypes.object,
toHeaderOnTab: PropTypes.bool,
toTabsOnTab: PropTypes.bool,
tabsShown: PropTypes.bool,
fixedTabs: PropTypes.bool,
fixedHeader: PropTypes.bool,
useScroll: PropTypes.bool,
fillScreen: PropTypes.bool,
title: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
titleArgs: PropTypes.object,
onScroll: PropTypes.func,
onScroll2Horizontal: PropTypes.func,
screenScrollThrottle: PropTypes.number,
errorToThrow: PropTypes.bool,
disableUnderLine: PropTypes.bool,
scrollOffset: PropTypes.number,
extraTabBar: PropTypes.any,
onTabsLayout: PropTypes.func,
};
static defaultProps = {
stacks: [],
firstIndex: null,
mappingProps: {},
header: null,
stickyHeader: null,
badges: [],
tabsStyle: {},
tabWrapStyle: {},
tabInnerStyle: {},
tabActiveOpacity: 0.6,
tabStyle: {},
tabsEnableAnimated: false,
tabsEnableAnimatedUnderlineWidth: 0,
useScrollStyle: {},
textStyle: {},
textActiveStyle: {},
tabUnderlineStyle: {},
syncToSticky: true,
onEndReachedThreshold: 0.2,
onBeforeRefresh: null,
onBeforeEndReached: null,
onTabviewChanged: null,
oneTabHidden: false,
enableCachePage: true,
carouselProps: {},
sectionListProps: {},
toHeaderOnTab: false,
toTabsOnTab: false,
tabsShown: true,
fixedTabs: false,
fixedHeader: false,
useScroll: false,
fillScreen: true,
title: null,
titleArgs: {
style: {},
interpolateOpacity: {},
interpolateHeight: {},
},
onScroll: null,
onScroll2Horizontal: null,
screenScrollThrottle: 60,
errorToThrow: false,
disableUnderLine: true,
scrollOffset: 10,
extraTabBar: null,
};
constructor(props) {
super(props);
this.state = {
...this._initialState(),
};
this._initialProperty();
this._initial();
}
UNSAFE_componentWillReceiveProps(newProps) {
this._initial(newProps, true);
}
_initialState() {
return {
checkedIndex: this._getFirstIndex(),
refsObj: {},
lazyIndexs: this._initLazyIndexs(),
isRefreshing: false,
};
}
_initialProperty() {
const { screenScrollThrottle } = this.props;
this.scroll2VerticalPos = new Animated.Value(0);
this.scroll2HorizontalPos = new Animated.Value(0);
this.tabsMeasurements = [];
this.tabWidth = 0;
this.tabWidthWrap = 0;
this.layoutHeight = {
container: 0,
header: 0,
stickyHeader: 0,
tabs: 0,
screen: 0,
};
this.titleInterpolateArgs = {
height: {
inputRange: [0, 160],
outputRange: [0, 80],
extrapolate: 'clamp',
},
opacity: {
inputRange: [160, 320],
outputRange: [0.2, 1],
extrapolate: 'clamp',
},
};
this.tabUnderlineInterpolateArgs = {
inputRange: [],
outputRange: [],
extrapolate: 'clamp',
};
this._throttleCallback = _throttle(this._onTabviewChange.bind(this, true), screenScrollThrottle, {
leading: false,
trailing: true,
});
this._renderSectionHeader = this._renderSectionHeader.bind(this);
this._onRefresh = this._onRefresh.bind(this);
this._onEndReached = this._onEndReached.bind(this);
this._renderItem = this._renderItem.bind(this);
this._toggledRefreshing = this._toggledRefreshing.bind(this);
this._onScroll2Vertical = this._onScroll2Vertical.bind(this);
this._onScroll2Horizontal = this._onScroll2Horizontal.bind(this);
this._setScrollHandler2Vertical();
this._setScrollHandler2Horizontal();
}
_initial(props = this.props, isProcess = false) {
isProcess && this._toProcess(props);
this.tabs = this._getTabs(props);
this.badges = this._getBadges(props);
this.stacks = this._getWrapChildren(props);
if (props.firstIndex > Math.max(this.stacks.length - 1, 0))
this._displayConsole('firstIndex cannot exceed the total number of stacks.length', CONSOLE_LEVEL.ERROR);
}
/**
* 避免reset栈时的默认 firstIndex 超出当前选中索引导致无法显示视图
* @param {*} props
* @memberof ScrollableTabView
*/
_toProcess(props) {
if (
props.stacks &&
props.stacks.length &&
props.stacks.length != this.stacks.length &&
props.firstIndex != this.state.checkedIndex
) {
const timer = setTimeout(() => {
this._onTabviewChange(false, props.firstIndex);
clearTimeout(timer);
});
}
}
_getTabs(props) {
return (
props.stacks &&
props.stacks.map((item, index) => {
return {
tabLabel: item.tabLabel || item.screen?.name,
tabLabelRender: item.tabLabelRender ?? null,
index,
};
})
);
}
_getBadges(props) {
return (
props.stacks &&
props.stacks.map(item => {
return item.badge ?? null;
})
);
}
_makeStacksID(item) {
if (item && !item.__id__)
item.__id__ = `${Math.random().toString().slice(2, 8)}_${Date.now().toString().slice(2, 8)}`;
}
_getWrapChildren(props) {
return (
props.stacks &&
props.stacks.map((item, index) => {
if (item.screen) {
if (this.isClassCompoent(item.screen) && !item.screen.__HOCNAME__) {
this._makeStacksID(item);
item.screen = HocComponent(item.screen, this._setCurrentRef(index, item.__id__));
} else {
this._makeStacksID(item);
triggerOnce(item.screen, this._setCurrentRef(index, item.__id__));
}
}
return item;
})
);
}
_setCurrentRef(index, id) {
return ref => {
if (this.state.refsObj[index] && this.state.refsObj[index] === ref) return;
this.state.refsObj[index] = ref;
this.state.refsObj[index].__id__ = id;
this.setState({
refsObj: this.state.refsObj,
});
};
}
clearStacks = callback => {
this.tabs = [];
this.badges = [];
this.stacks = [];
this.setState(
{
...this._initialState(),
},
() => typeof callback === 'function' && callback()
);
};
getCurrentRef(index) {
return this.state.refsObj[index ?? this.state.checkedIndex];
}
toTabView = indexOrLabel => {
switch (typeof indexOrLabel) {
case 'number':
this._onTabviewChange(false, indexOrLabel);
break;
case 'string':
const tab = this.tabs.filter(f => f.tabLabel == indexOrLabel)[0];
if (tab) {
this._onTabviewChange(false, tab.index);
}
break;
}
};
/**
* y 轴偏移量,0以Tab为基准点
* @memberof ScrollableTabView
*/
_scrollTo = y => {
const sectionList = this.section?.getNode();
if (typeof y === 'number' && sectionList?.scrollToLocation) {
sectionList.scrollToLocation({
itemIndex: 0,
viewOffset: 0 - y,
sectionIndex: 0,
});
}
};
_initLazyIndexs() {
const lazyIndexs = [],
firstIndex = this._getFirstIndex();
if (firstIndex != null) lazyIndexs.push(firstIndex);
return lazyIndexs;
}
_getFirstIndex() {
const { firstIndex, stacks } = this.props;
if (typeof firstIndex === 'number' && stacks && stacks.length) {
return this.props.firstIndex;
} else {
return null;
}
}
/**
* 作为吸顶组件与Screen之间的桥梁,用于同步吸顶组件与Screen之间的状态
* @memberof ScrollableTabView
*/
_refresh = () => {
this.setState({});
};
_getProps(props, screen) {
return Object.assign(
{
refresh: this._refresh,
scrollTo: this._scrollTo,
toTabView: this.toTabView,
layoutHeight: this.layoutHeight,
},
!!screen && {
initScreen: () => initScreen(screen),
onRefresh: callback => {
if (!screen.onRefresh) {
screen.onRefresh = () => callback(this._toggledRefreshing);
}
onRefresh(screen, callback);
},
onEndReached: callback => onEndReached(screen, callback),
},
props || {}
);
}
_renderSticky() {
const stacks = this.props.stacks[this.state.checkedIndex];
const ref = this.getCurrentRef();
if (stacks && stacks.sticky && typeof stacks.sticky === 'function' && ref && stacks.__id__ === ref.__id__) {
// 用于自动同步 Screen 数据流改变后仅会 render 自身 Screen 的问题,用于自动同步 screenContext 给吸顶组件
if (this.props.syncToSticky && !ref.__isOverride__ && this.isClassCompoent(ref.constructor)) {
const originalDidUpdate = ref.componentDidUpdate,
context = this;
ref.componentDidUpdate = function () {
context._refresh();
originalDidUpdate && originalDidUpdate.apply(this, [...arguments]);
};
ref.__isOverride__ = true;
}
return <stacks.sticky {...this._getProps(this.props.mappingProps || {})} screenContext={ref} />;
}
return null;
}
_renderBadges(tabIndex) {
const { useScroll, badges } = this.props;
const _badges = this.badges[tabIndex] || badges[tabIndex];
if (_badges && _badges.length) {
if (useScroll)
this._displayConsole(
'When useScroll and badges exist at the same time, the badge will not overflow the Tabs container'
);
return _badges.map(item => {
return item;
});
}
return null;
}
_measureTab(pageIndex, { nativeEvent }) {
const { x, width, height } = nativeEvent.layout;
this.tabsMeasurements[pageIndex] = { left: x, right: x + width, width, height };
if (this.props.onTabsLayout) {
this.props.onTabsLayout(x + width);
}
}
_renderTab({ item, index }) {
const {
tabActiveOpacity,
tabWrapStyle,
tabInnerStyle,
tabStyle,
textStyle,
textActiveStyle,
tabUnderlineStyle,
tabsEnableAnimated,
disableUnderLine,
} = this.props;
const _tabUnderlineStyle = Object.assign({ top: 6 }, styles.tabUnderlineStyle, tabUnderlineStyle);
const _checked = this.state.checkedIndex == index;
const _tabWrapStyle = typeof tabWrapStyle === 'function' ? tabWrapStyle(item, index, _checked) : tabWrapStyle;
const _tab =
typeof item.tabLabelRender === 'function' ? item.tabLabelRender(item.tabLabel, index, _checked) : item.tabLabel;
return (
<View onLayout={this._measureTab.bind(this, index)} key={index} style={_tabWrapStyle}>
{this._renderBadges(index)}
<TouchableOpacity
activeOpacity={tabActiveOpacity}
onPress={() => this._onTabviewChange(false, index)}
style={[styles.tabStyle, tabStyle]}
>
<View style={tabInnerStyle}>
{typeof _tab === 'string' ? (
<Text style={[styles.textStyle, textStyle, _checked && textActiveStyle]}>{_tab}</Text>
) : (
_tab
)}
{!disableUnderLine && !tabsEnableAnimated && _checked && <View style={_tabUnderlineStyle} />}
</View>
</TouchableOpacity>
</View>
);
}
_getTabUnderlineInterpolateArgs(tabsEnableAnimatedUnderlineWidth) {
const maxTranslateXCount = this.tabs.length * 2 - 1;
if (maxTranslateXCount === this.tabUnderlineInterpolateArgs.inputRange.length)
return this.tabUnderlineInterpolateArgs;
const _outputRange = [];
const _inputRange = Array.from({ length: maxTranslateXCount }, (v, k) => {
_outputRange.push(k % 2 ? this.tabWidth / tabsEnableAnimatedUnderlineWidth : 1);
return k == 0 ? k : (k * deviceWidth) / 2;
});
this.tabUnderlineInterpolateArgs.inputRange = _inputRange;
this.tabUnderlineInterpolateArgs.outputRange = _outputRange;
return this.tabUnderlineInterpolateArgs;
}
_renderAnimatedTabUnderline() {
const { useScroll, tabUnderlineStyle, useScrollStyle, tabStyle, tabsEnableAnimatedUnderlineWidth } = this.props;
const { marginLeft, marginRight, marginHorizontal } = tabStyle;
const { paddingLeft, paddingHorizontal } = useScrollStyle;
const _tabUnderlineStyle = Object.assign(
{ zIndex: 100, width: this.tabWidth, position: 'absolute' },
styles.tabUnderlineStyle,
tabUnderlineStyle
);
if (!_tabUnderlineStyle.top && _tabUnderlineStyle.height)
_tabUnderlineStyle.top = this.layoutHeight.tabs - _tabUnderlineStyle.height;
const underlineLeft = marginLeft || marginHorizontal || 0;
const underlineRight = marginRight || marginHorizontal || 0;
let outputLeft = 0;
let outputRight = deviceWidth;
this.tabWidthWrap = this.tabWidth + underlineLeft + underlineRight;
if (useScroll) {
outputLeft = paddingLeft || paddingHorizontal || 0;
outputRight = this.tabWidthWrap * this.tabs.length;
}
outputLeft = outputLeft + underlineLeft;
outputRight = outputRight + outputLeft;
const interpolateAnimated = {
transform: [
{
translateX: this.scroll2HorizontalPos.interpolate({
inputRange: [0, this.tabs.length * deviceWidth],
outputRange: [outputLeft, outputRight],
extrapolate: 'clamp',
}),
},
],
};
if (tabsEnableAnimatedUnderlineWidth) {
if (tabsEnableAnimatedUnderlineWidth >= this.tabWidth / 2)
this._displayConsole(
'The value of tabsEnableAnimatedUnderlineWidth we recommend to be one-third of tabStyle.width or a fixed 30px'
);
interpolateAnimated.marginLeft = this.tabWidth / 2 - tabsEnableAnimatedUnderlineWidth / 2;
interpolateAnimated.width = tabsEnableAnimatedUnderlineWidth;
interpolateAnimated.transform.push({
scaleX: this.scroll2HorizontalPos.interpolate(
this._getTabUnderlineInterpolateArgs(tabsEnableAnimatedUnderlineWidth)
),
});
}
return <Animated.View style={[styles.tabUnderlineStyle, _tabUnderlineStyle, interpolateAnimated]} />;
}
_displayConsole(message, level = CONSOLE_LEVEL.LOG) {
const { errorToThrow } = this.props;
const pluginName = 'ScrollableTabView';
const msg = `${pluginName}: ${message || ' --- '}`;
console[level](msg);
if (errorToThrow && level == CONSOLE_LEVEL.ERROR) throw new Error(msg);
}
_errorProps(propName, level) {
const props = this.props;
const property = props[propName];
const errorProps = {
tabStyle: ['left', 'right'],
};
if (errorProps[propName] && property) {
errorProps[propName].forEach(errorProperty => {
if (errorProperty in property) {
this._displayConsole(`Prop ${propName} is not allowed to configure the ${errorProperty} property`, level);
}
});
}
}
_renderTabs() {
const {
oneTabHidden,
tabsShown,
tabsStyle,
tabStyle,
useScroll,
tabsEnableAnimated,
useScrollStyle,
disableUnderLine,
extraTabBar,
} = this.props;
const { width } = tabStyle;
if (tabsEnableAnimated && tabStyle && width == undefined)
this._displayConsole(
'When tabsEnableAnimated is true, the width must be specified for tabStyle',
CONSOLE_LEVEL.ERROR
);
if (useScroll && tabStyle && width == undefined)
this._displayConsole('When useScroll is true, the width must be specified for tabStyle', CONSOLE_LEVEL.ERROR);
const renderTab = !(oneTabHidden && this.tabs && this.tabs.length == 1) && tabsShown;
const _tabsStyle = Object.assign(
{},
!useScroll && { alignItems: 'center', justifyContent: 'space-around' },
styles.tabsStyle,
tabsStyle
);
this.layoutHeight.tabs = renderTab ? _tabsStyle.height : 0;
this.tabWidth = width;
this._errorProps('tabStyle', CONSOLE_LEVEL.ERROR);
return (
renderTab &&
this.tabs &&
!!this.tabs.length &&
(useScroll ? (
<View>
<ScrollView
contentContainerStyle={useScrollStyle}
style={_tabsStyle}
showsHorizontalScrollIndicator={false}
showsVerticalScrollIndicator={false}
ref={rf => (this.scrollview = rf)}
horizontal={true}
>
{this.tabs.map((tab, index) => this._renderTab({ item: tab, index }))}
{!disableUnderLine &&
tabsEnableAnimated &&
this.state.checkedIndex !== null &&
this._renderAnimatedTabUnderline()}
</ScrollView>
{extraTabBar}
</View>
) : (
<View style={_tabsStyle}>
{this.tabs.map((tab, index) => this._renderTab({ item: tab, index }))}
{!disableUnderLine &&
tabsEnableAnimated &&
this.state.checkedIndex !== null &&
this._renderAnimatedTabUnderline()}
</View>
))
);
}
_renderSectionHeader() {
const { fixedHeader } = this.props;
return (
<View style={{ flex: 1 }}>
{this._renderHeader(fixedHeader)}
{this._renderStickyHeader()}
{this._renderTabs()}
{this._renderSticky()}
</View>
);
}
// 启用 useScroll 情况下保证滚动条跟随
_tabTranslateX(index = this.state.checkedIndex) {
const { useScroll, scrollOffset } = this.props;
const width = this.tabWidthWrap || this.tabWidth;
const measurement = this.tabsMeasurements[index];
if (useScroll && this.scrollview && width) {
this.scrollview.scrollTo({
x: measurement.left - scrollOffset,
// x: (index - 1) * width + width / 2
});
}
}
_resetOtherRefs() {
const checkedIndex = this.state.checkedIndex;
if (this.state.refsObj && this.state.refsObj[checkedIndex]) this.state.refsObj[checkedIndex] = null;
return this.state.refsObj;
}
_onTabviewChange(isCarouselScroll, index) {
if (!this.stacks.length) return;
if (!this.stacks[index]) return;
const { enableCachePage, toHeaderOnTab, toTabsOnTab, onTabviewChanged } = this.props;
if (index == this.state.checkedIndex) {
if (!isCarouselScroll && toHeaderOnTab)
return this._scrollTo(-(this.layoutHeight.header + this.layoutHeight.stickyHeader));
if (!isCarouselScroll && toTabsOnTab) return this._scrollTo(0);
return void 0;
}
// else {
// if (!isCarouselScroll) this._scrollTo(0);
// }
const state = {
checkedIndex: index,
lazyIndexs: this.state.lazyIndexs,
};
let isFirst = false;
if (!enableCachePage) {
isFirst = true;
state.refsObj = this._resetOtherRefs();
state.lazyIndexs = [index];
} else {
if (!this._getLazyIndexs(index)) state.lazyIndexs.push(index), (isFirst = true);
}
this.setState(state, () => {
if (onTabviewChanged) {
const tab = this.tabs[this.state.checkedIndex];
onTabviewChanged(this.state.checkedIndex, tab && tab.tabLabel, isFirst);
}
});
this._tabTranslateX(index);
// 切换后强制重置刷新状态
this._toggledRefreshing(false);
}
_getLazyIndexs(index) {
return this.state.lazyIndexs.includes(index);
}
_getScreenHeight() {
this.layoutHeight.screen =
this.layoutHeight.container -
(this.layoutHeight.header + this.layoutHeight.stickyHeader + this.layoutHeight.tabs);
return this.layoutHeight.screen;
}
_getMaximumScreenHeight() {
return this.layoutHeight.container - this.layoutHeight.stickyHeader - this.layoutHeight.tabs;
}
isClassCompoent(component) {
return !!(component.prototype && component.prototype.isReactComponent);
}
_renderItem({ item, index }) {
const { enableCachePage, fillScreen, fixedTabs, mappingProps } = this.props;
const screenHeight = this._getScreenHeight();
return (
(enableCachePage ? enableCachePage : this.state.checkedIndex == index) &&
(this.getCurrentRef(index) || this.getCurrentRef(index) == undefined) &&
this._getLazyIndexs(index) && (
<View
style={[
{ flex: 1 },
enableCachePage && this.state.checkedIndex != index && { maxHeight: screenHeight },
enableCachePage && this.state.checkedIndex == index && fillScreen && { minHeight: screenHeight },
enableCachePage &&
this.state.checkedIndex == index &&
fixedTabs && { minHeight: this._getMaximumScreenHeight() },
!enableCachePage && this.state.checkedIndex == index && { minHeight: screenHeight },
]}
>
<item.screen
{...this._getProps(mappingProps, !this.isClassCompoent(item.screen) && item.screen)}
{...(item.toProps || {})}
/>
</View>
)
);
}
_onEndReached() {
const next = () => {
const ref = this.getCurrentRef();
!!ref && this.isClassCompoent(ref.constructor)
? ref && ref.onEndReached && typeof ref.onEndReached === 'function' && ref.onEndReached()
: triggerEndReached(ref);
};
if (this.state.checkedIndex != null) {
const { onBeforeEndReached } = this.props;
onBeforeEndReached && typeof onBeforeEndReached === 'function' ? onBeforeEndReached(next) : next();
}
}
_toggledRefreshing(status) {
this.setState({
isRefreshing: status ?? !this.state.isRefreshing,
});
}
_onRefresh() {
const next = () => {
const ref = this.getCurrentRef();
!ref &&
this._displayConsole(
`The Screen Ref is lost when calling onRefresh. Please confirm whether the Stack is working properly.(index: ${this.state.checkedIndex})`
);
if (ref) {
this.isClassCompoent(ref.constructor)
? ref.onRefresh && typeof ref.onRefresh === 'function' && ref.onRefresh(this._toggledRefreshing)
: triggerRefresh(ref, this._toggledRefreshing);
} else {
this._toggledRefreshing(false);
}
};
const { onBeforeRefresh } = this.props;
onBeforeRefresh && typeof onBeforeRefresh === 'function' ? onBeforeRefresh(next, this._toggledRefreshing) : next();
}
_renderHeader = isRender => {
const { header } = this.props;
return (
header &&
isRender && (
<View
onLayout={({ nativeEvent }) => {
const { height } = nativeEvent.layout;
this.layoutHeight.header = height;
if (height !== 0) this._refresh();
}}
>
{typeof header === 'function' ? header() : header}
</View>
)
);
};
_renderStickyHeader = () => {
const { stickyHeader } = this.props;
return (
stickyHeader && (
<View
onLayout={({ nativeEvent }) => {
const { height } = nativeEvent.layout;
this.layoutHeight.stickyHeader = height;
if (height !== 0) this._refresh();
}}
>
{typeof stickyHeader === 'function' ? stickyHeader() : stickyHeader}
</View>
)
);
};
_renderTitle = () => {
const { title, titleArgs } = this.props;
if (!title) return null;
const { style, interpolateHeight, interpolateOpacity } = titleArgs;
return (
<Animated.View
style={[
{
height: this.scroll2VerticalPos.interpolate(
Object.assign(this.titleInterpolateArgs.height, interpolateHeight)
),
opacity: this.scroll2VerticalPos.interpolate(
Object.assign(this.titleInterpolateArgs.opacity, interpolateOpacity)
),
overflow: 'hidden',
},
style,
]}
>
{typeof title === 'function' ? title() : title}
</Animated.View>
);
};
_refreshControl() {
const ref = this.getCurrentRef();
const enabled = !!(ref && ref.onRefresh) || refreshMap.has(ref);
return <RefreshControl enabled={enabled} refreshing={this.state.isRefreshing} onRefresh={this._onRefresh} />;
}
_onScroll2Vertical(event) {
const { onScroll } = this.props;
// TODO...
if (typeof onScroll === 'function' && event) onScroll(event);
}
_setScrollHandler2Vertical() {
const { title } = this.props;
const scrollEventConfig = {
listener: this._onScroll2Vertical,
// Error: Style property 'height' is not supported by native animated module, Maybe replaced with scaleX in the future.
useNativeDriver: !title,
};
const argMapping = [];
argMapping.push({ nativeEvent: { contentOffset: { y: this.scroll2VerticalPos } } });
this._onScrollHandler2Vertical = Animated.event(argMapping, scrollEventConfig);
}
_onScroll2Horizontal(event) {
const { onScroll2Horizontal } = this.props;
// TODO...
if (typeof onScroll2Horizontal === 'function' && event) onScroll2Horizontal(event);
}
_setScrollHandler2Horizontal() {
const scrollEventConfig = {
listener: this._onScroll2Horizontal,
useNativeDriver: true,
};
const argMapping = [];
argMapping.push({ nativeEvent: { contentOffset: { x: this.scroll2HorizontalPos } } });
this._onScrollHandler2Horizontal = Animated.event(argMapping, scrollEventConfig);
}
render() {
const { style, onEndReachedThreshold, fixedHeader, carouselProps, sectionListProps } = this.props;
return (
<View
onLayout={({ nativeEvent }) => {
const { height } = nativeEvent.layout;
this.layoutHeight.container = height;
if (height !== 0) this._refresh();
}}
style={[styles.container, style]}
>
{this._renderTitle()}
<AnimatedSectionList
ref={rf => (this.section = rf)}
keyExtractor={(item, index) => `scrollable-tab-view-wrap-${index}`}
renderSectionHeader={this._renderSectionHeader}
onEndReached={this._onEndReached}
onEndReachedThreshold={onEndReachedThreshold}
refreshControl={this._refreshControl()}
sections={[{ data: [1] }]}
stickySectionHeadersEnabled={true}
ListHeaderComponent={this._renderHeader(!fixedHeader)}
showsVerticalScrollIndicator={false}
showsHorizontalScrollIndicator={false}
renderItem={() => {
return (
<AnimatedCarousel
pagingEnabled={true}
inactiveSlideOpacity={1}
inactiveSlideScale={1}
data={this.stacks}
renderItem={this._renderItem}
sliderWidth={deviceWidth}
itemWidth={deviceWidth}
onScrollIndexChanged={this._throttleCallback}
firstItem={this.state.checkedIndex}
onScroll={this._onScrollHandler2Horizontal}
{...carouselProps}
/>
);
}}
onScrollToIndexFailed={() => {}}
onScroll={this._onScrollHandler2Vertical}
{...sectionListProps}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: { flex: 1 },
tabsStyle: { flex: 1, zIndex: 100, flexDirection: 'row', backgroundColor: '#ffffff', height: 35, marginBottom: -0.5 },
tabStyle: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#ffffff' },
textStyle: { height: 20, fontSize: 12, color: '#11111180', textAlign: 'center', lineHeight: 20 },
tabUnderlineStyle: { height: 2, borderRadius: 2, backgroundColor: '#00aced' },
});