1*8d82ecd9S猫头猫import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; 2*8d82ecd9S猫头猫import {LayoutRectangle, StyleSheet, Text, View} from 'react-native'; 3*8d82ecd9S猫头猫import rpx from '@/utils/rpx'; 4*8d82ecd9S猫头猫import useDelayFalsy from '@/hooks/useDelayFalsy'; 5*8d82ecd9S猫头猫import {FlatList, TapGestureHandler} from 'react-native-gesture-handler'; 6*8d82ecd9S猫头猫import {fontSizeConst} from '@/constants/uiConst'; 7*8d82ecd9S猫头猫import {IconButtonWithGesture} from '@/components/base/iconButton'; 8*8d82ecd9S猫头猫import Loading from '@/components/base/loading'; 9*8d82ecd9S猫头猫import globalStyle from '@/constants/globalStyle'; 10*8d82ecd9S猫头猫import {showPanel} from '@/components/panels/usePanel'; 11*8d82ecd9S猫头猫import LyricManager from '@/core/lyricManager'; 12*8d82ecd9S猫头猫import TrackPlayer from '@/core/trackPlayer'; 13*8d82ecd9S猫头猫import {musicIsPaused} from '@/utils/trackUtils'; 14*8d82ecd9S猫头猫import delay from '@/utils/delay'; 15*8d82ecd9S猫头猫import DraggingTime from './draggingTime'; 16*8d82ecd9S猫头猫import LyricItemComponent from './lyricItem'; 17*8d82ecd9S猫头猫 18*8d82ecd9S猫头猫const ITEM_HEIGHT = rpx(92); 19*8d82ecd9S猫头猫 20*8d82ecd9S猫头猫interface IItemHeights { 21*8d82ecd9S猫头猫 blankHeight?: number; 22*8d82ecd9S猫头猫 [k: number]: number; 23*8d82ecd9S猫头猫} 24*8d82ecd9S猫头猫 25*8d82ecd9S猫头猫export default function Lyric() { 26*8d82ecd9S猫头猫 const {loading, meta, lyrics: lyric} = LyricManager.useLyricState(); 27*8d82ecd9S猫头猫 const currentLrcItem = LyricManager.useCurrentLyric(); 28*8d82ecd9S猫头猫 29*8d82ecd9S猫头猫 // 是否展示拖拽 30*8d82ecd9S猫头猫 const dragShownRef = useRef(false); 31*8d82ecd9S猫头猫 const [draggingIndex, setDraggingIndex, setDraggingIndexImmi] = 32*8d82ecd9S猫头猫 useDelayFalsy<number | undefined>(undefined, 2000); 33*8d82ecd9S猫头猫 const listRef = useRef<FlatList<ILyric.IParsedLrcItem> | null>(); 34*8d82ecd9S猫头猫 const musicState = TrackPlayer.useMusicState(); 35*8d82ecd9S猫头猫 36*8d82ecd9S猫头猫 const [layout, setLayout] = useState<LayoutRectangle>(); 37*8d82ecd9S猫头猫 38*8d82ecd9S猫头猫 // 用来缓存高度 39*8d82ecd9S猫头猫 const itemHeightsRef = useRef<IItemHeights>({}); 40*8d82ecd9S猫头猫 41*8d82ecd9S猫头猫 // 设置空白组件,获取组件高度 42*8d82ecd9S猫头猫 const blankComponent = useMemo(() => { 43*8d82ecd9S猫头猫 return ( 44*8d82ecd9S猫头猫 <View 45*8d82ecd9S猫头猫 style={style.empty} 46*8d82ecd9S猫头猫 onLayout={evt => { 47*8d82ecd9S猫头猫 itemHeightsRef.current.blankHeight = 48*8d82ecd9S猫头猫 evt.nativeEvent.layout.height; 49*8d82ecd9S猫头猫 }} 50*8d82ecd9S猫头猫 /> 51*8d82ecd9S猫头猫 ); 52*8d82ecd9S猫头猫 }, []); 53*8d82ecd9S猫头猫 54*8d82ecd9S猫头猫 const handleLyricItemLayout = useCallback( 55*8d82ecd9S猫头猫 (index: number, height: number) => { 56*8d82ecd9S猫头猫 itemHeightsRef.current[index] = height; 57*8d82ecd9S猫头猫 }, 58*8d82ecd9S猫头猫 [], 59*8d82ecd9S猫头猫 ); 60*8d82ecd9S猫头猫 61*8d82ecd9S猫头猫 useEffect(() => { 62*8d82ecd9S猫头猫 // 暂停且拖拽才返回 63*8d82ecd9S猫头猫 if ( 64*8d82ecd9S猫头猫 lyric.length === 0 || 65*8d82ecd9S猫头猫 draggingIndex !== undefined || 66*8d82ecd9S猫头猫 (draggingIndex === undefined && musicIsPaused(musicState)) || 67*8d82ecd9S猫头猫 lyric[lyric.length - 1].time < 1 68*8d82ecd9S猫头猫 ) { 69*8d82ecd9S猫头猫 return; 70*8d82ecd9S猫头猫 } 71*8d82ecd9S猫头猫 if (currentLrcItem?.index === -1 || !currentLrcItem) { 72*8d82ecd9S猫头猫 listRef.current?.scrollToIndex({ 73*8d82ecd9S猫头猫 index: 0, 74*8d82ecd9S猫头猫 viewPosition: 0.5, 75*8d82ecd9S猫头猫 }); 76*8d82ecd9S猫头猫 } else { 77*8d82ecd9S猫头猫 listRef.current?.scrollToIndex({ 78*8d82ecd9S猫头猫 index: Math.min(currentLrcItem.index ?? 0, lyric.length - 1), 79*8d82ecd9S猫头猫 viewPosition: 0.5, 80*8d82ecd9S猫头猫 }); 81*8d82ecd9S猫头猫 } 82*8d82ecd9S猫头猫 // 音乐暂停状态不应该影响到滑动,所以不放在依赖里,但是这样写不好。。 83*8d82ecd9S猫头猫 }, [currentLrcItem, lyric, draggingIndex]); 84*8d82ecd9S猫头猫 85*8d82ecd9S猫头猫 // 开始滚动时拖拽生效 86*8d82ecd9S猫头猫 const onScrollBeginDrag = () => { 87*8d82ecd9S猫头猫 dragShownRef.current = true; 88*8d82ecd9S猫头猫 }; 89*8d82ecd9S猫头猫 90*8d82ecd9S猫头猫 const onScrollEndDrag = async () => { 91*8d82ecd9S猫头猫 if (draggingIndex !== undefined) { 92*8d82ecd9S猫头猫 setDraggingIndex(undefined); 93*8d82ecd9S猫头猫 } 94*8d82ecd9S猫头猫 dragShownRef.current = false; 95*8d82ecd9S猫头猫 }; 96*8d82ecd9S猫头猫 97*8d82ecd9S猫头猫 const onScroll = (e: any) => { 98*8d82ecd9S猫头猫 if (dragShownRef.current) { 99*8d82ecd9S猫头猫 const offset = 100*8d82ecd9S猫头猫 e.nativeEvent.contentOffset.y + 101*8d82ecd9S猫头猫 e.nativeEvent.layoutMeasurement.height / 2; 102*8d82ecd9S猫头猫 103*8d82ecd9S猫头猫 const itemHeights = itemHeightsRef.current; 104*8d82ecd9S猫头猫 let height = itemHeights.blankHeight!; 105*8d82ecd9S猫头猫 if (offset <= height) { 106*8d82ecd9S猫头猫 setDraggingIndex(0); 107*8d82ecd9S猫头猫 return; 108*8d82ecd9S猫头猫 } 109*8d82ecd9S猫头猫 for (let i = 0; i < lyric.length; ++i) { 110*8d82ecd9S猫头猫 height += itemHeights[i] ?? 0; 111*8d82ecd9S猫头猫 if (height > offset) { 112*8d82ecd9S猫头猫 setDraggingIndex(i); 113*8d82ecd9S猫头猫 return; 114*8d82ecd9S猫头猫 } 115*8d82ecd9S猫头猫 } 116*8d82ecd9S猫头猫 } 117*8d82ecd9S猫头猫 }; 118*8d82ecd9S猫头猫 119*8d82ecd9S猫头猫 const onLyricSeekPress = async () => { 120*8d82ecd9S猫头猫 if (draggingIndex !== undefined) { 121*8d82ecd9S猫头猫 const time = lyric[draggingIndex].time + +(meta?.offset ?? 0); 122*8d82ecd9S猫头猫 if (time !== undefined && !isNaN(time)) { 123*8d82ecd9S猫头猫 await TrackPlayer.seekTo(time); 124*8d82ecd9S猫头猫 await TrackPlayer.play(); 125*8d82ecd9S猫头猫 setDraggingIndexImmi(undefined); 126*8d82ecd9S猫头猫 } 127*8d82ecd9S猫头猫 } 128*8d82ecd9S猫头猫 }; 129*8d82ecd9S猫头猫 130*8d82ecd9S猫头猫 console.log(draggingIndex, 'DD'); 131*8d82ecd9S猫头猫 132*8d82ecd9S猫头猫 return ( 133*8d82ecd9S猫头猫 <View style={globalStyle.fwflex1}> 134*8d82ecd9S猫头猫 {loading ? ( 135*8d82ecd9S猫头猫 <Loading color="white" /> 136*8d82ecd9S猫头猫 ) : lyric?.length ? ( 137*8d82ecd9S猫头猫 <FlatList 138*8d82ecd9S猫头猫 ref={_ => { 139*8d82ecd9S猫头猫 listRef.current = _; 140*8d82ecd9S猫头猫 }} 141*8d82ecd9S猫头猫 onLayout={e => { 142*8d82ecd9S猫头猫 setLayout(e.nativeEvent.layout); 143*8d82ecd9S猫头猫 }} 144*8d82ecd9S猫头猫 viewabilityConfig={{ 145*8d82ecd9S猫头猫 itemVisiblePercentThreshold: 100, 146*8d82ecd9S猫头猫 }} 147*8d82ecd9S猫头猫 onScrollToIndexFailed={({index}) => { 148*8d82ecd9S猫头猫 delay(120).then(() => { 149*8d82ecd9S猫头猫 listRef.current?.scrollToIndex({ 150*8d82ecd9S猫头猫 index: Math.min(index ?? 0, lyric.length - 1), 151*8d82ecd9S猫头猫 viewPosition: 0.5, 152*8d82ecd9S猫头猫 }); 153*8d82ecd9S猫头猫 }); 154*8d82ecd9S猫头猫 }} 155*8d82ecd9S猫头猫 fadingEdgeLength={120} 156*8d82ecd9S猫头猫 ListHeaderComponent={blankComponent} 157*8d82ecd9S猫头猫 ListFooterComponent={blankComponent} 158*8d82ecd9S猫头猫 onScrollBeginDrag={onScrollBeginDrag} 159*8d82ecd9S猫头猫 onMomentumScrollEnd={onScrollEndDrag} 160*8d82ecd9S猫头猫 onScroll={onScroll} 161*8d82ecd9S猫头猫 scrollEventThrottle={32} 162*8d82ecd9S猫头猫 style={style.wrapper} 163*8d82ecd9S猫头猫 data={lyric} 164*8d82ecd9S猫头猫 initialNumToRender={30} 165*8d82ecd9S猫头猫 overScrollMode="never" 166*8d82ecd9S猫头猫 extraData={currentLrcItem} 167*8d82ecd9S猫头猫 renderItem={({item, index}) => ( 168*8d82ecd9S猫头猫 <LyricItemComponent 169*8d82ecd9S猫头猫 index={index} 170*8d82ecd9S猫头猫 text={item.lrc} 171*8d82ecd9S猫头猫 onLayout={handleLyricItemLayout} 172*8d82ecd9S猫头猫 light={draggingIndex === index} 173*8d82ecd9S猫头猫 highlight={currentLrcItem?.index === index} 174*8d82ecd9S猫头猫 /> 175*8d82ecd9S猫头猫 )} 176*8d82ecd9S猫头猫 /> 177*8d82ecd9S猫头猫 ) : ( 178*8d82ecd9S猫头猫 <View style={globalStyle.fullCenter}> 179*8d82ecd9S猫头猫 <Text style={style.white}>暂无歌词</Text> 180*8d82ecd9S猫头猫 <TapGestureHandler 181*8d82ecd9S猫头猫 onActivated={() => { 182*8d82ecd9S猫头猫 showPanel('SearchLrc', { 183*8d82ecd9S猫头猫 musicItem: TrackPlayer.getCurrentMusic(), 184*8d82ecd9S猫头猫 }); 185*8d82ecd9S猫头猫 }}> 186*8d82ecd9S猫头猫 <Text style={style.searchLyric}>搜索歌词</Text> 187*8d82ecd9S猫头猫 </TapGestureHandler> 188*8d82ecd9S猫头猫 </View> 189*8d82ecd9S猫头猫 )} 190*8d82ecd9S猫头猫 {draggingIndex !== undefined && ( 191*8d82ecd9S猫头猫 <View 192*8d82ecd9S猫头猫 style={[ 193*8d82ecd9S猫头猫 style.draggingTime, 194*8d82ecd9S猫头猫 layout?.height 195*8d82ecd9S猫头猫 ? { 196*8d82ecd9S猫头猫 top: (layout.height - ITEM_HEIGHT) / 2, 197*8d82ecd9S猫头猫 } 198*8d82ecd9S猫头猫 : null, 199*8d82ecd9S猫头猫 ]}> 200*8d82ecd9S猫头猫 <DraggingTime 201*8d82ecd9S猫头猫 time={ 202*8d82ecd9S猫头猫 (lyric[draggingIndex]?.time ?? 0) + 203*8d82ecd9S猫头猫 +(meta?.offset ?? 0) 204*8d82ecd9S猫头猫 } 205*8d82ecd9S猫头猫 /> 206*8d82ecd9S猫头猫 <View style={style.singleLine} /> 207*8d82ecd9S猫头猫 208*8d82ecd9S猫头猫 <IconButtonWithGesture 209*8d82ecd9S猫头猫 style={style.playIcon} 210*8d82ecd9S猫头猫 sizeType="small" 211*8d82ecd9S猫头猫 name="play" 212*8d82ecd9S猫头猫 onPress={onLyricSeekPress} 213*8d82ecd9S猫头猫 /> 214*8d82ecd9S猫头猫 </View> 215*8d82ecd9S猫头猫 )} 216*8d82ecd9S猫头猫 </View> 217*8d82ecd9S猫头猫 ); 218*8d82ecd9S猫头猫} 219*8d82ecd9S猫头猫 220*8d82ecd9S猫头猫const style = StyleSheet.create({ 221*8d82ecd9S猫头猫 wrapper: { 222*8d82ecd9S猫头猫 width: '100%', 223*8d82ecd9S猫头猫 marginVertical: rpx(48), 224*8d82ecd9S猫头猫 flex: 1, 225*8d82ecd9S猫头猫 }, 226*8d82ecd9S猫头猫 empty: { 227*8d82ecd9S猫头猫 paddingTop: '70%', 228*8d82ecd9S猫头猫 }, 229*8d82ecd9S猫头猫 white: { 230*8d82ecd9S猫头猫 color: 'white', 231*8d82ecd9S猫头猫 }, 232*8d82ecd9S猫头猫 draggingTime: { 233*8d82ecd9S猫头猫 position: 'absolute', 234*8d82ecd9S猫头猫 width: '100%', 235*8d82ecd9S猫头猫 height: ITEM_HEIGHT, 236*8d82ecd9S猫头猫 top: '40%', 237*8d82ecd9S猫头猫 marginTop: rpx(48), 238*8d82ecd9S猫头猫 paddingHorizontal: rpx(18), 239*8d82ecd9S猫头猫 right: 0, 240*8d82ecd9S猫头猫 flexDirection: 'row', 241*8d82ecd9S猫头猫 alignItems: 'center', 242*8d82ecd9S猫头猫 justifyContent: 'space-between', 243*8d82ecd9S猫头猫 }, 244*8d82ecd9S猫头猫 draggingTimeText: { 245*8d82ecd9S猫头猫 color: '#dddddd', 246*8d82ecd9S猫头猫 fontSize: fontSizeConst.description, 247*8d82ecd9S猫头猫 width: rpx(90), 248*8d82ecd9S猫头猫 }, 249*8d82ecd9S猫头猫 singleLine: { 250*8d82ecd9S猫头猫 width: '67%', 251*8d82ecd9S猫头猫 height: 1, 252*8d82ecd9S猫头猫 backgroundColor: '#cccccc', 253*8d82ecd9S猫头猫 opacity: 0.4, 254*8d82ecd9S猫头猫 }, 255*8d82ecd9S猫头猫 playIcon: { 256*8d82ecd9S猫头猫 width: rpx(90), 257*8d82ecd9S猫头猫 textAlign: 'right', 258*8d82ecd9S猫头猫 color: 'white', 259*8d82ecd9S猫头猫 }, 260*8d82ecd9S猫头猫 searchLyric: { 261*8d82ecd9S猫头猫 width: rpx(180), 262*8d82ecd9S猫头猫 marginTop: rpx(14), 263*8d82ecd9S猫头猫 paddingVertical: rpx(10), 264*8d82ecd9S猫头猫 textAlign: 'center', 265*8d82ecd9S猫头猫 alignSelf: 'center', 266*8d82ecd9S猫头猫 color: '#66eeff', 267*8d82ecd9S猫头猫 textDecorationLine: 'underline', 268*8d82ecd9S猫头猫 }, 269*8d82ecd9S猫头猫}); 270