11import { View , NativeSyntheticEvent , LayoutChangeEvent } from 'react-native'
2- import { GestureDetector , Gesture , PanGesture } from 'react-native-gesture-handler'
2+ import { GestureDetector , Gesture , PanGesture , GestureStateChangeEvent , PanGestureHandlerEventPayload } from 'react-native-gesture-handler'
33import Animated , { useAnimatedStyle , useSharedValue , withTiming , Easing , runOnJS , useAnimatedReaction , cancelAnimation } from 'react-native-reanimated'
44
55import React , { JSX , forwardRef , useRef , useEffect , ReactNode , ReactElement , useMemo , createElement } from 'react'
@@ -26,8 +26,12 @@ import Portal from './mpx-portal'
2626 */
2727type EaseType = 'default' | 'linear' | 'easeInCubic' | 'easeOutCubic' | 'easeInOutCubic'
2828type StrAbsoType = 'absoluteX' | 'absoluteY'
29+ type StrVelocityType = 'velocityX' | 'velocityY'
2930type EventDataType = {
31+ // 和上一帧offset值的对比
3032 translation : number
33+ // onUpdate时根据上一个判断方向,onFinalize根据transformStart判断
34+ transdir : number
3135}
3236
3337interface SwiperProps {
@@ -46,15 +50,15 @@ interface SwiperProps {
4650 vertical ?: boolean
4751 style : {
4852 [ key : string ] : any
49- } ;
53+ }
5054 'easing-function' ?: EaseType
5155 'previous-margin' ?: string
5256 'next-margin' ?: string
5357 'enable-offset' ?: boolean
5458 'enable-var' : boolean
5559 'parent-font-size' ?: number
5660 'parent-width' ?: number
57- 'parent-height' ?: number ;
61+ 'parent-height' ?: number
5862 'external-var-context' ?: Record < string , any >
5963 'wait-for' ?: Array < GestureHandler >
6064 'simultaneous-handlers' ?: Array < GestureHandler >
@@ -199,14 +203,13 @@ const SwiperWrapper = forwardRef<HandlerRef<View, SwiperProps>, SwiperProps>((pr
199203 // 记录元素的偏移量
200204 const offset = useSharedValue ( getOffset ( props . current || 0 , initStep ) )
201205 const strAbso = 'absolute' + dir . toUpperCase ( ) as StrAbsoType
206+ const strVelocity = 'velocity' + dir . toUpperCase ( ) as StrVelocityType
202207 // 标识手指触摸和抬起, 起点在onBegin
203208 const touchfinish = useSharedValue ( true )
204209 // 记录上一帧的绝对定位坐标
205210 const preAbsolutePos = useSharedValue ( 0 )
206211 // 记录从onBegin 到 onTouchesUp 时移动的距离
207212 const moveTranstion = useSharedValue ( 0 )
208- // 记录从onBegin 到 onTouchesUp 的时间
209- const moveTime = useSharedValue ( 0 )
210213 const timerId = useRef ( 0 as number | ReturnType < typeof setTimeout > )
211214 const intervalTimer = props . interval || 500
212215
@@ -505,7 +508,11 @@ const SwiperWrapper = forwardRef<HandlerRef<View, SwiperProps>, SwiperProps>((pr
505508 } , [ children . length ] )
506509
507510 useEffect ( ( ) => {
508- updateCurrent ( props . current || 0 , step . value )
511+ // 1. 如果用户在touch的过程中, 外部更新了current以外部为准(小程序表现)
512+ // 2. 手指滑动过程中更新索引,外部会把current再传入进来,导致offset直接更新,增加判断不同才更新
513+ if ( props . current !== currentIndex . value ) {
514+ updateCurrent ( props . current || 0 , step . value )
515+ }
509516 } , [ props . current ] )
510517
511518 useEffect ( ( ) => {
@@ -529,17 +536,17 @@ const SwiperWrapper = forwardRef<HandlerRef<View, SwiperProps>, SwiperProps>((pr
529536 function getTargetPosition ( eventData : EventDataType ) {
530537 'worklet'
531538 // 移动的距离
532- const { translation } = eventData
539+ const { transdir } = eventData
533540 let resetOffsetPos = 0
534541 let selectedIndex = currentIndex . value
535542 // 是否临界点
536543 let isCriticalItem = false
537544 // 真实滚动到的偏移量坐标
538545 let moveToTargetPos = 0
539546 const tmp = ! circularShared . value ? 0 : preMarginShared . value
540- const currentOffset = translation < 0 ? offset . value - tmp : offset . value + tmp
547+ const currentOffset = transdir < 0 ? offset . value - tmp : offset . value + tmp
541548 const computedIndex = Math . abs ( currentOffset ) / step . value
542- const moveToIndex = translation < 0 ? Math . ceil ( computedIndex ) : Math . floor ( computedIndex )
549+ const moveToIndex = transdir < 0 ? Math . ceil ( computedIndex ) : Math . floor ( computedIndex )
543550 // 实际应该定位的索引值
544551 if ( ! circularShared . value ) {
545552 selectedIndex = moveToIndex
@@ -569,13 +576,17 @@ const SwiperWrapper = forwardRef<HandlerRef<View, SwiperProps>, SwiperProps>((pr
569576 }
570577 function canMove ( eventData : EventDataType ) {
571578 'worklet'
572- const { translation } = eventData
573- const currentOffset = Math . abs ( offset . value )
579+ // 旧版:如果在快速多次滑动时,只根据当前的offset判断,会出现offset没超出,加上translation后越界的场景(如在倒数第二个元素快速滑动)
580+ // 新版:会加上translation
581+ const { translation, transdir } = eventData
582+ const gestureMovePos = offset . value + translation
574583 if ( ! circularShared . value ) {
575- if ( translation < 0 ) {
576- return currentOffset < step . value * ( childrenLength . value - 1 )
584+ // 如果只判断区间,中间非滑动状态(handleResistanceMove)向左滑动,突然改为向右滑动,但是还在非滑动态,本应该可滑动判断为了不可滑动
585+ const posEnd = - step . value * ( childrenLength . value - 1 )
586+ if ( transdir < 0 ) {
587+ return gestureMovePos > posEnd
577588 } else {
578- return currentOffset > 0
589+ return gestureMovePos < 0
579590 }
580591 } else {
581592 return true
@@ -607,25 +618,16 @@ const SwiperWrapper = forwardRef<HandlerRef<View, SwiperProps>, SwiperProps>((pr
607618 } )
608619 }
609620 }
610- function handleBackInit ( ) {
611- 'worklet'
612- // 微信的效果
613- // 1. 只有一个元素,即使设置了circular,也不会产生循环的效果,2. 可以响应手势,但是会有回弹的效果
614- offset . value = withTiming ( 0 , {
615- duration : easeDuration ,
616- easing : easeMap [ easeingFunc ]
617- } )
618- }
619621 function handleBack ( eventData : EventDataType ) {
620622 'worklet'
621- const { translation } = eventData
623+ const { transdir } = eventData
622624 // 向右滑动的back:trans < 0, 向左滑动的back: trans < 0
623625 let currentOffset = Math . abs ( offset . value )
624626 if ( circularShared . value ) {
625- currentOffset += translation < 0 ? preMarginShared . value : - preMarginShared . value
627+ currentOffset += transdir < 0 ? preMarginShared . value : - preMarginShared . value
626628 }
627629 const curIndex = currentOffset / step . value
628- const moveToIndex = ( translation < 0 ? Math . floor ( curIndex ) : Math . ceil ( curIndex ) ) - patchElmNumShared . value
630+ const moveToIndex = ( transdir < 0 ? Math . floor ( curIndex ) : Math . ceil ( curIndex ) ) - patchElmNumShared . value
629631 const targetOffset = - ( moveToIndex + patchElmNumShared . value ) * step . value + ( circularShared . value ? preMarginShared . value : 0 )
630632 offset . value = withTiming ( targetOffset , {
631633 duration : easeDuration ,
@@ -637,112 +639,190 @@ const SwiperWrapper = forwardRef<HandlerRef<View, SwiperProps>, SwiperProps>((pr
637639 }
638640 } )
639641 }
640- function handleLongPress ( ) {
642+ // 当前的offset和index多对应的offset进行对比,判断是否超过一半
643+ function computeHalf ( eventData : EventDataType ) {
641644 'worklet'
645+ const { transdir } = eventData
642646 const currentOffset = Math . abs ( offset . value )
643647 let preOffset = ( currentIndex . value + patchElmNumShared . value ) * step . value
644648 if ( circularShared . value ) {
645649 preOffset -= preMarginShared . value
646650 }
647- // 正常事件中拿到的transition值 (正向滑动<0,倒着滑>0)
651+ // 正常事件中拿到的translation值 (正向滑动<0,倒着滑>0)
648652 const diffOffset = preOffset - currentOffset
649653 const half = Math . abs ( diffOffset ) > step . value / 2
654+ const isTriggerUpdateHalf = ( transdir < 0 && currentOffset < preOffset ) || ( transdir > 0 && currentOffset > preOffset )
655+ return {
656+ diffOffset,
657+ half,
658+ isTriggerUpdateHalf
659+ }
660+ }
661+ function handleLongPress ( eventData : EventDataType ) {
662+ 'worklet'
663+ const { diffOffset, half, isTriggerUpdateHalf } = computeHalf ( eventData )
650664 if ( + diffOffset === 0 ) {
651665 runOnJS ( resumeLoop ) ( )
666+ } else if ( isTriggerUpdateHalf ) {
667+ // 如果触发了onUpdate时的索引变更,则直接以update时的index为准
668+ const targetIndex = ! circularShared . value ? currentIndex . value : currentIndex . value + patchElmNumShared . value - 1
669+ offset . value = withTiming ( - targetIndex * step . value , {
670+ duration : easeDuration ,
671+ easing : easeMap [ easeingFunc ]
672+ } , ( ) => {
673+ if ( touchfinish . value !== false ) {
674+ currentIndex . value = targetIndex
675+ runOnJS ( resumeLoop ) ( )
676+ }
677+ } )
652678 } else if ( half ) {
653- handleEnd ( { translation : diffOffset } )
679+ handleEnd ( eventData )
654680 } else {
655- handleBack ( { translation : diffOffset } )
681+ handleBack ( eventData )
656682 }
657683 }
658684 function reachBoundary ( eventData : EventDataType ) {
659685 'worklet'
660- // 移动的距离
686+ // 1. 基于当前的offset和translation判断是否超过当前边界值
661687 const { translation } = eventData
662- const elementsLength = step . value * childrenLength . value
688+ const boundaryStart = - patchElmNumShared . value * step . value
689+ const boundaryEnd = - ( childrenLength . value + patchElmNumShared . value ) * step . value
690+ const moveToOffset = offset . value + translation
663691 let isBoundary = false
664692 let resetOffset = 0
665- // Y轴向下滚动, transDistance > 0, 向上滚动 < 0 X轴向左滚动, transDistance > 0
666- const currentOffset = offset . value
667- const moveStep = Math . ceil ( translation / elementsLength )
668- if ( translation < 0 ) {
669- const posEnd = ( childrenLength . value + patchElmNumShared . value + 1 ) * step . value
670- const posReverseEnd = ( patchElmNumShared . value - 1 ) * step . value
671- if ( currentOffset < - posEnd + step . value ) {
672- isBoundary = true
673- resetOffset = Math . abs ( moveStep ) === 0 ? patchElmNumShared . value * step . value + translation : moveStep * elementsLength
674- }
675- if ( currentOffset > - posReverseEnd ) {
676- isBoundary = true
677- resetOffset = moveStep * elementsLength
678- }
679- } else if ( translation > 0 ) {
680- const posEnd = ( patchElmNumShared . value - 1 ) * step . value
681- const posReverseEnd = ( patchElmNumShared . value + childrenLength . value ) * step . value
682- if ( currentOffset > - posEnd ) {
683- isBoundary = true
684- resetOffset = moveStep * elementsLength + step . value + ( moveStep === 1 ? translation : 0 )
685- }
686- if ( currentOffset < - posReverseEnd ) {
687- isBoundary = true
688- resetOffset = moveStep * elementsLength + patchElmNumShared . value * step . value
689- }
693+ if ( moveToOffset < boundaryEnd ) {
694+ isBoundary = true
695+ // 超过边界的距离
696+ const exceedLength = Math . abs ( moveToOffset ) - Math . abs ( boundaryEnd )
697+ // 计算对标正常元素所在的offset
698+ resetOffset = patchElmNumShared . value * step . value + exceedLength
699+ }
700+ if ( moveToOffset > boundaryStart ) {
701+ isBoundary = true
702+ // 超过边界的距离
703+ const exceedLength = Math . abs ( boundaryStart ) - Math . abs ( moveToOffset )
704+ // 计算对标正常元素所在的offset
705+ resetOffset = ( patchElmNumShared . value + childrenLength . value - 1 ) * step . value + ( step . value - exceedLength )
690706 }
691707 return {
692708 isBoundary,
693709 resetOffset : - resetOffset
694710 }
695711 }
712+ // 非循环超出边界,应用阻力; 开始滑动少阻力小,滑动越长阻力越大
713+ function handleResistanceMove ( eventData : EventDataType ) {
714+ 'worklet'
715+ const { translation, transdir } = eventData
716+ const moveToOffset = offset . value + translation
717+ const maxOverDrag = Math . floor ( step . value / 2 )
718+ const maxOffset = translation < 0 ? - ( childrenLength . value - 1 ) * step . value : 0
719+ let resistance = 0.1
720+ let overDrag = 0
721+ let finalOffset = 0
722+ // 向右向下小于0, 向左向上大于0;
723+ if ( transdir < 0 ) {
724+ overDrag = Math . abs ( moveToOffset - maxOffset )
725+ } else {
726+ overDrag = Math . abs ( moveToOffset )
727+ }
728+ // 滑动越多resistance越小
729+ resistance = 1 - overDrag / maxOverDrag
730+ // 确保阻力在合理范围内
731+ resistance = Math . min ( 0.5 , resistance )
732+ // 限制在最大拖拽范围内
733+ if ( transdir < 0 ) {
734+ const adjustOffset = offset . value + translation * resistance
735+ finalOffset = Math . max ( adjustOffset , maxOffset - maxOverDrag )
736+ } else {
737+ const adjustOffset = offset . value + translation * resistance
738+ finalOffset = Math . min ( adjustOffset , maxOverDrag )
739+ }
740+ return finalOffset
741+ }
696742 const gesturePan = Gesture . Pan ( )
697- . onBegin ( ( e ) => {
743+ . onBegin ( ( e : GestureStateChangeEvent < PanGestureHandlerEventPayload > ) => {
698744 'worklet'
699745 if ( ! step . value ) return
700746 touchfinish . value = false
701747 cancelAnimation ( offset )
702748 runOnJS ( pauseLoop ) ( )
703749 preAbsolutePos . value = e [ strAbso ]
704750 moveTranstion . value = e [ strAbso ]
705- moveTime . value = new Date ( ) . getTime ( )
706751 } )
707- . onTouchesMove ( ( e ) => {
752+ . onUpdate ( ( e : GestureStateChangeEvent < PanGestureHandlerEventPayload > ) => {
708753 'worklet'
709754 if ( touchfinish . value ) return
710- const touchEventData = e . changedTouches [ 0 ]
711- const moveDistance = touchEventData [ strAbso ] - preAbsolutePos . value
755+ const moveDistance = e [ strAbso ] - preAbsolutePos . value
712756 const eventData = {
713- translation : moveDistance
757+ translation : moveDistance ,
758+ transdir : moveDistance !== 0 ? moveDistance : e [ strAbso ] - moveTranstion . value
714759 }
715- // 处理用户一直拖拽到临界点的场景, 不会执行onEnd
716- if ( ! circularShared . value && ! canMove ( eventData ) ) {
760+ // 1. 支持滑动中超出一半更新索引的能力:只更新索引并不会影响onFinalize依据当前offset计算的索引
761+ const { half } = computeHalf ( eventData )
762+ if ( childrenLength . value > 1 && half ) {
763+ const { selectedIndex } = getTargetPosition ( eventData )
764+ currentIndex . value = selectedIndex
765+ }
766+ // 2. 非循环: 处理用户一直拖拽到临界点的场景,如果放到onFinalize无法阻止offset.value更新为越界的值
767+ if ( ! circularShared . value ) {
768+ if ( canMove ( eventData ) ) {
769+ offset . value = moveDistance + offset . value
770+ } else {
771+ const finalOffset = handleResistanceMove ( eventData )
772+ offset . value = finalOffset
773+ }
774+ preAbsolutePos . value = e [ strAbso ]
717775 return
718776 }
777+ // 3. 循环更新: 只有一个元素时可滑动,加入阻力
778+ if ( circularShared . value && childrenLength . value === 1 ) {
779+ const finalOffset = handleResistanceMove ( eventData )
780+ offset . value = finalOffset
781+ preAbsolutePos . value = e [ strAbso ]
782+ return
783+ }
784+ // 4. 循环更新:正常
719785 const { isBoundary, resetOffset } = reachBoundary ( eventData )
720786 if ( childrenLength . value > 1 && isBoundary && circularShared . value ) {
721787 offset . value = resetOffset
722788 } else {
723789 offset . value = moveDistance + offset . value
724790 }
725- preAbsolutePos . value = touchEventData [ strAbso ]
791+ preAbsolutePos . value = e [ strAbso ]
726792 } )
727- . onTouchesUp ( ( e ) => {
793+ . onFinalize ( ( e : GestureStateChangeEvent < PanGestureHandlerEventPayload > ) => {
728794 'worklet'
729795 if ( touchfinish . value ) return
730- const touchEventData = e . changedTouches [ 0 ]
731- const moveDistance = touchEventData [ strAbso ] - moveTranstion . value
732796 touchfinish . value = true
797+ // 触发过onUpdate正常情况下e[strAbso] - preAbsolutePos.value=0; 未触发过onUpdate的情况下e[strAbso] - preAbsolutePos.value 不为0
798+ const moveDistance = e [ strAbso ] - preAbsolutePos . value
733799 const eventData = {
734- translation : moveDistance
800+ translation : moveDistance ,
801+ transdir : moveDistance !== 0 ? moveDistance : e [ strAbso ] - moveTranstion . value
735802 }
803+ // 1. 只有一个元素:循环 和 非循环状态,都走回弹效果
736804 if ( childrenLength . value === 1 ) {
737- return handleBackInit ( )
805+ offset . value = withTiming ( 0 , {
806+ duration : easeDuration ,
807+ easing : easeMap [ easeingFunc ]
808+ } )
809+ return
738810 }
739- // 用户手指按下起来, 需要计算正确的位置, 比如在滑动过程中突然按下然后起来,需要计算到正确的位置
811+ // 2.非循环状态不可移动态:最后一个元素 和 第一个元素
812+ // 非循环支持最后元素可滑动能力后,向左快速移动未超过最大可移动范围一半,因为offset为正值,向左滑动handleBack,默认向上取整
813+ // 但是在offset大于0时,取0。[-100, 0](back取0), [0, 100](back取1), 所以handleLongPress里的处理逻辑需要兼容支持,因此这里直接单独处理,不耦合下方公共的判断逻辑。
740814 if ( ! circularShared . value && ! canMove ( eventData ) ) {
815+ if ( eventData . transdir < 0 ) {
816+ handleBack ( eventData )
817+ } else {
818+ handleEnd ( eventData )
819+ }
741820 return
742821 }
743- const strVelocity = moveDistance / ( new Date ( ) . getTime ( ) - moveTime . value ) * 1000
744- if ( Math . abs ( strVelocity ) < longPressRatio ) {
745- handleLongPress ( )
822+ // 3. 非循环状态可移动态、循环状态, 正常逻辑处理
823+ const velocity = e [ strVelocity ]
824+ if ( Math . abs ( velocity ) < longPressRatio ) {
825+ handleLongPress ( eventData )
746826 } else {
747827 handleEnd ( eventData )
748828 }
0 commit comments