From d253b3cc30538a2702dceeb2be37c99fb2ee2d67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E6=9D=B1=E6=BE=94?= Date: Thu, 9 Sep 2021 16:27:25 +0800 Subject: [PATCH] fix: fixed scrolling on Android --- example/metro.config.js | 52 +++++++++++----------- example/package.json | 2 +- example/src/App.tsx | 8 ++-- example/yarn.lock | 42 +++++++++--------- src/Carousel.tsx | 83 ++++++++++++++++++----------------- src/useCarouselController.tsx | 10 +++-- 6 files changed, 102 insertions(+), 95 deletions(-) diff --git a/example/metro.config.js b/example/metro.config.js index d1f468ab..81c7767e 100644 --- a/example/metro.config.js +++ b/example/metro.config.js @@ -6,35 +6,37 @@ const pak = require('../package.json'); const root = path.resolve(__dirname, '..'); const modules = Object.keys({ - ...pak.peerDependencies, + ...pak.peerDependencies, }); module.exports = { - projectRoot: __dirname, - watchFolders: [root], + projectRoot: __dirname, + watchFolders: [root], - // We need to make sure that only one version is loaded for peerDependencies - // So we blacklist them at the root, and alias them to the versions in example's node_modules - resolver: { - blacklistRE: blacklist( - modules.map( - (m) => - new RegExp(`^${escape(path.join(root, 'node_modules', m))}\\/.*$`) - ) - ), + // We need to make sure that only one version is loaded for peerDependencies + // So we blacklist them at the root, and alias them to the versions in example's node_modules + resolver: { + blacklistRE: blacklist( + modules.map( + (m) => + new RegExp( + `^${escape(path.join(root, 'node_modules', m))}\\/.*$` + ) + ) + ), - extraNodeModules: modules.reduce((acc, name) => { - acc[name] = path.join(__dirname, 'node_modules', name); - return acc; - }, {}), - }, + extraNodeModules: modules.reduce((acc, name) => { + acc[name] = path.join(__dirname, 'node_modules', name); + return acc; + }, {}), + }, - transformer: { - getTransformOptions: async () => ({ - transform: { - experimentalImportSupport: false, - inlineRequires: true, - }, - }), - }, + transformer: { + getTransformOptions: async () => ({ + transform: { + experimentalImportSupport: false, + inlineRequires: true, + }, + }), + }, }; diff --git a/example/package.json b/example/package.json index 34976400..bb31f478 100644 --- a/example/package.json +++ b/example/package.json @@ -27,6 +27,6 @@ "babel-preset-expo": "8.3.0", "expo-cli": "^4.0.13", "react-native-gesture-handler": "^1.10.3", - "react-native-reanimated": "^2.2.0" + "react-native-reanimated": "2.2.0" } } diff --git a/example/src/App.tsx b/example/src/App.tsx index 0159a109..75bd9ef2 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -25,10 +25,10 @@ export default function App() { mode="parallax" width={width} data={[ - // { color: 'red' }, - // { color: 'purple' }, - // { color: 'blue' }, - // { color: 'yellow' }, + { color: 'red' }, + { color: 'purple' }, + { color: 'blue' }, + { color: 'yellow' }, ]} parallaxScrollingScale={0.8} onSnapToItem={(index) => { diff --git a/example/yarn.lock b/example/yarn.lock index ba5652ef..1029c6a7 100644 --- a/example/yarn.lock +++ b/example/yarn.lock @@ -1171,8 +1171,8 @@ "@egjs/hammerjs@^2.0.17": version "2.0.17" - resolved "https://registry.npm.taobao.org/@egjs/hammerjs/download/@egjs/hammerjs-2.0.17.tgz#5dc02af75a6a06e4c2db0202cae38c9263895124" - integrity sha1-XcAq91pqBuTC2wICyuOMkmOJUSQ= + resolved "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz#5dc02af75a6a06e4c2db0202cae38c9263895124" + integrity sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A== dependencies: "@types/hammerjs" "^2.0.36" @@ -2301,8 +2301,8 @@ "@types/hammerjs@^2.0.36": version "2.0.40" - resolved "https://registry.nlark.com/@types/hammerjs/download/@types/hammerjs-2.0.40.tgz#ded0240b6ea1ad7afc1e60374c49087aaea5dbd8" - integrity sha1-3tAkC26hrXr8HmA3TEkIeq6l29g= + resolved "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.40.tgz#ded0240b6ea1ad7afc1e60374c49087aaea5dbd8" + integrity sha512-VbjwR1fhsn2h2KXAY4oy1fm7dCxaKy0D+deTb8Ilc3Eo3rc5+5eA4rfYmZaHgNJKxVyI0f6WIXzO2zLkVmQPHA== "@types/html-minifier-terser@^5.0.0": version "5.1.2" @@ -4465,8 +4465,8 @@ cron-parser@^2.13.0: cross-fetch@^3.0.4: version "3.1.4" - resolved "https://registry.nlark.com/cross-fetch/download/cross-fetch-3.1.4.tgz#9723f3a3a247bf8b89039f3a380a9244e8fa2f39" - integrity sha1-lyPzo6JHv4uJA586OAqSROj6Lzk= + resolved "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.4.tgz#9723f3a3a247bf8b89039f3a380a9244e8fa2f39" + integrity sha512-1eAtFWdIubi6T4XPy6ei9iUFoKpUkIF971QLN8lIvvvwueI65+Nw5haMNKUwfJxabqlIIDODJKGrQ66gxC0PbQ== dependencies: node-fetch "2.6.1" @@ -5950,8 +5950,8 @@ fbjs@^0.8.4: fbjs@^3.0.0: version "3.0.0" - resolved "https://registry.npm.taobao.org/fbjs/download/fbjs-3.0.0.tgz#0907067fb3f57a78f45d95f1eacffcacd623c165" - integrity sha1-CQcGf7P1enj0XZXx6s/8rNYjwWU= + resolved "https://registry.npmjs.org/fbjs/-/fbjs-3.0.0.tgz#0907067fb3f57a78f45d95f1eacffcacd623c165" + integrity sha512-dJd4PiDOFuhe7vk4F80Mba83Vr2QuK86FoxtgPmzBqEJahncp+13YCmfoa53KHCo6OnlXLG7eeMWPfB5CrpVKg== dependencies: cross-fetch "^3.0.4" fbjs-css-vars "^1.0.0" @@ -6729,8 +6729,8 @@ hmac-drbg@^1.0.1: hoist-non-react-statics@^3.3.0: version "3.3.2" - resolved "https://registry.npm.taobao.org/hoist-non-react-statics/download/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" - integrity sha1-7OCsr3HWLClpwuxZ/v9CpLGoW0U= + resolved "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== dependencies: react-is "^16.7.0" @@ -8931,8 +8931,8 @@ mkdirp@^1.0.3, mkdirp@^1.0.4: mockdate@^3.0.2: version "3.0.5" - resolved "https://registry.npm.taobao.org/mockdate/download/mockdate-3.0.5.tgz#789be686deb3149e7df2b663d2bc4392bc3284fb" - integrity sha1-eJvmht6zFJ598rZj0rxDkrwyhPs= + resolved "https://registry.npmjs.org/mockdate/-/mockdate-3.0.5.tgz#789be686deb3149e7df2b663d2bc4392bc3284fb" + integrity sha512-iniQP4rj1FhBdBYS/+eQv7j1tadJ9lJtdzgOpvsOHng/GbcDh2Fhdeq+ZRldrPYdXvCyfFUmFeEwEGXZB5I/AQ== moment-timezone@^0.5.31: version "0.5.33" @@ -9085,8 +9085,8 @@ nocache@^2.1.0: node-fetch@2.6.1: version "2.6.1" - resolved "https://registry.nlark.com/node-fetch/download/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" - integrity sha1-BFvTI2Mfdu0uK1VXM5RBa2OaAFI= + resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" + integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== node-fetch@^1.0.1: version "1.7.3" @@ -10784,8 +10784,8 @@ react-is@^17.0.1: react-native-gesture-handler@^1.10.3: version "1.10.3" - resolved "https://registry.npm.taobao.org/react-native-gesture-handler/download/react-native-gesture-handler-1.10.3.tgz#942bbf2963bbf49fa79593600ee9d7b5dab3cfc0" - integrity sha1-lCu/KWO79J+nlZNgDunXtdqzz8A= + resolved "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-1.10.3.tgz#942bbf2963bbf49fa79593600ee9d7b5dab3cfc0" + integrity sha512-cBGMi1IEsIVMgoox4RvMx7V2r6bNKw0uR1Mu1o7NbuHS6BRSVLq0dP34l2ecnPlC+jpWd3le6Yg1nrdCjby2Mw== dependencies: "@egjs/hammerjs" "^2.0.17" fbjs "^3.0.0" @@ -10793,10 +10793,10 @@ react-native-gesture-handler@^1.10.3: invariant "^2.2.4" prop-types "^15.7.2" -react-native-reanimated@^2.2.0: +react-native-reanimated@2.2.0: version "2.2.0" - resolved "https://registry.nlark.com/react-native-reanimated/download/react-native-reanimated-2.2.0.tgz#a6412c56b4e591d1f00fac949f62d0c72c357c78" - integrity sha1-pkEsVrTlkdHwD6yUn2LQxyw1fHg= + resolved "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-2.2.0.tgz#a6412c56b4e591d1f00fac949f62d0c72c357c78" + integrity sha512-lOJDd+5w1gY6DHGXG2jD1dsjzQmXQ2699HUc3IztvI2WP4zUT+UAA+zSG+5JiBS5DUnTL8YhhkmUQmr1KNGO5w== dependencies: "@babel/plugin-transform-object-assign" "^7.10.4" fbjs "^3.0.0" @@ -12012,8 +12012,8 @@ stream-shift@^1.0.0: string-hash-64@^1.0.3: version "1.0.3" - resolved "https://registry.npm.taobao.org/string-hash-64/download/string-hash-64-1.0.3.tgz#0deb56df58678640db5c479ccbbb597aaa0de322" - integrity sha1-DetW31hnhkDbXEecy7tZeqoN4yI= + resolved "https://registry.npmjs.org/string-hash-64/-/string-hash-64-1.0.3.tgz#0deb56df58678640db5c479ccbbb597aaa0de322" + integrity sha512-D5OKWKvDhyVWWn2x5Y9b+37NUllks34q1dCDhk/vYcso9fmhs+Tl3KR/gE4v5UNj2UA35cnX4KdVVGkG1deKqw== string-width@^1.0.1: version "1.0.2" diff --git a/src/Carousel.tsx b/src/Carousel.tsx index 28f45964..f2399727 100644 --- a/src/Carousel.tsx +++ b/src/Carousel.tsx @@ -13,7 +13,6 @@ import Animated, { withTiming, } from 'react-native-reanimated'; import { CarouselItem } from './CarouselItem'; -import { fillNum } from './fillNum'; import type { TMode } from './layouts'; import { ParallaxLayout } from './layouts/index'; import { useCarouselController } from './useCarouselController'; @@ -21,20 +20,8 @@ import { useComputedAnim } from './useComputedAnim'; import { useLoop } from './useLoop'; import { useComputedIndex } from './useComputedIndex'; -export const _withTiming = ( - num: number, - callback?: (isFinished: boolean) => void -) => { - 'worklet'; - return withTiming( - num, - { - duration: 250, - }, - (isFinished) => { - !!callback && runOnJS(callback)(isFinished); - } - ); +export const timingConfig = { + duration: 250, }; export interface ICarouselProps { @@ -211,53 +198,69 @@ function Carousel( useAnimatedGestureHandler( { onStart: (_, ctx: any) => { + if (ctx.lock) return; ctx.startContentOffsetX = handlerOffsetX.value; + ctx.currentContentOffsetX = handlerOffsetX.value; }, onActive: (e, ctx: any) => { + if (ctx.lock) return; + /** + * `onActive` and `onEnd` return different values of translationX!So that creates a bias!TAT + * */ + ctx.translationX = e.translationX; if (loop) { handlerOffsetX.value = - ctx.startContentOffsetX + - Math.round(e.translationX); + ctx.currentContentOffsetX + e.translationX; return; } handlerOffsetX.value = Math.max( - Math.min( - ctx.startContentOffsetX + - Math.round(e.translationX), - 0 - ), + Math.min(ctx.currentContentOffsetX + e.translationX, 0), -(data.length - 1) * width ); }, - onEnd: (e) => { - const intTranslationX = Math.round(e.translationX); - const sub = Math.abs(intTranslationX); + onEnd: (e, ctx: any) => { + if (ctx.lock) { + return; + } + const translationX = ctx.translationX; function _withTimingCallback(num: number) { - return _withTiming(num, callComputedIndex); + ctx.lock = true; + return withTiming(num, timingConfig, (isFinished) => { + if (isFinished) { + ctx.lock = false; + } + runOnJS(callComputedIndex)(isFinished); + }); } - if (intTranslationX > 0) { + if (translationX > 0) { + /** + * If not loop no , longer scroll when sliding to the start. + * */ if (!loop && handlerOffsetX.value >= 0) { return; } - if (sub > width / 2) { + if ( + Math.abs(translationX) + Math.abs(e.velocityX) > + width / 2 + ) { handlerOffsetX.value = _withTimingCallback( - fillNum( - width, - handlerOffsetX.value + (width - sub) - ) + handlerOffsetX.value + width - translationX ); } else { handlerOffsetX.value = _withTimingCallback( - fillNum(width, handlerOffsetX.value - sub) + handlerOffsetX.value - translationX ); } return; } - if (intTranslationX < 0) { + if (translationX < 0) { + /** + * If not loop , no longer scroll when sliding to the end. + * */ if ( !loop && handlerOffsetX.value <= -(data.length - 1) * width @@ -265,16 +268,16 @@ function Carousel( return; } - if (sub > width / 2) { + if ( + Math.abs(translationX) + Math.abs(e.velocityX) > + width / 2 + ) { handlerOffsetX.value = _withTimingCallback( - fillNum( - width, - handlerOffsetX.value - (width - sub) - ) + handlerOffsetX.value - width - translationX ); } else { handlerOffsetX.value = _withTimingCallback( - fillNum(width, handlerOffsetX.value + sub) + handlerOffsetX.value - translationX ); } return; diff --git a/src/useCarouselController.tsx b/src/useCarouselController.tsx index 74faf0c4..2706f742 100644 --- a/src/useCarouselController.tsx +++ b/src/useCarouselController.tsx @@ -1,7 +1,7 @@ import React from 'react'; -import { _withTiming } from './Carousel'; +import { timingConfig } from './Carousel'; import type Animated from 'react-native-reanimated'; -import { useSharedValue } from 'react-native-reanimated'; +import { useSharedValue, withTiming } from 'react-native-reanimated'; interface IOpts { width: number; @@ -35,8 +35,9 @@ export function useCarouselController(opts: IOpts): ICarouselController { if (disable) return; if (lock.value) return; openLock(); - handlerOffsetX.value = _withTiming( + handlerOffsetX.value = withTiming( handlerOffsetX.value - width, + timingConfig, (isFinished: boolean) => { callback?.(isFinished); closeLock(isFinished); @@ -51,8 +52,9 @@ export function useCarouselController(opts: IOpts): ICarouselController { if (disable) return; if (lock.value) return; openLock(); - handlerOffsetX.value = _withTiming( + handlerOffsetX.value = withTiming( handlerOffsetX.value + width, + timingConfig, (isFinished: boolean) => { callback?.(isFinished); closeLock(isFinished);