インスタグラムの様な切り替えが可能なタブのサンプル
.
ソースコード
.
import React from 'react';
import { StyleSheet, View, Image, ScrollView, Dimensions, NativeSyntheticEvent, NativeScrollEvent, TouchableOpacity, FlatList, Text, ViewProps, StyleProp, ViewStyle, GestureResponderEvent } from 'react-native';
interface Props {
content: JSX.Element
}
const colors = {
gray: "gray",
white: "white",
darkGray: "darkgray"
}
const images = {
icon: require("../../assets/images/favicon.png")
}
const TAB_ICON_AREA_HEIGHT = 44;
const component: React.FC<Props> = ({ content }) => {
const [myTabRef] = React.useState<MyTabRef>({})
const [index, setIndex] = React.useState(0);
const [viewY, setViewY] = React.useState(Dimensions.get("window").height) // host scroll view size
const [contentY, setContentY] = React.useState(Dimensions.get("window").height) // in content size
// const [isLockHost, setIsLockHost] = React.useState(false);
const tabContentScrollViewHeight = viewY - TAB_ICON_AREA_HEIGHT;
const [scrollY, setScrollY] = React.useState(0);
return (
<View>
<ScrollView
onLayout={(e)=>setViewY(e.nativeEvent.layout.height)}
// onContentSizeChange={(h)=> setContentY(h) }
onScroll={(e)=>{
const y = e.nativeEvent.contentOffset.y
if(y >= contentY){
myTabRef?.goLock?.(false);
console.log("isSpeed:", scrollY - y, "@", y, contentY)
// setIsLockHost(true);
}
setScrollY(y);
}}
scrollEventThrottle={16}
style={{ width: Dimensions.get("window").width }}
stickyHeaderIndices={[1]}
bounces={false}
>
<View>
<View onLayout={(e)=> setContentY(e.nativeEvent.layout.height)}>
{content}
</View>
<View>
<View style={{ display: "flex", flexDirection: "row", backgroundColor: colors.darkGray, height: TAB_ICON_AREA_HEIGHT /* 計算 */ }}>
<TouchableOpacity
style={[
myTabStyles.icon,
index == 0 ? { borderBottomColor: colors.white } : {}
]}
onPress={() => myTabRef.goTo?.(0)}
>
<Image style={myTabStyles.tabIcon} source={images.icon} />
</TouchableOpacity>
<TouchableOpacity
style={[
myTabStyles.icon,
index == 1 ? { borderBottomColor: colors.white } : {},
]}
onPress={() => myTabRef.goTo?.(1)}
>
<Image style={myTabStyles.tabIcon} source={images.icon} />
</TouchableOpacity>
</View>
</View>
<_MyTabs
maxHeight={tabContentScrollViewHeight}
myTabRef={myTabRef}
onChangeIndex={setIndex}
// setHostLockState={setIsLockHost}
/>
</View>
</ScrollView>
</View>
);
};
export default React.memo(component);
type MyTabRef = {
goTo?: (index: number) => void
goLock?: (state: boolean) => void
}
type MyTabProps = {
maxHeight: number
myTabRef?: MyTabRef
onChangeIndex?: (ix: number) => void
// setHostLockState: (s: boolean) => void
}
const _MyTabs: React.FC<MyTabProps> = ({ maxHeight, myTabRef, onChangeIndex }) => {
const scrollView = React.useRef<ScrollView>(null);
const width = Dimensions.get('window').width;
const goTo = (index: number) => {
scrollView.current?.scrollTo({
x: index * width,
});
};
const [isLock, setIsLock] = React.useState(true);
const goLock = (state: boolean = true) => {
if(!state && _chkNeedLock(index)) return; // "no need lock" request and chk.
setIsLock(state);
}
const [index, setIndex] = React.useState(0);
const _setIndex = (ix: number) => {
setIndex(ix);
onChangeIndex?.(ix);
if(_chkNeedLock(ix)) setIsLock(true);
else if(scrollYs[ix] > 0) setIsLock(false); // <!!!> Tab Change and Can Scroll
}
const _chkNeedLock = (ix: number) => {
return (contentSizes[ix] <= maxHeight) ? true : false // small and Need Lock
}
const onScroll: (event: NativeSyntheticEvent<NativeScrollEvent>) => void = (ev) => {
const x = ev.nativeEvent.contentOffset.x;
const i = Math.floor((width / 2 + x) / width);
if (index != i) _setIndex(i);
};
if(myTabRef){
myTabRef.goTo = goTo;
myTabRef.goLock = goLock;
}
/* ScrollView in Tab */
const [scrollYs, setScrollYs] = React.useState([0, 0]);
const [contentSizes, setContentSizes] = React.useState([500, 500])
const scRef0 = React.useRef<ScrollView>(null)
const scRef1 = React.useRef<ScrollView>(null)
const [touchY, setTouchY] = React.useState(0);
const _onTouchStart = (e: GestureResponderEvent) => {
// return;
setTouchY(e.nativeEvent.locationY)
}
const _onTouchMove = (e: GestureResponderEvent) => {
// return;
const y = e.nativeEvent.locationY;
if(touchY > y && isLock){
if(_chkNeedLock(index)) goLock(false);
}
}
return (
<View>
<ScrollView
style={{ maxHeight }}
snapToInterval={width}
horizontal
decelerationRate="fast"
showsHorizontalScrollIndicator={false}
bounces={false}
ref={scrollView}
onScroll={onScroll}
scrollEventThrottle={32}
>
<View style={{ width }} >
<ScrollView
ref={scRef0}
scrollEnabled={!isLock}
onScroll={(e)=>{
const y = e.nativeEvent.contentOffset.y;
const befY = scrollYs[0];
if( y <= 0 && befY > y ){
goLock(true);
e.stopPropagation()
scRef0.current?.scrollTo({
y: 0
})
}
setScrollYs([y, scrollYs[1]])
// setHostLockState(false); // Host UnLock
}}
onTouchStart={_onTouchStart}
onTouchMove={_onTouchMove}
scrollEventThrottle={16}
>
<View onLayout={(e)=>setContentSizes([e.nativeEvent.layout.height, contentSizes[1]])} >
<Image source={{ uri: "https://younaship.com/Nashi.png" }} style={{height: width, width, backgroundColor: "gray" }}/>
<Image source={{ uri: "https://younaship.com/Nashi.png" }} style={{height: width, width, backgroundColor: "gray" }}/>
<Image source={{ uri: "https://younaship.com/Nashi.png" }} style={{height: width, width, backgroundColor: "gray" }}/>
<Image source={{ uri: "https://younaship.com/Nashi.png" }} style={{height: width, width, backgroundColor: "gray" }}/>
</View>
</ScrollView>
</View>
<View style={{ width: Dimensions.get('window').width }}>
<ScrollView
ref={scRef1}
scrollEnabled={!isLock}
onScroll={(e)=>{
const y = e.nativeEvent.contentOffset.y;
const befY = scrollYs[1];
if( y <= 0 && befY > y ){
goLock(true);
e.stopPropagation()
scRef1.current?.scrollTo({
y: 0
})
}
setScrollYs([scrollYs[0], y])
// setHostLockState(false); // Host UnLock
}}
onTouchStart={_onTouchStart}
onTouchMove={_onTouchMove}
scrollEventThrottle={16}
>
<View onLayout={(e)=>setContentSizes([contentSizes[0], e.nativeEvent.layout.height])}>
<Image source={{ uri: "https://younaship.com/Nashi.png" }} style={{height: width, width, backgroundColor: "yellow" }}/>
</View>
</ScrollView>
</View>
</ScrollView>
</View>
);
};
const myTabStyles = StyleSheet.create({
icon: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 10,
borderBottomWidth: 2,
},
tabIcon: {
width: 24,
height: 24,
},
});