mirror of
https://github.com/ikun0014/lx-music-mobile.git
synced 2025-07-03 20:32:10 +08:00
新增是否允许通过歌词调整播放进度功能
This commit is contained in:
parent
cc5628252c
commit
3d735273e6
@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
- 新增列表设置-是否显示歌曲专辑名,默认关闭
|
- 新增列表设置-是否显示歌曲专辑名,默认关闭
|
||||||
- 新增列表设置-是否显示歌曲时长,默认开启
|
- 新增列表设置-是否显示歌曲时长,默认开启
|
||||||
|
- 新增是否允许通过歌词调整播放进度功能,默认关闭,可到播放详情页右上角设置开启
|
||||||
|
|
||||||
### 优化
|
### 优化
|
||||||
|
|
||||||
|
@ -31,6 +31,7 @@ const defaultSetting: LX.AppSetting = {
|
|||||||
'playDetail.style.align': 'center',
|
'playDetail.style.align': 'center',
|
||||||
'playDetail.vertical.style.lrcFontSize': 176,
|
'playDetail.vertical.style.lrcFontSize': 176,
|
||||||
'playDetail.horizontal.style.lrcFontSize': 180,
|
'playDetail.horizontal.style.lrcFontSize': 180,
|
||||||
|
'playDetail.isShowLyricProgressSetting': false,
|
||||||
|
|
||||||
'desktopLyric.enable': false,
|
'desktopLyric.enable': false,
|
||||||
'desktopLyric.isLock': false,
|
'desktopLyric.isLock': false,
|
||||||
|
@ -115,6 +115,7 @@
|
|||||||
"play_detail_setting_lrc_font_size": "Lyric font size",
|
"play_detail_setting_lrc_font_size": "Lyric font size",
|
||||||
"play_detail_setting_playback_rate": "Playback rate",
|
"play_detail_setting_playback_rate": "Playback rate",
|
||||||
"play_detail_setting_playback_rate_reset": "reset",
|
"play_detail_setting_playback_rate_reset": "reset",
|
||||||
|
"play_detail_setting_show_lyric_progress_setting": "Allows to adjust playback progress by lyrics",
|
||||||
"play_detail_setting_title": "Player settings",
|
"play_detail_setting_title": "Player settings",
|
||||||
"play_detail_setting_volume": "Volume",
|
"play_detail_setting_volume": "Volume",
|
||||||
"play_detail_todo_tip": "What do you want? No, this function has not been implemented yet 😛, But you can try to locate the currently playing song by long pressing (only valid for playing songs in \"My List\")",
|
"play_detail_todo_tip": "What do you want? No, this function has not been implemented yet 😛, But you can try to locate the currently playing song by long pressing (only valid for playing songs in \"My List\")",
|
||||||
|
@ -115,6 +115,7 @@
|
|||||||
"play_detail_setting_lrc_font_size": "歌词字体大小",
|
"play_detail_setting_lrc_font_size": "歌词字体大小",
|
||||||
"play_detail_setting_playback_rate": "播放速率",
|
"play_detail_setting_playback_rate": "播放速率",
|
||||||
"play_detail_setting_playback_rate_reset": "重置",
|
"play_detail_setting_playback_rate_reset": "重置",
|
||||||
|
"play_detail_setting_show_lyric_progress_setting": "允许通过歌词调整播放进度",
|
||||||
"play_detail_setting_title": "播放器设置",
|
"play_detail_setting_title": "播放器设置",
|
||||||
"play_detail_setting_volume": "音量大小",
|
"play_detail_setting_volume": "音量大小",
|
||||||
"play_detail_todo_tip": "你想干嘛?不可以的,这个功能还没有实现哦😛,不过你可以试着长按来定位当前播放的歌曲(仅对播放“我的列表”里的歌曲有效哦)",
|
"play_detail_todo_tip": "你想干嘛?不可以的,这个功能还没有实现哦😛,不过你可以试着长按来定位当前播放的歌曲(仅对播放“我的列表”里的歌曲有效哦)",
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { memo, useMemo, useEffect, useRef } from 'react'
|
import { memo, useMemo, useEffect, useRef, useCallback } from 'react'
|
||||||
import { View, FlatList, type FlatListProps } from 'react-native'
|
import { View, FlatList, type FlatListProps, type NativeSyntheticEvent, type NativeScrollEvent, type LayoutChangeEvent } from 'react-native'
|
||||||
// import { useLayout } from '@/utils/hooks'
|
// import { useLayout } from '@/utils/hooks'
|
||||||
import { type Line, useLrcPlay, useLrcSet } from '@/plugins/lyric'
|
import { type Line, useLrcPlay, useLrcSet } from '@/plugins/lyric'
|
||||||
import { createStyle } from '@/utils/tools'
|
import { createStyle } from '@/utils/tools'
|
||||||
@ -9,18 +9,21 @@ import { useSettingValue } from '@/store/setting/hook'
|
|||||||
import { AnimatedColorText } from '@/components/common/Text'
|
import { AnimatedColorText } from '@/components/common/Text'
|
||||||
import { setSpText } from '@/utils/pixelRatio'
|
import { setSpText } from '@/utils/pixelRatio'
|
||||||
import playerState from '@/store/player/state'
|
import playerState from '@/store/player/state'
|
||||||
|
import { scrollTo } from '@/utils/scroll'
|
||||||
|
import PlayLine, { type PlayLineType } from '../components/PlayLine'
|
||||||
// import { screenkeepAwake } from '@/utils/nativeModules/utils'
|
// import { screenkeepAwake } from '@/utils/nativeModules/utils'
|
||||||
// import { log } from '@/utils/log'
|
// import { log } from '@/utils/log'
|
||||||
// import { toast } from '@/utils/tools'
|
// import { toast } from '@/utils/tools'
|
||||||
|
|
||||||
type FlatListType = FlatListProps<Line>
|
type FlatListType = FlatListProps<Line>
|
||||||
|
|
||||||
|
interface LineProps {
|
||||||
const LrcLine = memo(({ line, lineNum, activeLine }: {
|
|
||||||
line: Line
|
line: Line
|
||||||
lineNum: number
|
lineNum: number
|
||||||
activeLine: number
|
activeLine: number
|
||||||
}) => {
|
onLayout: (lineNum: number, height: number, width: number) => void
|
||||||
|
}
|
||||||
|
const LrcLine = memo(({ line, lineNum, activeLine, onLayout }: LineProps) => {
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
const lrcFontSize = useSettingValue('playDetail.horizontal.style.lrcFontSize')
|
const lrcFontSize = useSettingValue('playDetail.horizontal.style.lrcFontSize')
|
||||||
const textAlign = useSettingValue('playDetail.style.align')
|
const textAlign = useSettingValue('playDetail.style.align')
|
||||||
@ -38,10 +41,14 @@ const LrcLine = memo(({ line, lineNum, activeLine }: {
|
|||||||
]
|
]
|
||||||
}, [activeLine, lineNum, theme])
|
}, [activeLine, lineNum, theme])
|
||||||
|
|
||||||
|
const handleLayout = ({ nativeEvent }: LayoutChangeEvent) => {
|
||||||
|
onLayout(lineNum, nativeEvent.layout.height, nativeEvent.layout.width)
|
||||||
|
}
|
||||||
|
|
||||||
// textBreakStrategy="simple" 用于解决某些设备上字体被截断的问题
|
// textBreakStrategy="simple" 用于解决某些设备上字体被截断的问题
|
||||||
// https://stackoverflow.com/a/72822360
|
// https://stackoverflow.com/a/72822360
|
||||||
return (
|
return (
|
||||||
<View style={styles.line}>
|
<View style={styles.line} onLayout={handleLayout}>
|
||||||
<AnimatedColorText style={{
|
<AnimatedColorText style={{
|
||||||
...styles.lineText,
|
...styles.lineText,
|
||||||
textAlign,
|
textAlign,
|
||||||
@ -69,11 +76,16 @@ export default () => {
|
|||||||
const lyricLines = useLrcSet()
|
const lyricLines = useLrcSet()
|
||||||
const { line } = useLrcPlay()
|
const { line } = useLrcPlay()
|
||||||
const flatListRef = useRef<FlatList>(null)
|
const flatListRef = useRef<FlatList>(null)
|
||||||
|
const playLineRef = useRef<PlayLineType>(null)
|
||||||
const isPauseScrollRef = useRef(true)
|
const isPauseScrollRef = useRef(true)
|
||||||
const scrollTimoutRef = useRef<NodeJS.Timeout | null>(null)
|
const scrollTimoutRef = useRef<NodeJS.Timeout | null>(null)
|
||||||
const delayScrollTimeout = useRef<NodeJS.Timeout | null>(null)
|
const delayScrollTimeout = useRef<NodeJS.Timeout | null>(null)
|
||||||
const lineRef = useRef(0)
|
const lineRef = useRef({ line: 0, prevLine: 0 })
|
||||||
const isFirstSetLrc = useRef(true)
|
const isFirstSetLrc = useRef(true)
|
||||||
|
const scrollInfoRef = useRef<NativeSyntheticEvent<NativeScrollEvent>['nativeEvent'] | null>(null)
|
||||||
|
const listLayoutInfoRef = useRef<{ spaceHeight: number, lineHeights: number[] }>({ spaceHeight: 0, lineHeights: [] })
|
||||||
|
const scrollCancelRef = useRef<(() => void) | null>(null)
|
||||||
|
const isShowLyricProgressSetting = useSettingValue('playDetail.isShowLyricProgressSetting')
|
||||||
// useLock()
|
// useLock()
|
||||||
// const [imgUrl, setImgUrl] = useState(null)
|
// const [imgUrl, setImgUrl] = useState(null)
|
||||||
// const theme = useGetter('common', 'theme')
|
// const theme = useGetter('common', 'theme')
|
||||||
@ -87,9 +99,25 @@ export default () => {
|
|||||||
// }, [playMusicInfo])
|
// }, [playMusicInfo])
|
||||||
|
|
||||||
// const imgWidth = useMemo(() => layout.width * 0.75, [layout.width])
|
// const imgWidth = useMemo(() => layout.width * 0.75, [layout.width])
|
||||||
const handleScrollToActive = (index = lineRef.current) => {
|
const handleScrollToActive = (index = lineRef.current.line) => {
|
||||||
if (index < 0) return
|
if (index < 0) return
|
||||||
if (flatListRef.current) {
|
if (flatListRef.current) {
|
||||||
|
// console.log('handleScrollToActive', index)
|
||||||
|
if (scrollCancelRef.current) {
|
||||||
|
scrollCancelRef.current()
|
||||||
|
scrollCancelRef.current = null
|
||||||
|
}
|
||||||
|
if (scrollInfoRef.current && lineRef.current.line - lineRef.current.prevLine == 1) {
|
||||||
|
let offset = listLayoutInfoRef.current.spaceHeight
|
||||||
|
for (let line = 0; line < index; line++) {
|
||||||
|
offset += listLayoutInfoRef.current.lineHeights[line]
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
scrollCancelRef.current = scrollTo(flatListRef.current, scrollInfoRef.current, offset - scrollInfoRef.current.layoutMeasurement.height * 0.36, 300, () => {
|
||||||
|
scrollCancelRef.current = null
|
||||||
|
})
|
||||||
|
} catch {}
|
||||||
|
} else {
|
||||||
try {
|
try {
|
||||||
flatListRef.current.scrollToIndex({
|
flatListRef.current.scrollToIndex({
|
||||||
index,
|
index,
|
||||||
@ -99,9 +127,17 @@ export default () => {
|
|||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleScroll = ({ nativeEvent }: NativeSyntheticEvent<NativeScrollEvent>) => {
|
||||||
|
scrollInfoRef.current = nativeEvent
|
||||||
|
if (isPauseScrollRef.current) {
|
||||||
|
playLineRef.current?.updateScrollInfo(nativeEvent)
|
||||||
|
}
|
||||||
|
}
|
||||||
const handleScrollBeginDrag = () => {
|
const handleScrollBeginDrag = () => {
|
||||||
isPauseScrollRef.current = true
|
isPauseScrollRef.current = true
|
||||||
|
playLineRef.current?.setVisible(true)
|
||||||
if (delayScrollTimeout.current) {
|
if (delayScrollTimeout.current) {
|
||||||
clearTimeout(delayScrollTimeout.current)
|
clearTimeout(delayScrollTimeout.current)
|
||||||
delayScrollTimeout.current = null
|
delayScrollTimeout.current = null
|
||||||
@ -116,6 +152,7 @@ export default () => {
|
|||||||
if (!isPauseScrollRef.current) return
|
if (!isPauseScrollRef.current) return
|
||||||
if (scrollTimoutRef.current) clearTimeout(scrollTimoutRef.current)
|
if (scrollTimoutRef.current) clearTimeout(scrollTimoutRef.current)
|
||||||
scrollTimoutRef.current = setTimeout(() => {
|
scrollTimoutRef.current = setTimeout(() => {
|
||||||
|
playLineRef.current?.setVisible(false)
|
||||||
scrollTimoutRef.current = null
|
scrollTimoutRef.current = null
|
||||||
isPauseScrollRef.current = false
|
isPauseScrollRef.current = false
|
||||||
if (!playerState.isPlay) return
|
if (!playerState.isPlay) return
|
||||||
@ -139,11 +176,17 @@ export default () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// linesRef.current = lyricLines
|
// linesRef.current = lyricLines
|
||||||
|
listLayoutInfoRef.current.lineHeights = []
|
||||||
|
lineRef.current.prevLine = 0
|
||||||
|
lineRef.current.line = 0
|
||||||
if (!flatListRef.current) return
|
if (!flatListRef.current) return
|
||||||
flatListRef.current.scrollToOffset({
|
flatListRef.current.scrollToOffset({
|
||||||
offset: 0,
|
offset: 0,
|
||||||
animated: false,
|
animated: false,
|
||||||
})
|
})
|
||||||
|
if (!lyricLines.length) return
|
||||||
|
playLineRef.current?.updateLyricLines(lyricLines)
|
||||||
|
requestAnimationFrame(() => {
|
||||||
if (isFirstSetLrc.current) {
|
if (isFirstSetLrc.current) {
|
||||||
isFirstSetLrc.current = false
|
isFirstSetLrc.current = false
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -151,13 +194,25 @@ export default () => {
|
|||||||
handleScrollToActive()
|
handleScrollToActive()
|
||||||
}, 100)
|
}, 100)
|
||||||
} else {
|
} else {
|
||||||
|
if (delayScrollTimeout.current) clearTimeout(delayScrollTimeout.current)
|
||||||
|
delayScrollTimeout.current = setTimeout(() => {
|
||||||
handleScrollToActive(0)
|
handleScrollToActive(0)
|
||||||
|
}, 100)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}, [lyricLines])
|
}, [lyricLines])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
lineRef.current = line
|
if (line < 0) return
|
||||||
|
lineRef.current.prevLine = lineRef.current.line
|
||||||
|
lineRef.current.line = line
|
||||||
if (!flatListRef.current || isPauseScrollRef.current) return
|
if (!flatListRef.current || isPauseScrollRef.current) return
|
||||||
|
|
||||||
|
if (line - lineRef.current.prevLine != 1) {
|
||||||
|
handleScrollToActive()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
delayScrollTimeout.current = setTimeout(() => {
|
delayScrollTimeout.current = setTimeout(() => {
|
||||||
delayScrollTimeout.current = null
|
delayScrollTimeout.current = null
|
||||||
handleScrollToActive()
|
handleScrollToActive()
|
||||||
@ -165,24 +220,39 @@ export default () => {
|
|||||||
}, [line])
|
}, [line])
|
||||||
|
|
||||||
const handleScrollToIndexFailed: FlatListType['onScrollToIndexFailed'] = (info) => {
|
const handleScrollToIndexFailed: FlatListType['onScrollToIndexFailed'] = (info) => {
|
||||||
// console.log(info)
|
|
||||||
void wait().then(() => {
|
void wait().then(() => {
|
||||||
handleScrollToActive(info.index)
|
handleScrollToActive(info.index)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleLineLayout = useCallback<LineProps['onLayout']>((lineNum, height) => {
|
||||||
|
listLayoutInfoRef.current.lineHeights[lineNum] = height
|
||||||
|
playLineRef.current?.updateLayoutInfo(listLayoutInfoRef.current)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleSpaceLayout = useCallback(({ nativeEvent }: LayoutChangeEvent) => {
|
||||||
|
listLayoutInfoRef.current.spaceHeight = nativeEvent.layout.height
|
||||||
|
playLineRef.current?.updateLayoutInfo(listLayoutInfoRef.current)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handlePlayLine = useCallback((time: number) => {
|
||||||
|
playLineRef.current?.setVisible(false)
|
||||||
|
global.app_event.setProgress(time)
|
||||||
|
}, [])
|
||||||
|
|
||||||
const renderItem: FlatListType['renderItem'] = ({ item, index }) => {
|
const renderItem: FlatListType['renderItem'] = ({ item, index }) => {
|
||||||
return (
|
return (
|
||||||
<LrcLine line={item} lineNum={index} activeLine={line} />
|
<LrcLine line={item} lineNum={index} activeLine={line} onLayout={handleLineLayout} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
const getkey: FlatListType['keyExtractor'] = (item, index) => `${index}${item.text}`
|
const getkey: FlatListType['keyExtractor'] = (item, index) => `${index}${item.text}`
|
||||||
|
|
||||||
const spaceComponent = useMemo(() => (
|
const spaceComponent = useMemo(() => (
|
||||||
<View style={styles.space}></View>
|
<View style={styles.space} onLayout={handleSpaceLayout}></View>
|
||||||
), [])
|
), [handleSpaceLayout])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<FlatList
|
<FlatList
|
||||||
data={lyricLines}
|
data={lyricLines}
|
||||||
renderItem={renderItem}
|
renderItem={renderItem}
|
||||||
@ -197,7 +267,10 @@ export default () => {
|
|||||||
fadingEdgeLength={100}
|
fadingEdgeLength={100}
|
||||||
initialNumToRender={Math.max(line + 10, 10)}
|
initialNumToRender={Math.max(line + 10, 10)}
|
||||||
onScrollToIndexFailed={handleScrollToIndexFailed}
|
onScrollToIndexFailed={handleScrollToIndexFailed}
|
||||||
|
onScroll={handleScroll}
|
||||||
/>
|
/>
|
||||||
|
{ isShowLyricProgressSetting ? <PlayLine ref={playLineRef} onPlayLine={handlePlayLine} /> : null }
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { memo, useMemo, useEffect, useRef } from 'react'
|
import { memo, useMemo, useEffect, useRef, useCallback } from 'react'
|
||||||
import { View, FlatList, type FlatListProps } from 'react-native'
|
import { View, FlatList, type FlatListProps, type LayoutChangeEvent, type NativeSyntheticEvent, type NativeScrollEvent } from 'react-native'
|
||||||
// import { useLayout } from '@/utils/hooks'
|
// import { useLayout } from '@/utils/hooks'
|
||||||
import { type Line, useLrcPlay, useLrcSet } from '@/plugins/lyric'
|
import { type Line, useLrcPlay, useLrcSet } from '@/plugins/lyric'
|
||||||
import { createStyle } from '@/utils/tools'
|
import { createStyle } from '@/utils/tools'
|
||||||
@ -9,6 +9,8 @@ import { useSettingValue } from '@/store/setting/hook'
|
|||||||
import { AnimatedColorText } from '@/components/common/Text'
|
import { AnimatedColorText } from '@/components/common/Text'
|
||||||
import { setSpText } from '@/utils/pixelRatio'
|
import { setSpText } from '@/utils/pixelRatio'
|
||||||
import playerState from '@/store/player/state'
|
import playerState from '@/store/player/state'
|
||||||
|
import { scrollTo } from '@/utils/scroll'
|
||||||
|
import PlayLine, { type PlayLineType } from '../components/PlayLine'
|
||||||
// import { screenkeepAwake } from '@/utils/nativeModules/utils'
|
// import { screenkeepAwake } from '@/utils/nativeModules/utils'
|
||||||
// import { log } from '@/utils/log'
|
// import { log } from '@/utils/log'
|
||||||
// import { toast } from '@/utils/tools'
|
// import { toast } from '@/utils/tools'
|
||||||
@ -54,11 +56,13 @@ type FlatListType = FlatListProps<Line>
|
|||||||
// }, [])
|
// }, [])
|
||||||
// }
|
// }
|
||||||
|
|
||||||
const LrcLine = memo(({ line, lineNum, activeLine }: {
|
interface LineProps {
|
||||||
line: Line
|
line: Line
|
||||||
lineNum: number
|
lineNum: number
|
||||||
activeLine: number
|
activeLine: number
|
||||||
}) => {
|
onLayout: (lineNum: number, height: number, width: number) => void
|
||||||
|
}
|
||||||
|
const LrcLine = memo(({ line, lineNum, activeLine, onLayout }: LineProps) => {
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
const lrcFontSize = useSettingValue('playDetail.vertical.style.lrcFontSize')
|
const lrcFontSize = useSettingValue('playDetail.vertical.style.lrcFontSize')
|
||||||
const textAlign = useSettingValue('playDetail.style.align')
|
const textAlign = useSettingValue('playDetail.style.align')
|
||||||
@ -76,11 +80,15 @@ const LrcLine = memo(({ line, lineNum, activeLine }: {
|
|||||||
]
|
]
|
||||||
}, [activeLine, lineNum, theme])
|
}, [activeLine, lineNum, theme])
|
||||||
|
|
||||||
|
const handleLayout = ({ nativeEvent }: LayoutChangeEvent) => {
|
||||||
|
onLayout(lineNum, nativeEvent.layout.height, nativeEvent.layout.width)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// textBreakStrategy="simple" 用于解决某些设备上字体被截断的问题
|
// textBreakStrategy="simple" 用于解决某些设备上字体被截断的问题
|
||||||
// https://stackoverflow.com/a/72822360
|
// https://stackoverflow.com/a/72822360
|
||||||
return (
|
return (
|
||||||
<View style={styles.line}>
|
<View style={styles.line} onLayout={handleLayout}>
|
||||||
<AnimatedColorText style={{
|
<AnimatedColorText style={{
|
||||||
...styles.lineText,
|
...styles.lineText,
|
||||||
textAlign,
|
textAlign,
|
||||||
@ -108,11 +116,16 @@ export default () => {
|
|||||||
const lyricLines = useLrcSet()
|
const lyricLines = useLrcSet()
|
||||||
const { line } = useLrcPlay()
|
const { line } = useLrcPlay()
|
||||||
const flatListRef = useRef<FlatList>(null)
|
const flatListRef = useRef<FlatList>(null)
|
||||||
|
const playLineRef = useRef<PlayLineType>(null)
|
||||||
const isPauseScrollRef = useRef(true)
|
const isPauseScrollRef = useRef(true)
|
||||||
const scrollTimoutRef = useRef<NodeJS.Timeout | null>(null)
|
const scrollTimoutRef = useRef<NodeJS.Timeout | null>(null)
|
||||||
const delayScrollTimeout = useRef<NodeJS.Timeout | null>(null)
|
const delayScrollTimeout = useRef<NodeJS.Timeout | null>(null)
|
||||||
const lineRef = useRef(0)
|
const lineRef = useRef({ line: 0, prevLine: 0 })
|
||||||
const isFirstSetLrc = useRef(true)
|
const isFirstSetLrc = useRef(true)
|
||||||
|
const scrollInfoRef = useRef<NativeSyntheticEvent<NativeScrollEvent>['nativeEvent'] | null>(null)
|
||||||
|
const listLayoutInfoRef = useRef<{ spaceHeight: number, lineHeights: number[] }>({ spaceHeight: 0, lineHeights: [] })
|
||||||
|
const scrollCancelRef = useRef<(() => void) | null>(null)
|
||||||
|
const isShowLyricProgressSetting = useSettingValue('playDetail.isShowLyricProgressSetting')
|
||||||
// useLock()
|
// useLock()
|
||||||
// const [imgUrl, setImgUrl] = useState(null)
|
// const [imgUrl, setImgUrl] = useState(null)
|
||||||
// const theme = useGetter('common', 'theme')
|
// const theme = useGetter('common', 'theme')
|
||||||
@ -126,9 +139,25 @@ export default () => {
|
|||||||
// }, [playMusicInfo])
|
// }, [playMusicInfo])
|
||||||
|
|
||||||
// const imgWidth = useMemo(() => layout.width * 0.75, [layout.width])
|
// const imgWidth = useMemo(() => layout.width * 0.75, [layout.width])
|
||||||
const handleScrollToActive = (index = lineRef.current) => {
|
const handleScrollToActive = (index = lineRef.current.line) => {
|
||||||
if (index < 0) return
|
if (index < 0) return
|
||||||
if (flatListRef.current) {
|
if (flatListRef.current) {
|
||||||
|
// console.log('handleScrollToActive', index)
|
||||||
|
if (scrollCancelRef.current) {
|
||||||
|
scrollCancelRef.current()
|
||||||
|
scrollCancelRef.current = null
|
||||||
|
}
|
||||||
|
if (scrollInfoRef.current && lineRef.current.line - lineRef.current.prevLine == 1) {
|
||||||
|
let offset = listLayoutInfoRef.current.spaceHeight
|
||||||
|
for (let line = 0; line < index; line++) {
|
||||||
|
offset += listLayoutInfoRef.current.lineHeights[line]
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
scrollCancelRef.current = scrollTo(flatListRef.current, scrollInfoRef.current, offset - scrollInfoRef.current.layoutMeasurement.height * 0.36, 300, () => {
|
||||||
|
scrollCancelRef.current = null
|
||||||
|
})
|
||||||
|
} catch {}
|
||||||
|
} else {
|
||||||
try {
|
try {
|
||||||
flatListRef.current.scrollToIndex({
|
flatListRef.current.scrollToIndex({
|
||||||
index,
|
index,
|
||||||
@ -138,9 +167,17 @@ export default () => {
|
|||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleScroll = ({ nativeEvent }: NativeSyntheticEvent<NativeScrollEvent>) => {
|
||||||
|
scrollInfoRef.current = nativeEvent
|
||||||
|
if (isPauseScrollRef.current) {
|
||||||
|
playLineRef.current?.updateScrollInfo(nativeEvent)
|
||||||
|
}
|
||||||
|
}
|
||||||
const handleScrollBeginDrag = () => {
|
const handleScrollBeginDrag = () => {
|
||||||
isPauseScrollRef.current = true
|
isPauseScrollRef.current = true
|
||||||
|
playLineRef.current?.setVisible(true)
|
||||||
if (delayScrollTimeout.current) {
|
if (delayScrollTimeout.current) {
|
||||||
clearTimeout(delayScrollTimeout.current)
|
clearTimeout(delayScrollTimeout.current)
|
||||||
delayScrollTimeout.current = null
|
delayScrollTimeout.current = null
|
||||||
@ -155,6 +192,7 @@ export default () => {
|
|||||||
if (!isPauseScrollRef.current) return
|
if (!isPauseScrollRef.current) return
|
||||||
if (scrollTimoutRef.current) clearTimeout(scrollTimoutRef.current)
|
if (scrollTimoutRef.current) clearTimeout(scrollTimoutRef.current)
|
||||||
scrollTimoutRef.current = setTimeout(() => {
|
scrollTimoutRef.current = setTimeout(() => {
|
||||||
|
playLineRef.current?.setVisible(false)
|
||||||
scrollTimoutRef.current = null
|
scrollTimoutRef.current = null
|
||||||
isPauseScrollRef.current = false
|
isPauseScrollRef.current = false
|
||||||
if (!playerState.isPlay) return
|
if (!playerState.isPlay) return
|
||||||
@ -178,11 +216,17 @@ export default () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// linesRef.current = lyricLines
|
// linesRef.current = lyricLines
|
||||||
|
listLayoutInfoRef.current.lineHeights = []
|
||||||
|
lineRef.current.prevLine = 0
|
||||||
|
lineRef.current.line = 0
|
||||||
if (!flatListRef.current) return
|
if (!flatListRef.current) return
|
||||||
flatListRef.current.scrollToOffset({
|
flatListRef.current.scrollToOffset({
|
||||||
offset: 0,
|
offset: 0,
|
||||||
animated: false,
|
animated: false,
|
||||||
})
|
})
|
||||||
|
if (!lyricLines.length) return
|
||||||
|
playLineRef.current?.updateLyricLines(lyricLines)
|
||||||
|
requestAnimationFrame(() => {
|
||||||
if (isFirstSetLrc.current) {
|
if (isFirstSetLrc.current) {
|
||||||
isFirstSetLrc.current = false
|
isFirstSetLrc.current = false
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -190,13 +234,25 @@ export default () => {
|
|||||||
handleScrollToActive()
|
handleScrollToActive()
|
||||||
}, 100)
|
}, 100)
|
||||||
} else {
|
} else {
|
||||||
|
if (delayScrollTimeout.current) clearTimeout(delayScrollTimeout.current)
|
||||||
|
delayScrollTimeout.current = setTimeout(() => {
|
||||||
handleScrollToActive(0)
|
handleScrollToActive(0)
|
||||||
|
}, 100)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}, [lyricLines])
|
}, [lyricLines])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
lineRef.current = line
|
if (line < 0) return
|
||||||
|
lineRef.current.prevLine = lineRef.current.line
|
||||||
|
lineRef.current.line = line
|
||||||
if (!flatListRef.current || isPauseScrollRef.current) return
|
if (!flatListRef.current || isPauseScrollRef.current) return
|
||||||
|
|
||||||
|
if (line - lineRef.current.prevLine != 1) {
|
||||||
|
handleScrollToActive()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
delayScrollTimeout.current = setTimeout(() => {
|
delayScrollTimeout.current = setTimeout(() => {
|
||||||
delayScrollTimeout.current = null
|
delayScrollTimeout.current = null
|
||||||
handleScrollToActive()
|
handleScrollToActive()
|
||||||
@ -204,24 +260,39 @@ export default () => {
|
|||||||
}, [line])
|
}, [line])
|
||||||
|
|
||||||
const handleScrollToIndexFailed: FlatListType['onScrollToIndexFailed'] = (info) => {
|
const handleScrollToIndexFailed: FlatListType['onScrollToIndexFailed'] = (info) => {
|
||||||
// console.log(info)
|
|
||||||
void wait().then(() => {
|
void wait().then(() => {
|
||||||
handleScrollToActive(info.index)
|
handleScrollToActive(info.index)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleLineLayout = useCallback<LineProps['onLayout']>((lineNum, height) => {
|
||||||
|
listLayoutInfoRef.current.lineHeights[lineNum] = height
|
||||||
|
playLineRef.current?.updateLayoutInfo(listLayoutInfoRef.current)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleSpaceLayout = useCallback(({ nativeEvent }: LayoutChangeEvent) => {
|
||||||
|
listLayoutInfoRef.current.spaceHeight = nativeEvent.layout.height
|
||||||
|
playLineRef.current?.updateLayoutInfo(listLayoutInfoRef.current)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handlePlayLine = useCallback((time: number) => {
|
||||||
|
playLineRef.current?.setVisible(false)
|
||||||
|
global.app_event.setProgress(time)
|
||||||
|
}, [])
|
||||||
|
|
||||||
const renderItem: FlatListType['renderItem'] = ({ item, index }) => {
|
const renderItem: FlatListType['renderItem'] = ({ item, index }) => {
|
||||||
return (
|
return (
|
||||||
<LrcLine line={item} lineNum={index} activeLine={line} />
|
<LrcLine line={item} lineNum={index} activeLine={line} onLayout={handleLineLayout} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
const getkey: FlatListType['keyExtractor'] = (item, index) => `${index}${item.text}`
|
const getkey: FlatListType['keyExtractor'] = (item, index) => `${index}${item.text}`
|
||||||
|
|
||||||
const spaceComponent = useMemo(() => (
|
const spaceComponent = useMemo(() => (
|
||||||
<View style={styles.space}></View>
|
<View style={styles.space} onLayout={handleSpaceLayout}></View>
|
||||||
), [])
|
), [handleSpaceLayout])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<FlatList
|
<FlatList
|
||||||
data={lyricLines}
|
data={lyricLines}
|
||||||
renderItem={renderItem}
|
renderItem={renderItem}
|
||||||
@ -236,7 +307,10 @@ export default () => {
|
|||||||
fadingEdgeLength={100}
|
fadingEdgeLength={100}
|
||||||
initialNumToRender={Math.max(line + 10, 10)}
|
initialNumToRender={Math.max(line + 10, 10)}
|
||||||
onScrollToIndexFailed={handleScrollToIndexFailed}
|
onScrollToIndexFailed={handleScrollToIndexFailed}
|
||||||
|
onScroll={handleScroll}
|
||||||
/>
|
/>
|
||||||
|
{ isShowLyricProgressSetting ? <PlayLine ref={playLineRef} onPlayLine={handlePlayLine} /> : null }
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -251,8 +325,8 @@ const styles = createStyle({
|
|||||||
paddingTop: '80%',
|
paddingTop: '80%',
|
||||||
},
|
},
|
||||||
line: {
|
line: {
|
||||||
marginTop: 10,
|
paddingTop: 10,
|
||||||
marginBottom: 10,
|
paddingBottom: 10,
|
||||||
// opacity: 0,
|
// opacity: 0,
|
||||||
},
|
},
|
||||||
lineText: {
|
lineText: {
|
||||||
|
136
src/screens/PlayDetail/components/PlayLine.tsx
Normal file
136
src/screens/PlayDetail/components/PlayLine.tsx
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
import { forwardRef, useImperativeHandle, useRef, useState } from 'react'
|
||||||
|
import { type NativeScrollEvent, type NativeSyntheticEvent, View, TouchableOpacity, Animated } from 'react-native'
|
||||||
|
import Text from '@/components/common/Text'
|
||||||
|
import { createStyle } from '@/utils/tools'
|
||||||
|
import { type Lines } from 'lrc-file-parser'
|
||||||
|
import { useTheme } from '@/store/theme/hook'
|
||||||
|
import { BorderWidths } from '@/theme'
|
||||||
|
import { formatPlayTime2 } from '@/utils'
|
||||||
|
import { Icon } from '@/components/common/Icon'
|
||||||
|
|
||||||
|
|
||||||
|
export interface PlayLineType {
|
||||||
|
updateScrollInfo: (scrollInfo: NativeSyntheticEvent<NativeScrollEvent>['nativeEvent'] | null) => void
|
||||||
|
updateLayoutInfo: (listLayoutInfo: { spaceHeight: number, lineHeights: number[] }) => void
|
||||||
|
updateLyricLines: (lyricLines: Lines) => void
|
||||||
|
setVisible: (visible: boolean) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlayLineProps {
|
||||||
|
onPlayLine: (time: number) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const ANIMATION_DURATION = 300
|
||||||
|
|
||||||
|
export default forwardRef<PlayLineType, PlayLineProps>(({ onPlayLine }, ref) => {
|
||||||
|
const theme = useTheme()
|
||||||
|
const [scrollInfo, setScrollInfo] = useState<NativeSyntheticEvent<NativeScrollEvent>['nativeEvent'] | null>(null)
|
||||||
|
const [listLayoutInfo, setListLayoutInfo] = useState<{ spaceHeight: number, lineHeights: number[] }>({ spaceHeight: 0, lineHeights: [] })
|
||||||
|
const [lyricLines, setLyricLines] = useState<Lines>([])
|
||||||
|
const [visible, setVisible] = useState(false)
|
||||||
|
const opsAnim = useRef<Animated.Value>(
|
||||||
|
new Animated.Value(0),
|
||||||
|
).current
|
||||||
|
|
||||||
|
const setShow = (visible: boolean) => {
|
||||||
|
Animated.timing(opsAnim, {
|
||||||
|
toValue: visible ? 1 : 0,
|
||||||
|
duration: ANIMATION_DURATION,
|
||||||
|
useNativeDriver: true,
|
||||||
|
}).start(() => {
|
||||||
|
if (!visible) setVisible(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
updateScrollInfo(scrollInfo) {
|
||||||
|
setScrollInfo(scrollInfo)
|
||||||
|
},
|
||||||
|
updateLayoutInfo(listLayoutInfo) {
|
||||||
|
setListLayoutInfo(listLayoutInfo)
|
||||||
|
},
|
||||||
|
updateLyricLines(lyricLines) {
|
||||||
|
setLyricLines(lyricLines)
|
||||||
|
},
|
||||||
|
setVisible(visible) {
|
||||||
|
if (visible) {
|
||||||
|
setVisible(true)
|
||||||
|
}
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
setShow(visible)
|
||||||
|
})
|
||||||
|
// setVisible()
|
||||||
|
},
|
||||||
|
}), [])
|
||||||
|
|
||||||
|
const handlePlayLine = () => {
|
||||||
|
onPlayLine(time / 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!scrollInfo || !visible) return null
|
||||||
|
const offset = scrollInfo.contentOffset.y + scrollInfo.layoutMeasurement.height * 0.4
|
||||||
|
let lineOffset = listLayoutInfo.spaceHeight
|
||||||
|
let targetLineNum = -1
|
||||||
|
for (let line = 0; line < listLayoutInfo.lineHeights.length; line++) {
|
||||||
|
lineOffset += listLayoutInfo.lineHeights[line]
|
||||||
|
if (lineOffset < offset) continue
|
||||||
|
targetLineNum = line
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (targetLineNum == -1) targetLineNum = listLayoutInfo.lineHeights.length - 1
|
||||||
|
const time = lyricLines[targetLineNum]?.time ?? 0
|
||||||
|
const timeLabel = formatPlayTime2(time / 1000)
|
||||||
|
return (
|
||||||
|
<Animated.View style={{ ...styles.playLine, opacity: opsAnim }}>
|
||||||
|
<Text style={styles.label} color={theme['c-primary-font']} size={13}>{timeLabel}</Text>
|
||||||
|
<View style={styles.lineContent}>
|
||||||
|
<View style={{ ...styles.line, borderBottomColor: theme['c-primary-alpha-300'] }} />
|
||||||
|
<TouchableOpacity style={styles.button} onPress={handlePlayLine}>
|
||||||
|
<Icon name="play" color={theme['c-button-font']} size={14} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</Animated.View>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const styles = createStyle({
|
||||||
|
playLine: {
|
||||||
|
position: 'absolute',
|
||||||
|
width: '100%',
|
||||||
|
top: '40%',
|
||||||
|
left: 0,
|
||||||
|
height: 2,
|
||||||
|
// paddingTop: 5,
|
||||||
|
// paddingBottom: 5,
|
||||||
|
// backgroundColor: 'rgba(0,0,0,0.1)',
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
position: 'absolute',
|
||||||
|
right: 37,
|
||||||
|
bottom: 0,
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 5,
|
||||||
|
},
|
||||||
|
lineContent: {
|
||||||
|
// backgroundColor: 'rgba(0,0,0,0.1)',
|
||||||
|
position: 'absolute',
|
||||||
|
width: '100%',
|
||||||
|
height: 20,
|
||||||
|
top: -10,
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 5,
|
||||||
|
},
|
||||||
|
line: {
|
||||||
|
marginLeft: 15,
|
||||||
|
borderBottomWidth: BorderWidths.normal,
|
||||||
|
borderStyle: 'dashed',
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
flex: 0,
|
||||||
|
paddingLeft: 5,
|
||||||
|
paddingRight: 15,
|
||||||
|
},
|
||||||
|
})
|
@ -3,6 +3,7 @@ import { ScrollView, View } from 'react-native'
|
|||||||
import Popup, { type PopupType, type PopupProps } from '@/components/common/Popup'
|
import Popup, { type PopupType, type PopupProps } from '@/components/common/Popup'
|
||||||
import { useI18n } from '@/lang'
|
import { useI18n } from '@/lang'
|
||||||
|
|
||||||
|
import SettingLyricProgress from './settings/SettingLyricProgress'
|
||||||
import SettingVolume from './settings/SettingVolume'
|
import SettingVolume from './settings/SettingVolume'
|
||||||
import SettingPlaybackRate from './settings/SettingPlaybackRate'
|
import SettingPlaybackRate from './settings/SettingPlaybackRate'
|
||||||
import SettingLrcFontSize from './settings/SettingLrcFontSize'
|
import SettingLrcFontSize from './settings/SettingLrcFontSize'
|
||||||
@ -41,6 +42,7 @@ export default forwardRef<SettingPopupType, SettingPopupProps>(({ direction, ...
|
|||||||
<Popup ref={popupRef} title={t('play_detail_setting_title')} {...props}>
|
<Popup ref={popupRef} title={t('play_detail_setting_title')} {...props}>
|
||||||
<ScrollView>
|
<ScrollView>
|
||||||
<View onStartShouldSetResponder={() => true}>
|
<View onStartShouldSetResponder={() => true}>
|
||||||
|
<SettingLyricProgress />
|
||||||
<SettingVolume />
|
<SettingVolume />
|
||||||
<SettingPlaybackRate />
|
<SettingPlaybackRate />
|
||||||
<SettingLrcFontSize direction={direction} />
|
<SettingLrcFontSize direction={direction} />
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
import { View } from 'react-native'
|
||||||
|
// import Text from '@/components/common/Text'
|
||||||
|
import { useSettingValue } from '@/store/setting/hook'
|
||||||
|
import { updateSetting } from '@/core/common'
|
||||||
|
import { useI18n } from '@/lang'
|
||||||
|
import CheckBox from '@/components/common/CheckBox'
|
||||||
|
import styles from './style'
|
||||||
|
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
const t = useI18n()
|
||||||
|
const isShowLyricProgressSetting = useSettingValue('playDetail.isShowLyricProgressSetting')
|
||||||
|
const setShowLyricProgressSetting = (showLyricProgressSetting: boolean) => {
|
||||||
|
updateSetting({ 'playDetail.isShowLyricProgressSetting': showLyricProgressSetting })
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<View style={styles.content}>
|
||||||
|
<View style={styles.content}>
|
||||||
|
<CheckBox marginBottom={3} check={isShowLyricProgressSetting} label={t('play_detail_setting_show_lyric_progress_setting')} onChange={setShowLyricProgressSetting} />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
5
src/types/app_setting.d.ts
vendored
5
src/types/app_setting.d.ts
vendored
@ -171,6 +171,11 @@ declare global {
|
|||||||
*/
|
*/
|
||||||
'playDetail.horizontal.style.lrcFontSize': number
|
'playDetail.horizontal.style.lrcFontSize': number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 播放详情页-是否允许通过歌词调整播放进度
|
||||||
|
*/
|
||||||
|
'playDetail.isShowLyricProgressSetting': boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否启用桌面歌词
|
* 是否启用桌面歌词
|
||||||
*/
|
*/
|
||||||
|
93
src/utils/scroll.ts
Normal file
93
src/utils/scroll.ts
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import { type NativeScrollEvent, type FlatList, type NativeSyntheticEvent } from 'react-native'
|
||||||
|
|
||||||
|
const easeInOutQuad = (t: number, b: number, c: number, d: number): number => {
|
||||||
|
t /= d / 2
|
||||||
|
if (t < 1) return (c / 2) * t * t + b
|
||||||
|
t--
|
||||||
|
return (-c / 2) * (t * (t - 2) - 1) + b
|
||||||
|
}
|
||||||
|
|
||||||
|
type Noop = () => void
|
||||||
|
const noop: Noop = () => {}
|
||||||
|
|
||||||
|
const handleScrollY = (element: FlatList<any>, info: NativeSyntheticEvent<NativeScrollEvent>['nativeEvent'], to: number, duration = 300, fn = noop): Noop => {
|
||||||
|
if (element == null) {
|
||||||
|
fn()
|
||||||
|
return noop
|
||||||
|
}
|
||||||
|
|
||||||
|
const key = Math.random()
|
||||||
|
|
||||||
|
|
||||||
|
const start = info.contentOffset.y
|
||||||
|
let cancel = false
|
||||||
|
if (to > start) {
|
||||||
|
let maxScrollTop = info.contentSize.height - info.layoutMeasurement.height
|
||||||
|
if (to > maxScrollTop) to = maxScrollTop
|
||||||
|
} else if (to < start) {
|
||||||
|
if (to < 0) to = 0
|
||||||
|
} else {
|
||||||
|
fn()
|
||||||
|
return noop
|
||||||
|
}
|
||||||
|
const change = to - start
|
||||||
|
const increment = 10
|
||||||
|
if (!change) {
|
||||||
|
fn()
|
||||||
|
return noop
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentTime = 0
|
||||||
|
let val
|
||||||
|
|
||||||
|
const animateScroll = () => {
|
||||||
|
// @ts-expect-error
|
||||||
|
if (cancel || element.lx_scrollKey != key) {
|
||||||
|
fn()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
currentTime += increment
|
||||||
|
val = Math.trunc(easeInOutQuad(currentTime, start, change, duration))
|
||||||
|
element.scrollToOffset({ offset: val, animated: false })
|
||||||
|
if (currentTime < duration) {
|
||||||
|
setTimeout(animateScroll, increment)
|
||||||
|
} else {
|
||||||
|
fn()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// @ts-expect-error
|
||||||
|
element.lx_scrollKey = key
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
animateScroll()
|
||||||
|
})
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cancel = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 设置滚动条位置
|
||||||
|
* @param element 要设置滚动的容器 dom
|
||||||
|
* @param to 滚动的目标位置
|
||||||
|
* @param duration 滚动完成时间 ms
|
||||||
|
* @param fn 滚动完成后的回调
|
||||||
|
* @param delay 延迟执行时间
|
||||||
|
*/
|
||||||
|
export const scrollTo = (element: FlatList<any>, info: NativeSyntheticEvent<NativeScrollEvent>['nativeEvent'], to: number, duration = 300, fn = () => {}, delay = 0): () => void => {
|
||||||
|
let cancelFn: () => void
|
||||||
|
let timeout: NodeJS.Timeout | null
|
||||||
|
if (delay) {
|
||||||
|
let scrollCancelFn: Noop
|
||||||
|
cancelFn = () => {
|
||||||
|
timeout == null ? scrollCancelFn?.() : clearTimeout(timeout)
|
||||||
|
}
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
timeout = null
|
||||||
|
scrollCancelFn = handleScrollY(element, info, to, duration, fn)
|
||||||
|
}, delay)
|
||||||
|
} else {
|
||||||
|
cancelFn = handleScrollY(element, info, to, duration, fn) ?? noop
|
||||||
|
}
|
||||||
|
return cancelFn
|
||||||
|
}
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user