README (README.md)
<img src="https://user-images.githubusercontent.com/16062886/117444014-2d1ffd80-af39-11eb-9bbb-33c320599d93.png" width="100%" alt="React Native Gesture Handler by Software Mansion">
[](https://swm-delivery.com/www/delivery/ck-slug.php?zoneid=zone-gh-react-native-gesture-handler-1&n=1)
[](https://swm-delivery.com/www/delivery/ck-slug.php?zoneid=zone-gh-react-native-gesture-handler-2&n=1)
[](https://swm-delivery.com/www/delivery/ck-slug.php?zoneid=zone-gh-react-native-gesture-handler-3&n=1)
Declarative API exposing platform native touch and gesture system to React Native.
React Native Gesture Handler provides native-driven gesture management APIs for building best possible touch-based experiences in React Native.
With this library gestures are no longer controlled by the JS responder system, but instead are recognized and tracked in the UI thread.
It makes touch interactions and gesture tracking not only smooth, but also dependable and deterministic.
Installation
Check getting started section of our docs for the detailed installation instructions.
Documentation
Check out our dedicated documentation page for info about this library, API reference and more: https://docs.swmansion.com/react-native-gesture-handler/docs/
Examples
If you want to play with the API but don't feel like trying it on a real app, you can run the example project. Clone the repo, go to the example folder and run:
yarn installRun yarn start to start the metro bundler
Run yarn android or yarn ios (depending on which platform you want to run the example app on).
You will need to have an Android or iOS device or emulator connected.
React Native Support
react-native-gesture-handler supports the three latest minor releases of react-native.
Gesture Handler 3
Check out our compatibility table in documentation.
Minimal supported react-native version for Gesture Handler 3 is 0.82
Gesture Handler 2
| version | react-native version |
| ------- | -------------------- |
| 2.32.0+ | 0.84.0+ |
| 2.28.0+ | 0.79.0+ |
| 2.26.0+ | 0.78.0+ |
| 2.25.0+ | 0.76.0+ |
| 2.24.0+ | 0.75.0+ |
| 2.21.0+ | 0.74.0+ |
| 2.18.0+ | 0.73.0+ |
| 2.16.0+ | 0.68.0+ |
| 2.14.0+ | 0.67.0+ |
| 2.10.0+ | 0.64.0+ |
| 2.0.0+ | 0.63.0+ |
It may be possible to use newer versions of react-native-gesture-handler on React Native with version <= 0.59 by reverse Jetifying.
Read more on that here <https://github.com/mikehardy/jetifier#to-reverse-jetify--convert-node_modules-dependencies-to-support-libraries>
License
Gesture handler library is licensed under The MIT License.
Credits
This project has been build and is maintained thanks to the support from Expo.io and Software Mansion
[](https://expo.io)
[](https://swmansion.com)
Community Discord
Join the Software Mansion Community Discord to chat about Gesture Handler or other Software Mansion libraries.
Gesture Handler is created by Software Mansion
Since 2012 Software Mansion is a software agency with experience in building web and mobile apps. We are Core React Native Contributors and experts in dealing with all kinds of React Native issues. We can help you build your next dream product – Hire us.
---
AGENTS (AGENTS.md)
react-native-gesture-handler library
Project structure
- This project has monorepo structure.
- It contains both, library (/packages/react-native-gesture-handler) and documentation (/packages/docs-gesture-handler).
- It is multiplatform. Library supports android, iOS, web and macos.
- Android codebase is located in /packages/react-native-gesture-handler/android directory. iOS and macos in /packages/react-native-gesture-handler/apple. Web can be found in /packages/react-native-gesture-handler/src/web.
- Some files are platform specific. Those have platform in the file extension, e.g. RNGestureHandlerModule.web.ts
Example apps
- Example apps are located in /apps directory
- basic-example is mostly used to check that android and iOS build correctly.
- expo-example is used to test more advanced examples. Sources are located in apps/common-app.
- macos-example is used to check if library works on macos. Sources are located in apps/common-app.
Packages
- /packages contains documentation and main library package
- This project contains 3 versions of API. The newest is located in packages/react-native-gesture-handler/src/v3 directory. Most of the logic is shared, but make sure that your changes do not break older APIs.
- When writing code, you can use usesNativeOrVirtualDetector function to either include only, or exclude new API v3. It is available on all platforms.
API versions — defaults for new code
- Always use the v3 API by default for any new code, examples, reproductions, or test screens. Gesture. builders (v2) and GestureHandler components (v1) are legacy. Even when a bug is reported against a legacy API, build the reproduction with v3 first to check whether it affects v3 too — switch to the legacy API only if the issue does not reproduce on v3, or the user explicitly asks for a legacy reproduction.
- v3 usage: hook-based gestures (usePanGesture, useTapGesture, useLongPressGesture, …) attached via GestureDetector, all imported from react-native-gesture-handler (the main entry re-exports src/v3).
Build checks
- To check Android build go to apps/basic-example and run yarn android command.
- To check iOS build, go to apps/basic-example and run yarn ios.
- To check macos build, go to apps/macos-example and run yarn macos.
- After any build on macOS/Linux stop the Metro server with for pid in $(lsof -ti :8081); do kill "$pid"; done (this no-ops cleanly when nothing is listening on port 8081; pkill -f "metro" is not sufficient)
JavaScript checks
- To run TypeScript checks use yarn ts-check command. You can run it directly in packages/react-native-gesture-handler if working on package, or from root of the repository.
- To run Jest tests, use yarn test command in packages/react-native-gesture-handler. You can also pass filename to run tests from specific file.
- To run eslint check, use yarn lint:js
Formatting
- To format code use use yarn format:{apple | android | js}. apple works for both, iOS and macos.
---
Package.Json (package.json)
{
"name": "react-native-gesture-handler-monorepo",
"version": "0.0.0",
"private": true,
"workspaces": [
"packages/react-native-gesture-handler",
"apps/basic-example",
"apps/expo-example",
"apps/macos-example",
"apps/common-app"
],
"scripts": {
"postinstall": "yarn build",
"build": "husky install && yarn workspaces foreach --all --parallel --topological-dev run build",
"ts-check": "yarn workspaces foreach --all --parallel --topological-dev run ts-check",
"lint-js": "yarn workspaces foreach --all --parallel --topological-dev run lint-js",
"format-js": "yarn workspaces foreach --all --parallel --topological-dev run format-js",
"clean": "yarn workspaces foreach --all --parallel --topological-dev run clean && rm -rf node_modules yarn.lock"
},
"devDependencies": {
"@types/react": "^19.0.12",
"@typescript-eslint/eslint-plugin": "^6.9.0",
"@typescript-eslint/parser": "^6.9.0",
"@yarnpkg/types": "^4.0.1",
"eslint": "^8.57.0",
"eslint-config-satya164": "3.3.0",
"eslint-import-resolver-babel-module": "^5.2.0",
"eslint-plugin-jest": "27.4.3",
"eslint-plugin-prettier": "^5.0.1",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-simple-import-sort": "^13.0.0",
"husky": "^8.0.1",
"jest": "^29.7.0",
"lint-staged": "^12.3.2",
"prettier": "3.3.3",
"typescript": "~6.0.3"
},
"packageManager": "[email protected]",
"resolutions": {
"expo-modules-core@npm:~57.0.10": "patch:expo-modules-core@npm%3A57.0.10#~/.yarn/patches/expo-modules-core-npm-57.0.10-1d55439aaa.patch"
}
}
---
Rnrepo.Config.Json (rnrepo.config.json)
{
"denyList": {
"android": ["react-native-gesture-handler"],
"ios": ["react-native-gesture-handler"]
}
}
---
Tsconfig.Json (tsconfig.json)
{
"compilerOptions": {
"esModuleInterop": true,
"jsx": "react-native",
"lib": ["esnext", "dom"],
"types": ["jest"],
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": true,
"target": "esnext",
"declaration": true,
"declarationMap": false,
"noStrictGenericChecks": false,
"forceConsistentCasingInFileNames": true,
"noImplicitUseStrict": false,
"noImplicitOverride": true,
"noUnusedParameters": true,
"noUnusedLocals": true
}
}
---
.Eslintrc.Json (.eslintrc.json)
{
"root": true,
"extends": [
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking",
"satya164"
],
"settings": {
"react": { "version": "19" },
"import/resolver": {
"babel-module": {}
}
},
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": ["./tsconfig.json"]
},
"env": { "browser": true, "node": true, "jest/globals": true },
"plugins": ["react", "jest", "simple-import-sort"],
"ignorePatterns": [
"packages/react-native-gesture-handler/lib//*",
"/*.config.js",
"scripts//*.js",
"/node_modules//*"
],
"rules": {
// removed in new jest-eslint-plugin, referenced in satya config
"jest/no-truthy-falsy": "off",
"jest/valid-describe": "off",
"jest/expect-expect": [
"warn",
{ "assertFunctionNames": ["expect", "assert"] }
],
"jest/no-conditional-expect": "warn",
// temporary, remove after we type internals better
"@typescript-eslint/restrict-template-expressions": "warn",
"@typescript-eslint/no-unsafe-member-access": "warn",
"@typescript-eslint/no-unsafe-call": "warn",
"@typescript-eslint/no-unsafe-assignment": "warn",
"@typescript-eslint/no-unsafe-argument": "warn",
"@typescript-eslint/no-unsafe-return": "warn",
"@typescript-eslint/no-non-null-assertion": "warn",
"@typescript-eslint/ban-types": "warn",
"@typescript-eslint/consistent-type-imports": "error",
"@typescript-eslint/consistent-type-exports": "error",
"@typescript-eslint/no-unsafe-enum-comparison": "off",
// common
"@typescript-eslint/explicit-module-boundary-types": "off",
"import/named": "off",
"react/sort-comp": "off",
"react/no-unused-prop-types": "warn",
"react-hooks/exhaustive-deps": "warn",
"@typescript-eslint/no-floating-promises": "warn",
"@eslint-react/no-missing-component-display-name": "warn",
"@eslint-react/no-nested-components": "warn",
"@eslint-react/no-nested-component-definitions": "warn",
"prefer-const": [
"error",
{
"destructuring": "all"
}
],
"@typescript-eslint/no-unused-vars": [
"error",
{ "argsIgnorePattern": "^_" }
],
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/no-redundant-type-constituents": "warn",
"@typescript-eslint/no-empty-function": "error",
"@typescript-eslint/no-misused-promises": "warn",
"@eslint-react/no-array-index-key": "warn",
"@eslint-react/hooks-extra/no-direct-set-state-in-use-effect": "warn",
"@eslint-react/hooks-extra/prefer-use-state-lazy-initialization": "warn",
"@eslint-react/hooks-extra/ensure-custom-hooks-using-other-hooks": "warn",
"no-redeclare": "off",
"@typescript-eslint/no-redeclare": "error",
"no-use-before-define": "off",
"@typescript-eslint/no-use-before-define": "error",
"@typescript-eslint/ban-ts-comment": [
"error",
{
"ts-ignore": "allow-with-description",
"ts-expect-error": "allow-with-description"
}
],
"curly": "error",
"spaced-comment": "error",
"no-alert": "warn",
"simple-import-sort/imports": "error",
"simple-import-sort/exports": "error"
},
"overrides": [
{
"files": [
"packages/react-native-gesture-handler/src/v3/",
"packages/react-native-gesture-handler/src/web/",
"packages/react-native-gesture-handler/src/*.ts"
],
"rules": {
"no-restricted-globals": [
"error",
{
"name": "global",
"message": "Use the standard globalThis — the bare global binding is React Native/Node-specific and doesn't exist in browsers."
}
],
"no-restricted-syntax": [
"error",
{
"selector": "Identifier[name='__DEV__']:not(:function *)",
"message": "Don't read __DEV__ at module scope — the global may not be defined yet outside Metro when modules are evaluated. Read it lazily inside a function instead."
}
]
}
},
{
"files": ["packages/react-native-gesture-handler/src/web/"],
"rules": {
"no-restricted-imports": [
"error",
{
"paths": [
{
"name": "react-native",
"message": "The web handlers are platform-agnostic engine code — they must not depend on react-native. Guard on runtime capabilities (e.g. DOM availability) instead."
}
]
}
]
}
}
]
}
---
.Lintstagedrc.Json (.lintstagedrc.json)
{
"{apps/basic-example,packages/react-native-gesture-handler}/package.json": "node scripts/check-rn-versions.js",
"/*.{ts,tsx}": ["prettier --write", "eslint"],
"packages/react-native-gesture-handler/android//*.kt": [
"node scripts/check-android-dirs.js",
"yarn format:android"
],
"packages/react-native-gesture-handler/apple//*.{h,m,mm,cpp}": "yarn format:apple",
"packages/react-native-gesture-handler/{shared,android/src}//*.{h,cpp}": "yarn format:cpp"
}
---
.Prettierrc.Json (.prettierrc.json)
{
"bracketSameLine": true,
"printWidth": 80,
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"trailingComma": "es5"
}
---
.Yarnrc.Yml (.yarnrc.yml)
nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-4.13.0.cjs
---
Apps/Basic Example/README (apps/basic-example/README.md)
Basic Gesture Handler example app
This app shows basic capabilities of Gesture Handler. It doesn't depend on any other package - this makes it easier to confirm that Gesture Handler works correctly.
Installing & running application
Before running application you need to install all dependencies. To do that:
- In project's root directory run yarn
Android
You can run this application by yarn android or from Android Studio.
iOS
To run on iOS first go to BasicExample/ios and run pod install.
Then in BasicExample run yarn ios or run application from Xcode.
---
Apps/Basic Example/App.Json (apps/basic-example/app.json)
{
"name": "BasicExample",
"displayName": "BasicExample"
}
---
Apps/Basic Example/Package.Json (apps/basic-example/package.json)
{
"name": "basic-example",
"version": "0.0.1",
"private": true,
"scripts": {
"android": "react-native run-android",
"ios": "react-native run-ios",
"lint": "eslint .",
"start": "react-native start",
"test": "jest",
"ts-check": "yarn tsc --noEmit",
"lint-js": "eslint --ext '.js,.ts,.tsx' src/ && yarn prettier --check './src//*.{js,jsx,ts,tsx}'",
"format-js": "prettier --write --list-different './src//*.{js,jsx,ts,tsx}'",
"clean-android": "rm -rf android/.gradle android/.kotlin android/build",
"clean-ios": "rm -rf ios/build ios/Pods ios/Podfile.lock",
"clean": "rm -rf node_modules && yarn clean-android && yarn clean-ios",
"pods": "cd ios && bundle install && bundle exec pod install"
},
"dependencies": {
"react": "19.2.3",
"react-native": "0.87.0",
"react-native-gesture-handler": "workspace:*",
"react-native-reanimated": "4.6.0-nightly-20260811-248dee712",
"react-native-worklets": "patch:react-native-worklets@npm%3A0.12.0-nightly-20260810-fb9cb5596#~/.yarn/patches/react-native-worklets-npm-0.12.0-nightly-20260810-fb9cb5596-f08a72b88e.patch"
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@babel/preset-env": "^7.25.3",
"@babel/runtime": "^7.25.0",
"@react-native-community/cli": "20.2.0",
"@react-native-community/cli-platform-android": "20.2.0",
"@react-native-community/cli-platform-ios": "20.2.0",
"@react-native/babel-preset": "0.87.0",
"@react-native/eslint-config": "0.87.0",
"@react-native/jest-preset": "0.87.0",
"@react-native/metro-config": "0.87.0",
"@react-native/typescript-config": "0.87.0",
"@rnrepo/build-tools": "~0.1.3-beta.0",
"@types/jest": "^29.5.13",
"@types/react": "^19.2.0",
"@types/react-test-renderer": "^19.1.0",
"@typescript-eslint/eslint-plugin": "^6.9.0",
"@typescript-eslint/parser": "^6.9.0",
"eslint": "^8.57.0",
"eslint-config-satya164": "3.3.0",
"eslint-import-resolver-babel-module": "^5.2.0",
"eslint-plugin-jest": "27.4.3",
"eslint-plugin-prettier": "^5.0.1",
"eslint-plugin-react": "^7.37.5",
"jest": "^29.6.3",
"prettier": "3.3.3",
"react-test-renderer": "19.2.3",
"typescript": "~6.0.3"
},
"engines": {
"node": ">=22.11.0"
},
"installConfig": {
"selfReferences": false
}
}
---
Apps/Basic Example/Tsconfig.Json (apps/basic-example/tsconfig.json)
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"ignoreDeprecations": "6.0",
"noEmit": true,
"paths": {
"react-native-gesture-handler": [
"../../packages/react-native-gesture-handler/src"
],
"react-native-gesture-handler/ReanimatedSwipeable": [
"../../packages/react-native-gesture-handler/src/components/ReanimatedSwipeable/"
],
"react-native-gesture-handler/ReanimatedDrawerLayout": [
"../../packages/react-native-gesture-handler/src/components/ReanimatedDrawerLayout.tsx"
],
"react-native-gesture-handler/jest-utils": [
"../../packages/react-native-gesture-handler/src/jestUtils/index.ts"
]
},
"types": ["../../packages/react-native-gesture-handler/src/global.d.ts"]
},
"include": ["src//.ts", "src//.tsx"]
}
---
Apps/Basic Example/Ios/BasicExample/Images.Xcassets/Contents.Json (apps/basic-example/ios/BasicExample/Images.xcassets/Contents.json)
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}
---
Apps/Basic Example/Ios/BasicExample/Images.Xcassets/AppIcon.Appiconset/Contents.Json (apps/basic-example/ios/BasicExample/Images.xcassets/AppIcon.appiconset/Contents.json)
{
"images" : [
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "20x20"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "20x20"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "29x29"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "29x29"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "40x40"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "40x40"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "60x60"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "60x60"
},
{
"idiom" : "ios-marketing",
"scale" : "1x",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
Apps/Common App/README (apps/common-app/README.md)
Gesture Handler example app
This is project contains source code for examples shared betwwen example apps.
---
Apps/Common App/Package.Json (apps/common-app/package.json)
{
"name": "common-app",
"version": "0.0.1",
"private": true,
"scripts": {
"android": "react-native run-android",
"ios": "react-native run-ios",
"lint": "eslint .",
"start": "react-native start",
"test": "jest",
"ts-check": "yarn tsc --noEmit",
"lint-js": "eslint --ext '.js,.ts,.tsx' src/ && yarn prettier --check './src//*.{js,jsx,ts,tsx}'",
"format-js": "prettier --write --list-different App.tsx './src//*.{js,jsx,ts,tsx}'"
},
"peerDependencies": {
"react": "*",
"react-native": "*"
},
"dependencies": {
"@react-native-async-storage/async-storage": "^2.1.2",
"@react-navigation/elements": "^2.3.8",
"@react-navigation/native": "^7.1.6",
"@react-navigation/stack": "^7.2.10",
"@swmansion/icons": "^0.0.1",
"react-native-gesture-handler": "workspace:*",
"react-native-pager-view": "^8.0.2",
"react-native-reanimated": "4.6.0-nightly-20260811-248dee712",
"react-native-safe-area-context": "~5.7.0",
"react-native-screens": "4.25.2",
"react-native-svg": "15.15.4",
"react-native-worklets": "patch:react-native-worklets@npm%3A0.12.0-nightly-20260810-fb9cb5596#~/.yarn/patches/react-native-worklets-npm-0.12.0-nightly-20260810-fb9cb5596-f08a72b88e.patch"
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@babel/preset-env": "^7.25.3",
"@babel/runtime": "^7.25.0",
"@react-native-community/cli": "18.0.1",
"@react-native-community/cli-platform-android": "18.0.0",
"@react-native-community/cli-platform-ios": "18.0.0",
"@react-native/babel-preset": "0.79.0",
"@react-native/eslint-config": "0.79.0",
"@react-native/metro-config": "0.79.0",
"@react-native/typescript-config": "0.79.0",
"@types/jest": "^29.5.13",
"@types/react": "^19.0.12",
"@types/react-test-renderer": "^19.0.0",
"@typescript-eslint/eslint-plugin": "^6.9.0",
"@typescript-eslint/parser": "^6.9.0",
"eslint": "^8.57.0",
"eslint-config-satya164": "3.3.0",
"eslint-import-resolver-babel-module": "^5.2.0",
"eslint-plugin-jest": "27.4.3",
"eslint-plugin-prettier": "^5.0.1",
"eslint-plugin-react": "^7.37.5",
"expo-camera": "~16.1.1",
"jest": "^29.6.3",
"prettier": "3.3.3",
"react-test-renderer": "19.0.0",
"typescript": "~6.0.3"
},
"engines": {
"node": ">=18"
}
}
---
Apps/Common App/Tsconfig.Json (apps/common-app/tsconfig.json)
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"noEmit": true,
"declaration": false,
"noImplicitOverride": false,
"paths": {
"@/": ["./"],
"react-native-gesture-handler": [
"../../packages/react-native-gesture-handler/src"
],
"react-native-gesture-handler/ReanimatedSwipeable": [
"../../packages/react-native-gesture-handler/src/components/ReanimatedSwipeable/"
],
"react-native-gesture-handler/ReanimatedDrawerLayout": [
"../../packages/react-native-gesture-handler/src/components/ReanimatedDrawerLayout.tsx"
],
"react-native-gesture-handler/jest-utils": [
"../../packages/react-native-gesture-handler/src/jestUtils/index.ts"
]
},
"types": ["../../packages/react-native-gesture-handler/src/global.d.ts"]
},
"include": ["src//.ts", "src//.tsx", "index.ts"]
}
---
Apps/Expo Example/README (apps/expo-example/README.md)
Gesture Handler Expo example app
This is an example application demonstrating the functionality of the react-native-gesture-handler library with Expo.
Installing & running application
Before running the application, you need to install all dependencies. To do that:
- In the project's root directory, run yarn.
Next run npx expo prebuild.
Android
Run this application by yarn android or from Android Studio.
iOS
Run yarn ios or run application from Xcode.
Web
Run yarn web. Alternatively run yarn start and in browser navigate to localhost:8081
---
Apps/Expo Example/Package.Json (apps/expo-example/package.json)
{
"name": "expo-example",
"version": "1.0.0",
"main": "index.ts",
"scripts": {
"postinstall": "npx expo prebuild",
"start": "expo start --dev-client",
"android": "expo run:android",
"ios": "expo run:ios",
"web": "expo start --web",
"clean": "rm -rf node_modules android ios"
},
"dependencies": {
"@expo/metro-runtime": "~57.0.9",
"@react-native-async-storage/async-storage": "2.2.0",
"@react-navigation/elements": "^2.3.8",
"@react-navigation/native": "^7.1.6",
"@react-navigation/stack": "^7.2.10",
"@swmansion/icons": "^0.0.1",
"common-app": "workspace:*",
"expo": "^57.0.0",
"expo-camera": "~57.0.3",
"expo-status-bar": "~57.0.1",
"react": "19.2.3",
"react-dom": "19.2.3",
"react-native": "0.86.2",
"react-native-gesture-handler": "workspace:*",
"react-native-pager-view": "^8.0.2",
"react-native-reanimated": "4.6.0-nightly-20260811-248dee712",
"react-native-safe-area-context": "~5.7.0",
"react-native-screens": "4.25.2",
"react-native-svg": "15.15.4",
"react-native-web": "^0.21.0",
"react-native-worklets": "patch:react-native-worklets@npm%3A0.12.0-nightly-20260810-fb9cb5596#~/.yarn/patches/react-native-worklets-npm-0.12.0-nightly-20260810-fb9cb5596-f08a72b88e.patch"
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@rnrepo/expo-config-plugin": "0.3.0-beta.3",
"@types/react": "~19.1.10",
"@types/react-dom": "~19.1.7",
"@types/react-native-web": "^0",
"typescript": "~6.0.3"
},
"private": true,
"installConfig": {
"hoistingLimits": "workspaces",
"selfReferences": false
}
}
---
Apps/Expo Example/Rnrepo.Config.Json (apps/expo-example/rnrepo.config.json)
{
"denyList": {
"android": ["react-native-gesture-handler"],
"ios": ["react-native-gesture-handler"]
}
}
---
Apps/Expo Example/Tsconfig.Json (apps/expo-example/tsconfig.json)
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"noEmit": true,
"baseUrl": ".",
"paths": {
"common-app": ["../common-app/index.ts"],
"react-native-gesture-handler": [
"../../packages/react-native-gesture-handler/src"
],
"react-native-gesture-handler/ReanimatedSwipeable": [
"../../packages/react-native-gesture-handler/src/components/ReanimatedSwipeable/"
],
"react-native-gesture-handler/ReanimatedDrawerLayout": [
"../../packages/react-native-gesture-handler/src/components/ReanimatedDrawerLayout.tsx"
],
"react-native-gesture-handler/jest-utils": [
"../../packages/react-native-gesture-handler/src/jestUtils/index.ts"
]
},
"types": ["../../packages/react-native-gesture-handler/src/global.d.ts"]
},
"include": ["src//.ts", "src//.tsx", "App.tsx"],
"exclude": ["metro.config.js", "android", "ios", ".bundle", "node_modules"]
}
---
Apps/Macos Example/README (apps/macos-example/README.md)
macOS Gesture Handler example app
This is an example application demonstrating the functionality of the react-native-gesture-handler library on macOS.
Installing & running application
Before running the application, you need to install all dependencies. To do that:
- In the project's root directory, run yarn.
- Navigate to the MacOSExample/macos directory and run bundle install && bundle exec pod install
- Navigate to the MacOSExample and run yarn start to start the Metro bundler.
- Open the MacOSExample project in Xcode and build the app to run it on a macOS simulator or device.
---
Apps/Macos Example/App.Json (apps/macos-example/app.json)
{
"name": "MacOSExample",
"displayName": "MacOSExample"
}
---
Apps/Macos Example/Package.Json (apps/macos-example/package.json)
{
"name": "macos-example",
"version": "0.0.1",
"private": true,
"scripts": {
"macos": "npx react-native-macos run-macos",
"lint": "eslint .",
"start": "react-native start",
"test": "jest",
"clean": "rm -rf node_modules && cd macos && rm -rf build Pods Podfile.lock",
"pods": "cd macos && bundle install && bundle exec pod install"
},
"dependencies": {
"@react-native-async-storage/async-storage": "2.1.2",
"@react-navigation/elements": "^2.3.8",
"@react-navigation/native": "^7.1.6",
"@react-navigation/stack": "^7.2.10",
"@swmansion/icons": "^0.0.1",
"common-app": "workspace:*",
"react": "19.1.4",
"react-native": "0.81.2",
"react-native-gesture-handler": "workspace:*",
"react-native-macos": "0.81.2",
"react-native-reanimated": "~4.3.1",
"react-native-safe-area-context": "~5.7.0",
"react-native-screens": "4.25.2",
"react-native-svg": "15.15.4",
"react-native-worklets": "0.8.1"
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@babel/preset-env": "^7.25.3",
"@babel/runtime": "^7.25.0",
"@react-native-community/cli": "20.0.0",
"@react-native-community/cli-platform-android": "20.0.0",
"@react-native-community/cli-platform-ios": "20.0.0",
"@react-native/babel-preset": "0.81.2",
"@react-native/eslint-config": "0.81.2",
"@react-native/metro-config": "0.81.2",
"@react-native/typescript-config": "0.81.2",
"@types/jest": "^29.5.13",
"@types/react": "^19.1.4",
"@types/react-test-renderer": "^19.1.0",
"jest": "^29.6.3",
"react-test-renderer": "19.1.4",
"typescript": "~6.0.3"
},
"engines": {
"node": ">=20"
},
"installConfig": {
"hoistingLimits": "workspaces",
"selfReferences": false
}
}
---
Apps/Macos Example/Tsconfig.Json (apps/macos-example/tsconfig.json)
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"noEmit": true,
"baseUrl": ".",
"paths": {
"common-app": ["../common-app/index.ts"],
"react-native-gesture-handler": [
"../../packages/react-native-gesture-handler/src"
],
"react-native-gesture-handler/ReanimatedSwipeable": [
"../../packages/react-native-gesture-handler/src/components/ReanimatedSwipeable/"
],
"react-native-gesture-handler/ReanimatedDrawerLayout": [
"../../packages/react-native-gesture-handler/src/components/ReanimatedDrawerLayout.tsx"
],
"react-native-gesture-handler/jest-utils": [
"../../packages/react-native-gesture-handler/src/jestUtils/index.ts"
]
},
"types": ["../../packages/react-native-gesture-handler/src/global.d.ts"]
},
"include": ["src//.ts", "src//.tsx", "index.ts"],
"exclude": ["metro.config.js", "macos", ".bundle", "node_modules", "/Pods"]
}
---
Apps/Macos Example/Macos/MacOSExample MacOS/Assets.Xcassets/Contents.Json (apps/macos-example/macos/MacOSExample-macOS/Assets.xcassets/Contents.json)
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
Apps/Macos Example/Macos/MacOSExample MacOS/Assets.Xcassets/AppIcon.Appiconset/Contents.Json (apps/macos-example/macos/MacOSExample-macOS/Assets.xcassets/AppIcon.appiconset/Contents.json)
{
"images" : [
{
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
Packages/Docs Gesture Handler/README (packages/docs-gesture-handler/README.md)
Website
This website is built using Docusaurus 2, a modern static website generator.
Installation
yarn installLocal Development
yarn startThis command starts a local development server and open up a browser window. Most changes are reflected live without having to restart the server.
Build
yarn buildThis command generates static content into the build directory and can be served using any static contents hosting service.
Deployment
GIT_USER=<Your GitHub username> USE_SSH=true yarn deployIf you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the gh-pages branch.
---
Packages/Docs Gesture Handler/Package.Json (packages/docs-gesture-handler/package.json)
{
"name": "react-native-gesture-handler-docs",
"version": "0.0.0",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
"start": "docusaurus start",
"build": "docusaurus build && node -r esbuild-register scripts/build-og-images.jsx",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy",
"clear": "docusaurus clear",
"serve": "docusaurus serve",
"write-translations": "docusaurus write-translations",
"write-heading-ids": "docusaurus write-heading-ids",
"typecheck": "tsc --noEmit",
"lint": "prettier --check docs src versioned_docs versioned_docs",
"format": "prettier --write --list-different docs src versioned_docs versioned_docs"
},
"dependencies": {
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-proposal-export-namespace-from": "^7.18.9",
"@babel/preset-env": "^7.24.4",
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.21.0",
"@docusaurus/core": "3.9.2",
"@docusaurus/plugin-client-redirects": "3.9.2",
"@docusaurus/plugin-debug": "3.9.2",
"@docusaurus/plugin-google-tag-manager": "3.9.2",
"@docusaurus/preset-classic": "3.9.2",
"@docusaurus/theme-common": "3.9.2",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@mdx-js/react": "^3.0.0",
"@mui/material": "^7.1.0",
"@swmansion/t-rex-ui": "1.3.7",
"@vercel/og": "^0.6.2",
"babel-polyfill": "^6.26.0",
"babel-preset-expo": "^9.2.2",
"babel-preset-react-native": "^4.0.1",
"clsx": "^2.1.0",
"copy-text-to-clipboard": "3.2.2",
"eslint-plugin-simple-import-sort": "^13.0.0",
"prism-react-renderer": "^2.1.0",
"raf": "^3.4.1",
"raw-loader": "^4.0.2",
"react": "19.1.1",
"react-colorful": "^5.6.1",
"react-dom": "19.1.1",
"react-draggable": "^4.4.5",
"react-native": "0.83.0",
"react-native-gesture-handler": "3.1.0",
"react-native-reanimated": "4.3.0",
"react-native-web": "0.21.2",
"react-native-worklets": "0.8.1",
"source-map": "^0.7.4",
"source-map-loader": "^4.0.1",
"usehooks-ts": "^2.9.1"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "3.9.2",
"@docusaurus/tsconfig": "3.9.2",
"copy-webpack-plugin": "^11.0.0",
"esbuild": "^0.28.1",
"esbuild-register": "^3.5.0",
"eslint-plugin-mdx": "^2.2.0",
"prettier": "^3.3.3",
"typescript": "~5.2.2",
"webpack-cli": "^7.0.2"
},
"browserslist": {
"production": [
">0.5%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"engines": {
"node": ">=18.0"
},
"resolutions": {
"webpackbar": "^7.0.0"
},
"packageManager": "[email protected]"
}
---
Packages/Docs Gesture Handler/Redirects.Json (packages/docs-gesture-handler/redirects.json)
{
"redirects": [
{
"to": "/docs/fundamentals/getting-started",
"from": "/docs/"
},
{
"to": "/docs/fundamentals/getting-started",
"from": "/docs/fundamentals/introduction"
},
{
"to": "/docs/fundamentals/getting-started",
"from": "/docs/fundamentals/installation"
},
{
"to": "/docs/legacy-gestures/pan-gesture",
"from": "/docs/gestures/pan-gesture"
},
{
"to": "/docs/legacy-gestures/tap-gesture",
"from": "/docs/gestures/tap-gesture"
},
{
"to": "/docs/legacy-gestures/long-press-gesture",
"from": "/docs/gestures/long-press-gesture"
},
{
"to": "/docs/legacy-gestures/rotation-gesture",
"from": "/docs/gestures/rotation-gesture"
},
{
"to": "/docs/legacy-gestures/pinch-gesture",
"from": "/docs/gestures/pinch-gesture"
},
{
"to": "/docs/legacy-gestures/fling-gesture",
"from": "/docs/gestures/fling-gesture"
},
{
"to": "/docs/legacy-gestures/hover-gesture",
"from": "/docs/gestures/hover-gesture"
},
{
"to": "/docs/legacy-gestures/native-gesture",
"from": "/docs/gestures/native-gesture"
},
{
"to": "/docs/legacy-gestures/manual-gesture",
"from": "/docs/gestures/manual-gesture"
},
{
"to": "/docs/legacy-gestures/force-touch-gesture",
"from": "/docs/gestures/force-touch-gesture"
},
{
"to": "/docs/2.x/guides/upgrading-to-2",
"from": "/docs/guides/upgrading-to-2"
},
{
"to": "/docs/core-components/gesture-detectors",
"from": "/docs/gestures/gesture-detector"
},
{
"to": "/docs/core-components/gesture-detectors",
"from": "/docs/fundamentals/gesture-detectors"
},
{
"to": "/docs/core-components/root-view",
"from": "/docs/fundamentals/root-view"
},
{
"to": "/docs/fundamentals/gesture-animation",
"from": "/docs/guides/quickstart"
},
{
"to": "/docs/gestures/state-manager",
"from": "/docs/fundamentals/state-manager"
}
]
}
---
Packages/Docs Gesture Handler/Tsconfig.Json (packages/docs-gesture-handler/tsconfig.json)
{
// This file is not used in compilation. It is here just for a nice editor experience.
"extends": "@docusaurus/tsconfig",
"compilerOptions": {
"baseUrl": "."
}
}
---
Packages/Docs Gesture Handler/Docs/Components/ Category .Json (packages/docs-gesture-handler/docs/components/_category_.json)
{
"label": "Components",
"position": 6,
"link": {
"type": "generated-index"
}
}
---
Packages/Docs Gesture Handler/Docs/Components/Buttons (packages/docs-gesture-handler/docs/components/buttons.mdx)
---
id: buttons
title: Buttons
sidebar_label: Buttons
sidebar_position: 6
---
import useBaseUrl from '@docusaurus/useBaseUrl';
import GifGallery from '@site/components/GifGallery';
:::danger
Button components described in this section are deprecated and will be removed in the future. Please use Touchable instead.
:::
<GifGallery>
<img src={useBaseUrl('gifs/samplebutton.gif')} width="280" />
</GifGallery>
The Gesture Handler library offers native components that function as buttons, serving as alternatives to TouchableHighlight or TouchableOpacity from the core React Native framework. These buttons process touch recognition natively, which ensures a deterministic response. This capability significantly enhances performance; for example, it allows for immediate ripple effects on Android, unlike TouchableNativeFeedback, which requires a touch event roundtrip to JavaScript that can cause delays, especially noticeable on older devices. Additionally, these components handle default platform interactions natively, particularly in scrollable containers where interactions are smartly delayed to prevent unintended highlighting during a fling.
Gesture Handler library exposes components that render native touchable elements under the hood:
- RawButton
- BaseButton
- RectButton
- BorderlessButton
On top of that all the buttons are wrapped with Native gesture and therefore allow for all its properties to be applied to them.
RawButton
The most basic button component does not provide any feedback and lacks props such as onPress. It serves as a foundation for other button components and is ideal if you wish to implement your own custom interactions for when the button is pressed.
RawButton accepts all Native gesture props, accessibility props, along with the following additional properties:
exclusive
exclusive?: boolean;Defines whether pressing this button prevents other buttons exported by Gesture Handler from being pressed. By default set to true.
<Badges platforms={['android']}>
rippleColor [A]
</Badges>
rippleColor?: number | ColorValue | null;Defines color of native ripple animation.
<Badges platforms={['android']}>
rippleRadius [A]
</Badges>
rippleRadius?: number | null;Defines radius of native ripple animation.
<Badges platforms={['android']}>
borderless [A]
</Badges>
borderless?: boolean;If set to false, ripple animation will render only within view bounds.
<Badges platforms={['android']}>
foreground [A]
</Badges>
foreground?: boolean;Defines whether the ripple animation should be drawn on the foreground of the view.
<Badges platforms={['android']}>
touchSoundDisabled [A]
</Badges>
touchSoundDisabled?: boolean;If set to true, the system will not play a sound when the button is pressed.
BaseButton
Can be used as a base class if you'd like to implement some custom interaction for when the button is pressed. It has all the props of RawButton and in addition to that it also provides the following props:
onPress
onPress?: (pointerInside: boolean) => void;Triggered when the button gets pressed (analogous to onPress in TouchableHighlight from RN core).
onLongPress
onLongPress?: () => void;Triggered when the button gets pressed for at least delayLongPress milliseconds.
onActiveStateChange
onActiveStateChange?: (active: boolean) => void;Triggered when the button transitions between active and inactive states. It passes the current active state as a boolean variable to the method as the first parameter.
delayLongPress
delayLongPress?: number;Defines the delay, in milliseconds, after which the onLongPress callback gets called. By default set to 600.
RectButton
This type of button component is ideal for use with rectangular elements or content blocks that can be pressed, such as table rows or buttons featuring text and icons. It ensures platform-specific interactions, such as rendering a rectangular ripple on Android or highlighting the background on iOS and older Android versions. In addition to the props offered by BaseButton, it accepts the following:
underlayColor
underlayColor?: string;Background color that will be dimmed when the button is in an active state.
<Badges platforms={['ios']}>
activeOpacity [I]
</Badges>
activeOpacity?: number;Opacity applied to the underlay when the button is in an active state.
BorderlessButton
This type of button component should be used with simple icon-only or text-only buttons. The interaction will be different depending on platform: on Android a borderless ripple will be rendered (it means that the ripple will animate into a circle that can span outside of the view bounds), whereas on iOS the button will be dimmed (similar to how TouchableOpacity works). In addition to the props of BaseButton, it accepts the following:
<Badges platforms={['ios']}>
activeOpacity [I]
</Badges>
activeOpacity?: number;Opacity applied to the button when it is in an active state.
Accessibility
To guarantee that buttons are fully accessible, you must wrap your children in a View marked as accessible and include the accessibilityRole="button" prop. This requirement applies to both iOS and Android platforms. Without these adjustments, the button won't be selectable on iOS, and on Android, it won't be clickable in accessibility mode.
// Not accessible:
const NotAccessibleButton = () => (
<RectButton onPress={this._onPress}>
<Text>Foo</Text>
</RectButton>
);// Accessible:
const AccessibleButton = () => (
<RectButton onPress={this._onPress}>
<View accessible accessibilityRole="button">
<Text>Bar</Text>
</View>
</RectButton>
);
Design patterns
Components listed here were not designed to behave and look in the same way on both platforms, but rather to be used for handling similar behaviour on iOS and Android taking into consideration their design concepts.
If you wish to get specific information about platforms design patterns, visit official Apple docs and Material.io guideline, which widely describe how to implement coherent design.
This library allows the use of native components with native feedback in adequate situations.
If you do not wish to implement a custom design approach, RectButton and BorderlessButton seem to be absolutely enough and there's no need to use anything else. In all the remaining cases, you can always rely on BaseButton which is a superclass for the other button classes and can be used as a generic React Native Touchable replacement that can be customized to your needs.
Below we list some of the common usecases for button components to be used along with the type of button that should be used according to the platform specific design guidelines.
Lists and action buttons
If you have a list with clickable items or have an action button that needs to display as a separate UI block (vs being inlined in a text) you should use RectButton. It changes opacity on click and additionally supports a ripple effect on Android.
<GifGallery>
<img src={useBaseUrl('gifs/androidsettings.gif')} width="280" />
<img src={useBaseUrl('gifs/iossettings.gif')} width="280" />
</GifGallery>
To determine the emphasis of a button, it's vital to use a fill color or leave it transparent, especially on Android.
For medium emphasis, you may consider outlined buttons which are used for lower impact than fill buttons.
<GifGallery>
<img src={useBaseUrl('gifs/androidbutton.gif')} width="280" />
</GifGallery>
Icon or text only buttons
Use BorderlessButton for simple icon-only or text-only buttons. The interaction will be different depending on platform: on Android a borderless ripple will be rendered, whereas on iOS the button will be dimmed.
It should be used if you wish to handle non-crucial actions and supportive behaviour.
<GifGallery>
<img src={useBaseUrl('gifs/androidmail.gif')} width="280" />
<img src={useBaseUrl('gifs/iosmail.gif')} width="280" />
</GifGallery>
---
Packages/Docs Gesture Handler/Docs/Components/Pressable (packages/docs-gesture-handler/docs/components/pressable.mdx)
---
id: pressable
title: Pressable
sidebar_label: Pressable
sidebar_position: 2
---
import useBaseUrl from '@docusaurus/useBaseUrl';
import GifGallery from '@site/components/GifGallery';
:::info
This component is a drop-in replacement for the Pressable component.
:::
<GifGallery>
<img src={useBaseUrl('gifs/pressable.gif')} width="70%" />
</GifGallery>
Pressable is a component that can detect various stages of tap, press, and hover interactions on any of its children.
To use Pressable, ensure that your app is wrapped in GestureHandlerRootView and import it as follows:
import { Pressable } from 'react-native-gesture-handler';Properties
children
children?:
| React.ReactNode
| ((state: PressableStateCallbackType) => React.ReactNode);Either children or a render prop that receives a boolean reflecting whether the component is currently pressed.
style
style?:
| StyleProp<ViewStyle>
| ((state: PressableStateCallbackType) => StyleProp<ViewStyle>);Either view styles or a function that receives a boolean reflecting whether the component is currently pressed and returns view styles.
onPress
onPress?: null | ((event: PressableEvent) => void);Called after onPressOut when a single tap gesture is detected. Details about the event object can be found in the PressableEvent section below.
onPressIn
onPressIn?: null | ((event: PressableEvent) => void);Called before onPress when a touch is engaged. Details about the event object can be found in the PressableEvent section below.
onPressOut
onPressOut?: null | ((event: PressableEvent) => void);Called before onPress when a touch is released (before onPress). Details about the event object can be found in the PressableEvent section below.
onLongPress
onLongPress?: null | ((event: PressableEvent) => void);Called immediately after pointer has been down for at least delayLongPress milliseconds.
After onLongPress has been called, onPressOut will be called as soon as the pointer is lifted and onPress will not be called at all.
cancelable
cancelable?: null | boolean;Whether a press gesture can be interrupted by a parent gesture such as a scroll event. Defaults to true.
<Badges platforms={['android', 'web']}>
onHoverIn [A][W]
</Badges>
onHoverIn?: null | ((event: PressableEvent) => void);Called when pointer is hovering over the element.
<Badges platforms={['android', 'web']}>
onHoverOut [A][W]
</Badges>
onHoverOut?: null | ((event: PressableEvent) => void);Called when pointer stops hovering over the element.
<Badges platforms={['web']}>
delayHoverIn [W]
</Badges>
delayHoverIn?: number | null;Duration to wait after hover in before calling onHoverIn.
<Badges platforms={['web']}>
delayHoverOut [W]
</Badges>
delayHoverOut?: number | null;Duration to wait after hover out before calling onHoverOut.
delayLongPress
delayLongPress?: null | number;Duration (in milliseconds) from onPressIn before onLongPress is called. Default value is 500 ms.
disabled
disabled?: null | boolean;Whether the Pressable behavior is disabled.
<Badges platforms={['android', 'ios']}>
hitSlop [A][I]
</Badges>
hitSlop?: null | Insets | number;Additional distance outside of the view in which a press is detected and onPressIn is triggered.
The Insets type is essentially the same as Rect.
<Badges platforms={['android', 'ios']}>
pressRetentionOffset [A][I]
</Badges>
pressRetentionOffset?: null | Insets | number;Additional distance outside of the view (or hitSlop if present) in which a touch is considered a
press before onPressOut is triggered.
The Insets type is essentially the same as Rect.
<Badges platforms={['android']}>
android_disableSound [A]
</Badges>
android_disableSound?: null | boolean;If true, doesn't play system sound on touch.
<Badges platforms={['android']}>
android_ripple [A]
</Badges>
android_ripple?: null | PressableAndroidRippleConfig;Enables the Android ripple effect and configures its color, radius and other parameters.
Accepts values of type RippleConfig.
<Badges platforms={['android']}>
needsOffscreenAlphaCompositing [A]
</Badges>
needsOffscreenAlphaCompositing?: boolean;Whether the view should render with an offscreen alpha-compositing buffer when its opacity is less than 1. Defaults to false.
testOnly_pressed
testOnly_pressed?: null | boolean;Used only for documentation or testing (e.g. snapshot testing).
unstable_pressDelay
unstable_pressDelay?: number | undefined;Duration (in milliseconds) to wait after press down before calling onPressIn.
PressableEvent
All Pressable callbacks receive an event object as a parameter, which is of type PressableEvent and has the following structure:
export type PressableEvent = { nativeEvent: InnerPressableEvent };export type InnerPressableEvent = {
changedTouches: InnerPressableEvent[];
identifier: number;
locationX: number;
locationY: number;
pageX: number;
pageY: number;
target: number;
timestamp: number;
touches: InnerPressableEvent[];
force?: number;
};
Example
See the full example in Gesture Handler repository.
<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[5, 20]}
src={
import { View, Text, StyleSheet } from 'react-native';
import {
GestureHandlerRootView,
Pressable,
} from 'react-native-gesture-handler';
export default function Example() {
return (
<GestureHandlerRootView>
<Pressable
style={({ pressed }) => (pressed ? styles.highlight : styles.pressable)}
hitSlop={20}
pressRetentionOffset={20}>
<View style={styles.textWrapper}>
<Text style={styles.text}>Pressable!</Text>
</View>
</Pressable>
</GestureHandlerRootView>
);
}
const styles = StyleSheet.create({
pressable: {
width: 120,
height: 120,
backgroundColor: 'mediumpurple',
borderWidth: StyleSheet.hairlineWidth,
},
highlight: {
width: 120,
height: 120,
backgroundColor: 'red',
borderWidth: StyleSheet.hairlineWidth,
},
textWrapper: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
text: {
color: 'black',
},
});
}/>
---
Packages/Docs Gesture Handler/Docs/Components/Reanimated Drawer Layout (packages/docs-gesture-handler/docs/components/reanimated-drawer-layout.mdx)
---
id: reanimated-drawer-layout
title: Reanimated Drawer Layout
sidebar_label: Reanimated Drawer Layout
sidebar_position: 4
---
import useBaseUrl from '@docusaurus/useBaseUrl';
import MouseButtonProp from '../gestures/\_shared/mouse-button.mdx';
:::info
This component acts as a cross-platform replacement for React Native's DrawerLayoutAndroid component, written using Reanimated. For detailed information on standard parameters, please refer to the React Native documentation.
:::
To use ReanimatedDrawerLayout, first ensure that Reanimated is installed and that your app is wrapped in GestureHandlerRootView. You can then import it as follows:
import ReanimatedDrawerLayout from 'react-native-gesture-handler/ReanimatedDrawerLayout';Properties
drawerType
drawerType?: DrawerType;Specifies the way the drawer will be displayed.
Accepts values of the DrawerType enum. Defaults to FRONT.
- FRONT The drawer will be displayed above the content view.
- BACK The drawer will be displayed below the content view, revealed by sliding away the content view.
- SLIDE The drawer will appear attached to the content view, opening it slides both the drawer and the content view.
| FRONT | BACK | SLIDE |
| ----------------------------------------------------- | ---------------------------------------------------- | ----------------------------------------------------- |
| <img src={useBaseUrl('gifs/new-drawer-front.gif')} /> | <img src={useBaseUrl('gifs/new-drawer-back.gif')} /> | <img src={useBaseUrl('gifs/new-drawer-slide.gif')} /> |
drawerBackgroundColor
drawerBackgroundColor?: string;Color of the drawer's background.
drawerWidth
drawerWidth?: number;Width of the drawer. Defaults to 200.
drawerLockMode
<CollapsibleCode
label="Show composed types definitions"
expandedLabel="Hide composed types definitions"
lineBounds={[0, 1]}
src={
drawerLockMode?: DrawerLockMode;
export enum DrawerLockMode {
UNLOCKED,
LOCKED_CLOSED,
LOCKED_OPEN,
}
}/>
Specifies the lock mode of the drawer.
- UNLOCKED The drawer is unlocked and can be opened or closed by gestures.
- LOCKED_CLOSED The drawer will move freely until it settles in a closed position, then the gestures will be disabled.
- LOCKED_OPEN The drawer will move freely until it settles in an opened position, then the gestures will be disabled.
keyboardDismissMode
<CollapsibleCode
label="Show composed types definitions"
expandedLabel="Hide composed types definitions"
lineBounds={[0, 1]}
src={
keyboardDismissMode?: DrawerKeyboardDismissMode;
export enum DrawerKeyboardDismissMode {
NONE,
ON_DRAG,
}
}/>
Determines if the system keyboard should be closed upon dragging the drawer.
animationSpeed
animationSpeed?: number;Speed of animation that will play when letting go, or dismissing the drawer.
minSwipeDistance
minSwipeDistance?: number;Minimal distance to swipe before the drawer starts moving.
rootContainerStyle
rootContainerStyle?: StyleProp<ViewStyle>;Style applied to the outermost container that wraps both the content view and the drawer. Note that this container has flex: 1 and overflow: 'hidden' applied by default.
contentContainerStyle
contentContainerStyle?: StyleProp<ViewStyle>;Style applied to the container wrapping the content view (the children) and the background overlay.
drawerContainerStyle
drawerContainerStyle?: StyleProp<ViewStyle>;Style applied to the container wrapping the drawer (the view returned by renderNavigationView).
edgeWidth
edgeWidth?: number;Width of the invisible, draggable area on the edge of the content view, which can be dragged to open the drawer.
hideStatusBar
hideStatusBar?: boolean;When set to true, drawer component will use StatusBar API to hide the OS status bar when the drawer is dragged or idle in the open position.
statusBarAnimation
<CollapsibleCode
label="Show composed types definitions"
expandedLabel="Hide composed types definitions"
lineBounds={[0, 1]}
collapsed={false}
src={
statusBarAnimation?: StatusBarAnimation;
export type StatusBarAnimation = 'none' | 'fade' | 'slide';
}/>
May be used in combination with hideStatusBar to select the animation used for hiding the status bar.
See StatusBar API docs. Defaults to slide.
overlayColor
overlayColor?: string;Color of the background overlay on top of the content window when the drawer is open.
This color's opacity animates from 0% to 100% as the drawer transitions from closed to open. Defaults to rgba(0, 0, 0, 0.7).
renderNavigationView
renderNavigationView: (
progressAnimatedValue: SharedValue<number>
) => ReactNode;A renderer function for the drawer component is provided with a
progress parameter called progressAnimatedValue, which is a SharedValue indicating the progress of the drawer's opening or closing animation. This value is 0 when the drawer is fully closed and 1 when it is fully opened. The drawer component can use this value to animate its children during the opening or closing process. This function must return a ReactNode.onDrawerClose
onDrawerClose?: () => void;A function which is called when the drawer has been closed.
onDrawerOpen
onDrawerOpen?: () => void;A function which is called when the drawer has been opened.
onDrawerSlide
onDrawerSlide?: (position: number) => void;A function is called when the drawer is moving or animating, provided with a position parameter. This position value indicates the progress of the drawer's opening or closing animation. It equals 0 when the drawer is closed and 1 when the drawer is fully opened. This value can be utilized by the drawer component to animate its children as the drawer opens or closes.
onDrawerStateChanged
<CollapsibleCode
label="Show composed types definitions"
expandedLabel="Hide composed types definitions"
lineBounds={[0, 4]}
src={
onDrawerStateChanged?: (
newState: DrawerState,
drawerWillShow: boolean
) => void;
export enum DrawerState {
IDLE,
DRAGGING,
SETTLING,
}
}/>
A function is called when the status of the drawer changes, taking newState to represent the drawer's interaction state and drawerWillShow, which is true when the drawer starts animating towards the open position and false otherwise.
<Badges platforms={['ios', 'web']}>
enableTrackpadTwoFingerGesture [I][W]
</Badges>
enableTrackpadTwoFingerGesture?: boolean | SharedValue<boolean>;Enables two-finger gestures on supported devices, for example iPads with trackpads. If not enabled the gesture will require click + drag, with enableTrackpadTwoFingerGesture swiping with two fingers will also trigger the gesture.
children
children?: ReactNode | ((openValue?: SharedValue<number>) => ReactNode);Either a component rendered in the content view or a function. If children is a function, it receives an openValue parameter - SharedValue that indicates the progress of the drawer's opening or closing animation. This value equals 0 when the drawer is closed and 1 when it is fully opened. The drawer component can use this value to animate its children during the opening or closing process. This function must return a ReactNode.
<MouseButtonProp />
<Badges platforms={['web']}>
enableContextMenu [W]
</Badges>
enableContextMenu: boolean;Specifies whether the context menu should be enabled after clicking on the underlying view with the right mouse button. Default value is set to false if MouseButton.RIGHT is specified.
<Badges platforms={['web']}>
userSelect [W]
</Badges>
userSelect: 'none' | 'auto' | 'text';This parameter allows specifying which userSelect property should be applied to the underlying view. Default value is set to "none".
<Badges platforms={['web']}>
activeCursor [W]
</Badges>
activeCursor?: ActiveCursor | SharedValue<ActiveCursor>;This parameter allows specifying which cursor should be used when the gesture activates. Supports all CSS cursor values (e.g. "grab", "zoom-in"). Default value is set to "auto".
Drawer ref methods
Using a reference to ReanimatedDrawerLayout allows you to manually trigger the opening and closing of the component.
const drawerRef = useRef<DrawerLayoutMethods>(null);Both methods accept an optional options parameter, which allows you to customize the animation of the drawer movement.
export type DrawerMovementOption = {
initialVelocity?: number;
animationSpeed?: number;
};openDrawer
openDrawer: (options?: DrawerMovementOption) => void;Allows manually opening the drawer.
closeDrawer
closeDrawer: (options?: DrawerMovementOption) => void;Allows manually closing the drawer.
Example
Example of a ReanimatedDrawerLayout component can be found in Gesture Handler repository.
<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[14, 49]}
src={
import React, { useRef } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import {
GestureDetector,
GestureHandlerRootView,
useTapGesture,
} from 'react-native-gesture-handler';
import ReanimatedDrawerLayout, {
DrawerType,
DrawerPosition,
DrawerLayoutMethods,
} from 'react-native-gesture-handler/ReanimatedDrawerLayout';
const DrawerPage = () => {
return (
<View style={styles.drawerContainer}>
<Text>Lorem ipsum</Text>
</View>
);
};
export default function ReanimatedDrawerExample() {
const drawerRef = useRef<DrawerLayoutMethods>(null);
const tapGesture = useTapGesture({
onDeactivate: () => {
drawerRef.current?.openDrawer();
},
runOnJS: true,
});
return (
<GestureHandlerRootView>
<ReanimatedDrawerLayout
ref={drawerRef}
renderNavigationView={() => <DrawerPage />}
drawerPosition={DrawerPosition.LEFT}
drawerType={DrawerType.FRONT}>
<View style={styles.innerContainer}>
<GestureDetector gesture={tapGesture}>
<View style={styles.box}>
<Text>Open drawer</Text>
</View>
</GestureDetector>
</View>
</ReanimatedDrawerLayout>
</GestureHandlerRootView>
);
}
const styles = StyleSheet.create({
drawerContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'pink',
},
innerContainer: {
flex: 1,
backgroundColor: 'white',
alignItems: 'center',
justifyContent: 'center',
gap: 20,
},
box: {
padding: 20,
backgroundColor: 'pink',
},
});
}/>
---
Packages/Docs Gesture Handler/Docs/Components/Reanimated Swipeable (packages/docs-gesture-handler/docs/components/reanimated_swipeable.mdx)
---
id: reanimated_swipeable
title: Reanimated Swipeable
sidebar_label: Reanimated Swipeable
sidebar_position: 3
---
import useBaseUrl from '@docusaurus/useBaseUrl';
import GifGallery from '@site/components/GifGallery'
:::info
This component is a drop-in replacement for the Swipeable component, rewritten using Reanimated.
:::
<GifGallery>
<img src={useBaseUrl("gifs/sampleswipeable.gif")} height="120" />
</GifGallery>
ReanimatedSwipeable is designed for implementing swipeable rows or similar interactions. It places its children inside a pannable container that enables horizontal swiping to the left and right. Depending on the direction of the swipe, one of two "action" containers will be displayed, which can be configured using the renderLeftActions or renderRightActions props.
To use ReanimatedSwipeable, first ensure that Reanimated is installed and that your app is wrapped in GestureHandlerRootView. You can then import it as follows:
import Swipeable from 'react-native-gesture-handler/ReanimatedSwipeable';Properties
friction
friction?: number;Number that specifies how much the visual interaction will be delayed compared to the gesture distance.
e.g. value of 1 will indicate that the swipeable panel should exactly follow the gesture, 2 means it is going to be two times "slower".
leftThreshold
leftThreshold?: number;Distance from the left edge at which released panel will animate to the open state (or the open panel will animate into the closed state). By default it's a half of the panel's width.
rightThreshold
rightThreshold?: number;Distance from the right edge at which released panel will animate to the open state (or the open panel will animate into the closed state). By default it's a half of the panel's width.
dragOffsetFromLeft
dragOffsetFromLeft?: number | SharedValue<number>;The horizontal offset from the starting point required to trigger a right-swipe gesture. Defaults to 10.
dragOffsetFromRight
dragOffsetFromRight?: number | SharedValue<number>;The horizontal offset from the starting point required to trigger a left-swipe gesture. Defaults to -10.
overshootLeft
overshootLeft?: boolean;A boolean value indicating if the swipeable panel can be pulled further than the left actions panel's width. It is set to true by default as long as the left panel render function is present.
overshootRight
overshootRight?: boolean;A boolean value indicating if the swipeable panel can be pulled further than the right actions panel's width. It is set to true by default as long as the right panel render function is present.
overshootFriction
overshootFriction?: number;A number specifying the delay of visual interaction compared to the gesture distance when overshooting. The default value is 1, which means no friction. For a more native feel, try using a value of 8 or higher.
onSwipeableOpen
onSwipeableOpen?: (
direction: SwipeDirection.LEFT | SwipeDirection.RIGHT
) => void;A function that is called when Swipeable is opened (either right or left).
Receives swipe direction as an argument.
onSwipeableClose
onSwipeableClose?: (
direction: SwipeDirection.LEFT | SwipeDirection.RIGHT
) => void;A function that is called when Swipeable is closed.
Receives swipe direction as an argument.
onSwipeableWillOpen
onSwipeableWillOpen?: (
direction: SwipeDirection.LEFT | SwipeDirection.RIGHT
) => void;A function that is called when Swipeable starts animating on open (either right or left).
Receives swipe direction as an argument.
onSwipeableWillClose
onSwipeableWillClose?: (
direction: SwipeDirection.LEFT | SwipeDirection.RIGHT
) => void;A function that is called when Swipeable starts animating on close.
Receives swipe direction as an argument.
onSwipeableOpenStartDrag
onSwipeableOpenStartDrag?: (
direction: SwipeDirection.LEFT | SwipeDirection.RIGHT
) => void;A function that is called when a user starts to drag the Swipeable to open.
Receives swipe direction as an argument.
onSwipeableCloseStartDrag
onSwipeableCloseStartDrag?: (
direction: SwipeDirection.LEFT | SwipeDirection.RIGHT
) => void;A function that is called when a user starts to drag the Swipeable to close.
Receives swipe direction as an argument.
renderLeftActions
renderLeftActions?: (
progress: SharedValue<number>,
translation: SharedValue<number>,
swipeableMethods: SwipeableMethods
) => React.ReactNode;A function that returns a component which will be rendered beneath the Swipeable after it is swiped to the right. This function accepts the following parameters:
- progress - a SharedValue that represents the swiping progress relative to the width of the returned element.
- It equals 0 when the Swipeable is fully closed and 1 when it is fully opened.
- As the element overshoots its open position, the value approaches Infinity.
- translation - a horizontal offset of the Swipeable relative to its closed position.
- swipeableMethods - provides an object exposing methods detailed in the methods section.
This function must return a ReactNode. To accommodate rtl (right-to-left) flexbox layouts, use the flexDirection style property.
renderRightActions
renderRightActions?: (
progress: SharedValue<number>,
translation: SharedValue<number>,
swipeableMethods: SwipeableMethods
) => React.ReactNode;A function that returns a component which will be rendered beneath the Swipeable after it is swiped to the left. This function accepts the following parameters:
- progress - a SharedValue that represents the swiping progress relative to the width of the returned element.
- It equals 0 when the Swipeable is fully closed and 1 when it is fully opened.
- As the element overshoots its open position, the value approaches Infinity.
- translation - a horizontal offset of the Swipeable relative to its closed position.
- swipeableMethods - provides an object exposing methods detailed in the methods section.
This function must return a ReactNode. To accommodate rtl (right-to-left) flexbox layouts, use the flexDirection style property.
containerStyle
containerStyle?: StyleProp<ViewStyle>;Style object for the container (Animated.View).
childrenContainerStyle
childrenContainerStyle?: StyleProp<ViewStyle>;Style object for the children container (Animated.View).
simultaneousWith
simultaneousWith?: AnyGesture | AnyGesture[];Gestures to be recognized simultaneously with the Swipeable (see simultaneousWith).
requireToFail
requireToFail?: AnyGesture | AnyGesture[];Gestures that Swipeable has to wait for before activating (see requireToFail).
block
block?: AnyGesture | AnyGesture[];Gestures that Swipeable will prevent from activating (see block).
<Badges platforms={['ios', 'web']}>
enableTrackpadTwoFingerGesture [I][W]
</Badges>
enableTrackpadTwoFingerGesture?: boolean | SharedValue<boolean>;Enables two-finger gestures on supported devices, for example iPads with trackpads. If not enabled the gesture will require click + drag, with enableTrackpadTwoFingerGesture swiping with two fingers will also trigger the gesture.
enabled
enabled?: boolean | SharedValue<boolean>;Indicates whether ReanimatedSwipeable should be analyzing the stream of touch events or not. Defaults to true.
testID
testID?: string;Sets a testID property, allowing for querying ReanimatedSwipeable for it in tests.
hitSlop
<CollapsibleCode
label="Show composed types definitions"
expandedLabel="Hide composed types definitions"
lineBounds={[0, 1]}
collapsed={false}
src={
hitSlop?: HitSlop | SharedValue<HitSlop>;
type HitSlop =
| number
| null
| undefined
| Partial<
Record<
'left' | 'right' | 'top' | 'bottom' | 'vertical' | 'horizontal',
number
>
>
| Record<'width' | 'left', number>
| Record<'width' | 'right', number>
| Record<'height' | 'top', number>
| Record<'height' | 'bottom', number>;
}/>
This parameter enables control over what part of the connected view area can be used to begin recognizing the gesture.
When a negative number is provided, the bounds of the view will reduce the area by the given number of points in each of the sides evenly.
See hitSlop section in Pan gesture for more details.
Swipeable ref methods
Using a reference to Swipeable allows you to manually trigger the opening and closing of the component, as well as reset its swiping state.
const swipeableRef = useRef<SwipeableMethods>(null);close
close: () => void;A method that closes component.
openLeft
openLeft: () => void;A method that opens component on left side.
openRight
openRight: () => void;A method that opens component on right side.
reset
reset: () => void;A method that resets the swiping states of this Swipeable component. Unlike close, this method does not trigger any animation.
Example
Example of a Swipeable component can be found in Gesture Handler repository.
<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[10, 41]}
src={
import React from 'react';
import { Text, StyleSheet } from 'react-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import ReanimatedSwipeable from 'react-native-gesture-handler/ReanimatedSwipeable';
import Reanimated, {
SharedValue,
useAnimatedStyle,
} from 'react-native-reanimated';
function RightAction(prog: SharedValue<number>, drag: SharedValue<number>) {
const styleAnimation = useAnimatedStyle(() => {
console.log('showRightProgress:', prog.value);
console.log('appliedTranslation:', drag.value);
return {
transform: [{ translateX: drag.value + 50 }],
};
});
return (
<Reanimated.View style={styleAnimation}>
<Text style={styles.rightAction}>Text</Text>
</Reanimated.View>
);
}
export default function Example() {
return (
<GestureHandlerRootView>
<ReanimatedSwipeable
containerStyle={styles.swipeable}
friction={2}
enableTrackpadTwoFingerGesture
rightThreshold={40}
renderRightActions={RightAction}>
<Text>Swipe me!</Text>
</ReanimatedSwipeable>
</GestureHandlerRootView>
);
}
const styles = StyleSheet.create({
rightAction: { width: 50, height: 50, backgroundColor: 'purple' },
separator: {
width: '100%',
borderTopWidth: 1,
},
swipeable: {
height: 50,
backgroundColor: 'papayawhip',
alignItems: 'center',
},
});
}/>
---
Packages/Docs Gesture Handler/Docs/Components/Touchable (packages/docs-gesture-handler/docs/components/touchable.mdx)
---
id: touchable
title: Touchable
sidebar_label: Touchable
sidebar_position: 1
---
:::note
This section refers to new Touchable component, meant to replace both buttons and touchables. If you are looking for documentation for the deprecated touchable components, check out the Legacy Touchables section.
:::
Touchable is a versatile new component introduced in Gesture Handler 3 to supersede previous button implementations. Designed for maximum flexibility, it provides a highly customizable interface for native touch handling while ensuring consistent behavior across platforms.
Touchable provides a simple interface for the common animations like opacity, underlay, and scale, implemented entirely on the platform. On Android, it also exposes the native ripple effect on press (turned off by default).
If the provided animations are not sufficient, it's possible to use Touchable to create fully custom interactions using either Reanimated or Animated API.
Replacing old buttons
If you were using RectButton or BorderlessButton in your app, you should replace them with Touchable. Check out the full code in the example section below.
RectButton
To replace RectButton with Touchable, add underlayColor="black" to your Touchable. This will tint the underlay when the button is pressed. The legacy RectButton switches states instantly with no fade, so also set animationDuration={0} to match its behavior.
<Touchable
...
underlayColor="black"
animationDuration={0}/>:::note Android ripple
The legacy RectButton shows the native theme ripple on Android by default, while Touchable disables the ripple unless androidRipple is set. To keep the legacy Android feedback, set androidRipple={{}} on Android instead of underlayColor/animationDuration — combining the two would render a ripple and an underlay animation simultaneously. Use Platform.select to apply the right props per platform:
import { Platform } from 'react-native';<Touchable
{...Platform.select({
android: { androidRipple: {} },
default: { underlayColor: 'black', animationDuration: 0 },
})}
/>
:::
BorderlessButton
Replacing BorderlessButton with Touchable is as easy as replacing RectButton. Add activeOpacity={0.3} to dim the whole component on press, and animationDuration={0} to keep the transition instant — matching the legacy BorderlessButton.
<Touchable
...
activeOpacity={0.3}
animationDuration={0}/>:::note Android ripple
Same caveat as RectButton: the legacy BorderlessButton shows the native theme ripple on Android. To preserve that, set androidRipple={{ borderless: true }} on Android instead of activeOpacity/animationDuration:
<Touchable
{...Platform.select({
android: { androidRipple: { borderless: true } },
default: { activeOpacity: 0.3, animationDuration: 0 },
})}
/>:::
Migrating from legacy Touchable variants
If you were using the specialized touchable components (TouchableOpacity, TouchableHighlight, TouchableWithoutFeedback, or TouchableNativeFeedback), you can replicate their behavior with the unified Touchable component.
TouchableOpacity
To replace TouchableOpacity, add activeOpacity={0.2}. The legacy TouchableOpacity dims instantly on press-in and fades back over 150ms on release, so set animationDuration={{ in: 0, out: 150 }} to mirror that timing.
<Touchable
...
activeOpacity={0.2}
animationDuration={{ in: 0, out: 150 }}/>TouchableHighlight
A perfect 1:1 replacement isn't possible — in TouchableHighlight the container's own background becomes the underlay (solid underlayColor) and activeOpacity dims just the children on top, so the underlay shows through the dimmed children. Touchable instead has a separate underlay layer between the background and children, and its activeOpacity dims the whole component (background + underlay + children together). The closest approximation: carry underlayColor and activeOpacity over unchanged and add activeUnderlayOpacity={1} so the underlay layer is rendered solid.
<Touchable
...
underlayColor="#DDDDDD"
activeUnderlayOpacity={1}
activeOpacity={0.6}/>TouchableWithoutFeedback
To replace TouchableWithoutFeedback, use a plain Touchable.
<Touchable ... />TouchableNativeFeedback
To replicate TouchableNativeFeedback behavior, use the androidRipple prop. The legacy component defaults to useForeground: true, so set foreground: true to match — drop it only if the original code passed useForeground={false}. Add color, radius, or borderless if the original code customized the background prop.
<Touchable
...
androidRipple={{ foreground: true }}/>Example
In this example we will demonstrate how to recreate RectButton and BorderlessButton effects using the Touchable component.
<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[7, 40]}
src={
import React from 'react';
import { StyleSheet, Text } from 'react-native';
import {
GestureHandlerRootView,
Touchable,
} from 'react-native-gesture-handler';
export default function TouchableExample() {
return (
<GestureHandlerRootView style={styles.container}>
<Touchable
onPress={() => {
console.log('BaseButton built with Touchable');
}}
style={[styles.button, { backgroundColor: '#7d63d9' }]}>
<Text style={styles.buttonText}>BaseButton</Text>
</Touchable>
<Touchable
onPress={() => {
console.log('RectButton built with Touchable');
}}
style={[styles.button, { backgroundColor: '#4f9a84' }]}
underlayColor="black"
animationDuration={0}>
<Text style={styles.buttonText}>RectButton</Text>
</Touchable>
<Touchable
onPress={() => {
console.log('BorderlessButton built with Touchable');
}}
style={[styles.button, { backgroundColor: '#5f97c8' }]}
activeOpacity={0.3}
animationDuration={0}>
<Text style={styles.buttonText}>BorderlessButton</Text>
</Touchable>
</GestureHandlerRootView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
gap: 20,
},
button: {
width: 200,
height: 70,
borderRadius: 15,
alignItems: 'center',
justifyContent: 'center',
},
buttonText: {
color: 'white',
fontSize: 14,
fontWeight: '600',
},
});
}/>
Properties
activeOpacity
activeOpacity?: number;Defines the opacity of the whole component when the button is active.
defaultOpacity
defaultOpacity?: number;Defines the opacity of the whole component when the button is active. By default set to 1.
activeScale
activeScale?: number;Defines the scale of the whole component when the button is active.
defaultScale
defaultScale?: number;Defines the scale of the whole component when the button is inactive. By default set to 1.
activeUnderlayOpacity
activeUnderlayOpacity?: number;Defines the opacity of the underlay when the button is active. By default set to 0.105.
defaultUnderlayOpacity
defaultUnderlayOpacity?: number;Defines the initial opacity of underlay when the button is inactive. By default set to 0.
hoverOpacity
hoverOpacity?: number;Defines the opacity of the whole component when the button is hovered. By default falls back to defaultOpacity.
hoverScale
hoverScale?: number;Defines the scale of the whole component when the button is hovered. By default falls back to defaultScale.
hoverUnderlayOpacity
hoverUnderlayOpacity?: number;Defines the opacity of the underlay when the button is hovered. By default falls back to defaultUnderlayOpacity.
animationDuration
<CollapsibleCode
label="Show composed types definitions"
expandedLabel="Hide composed types definitions"
lineBounds={[0, 1]}
src={
animationDuration?: AnimationDuration;
type InOutDuration = { in: number; out: number };
type AnimationDuration =
| number
| (InOutDuration & {
tap?: Partial<InOutDuration>;
hover?: Partial<InOutDuration>;
longPress?: { out: number };
})
| {
tap: InOutDuration;
hover: InOutDuration;
longPress?: { out: number };
};
}/>
Press and hover animation timing, in milliseconds. Defaults to 50ms for the in phase and 100ms for the out phase.
Each animation has two phases — in (running while the pointer engages the component) and out (running after the pointer releases) — across two categories:
- tap — applies to presses.
- hover — pointer hover.
longPress is an optional override for the press-out timing once the press has been held past delayLongPress. It only has an out field (the press-in is always the tap in duration). If omitted, the long-press release uses the resolved tap out duration.
Three input shapes are accepted:
1. A single number applied to every phase of every category:
<Touchable animationDuration={200} />2. A baseline in / out with optional per-category overrides — categories that aren't specified inherit the baseline, and within a category any field left out also inherits from the baseline:
<Touchable
animationDuration={{
in: 50,
out: 200,
hover: { in: 400 },
}}
/>3. Both categories specified explicitly (no baseline) — every field must be supplied:
<Touchable
animationDuration={{
tap: { in: 0, out: 200 },
hover: { in: 300, out: 300 },
}}
/>To give a long press its own release timing, add longPress.out. The switch fires when the press is held past delayLongPress:
<Touchable
delayLongPress={400}
onLongPress={() => {}}
animationDuration={{
in: 50,
out: 100,
longPress: { out: 500 },
}}
/>underlayColor
underlayColor?: string;Background color of the underlay. This only takes effect when activeUnderlayOpacity or defaultUnderlayOpacity is set. By default set to transparent.
exclusive
exclusive?: boolean;Defines whether pressing this button prevents other buttons exported by Gesture Handler from being pressed. By default set to true.
<Badges platforms={['android']}>
touchSoundDisabled [A]
</Badges>
touchSoundDisabled?: boolean;If set to true, the system will not play a sound when the button is pressed.
onPressIn
<CollapsibleCode
label="Show composed types definitions"
expandedLabel="Hide composed types definitions"
lineBounds={[0, 1]}
src={
onPressIn?: (e: ButtonEvent) => void;
type ButtonEvent = {
pointerInside: boolean;
x: number;
y: number;
absoluteX: number;
absoluteY: number;
numberOfPointers: number;
pointerType: PointerType;
}
enum PointerType {
TOUCH,
STYLUS,
MOUSE,
KEY,
OTHER,
}
}/>
Triggered when the button gets pressed (analogous to onPressIn in Pressable from RN core).
onPressOut
<CollapsibleCode
label="Show composed types definitions"
expandedLabel="Hide composed types definitions"
lineBounds={[0, 1]}
src={
onPressOut?: (e: ButtonEvent) => void;
type ButtonEvent = {
pointerInside: boolean;
x: number;
y: number;
absoluteX: number;
absoluteY: number;
numberOfPointers: number;
pointerType: PointerType;
}
enum PointerType {
TOUCH,
STYLUS,
MOUSE,
KEY,
OTHER,
}
}/>
Triggered when the button gets released or the pointer moves outside of the button area (analogous to onPressOut in Pressable from RN core).
onPress
<CollapsibleCode
label="Show composed types definitions"
expandedLabel="Hide composed types definitions"
lineBounds={[0, 1]}
src={
onPress?: (e: ButtonEvent) => void;
type ButtonEvent = {
pointerInside: boolean;
x: number;
y: number;
absoluteX: number;
absoluteY: number;
numberOfPointers: number;
pointerType: PointerType;
}
enum PointerType {
TOUCH,
STYLUS,
MOUSE,
KEY,
OTHER,
}
}/>
Triggered when the button gets pressed (analogous to onPress in Pressable from RN core).
onLongPress
<CollapsibleCode
label="Show composed types definitions"
expandedLabel="Hide composed types definitions"
lineBounds={[0, 1]}
src={
onLongPress?: (e: ButtonEvent) => void;
type ButtonEvent = {
pointerInside: boolean;
x: number;
y: number;
absoluteX: number;
absoluteY: number;
numberOfPointers: number;
pointerType: PointerType;
}
enum PointerType {
TOUCH,
STYLUS,
MOUSE,
KEY,
OTHER,
}
}/>
Triggered when the button gets pressed for at least delayLongPress milliseconds.
onHoverIn
<CollapsibleCode
label="Show composed types definitions"
expandedLabel="Hide composed types definitions"
lineBounds={[0, 1]}
src={
onHoverIn?: (e: ButtonEvent) => void;
type ButtonEvent = {
pointerInside: boolean;
x: number;
y: number;
absoluteX: number;
absoluteY: number;
numberOfPointers: number;
pointerType: PointerType;
}
enum PointerType {
TOUCH,
STYLUS,
MOUSE,
KEY,
OTHER,
}
}/>
Triggered when a non-touch pointer - a mouse, a trackpad cursor, or a hovering stylus - moves over the button (analogous to onHoverIn in Pressable from RN core). Touch pointers never report hover.
onHoverOut
<CollapsibleCode
label="Show composed types definitions"
expandedLabel="Hide composed types definitions"
lineBounds={[0, 1]}
src={
onHoverOut?: (e: ButtonEvent) => void;
type ButtonEvent = {
pointerInside: boolean;
x: number;
y: number;
absoluteX: number;
absoluteY: number;
numberOfPointers: number;
pointerType: PointerType;
}
enum PointerType {
TOUCH,
STYLUS,
MOUSE,
KEY,
OTHER,
}
}/>
Triggered when the hovering pointer leaves the button (analogous to onHoverOut in Pressable from RN core).
delayLongPress
delayLongPress?: number;Defines the delay, in milliseconds, after which the onLongPress callback gets called. By default set to 600.
<Badges platforms={['android']}>
androidRipple [A]
</Badges>
<CollapsibleCode
label="Show composed types definitions"
expandedLabel="Hide composed types definitions"
lineBounds={[0, 1]}
src={
androidRipple?: PressableAndroidRippleConfig;
type PressableAndroidRippleConfig = {
color?: (string | OpaqueColorValue);
borderless?: boolean;
radius?: number;
foreground?: boolean;
}
}/>
Configuration for the ripple effect on Android. If not provided, the ripple effect will be disabled. If {} is provided, the ripple effect will be enabled with default configuration.
<Badges platforms={['android', 'ios']}>
cancelOnLeave [A][I]
</Badges>
cancelOnLeave?: boolean;Whether the touch should be canceled when the pointer leaves the component. By default set to true. On web this prop doesn't have any effect and behaves as if true was set.
<Badges platforms={['android']}>
needsOffscreenAlphaCompositing [A]
</Badges>
needsOffscreenAlphaCompositing?: boolean;Whether the view should render with an offscreen alpha-compositing buffer when its opacity is less than 1. Defaults to false.
---
Packages/Docs Gesture Handler/Docs/Components/Touchables (packages/docs-gesture-handler/docs/components/touchables.md)
---
id: legacy-touchables
title: Legacy Touchables
sidebar_label: Legacy Touchables
sidebar_position: 7
---
:::warning
Touchables will be removed in the future version of Gesture Handler. Use Touchable instead.
:::
Gesture Handler library provides an implementation of RN's touchable components that are based on native buttons and do not rely on the JS responder system utilized by RN. Our touchable implementation follows the same API and aims to be a drop-in replacement for touchables available in React Native.
React Native's touchables API can be found here:
- Touchable Native Feedback
- Touchable Highlight
- Touchable Opacity
- Touchable Without Feedback
All major touchable properties (except for pressRetentionOffset) have been adopted and should behave in a similar way as with RN's touchables.
The motivation for using RNGH touchables as a replacement for these imported from React Native is to follow built-in native behavior more closely by utilizing the platform native touch system instead of relying on the JS responder system.
These touchables and their feedback behavior are deeply integrated with native
gesture ecosystem and could be connected with other native components (e.g. ScrollView) and Gesture Handlers easily and in a more predictable way, which
follows native apps' behavior.
Our intention was to make the switch for these touchables as simple as possible. In order to use RNGH's touchables, the only thing you need to do is to change the library from which you import touchable components.
:::info
Gesture Handler's TouchableOpacity uses native driver for animations by default. If this causes problems for you, you can set useNativeAnimations prop to false.
:::
Example:
import {
TouchableNativeFeedback,
TouchableHighlight,
TouchableOpacity,
TouchableWithoutFeedback,
} from 'react-native';has to be replaced with:
import {
TouchableNativeFeedback,
TouchableHighlight,
TouchableOpacity,
TouchableWithoutFeedback,
} from 'react-native-gesture-handler';For a comparison of both touchable implementations see our touchables example
---
Packages/Docs Gesture Handler/Docs/Components/Wrapped Components (packages/docs-gesture-handler/docs/components/wrapped-components.mdx)
---
id: wrapped-components
title: Wrapped Components
sidebar_label: Wrapped Components
sidebar_position: 5
---
Some components come with a Native gesture pre-applied. This allows them to participate in the gesture recognition process. Have a look at the example below.
import { useState } from 'react';
import { Switch } from 'react-native';
import {
GestureDetector,
GestureHandlerRootView,
useTapGesture,
Switch as RNGHSwitch,
} from 'react-native-gesture-handler';export default function App() {
const [isEnabled, setIsEnabled] = useState(false);
const tap1 = useTapGesture({
onDeactivate: () => {
console.log('Tapped!');
},
});
const tap2 = useTapGesture({
onDeactivate: () => {
console.log('Tapped!');
},
});
return (
<GestureHandlerRootView style={{ flex: 1, paddingTop: 100 }}>
<GestureDetector gesture={tap1}>
<Switch value={isEnabled} onValueChange={setIsEnabled} />
</GestureDetector>
<GestureDetector gesture={tap2}>
<RNGHSwitch value={isEnabled} onValueChange={setIsEnabled} />
</GestureDetector>
</GestureHandlerRootView>
);
}
On Android, in this scenario, the Switch from React Native cannot be toggled on because the tap1 gesture intercepts it. However, using RNGHSwitch makes it capable of participating in the gesture recognition process. This setup allows the switch to be toggled on while still enabling tap2 to recognize taps on it.
List of wrapped components
Components listed below come with a pre-applied Native gesture.
- FlatList
- ScrollView
- RefreshControl
- TextInput
- Switch
onGestureUpdate_CAN_CAUSE_INFINITE_RERENDER
:::danger
This callback may lead to infinite re-renders if not used carefully.
export default function App() {
const [gesture, setGesture] = useState<NativeGesture | null>(null); const updateGesture = (g: NativeGesture) => {
// ❌ Wrong usage: calling setState here triggers a re-render,
// which re-creates the ScrollView's Native gesture, which fires
// this callback again → infinite re-render loop.
setGesture(g);
};
return (
<GestureHandlerRootView style={{ flex: 1 }}>
<ScrollView onGestureUpdate_CAN_CAUSE_INFINITE_RERENDER={updateGesture} />
</GestureHandlerRootView>
);
}
:::
Those components also receive an additional prop named onGestureUpdate_CAN_CAUSE_INFINITE_RERENDER.
onGestureUpdate_CAN_CAUSE_INFINITE_RERENDER?: (gesture: NativeGesture) => void;This callback is invoked when the wrapped component's underlying Native gesture instance or configuration changes, providing access to the underlying gesture. This can be helpful when setting up relations with other gestures. You can check example usage in our ScrollView component.
---
Packages/Docs Gesture Handler/Docs/Composition/ Category .Json (packages/docs-gesture-handler/docs/composition/_category_.json)
{
"label": "Gesture composition & interactions",
"position": 5,
"link": {
"type": "generated-index"
}
}
---
Packages/Docs Gesture Handler/Docs/Composition/Block (packages/docs-gesture-handler/docs/composition/block.mdx)
---
id: block
title: block
sidebar_label: block
sidebar_position: 5
---
import Block from '!!raw-loader!.//\_examples/props/Block';
block works similarly to requireToFail but the direction of the relation is reversed - instead of being a one-to-many relation, it's many-to-one. It's especially useful for making lists where the ScrollView component needs to wait for every gesture underneath it. All that's required to do is to pass a ref, for example:
<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[22, 86]}
src={Block}/>
---
Packages/Docs Gesture Handler/Docs/Composition/Overview (packages/docs-gesture-handler/docs/composition/overview.mdx)
---
id: overview
title: Gesture composition & interactions
sidebar_label: Overview
sidebar_position: 0
---
Gesture Handler simplifies gesture interactions through dedicated composition hooks and relation properties. To choose the right approach, simply ask: are all the gestures attached to the same component?
- If yes — use composition hooks. These allow you to bundle multiple gestures — including previously composed ones — into a single object for a GestureDetector.
- If no — use relation properties to manually define how gestures interact. Since these properties also support composed gestures, you can mix both methods for more complex layouts.
Composition hooks
- useCompetingGestures — only one of the provided gestures can become active at the same time. The first gesture to activate cancels the rest.
- useSimultaneousGestures — all of the provided gestures can activate at the same time. Activation of one does not cancel the others.
- useExclusiveGestures — only one of the provided gestures can become active, with priority determined by the order of the arguments. A gesture activates only after all higher-priority gestures have failed.
Cross-component interactions
- simultaneousWith — allows gestures attached to different components to be recognized simultaneously.
- requireToFail — delays activation of a gesture until all gestures passed as arguments fail (or don't begin at all).
- block — works like requireToFail with the direction of the relation reversed. Especially useful for lists, where a ScrollView needs to wait for every gesture underneath it.
---
Packages/Docs Gesture Handler/Docs/Composition/Require To Fail (packages/docs-gesture-handler/docs/composition/require-to-fail.mdx)
---
id: require-to-fail
title: requireToFail
sidebar_label: requireToFail
sidebar_position: 4
---
import RequireToFail from '!!raw-loader!.//\_examples/props/RequireToFail';
requireToFail allows delaying activation of the handler until all handlers passed as arguments to this method fail (or don't begin at all).
For example, you may want to have two nested components, both of them can be tapped by the user to trigger different actions: outer view requires one tap, but the inner one requires 2 taps. If you don't want the first tap on the inner view to activate the outer handler, you must make the outer gesture wait until the inner one fails:
<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[8, 38]}
src={RequireToFail}/>
---
Packages/Docs Gesture Handler/Docs/Composition/SimultaneousWith (packages/docs-gesture-handler/docs/composition/simultaneousWith.mdx)
---
id: simultaneous-with
title: simultaneousWith
sidebar_label: simultaneousWith
sidebar_position: 6
---
import SimultaneousWith from '!!raw-loader!.//\_examples/props/SimultaneousWith';
simultaneousWith allows gestures across different components to be recognized simultaneously. For example, you may want to have two nested views, both with tap gesture attached. Both of them require one tap, but tapping the inner one should also activate the gesture attached to the outer view:
<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[8, 37]}
src={SimultaneousWith}/>
---
Packages/Docs Gesture Handler/Docs/Composition/Use Competing Gestures (packages/docs-gesture-handler/docs/composition/use-competing-gestures.mdx)
---
id: use-competing-gestures
title: useCompetingGestures
sidebar_label: useCompetingGestures
sidebar_position: 1
---
import Competing from '!!raw-loader!.//\_examples/hooks/Competing';
Only one of the provided gestures can become active at the same time. The first gesture to become active will cancel the rest of the gestures. It accepts a variable number of arguments.
For example, let's say that you have a component that you want to make draggable but you also want to show additional options on long press. Presumably you would not want the component to move after the long press activates. You can accomplish this using useCompetingGestures:
<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[9, 32]}
src={Competing}/>
---
Packages/Docs Gesture Handler/Docs/Composition/Use Exclusive Gestures (packages/docs-gesture-handler/docs/composition/use-exclusive-gestures.mdx)
---
id: use-exclusive-gestures
title: useExclusiveGestures
sidebar_label: useExclusiveGestures
sidebar_position: 3
---
import Exclusive from '!!raw-loader!.//\_examples/hooks/Exclusive';
Only one of the provided gestures can become active. Priority is determined by the order of the arguments, where the first gesture has the highest priority, and the last has the lowest. A gesture can activate only after all higher-priority gestures before it have failed.
For example, if you want to make a component that responds to single tap as well as to a double tap, you can accomplish that using useExclusiveGestures:
<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[8, 35]}
src={Exclusive}/>
---
Packages/Docs Gesture Handler/Docs/Composition/Use Simultaneous Gestures (packages/docs-gesture-handler/docs/composition/use-simultaneous-gestures.mdx)
---
id: use-simultaneous-gestures
title: useSimultaneousGestures
sidebar_label: useSimultaneousGestures
sidebar_position: 2
---
import Simultaneous from '!!raw-loader!.//\_examples/hooks/Simultaneous';
All of the provided gestures can activate at the same time. Activation of one will not cancel the other.
For example, if you want to make a gallery app, you might want the user to be able to zoom, rotate and pan around photos. You can do it with useSimultaneousGestures:
Note: theuseSharedValueanduseAnimatedStyleare part ofreact-native-reanimated.
<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[14, 81]}
src={Simultaneous}/>
---
Packages/Docs Gesture Handler/Docs/Core Components/ Category .Json (packages/docs-gesture-handler/docs/core-components/_category_.json)
{
"label": "Core Components",
"position": 3,
"link": {
"type": "generated-index"
}
}
---
Packages/Docs Gesture Handler/Docs/Core Components/Gesture Detector (packages/docs-gesture-handler/docs/core-components/gesture-detector.mdx)
---
id: gesture-detectors
title: Gesture Detectors
sidebar_label: Gesture detectors
sidebar_position: 2
---
Gesture Detector
The GestureDetector is a key component of react-native-gesture-handler. It supports gestures created either using the hook-based API or the builder pattern. Additionally, it allows for the recognition of multiple gestures through gesture composition. GestureDetector interacts closely with Reanimated. For more details, refer to the Integration with Reanimated section.
When using hook API, you can also integrate it directly with the Animated API. More on that can be found in Integration with Animated section.
:::danger
#### Nesting Gesture Detectors
Because GestureDetector supports both the hook API and the builder pattern, it is important to avoid nesting detectors that use different APIs, as this can result in undefined behavior.
#### Reusing Gestures
Using the same instance of a gesture across multiple Gesture Detectors may result in undefined behavior.
:::
import { GestureDetector, useTapGesture } from 'react-native-gesture-handler';export default function App() {
const tap = useTapGesture({
onDeactivate: () => {
console.log('Tap!');
},
});
return (
<GestureHandlerRootView>
// highlight-next-line
<GestureDetector gesture={tap}>
<Animated.View />
// highlight-next-line
</GestureDetector>
</GestureHandlerRootView>
);
}
Virtual Detectors
Since RNGH3, GestureDetector is a standalone host component. Depending on the view hierarchy, this can occasionally disrupt interactions between specific components. To resolve this, use InterceptingGestureDetector in combination with VirtualGestureDetector.
InterceptingGestureDetector
InterceptingGestureDetector functions similarly to a GestureDetector, but it can also act as a proxy for VirtualGestureDetector within its component subtree. Because it can be used solely to establish the context for virtual detectors, the gesture property is optional.
VirtualGestureDetector
VirtualGestureDetector is similar to the GestureDetector from RNGH2. Because it is not a host component, it does not interfere with the host view hierarchy. This allows you to attach gestures without disrupting functionality that depends on it.
Known use cases
Here are some of the most common use cases for virtual gesture detectors.
#### SVG
You can combine VirtualGestureDetector with react-native-svg to add gesture handling to individual SVG elements.
<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[10, 39]}
src={
import React from 'react';
import { StyleSheet } from 'react-native';
import {
GestureHandlerRootView,
InterceptingGestureDetector,
useTapGesture,
VirtualGestureDetector,
} from 'react-native-gesture-handler';
import Svg, { Circle } from 'react-native-svg';
export default function App() {
const outerTap = useTapGesture({
onDeactivate: () => {
console.log('Box tapped!');
},
});
const innerTap = useTapGesture({
onDeactivate: () => {
console.log('Circle tapped!');
},
});
return (
<GestureHandlerRootView style={styles.container}>
<InterceptingGestureDetector gesture={outerTap}>
<Svg height="250" width="250" style={{ backgroundColor: '#b58df1' }}>
<VirtualGestureDetector gesture={innerTap}>
<Circle
cx="125"
cy="125"
r="125"
fill="#001A72"
onPress={() => {}}
/>
</VirtualGestureDetector>
</Svg>
</InterceptingGestureDetector>
</GestureHandlerRootView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
});
}/>
#### Text
You can use VirtualGestureDetector to add gesture handling to specific parts of a Text component.
<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[8, 37]}
src={
import * as React from 'react';
import { StyleSheet, Text } from 'react-native';
import {
GestureHandlerRootView,
InterceptingGestureDetector,
VirtualGestureDetector,
useTapGesture,
} from 'react-native-gesture-handler';
export default function App() {
const outerTap = useTapGesture({
onDeactivate: () => {
console.log('Tapped on text!');
},
});
const nestedTap = useTapGesture({
onDeactivate: () => {
console.log('Tapped on nested part!');
},
});
return (
<GestureHandlerRootView style={styles.container}>
<InterceptingGestureDetector gesture={outerTap}>
<Text style={{ fontSize: 18, textAlign: 'center' }}>
Nested text
<VirtualGestureDetector gesture={nestedTap}>
<Text style={{ fontSize: 24, color: '#001A72' }}>
try tapping on this part.
</Text>
</VirtualGestureDetector>
This part is not special :c
</Text>
</InterceptingGestureDetector>
</GestureHandlerRootView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'space-around',
},
});
}/>
Properties
gesture
gesture: SingleGesture | ComposedGesture;A gesture object containing the configuration and callbacks. Can be any of the base gestures or any ComposedGesture.
<Badges platforms={['web']}>
userSelect [W]
</Badges>
userSelect: 'none' | 'auto' | 'text';This parameter allows specifying which userSelect property should be applied to the underlying view. Default value is set to "none".
<Badges platforms={['web']}>
touchAction [W]
</Badges>
touchAction: TouchAction;This parameter allows specifying which touchAction property should be applied to the underlying view. Supports all CSS touch-action values. Default value is set to "none".
<Badges platforms={['web']}>
enableContextMenu [W]
</Badges>
enableContextMenu: boolean;Specifies whether the context menu should be enabled after clicking on the underlying view with the right mouse button. Default value is set to false.
---
Packages/Docs Gesture Handler/Docs/Core Components/Root View (packages/docs-gesture-handler/docs/core-components/root-view.mdx)
---
id: root-view
title: GestureHandlerRootView
sidebar_label: GestureHandlerRootView
sidebar_position: 1
---
GestureHandlerRootView is a key component that enables Gesture Handler to intercept touch events, allowing for the implementation of gestures in your app. It should wrap your app's main component, and any component that relies on Gesture Handler's gestures has to be a descendant of this view. For more detailed information, you can check out the under-the-hood section.
import { GestureHandlerRootView } from 'react-native-gesture-handler';export default function App() {
return (
<GestureHandlerRootView>
<ActualApp />
</GestureHandlerRootView>
);
}
Keep GestureHandlerRootView as close to the actual root of the app as possible. It's the entry point for all gestures and all gesture relations. The gestures won't be recognized outside of the root view, and relations only work between gestures mounted under the same root view.
:::note
When integrating with navigation libraries, wrapping the navigator component with GestureHandlerRootView is generally sufficient. However, if you encounter issues with gestures not functioning properly, you might need to wrap each individual screen component with GestureHandlerRootView as well.
:::
:::tip
If you're using Gesture Handler in your component library, you may want to wrap your library's code in the GestureHandlerRootView component. This will avoid extra configuration for the user.
:::
Styling
GestureHandlerRootView can be thought of as a regular View component, therefore it accepts all the same props, including style.
If you don't provide anything to the style prop, it will default to { flex: 1 }. If you want to customize the styling of the root view, don't forget to also include flex: 1 in the custom style, otherwise your app won't render anything.
Nesting root views
In case of nested root views, Gesture Handler will only use the top-most one and ignore the nested ones. If you're unsure if one of your dependencies already renders GestureHandlerRootView on its own, don't worry and add one at the root anyway.
unstable_forceActive
unstable_forceActive?: boolean;If you're having trouble with gestures not working when inside a component provided by a third-party library, even though you've wrapped the entry point with <GestureHandlerRootView>, you can try adding another <GestureHandlerRootView unstable_forceActive> closer to the place the gestures are defined. This way, you can prevent Android from canceling relevant gestures when one of the native views tries to grab lock for delivering touch events.
---
Packages/Docs Gesture Handler/Docs/Fundamentals/ Category .Json (packages/docs-gesture-handler/docs/fundamentals/_category_.json)
{
"label": "Fundamentals",
"position": 1,
"link": {
"type": "generated-index"
}
}
---
Packages/Docs Gesture Handler/Docs/Fundamentals/Animated Interactions (packages/docs-gesture-handler/docs/fundamentals/animated-interactions.mdx)
---
id: animated-interactions
title: Integration with Animated
sidebar_label: Integration with Animated
sidebar_position: 5
---
Using hook API allows for smooth integration with the Animated API by allowing for passing an Animated.event as the argument to the onUpdate callback. The event mapping of Animated.event depends on the useNativeDriver property.
When using Animated API, remember to set useAnimated property to true.
:::danger Mixing Reanimated and Animated
It is not possible to mix Reanimated and Animated within any of the gesture detectors.
:::
<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[8, 33]}
src={
import * as React from 'react';
import { Animated, StyleSheet, useAnimatedValue } from 'react-native';
import {
GestureHandlerRootView,
GestureDetector,
usePanGesture,
} from 'react-native-gesture-handler';
export default function App() {
const value = useAnimatedValue(0);
const event = Animated.event(
[{ nativeEvent: { handlerData: { translationX: value } } }],
{
useNativeDriver: true,
}
);
const gesture = usePanGesture({
// highlight-next-line
onUpdate: event,
// highlight-next-line
useAnimated: true,
});
return (
<GestureHandlerRootView>
<GestureDetector gesture={gesture}>
<Animated.View
style={[styles.box, { transform: [{ translateX: value }] }]}
/>
</GestureDetector>
</GestureHandlerRootView>
);
}
const styles = StyleSheet.create({
box: {
width: 150,
height: 150,
backgroundColor: '#b58df1',
},
});
}/>
useNativeDriver
When using Animated.event with useNativeDriver set to false, it is required to set disableReanimated to true in the gesture configuration.
Mapping of Animated.event depends on the value of useNativeDriver property. When set to true, event data can be accessed through nativeEvent.handlerData property:
const value = useAnimatedValue(0); const event = Animated.event(
[{ nativeEvent: { handlerData: { / translationX: value, ... / } } }],
{ useNativeDriver: true }
);
In case of useNativeDriver set to false, event data is accessed directly:
const value = useAnimatedValue(0); const event = Animated.event(
[ { / translationX: value, ... / } ],
{ useNativeDriver: false }
);
Usage with VirtualGestureDetector
Using Animated.event with VirtualGestureDetector is possible only when useNativeDriver is set to false.
<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[8, 33]}
src={
import React from 'react';
import { View, StyleSheet, Animated, useAnimatedValue } from 'react-native';
import {
GestureHandlerRootView,
InterceptingGestureDetector,
usePanGesture,
VirtualGestureDetector,
} from 'react-native-gesture-handler';
export default function App() {
const value = useAnimatedValue(0);
const event = Animated.event([{ translationX: value }], {
useNativeDriver: false,
});
const panGesture = usePanGesture({
onUpdate: event,
disableReanimated: true,
});
return (
<GestureHandlerRootView style={styles.container}>
<InterceptingGestureDetector>
<View style={styles.outerBox}>
<VirtualGestureDetector gesture={panGesture}>
<Animated.View
style={[styles.innerBox, { transform: [{ translateX: value }] }]}
/>
</VirtualGestureDetector>
</View>
</InterceptingGestureDetector>
</GestureHandlerRootView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
outerBox: {
backgroundColor: '#b58df1',
width: 150,
height: 150,
},
innerBox: {
width: 100,
height: 100,
backgroundColor: 'blue',
},
});
}/>
---
Packages/Docs Gesture Handler/Docs/Fundamentals/Callbacks Events (packages/docs-gesture-handler/docs/fundamentals/callbacks-events.mdx)
---
id: callbacks-events
title: Gesture callbacks & events
sidebar_label: Gesture callbacks & events
sidebar_position: 3
---
import {
GestureCallbacksExample,
TouchCallbacksExample,
} from '@site/src/examples/CallbacksFlowExamples';
At any given time, each handler instance has an assigned state that can change when new touch events occur or can be forced to change by the touch system under certain circumstances. You can hook into state transitions using specific gesture callbacks.
When Reanimated is installed, all callbacks are automatically workletized. For more details, refer to the Integration with Reanimated section.
Callbacks flow
GestureEvent callbacks
Drag or hold the circle below to see how the callback chain reacts.
<GestureCallbacksExample />
Note that some of these callbacks are complementary:
- if onBegin was called, it is guaranteed that onFinalize will be called later.
- if onActivate was called, it is guaranteed that onDeactivate will be called later.
TouchEvent callbacks
Low-level TouchEvent callbacks are tied to raw pointer activity. Drag out of the card to cancel, or release inside it to see how the callback chain reacts.
<TouchCallbacksExample />
Callbacks
onBegin
onBegin: (event: GestureEvent<HandlerData>) => voidCalled when a handler begins to recognize gestures. If onBegin was called, it is guaranteed that onFinalize will be called later.
onActivate
onActivate: (event: GestureEvent<HandlerData>) => voidCalled when activation criteria for the handler are met. If onActivate was called, it is guaranteed that onDeactivate will be called later.
onUpdate
onUpdate: (event: GestureEvent<HandlerData>) => voidCalled each time a pointer tracked by the gesture changes state, typically due to movement, after the gesture has been activated.
onDeactivate
onDeactivate: (event: GestureEndEvent<HandlerData>) => voidCalled when handler stops recognizing gestures, but only if the handler activated. It is called before onFinalize. The event object contains a canceled property — if the gesture was interrupted, canceled is set to true. Otherwise it is set to false.
onFinalize
onFinalize: (event: GestureEndEvent<HandlerData>) => voidCalled when handler stops recognizing gestures. The event object contains a canceled property — if the handler failed to activate or was interrupted, canceled is set to true. If the handler managed to activate and completed successfully, canceled is set to false and onFinalize will be called right after onDeactivate.
onTouchesDown
onTouchesDown: (event: GestureTouchEvent) => voidCalled when new pointers are placed on the screen. It may carry information about more than one pointer because the events are batched.
onTouchesMove
onTouchesMove: (event: GestureTouchEvent) => voidCalled when pointers are moved on the screen. It may carry information about more than one pointer because the events are batched.
onTouchesUp
onTouchesUp: (event: GestureTouchEvent) => voidCalled when pointers are lifted from the screen. It may carry information about more than one pointer because the events are batched.
onTouchesCancel
onTouchesCancel: (event: GestureTouchEvent) => voidCalled when there will be no more information about this pointer. It may be called because the gesture has ended or was interrupted. It may carry information about more than one pointer because the events are batched.
Events
GestureEvent
<CollapsibleCode
label="Show composed types definitions"
expandedLabel="Hide composed types definitions"
lineBounds={[0, 8]}
src={
export type GestureEvent<HandlerData> = {
handlerTag: number;
numberOfPointers: number;
pointerType: PointerType;
} & HandlerData;
export type GestureEndEvent<HandlerData> = {
canceled: boolean;
} & GestureEvent<HandlerData>;
export enum PointerType {
TOUCH,
STYLUS,
MOUSE,
KEY,
OTHER,
}
}/>
GestureEvent contains properties common to all gestures (handlerTag, numberOfPointers, pointerType) along with gesture-specific data defined in each gesture's documentation.
GestureEndEvent
GestureEndEvent extends GestureEvent with a canceled property. It is used in the onDeactivate and onFinalize callbacks. When canceled is true, the gesture was interrupted or failed to activate. When false, the gesture completed successfully.
TouchEvent
<CollapsibleCode
label="Show composed types definitions"
expandedLabel="Hide composed types definitions"
lineBounds={[0, 8]}
src={
export type GestureTouchEvent = {
handlerTag: number;
numberOfTouches: number;
state: State;
eventType: TouchEventType;
allTouches: TouchData[];
changedTouches: TouchData[];
pointerType: PointerType;
};
export const State = {
UNDETERMINED: 0,
FAILED: 1,
BEGAN: 2,
CANCELLED: 3,
ACTIVE: 4,
END: 5,
} as const;
export const TouchEventType = {
UNDETERMINED: 0,
TOUCHES_DOWN: 1,
TOUCHES_MOVE: 2,
TOUCHES_UP: 3,
TOUCHES_CANCEL: 4,
} as const;
export enum PointerType {
TOUCH,
STYLUS,
MOUSE,
KEY,
OTHER,
}
export type TouchData = {
id: number;
x: number;
y: number;
absoluteX: number;
absoluteY: number;
};
}/>
TouchEvent carries information about raw touch events, like touching the screen or moving the finger.
- eventType - Type of the current event - whether the finger was placed on the screen, moved, lifted or cancelled.
- changedTouches - An array of objects where every object represents a single touch. Contains information only about the touches that were affected by the event i.e. those that were placed down, moved, lifted or cancelled.
- allTouches - An array of objects where every object represents a single touch. Contains information about all active touches.
- numberOfTouches - Number representing the count of currently active touches.
TouchData contains information about a single touch.
- id - A number representing the id of the touch. It may be used to track the touch between events as the id will not change while it is being tracked.
- x - X coordinate of the current position of the touch relative to the view attached to the GestureDetector. Expressed in point units.
- y - Y coordinate of the current position of the touch relative to the view attached to the GestureDetector. Expressed in point units.
- absoluteX - X coordinate of the current position of the touch relative to the window. The value is expressed in point units. It is recommended to use it instead of x in cases when the original view can be transformed as an effect of the gesture.
- absoluteY - Y coordinate of the current position of the touch relative to the window. The value is expressed in point units. It is recommended to use it instead of y in cases when the original view can be transformed as an effect of the gesture.
:::danger
Don't rely on the order of items in the changedTouches/allTouches arrays as it may change during the gesture, instead use the id attribute to track individual touches across events.
:::
---
Packages/Docs Gesture Handler/Docs/Fundamentals/Getting Started (packages/docs-gesture-handler/docs/fundamentals/getting-started.mdx)
---
id: getting-started
title: Getting started
sidebar_position: 1
---
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import { GestureHandlerCompatibility } from '../../components/Compatibility';
Gesture Handler provides a declarative API exposing the native platform's touch and gesture system to React Native. It's designed to be a replacement of React Native's built in touch system called Gesture Responder System. Using native touch handling allows addressing the performance limitations of React Native's Gesture Responder System. It also provides more control over the platform's native components that can handle gestures on their own.
Installation
Requirements
#### Compatibility with React Native versions
react-native-gesture-handler supports the three latest minor releases of react-native.
<GestureHandlerCompatibility spacerAfterIndex={1} />
#### Running gestures on the UI thread
Using Reanimated is the recommended method of handling gesture-driven interactions on the UI thread. In order to use it, you need to install react-native-reanimated along with react-native-worklets. Another approach is to use React Native's Animated API.
For more details on how to implement these, refer to the dedicated sections for Reanimated and Animated.
Setup
Setting up react-native-gesture-handler is pretty straightforward:
#### 1. Start with installing the package from npm:
<Tabs groupId="package-managers">
<TabItem value="npm" label="NPM" default>
npm install react-native-gesture-handler</TabItem>
<TabItem value="yarn" label="YARN">
yarn add react-native-gesture-handler</TabItem>
<TabItem value="expo" label="EXPO">
npx expo install react-native-gesture-handler</TabItem>
</Tabs>
#### 2. Wrap your app with GestureHandlerRootView component
import { GestureHandlerRootView } from 'react-native-gesture-handler';export default function App() {
return (
<GestureHandlerRootView>
<ActualApp />
</GestureHandlerRootView>
);
}
Keep
GestureHandlerRootView as close to the actual root of the app as possible. It's the entry point for all gestures and all gesture relations. The gestures won't be recognized outside of the root view, and relations only work between gestures mounted under the same root view.Check out GestureHandlerRootView section for more details.
#### 3. Platform specific setup
When using an Expo development build, run prebuild to update the native code in the ios and android directories.
npx expo prebuild##### Android
Setting up Gesture Handler on Android doesn't require any more steps. Keep in mind that if you want to use gestures in Modals you need to wrap Modal's content with GestureHandlerRootView:
import { Modal } from 'react-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';export function CustomModal({ children, ...rest }) {
return (
<Modal {...rest}>
<GestureHandlerRootView>{children}</GestureHandlerRootView>
</Modal>
);
}
##### iOS
While developing for iOS, make sure to install pods first before running the app:
cd ios && bundle install && bundle exec pod install && cd ..##### macOS
While developing for macOS, make sure to install pods first before running the app:
cd macos && bundle install && bundle exec pod install && cd ..##### Web
There is no additional configuration required for the web.
##### With wix/react-native-navigation
If you are using a native navigation library like wix/react-native-navigation you need to make sure that every screen is wrapped with GestureHandlerRootView. This can be done for example at the stage when you register your screens. Here's an example:
import { Navigation } from 'react-native-navigation';
import FirstTabScreen from './FirstTabScreen';
import SecondTabScreen from './SecondTabScreen';
import PushedScreen from './PushedScreen';// Register all screens of the app (including internal ones)
export function registerScreens() {
Navigation.registerComponent(
'example.FirstTabScreen',
() => {
return (
<GestureHandlerRootView>
<FirstTabScreen />
</GestureHandlerRootView>
);
},
() => FirstTabScreen
);
Navigation.registerComponent(
'example.SecondTabScreen',
() => {
return (
<GestureHandlerRootView>
<SecondTabScreen />
</GestureHandlerRootView>
);
},
() => SecondTabScreen
);
Navigation.registerComponent(
'example.PushedScreen',
() => {
return (
<GestureHandlerRootView>
<PushedScreen />
</GestureHandlerRootView>
);
},
() => PushedScreen
);
}
You can check out this example project to see this kind of set up in action.
Your first gesture
With the setup done, you're ready to add your first gesture. Create a tap gesture with the useTapGesture hook, attach it to a component with GestureDetector. Don't forget to wrap your app with GestureHandlerRootView, as described in previous section.
<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[1, 21]}
src={
import { StyleSheet, View } from 'react-native';
import {
GestureDetector,
GestureHandlerRootView,
useTapGesture,
} from 'react-native-gesture-handler';
export default function App() {
const tap = useTapGesture({
onActivate: () => {
console.log('Tapped!');
},
});
return (
<GestureHandlerRootView style={styles.container}>
<GestureDetector gesture={tap}>
<View style={styles.box} />
</GestureDetector>
</GestureHandlerRootView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
box: {
width: 80,
height: 80,
backgroundColor: '#b58df1',
borderRadius: 12,
},
});
}/>
That's it! The gesture fires onActivate whenever a tap is recognized. Check out Gesture callbacks & events for the full set of callbacks, or browse all gestures to keep exploring.
---
Packages/Docs Gesture Handler/Docs/Fundamentals/Reanimated Interactions (packages/docs-gesture-handler/docs/fundamentals/reanimated-interactions.mdx)
---
id: reanimated-interactions
title: Integration with Reanimated
sidebar_label: Integration with Reanimated
sidebar_position: 4
---
GestureDetector will decide whether to use Reanimated to process provided gestures based on their configuration. If any of the callbacks is a worklet and Reanimated is not explicitly turned off, tools provided by the Reanimated will be utilized, bringing the ability to handle gestures synchronously on the main thread.
Automatic workletization of gesture callbacks
Worklets' Babel plugin is set up in a way that automatically marks callbacks passed to gestures in the configuration chain as worklets. This means that you don't need to add a 'worklet'; directive at the beginning of the functions. Here is an example that will be automatically workletized:
const gesture = useTapGesture({
onBegin: () => {
console.log(_WORKLET);
},
});And here is one that won't:
const callback = () => {
console.log(_WORKLET);
};const gesture = useTapGesture({
onBegin: callback,
});
It also won't work when wrapped with hooks like useCallback or useMemo, e.g.:
const callback = useCallback(() => {
console.log(_WORKLET);
}, []);const gesture = useTapGesture({
onBegin: callback,
});
In the above cases, you should add a "worklet"; directive at the beginning of the callbacks, like so:
const callback = () => {
// highlight-next-line
'worklet';
console.log(_WORKLET);
};const gesture = useTapGesture({
onBegin: callback,
});
const callback = useCallback(() => {
// highlight-next-line
'worklet';
console.log(_WORKLET);
}, []);const gesture = useTapGesture({
onBegin: callback,
});
Using SharedValue in gesture config
RNGH3 allows passing SharedValue to gestures' configurations. This allows reacting to configuration changes without unnecessary rerenders.
<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[10, 19]}
src={
import * as React from 'react';
import { Animated } from 'react-native';
import {
GestureHandlerRootView,
GestureDetector,
useTapGesture,
} from 'react-native-gesture-handler';
import { useSharedValue } from 'react-native-reanimated';
export default function App() {
// highlight-next-line
const taps = useSharedValue(2);
const gesture = useTapGesture({
// highlight-next-line
numberOfTaps: taps,
onDeactivate: () => {
taps.value += 1;
},
});
return (
<GestureHandlerRootView>
<GestureDetector gesture={gesture}>
<Animated.View
style={{
width: 150,
height: 150,
backgroundColor: 'blue',
}}
/>
</GestureDetector>
</GestureHandlerRootView>
);
}
}/>
Disabling Reanimated
Gestures created with the hook API have Reanimated integration enabled by default, meaning all callbacks are executed on the UI thread. There are two methods available to disable this behavior for a specific gesture.
disableReanimated
When disableReanimated is set to true in the gesture configuration, Reanimated integration will be completely turned off for that gesture throughout its entire lifecycle. This setting eliminates all interaction points with Reanimated, thereby reducing any potential overhead. Default value for this property is false.
This property cannot be changed dynamically during the gesture's lifecycle.
const gesture = usePanGesture({
// highlight-next-line
disableReanimated: true, onUpdate: () => {
console.log('Panning');
},
});
runOnJS
The runOnJS property allows you to dynamically control whether callbacks are executed on the JS thread or the UI thread. When set to true, callbacks will run on the JS thread. Setting it to false will execute them on the UI thread. Default value for this property is false.
This property can be changed dynamically throughout the gesture's lifecycle.
<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[9, 37]}
src={
import React from 'react';
import { View, StyleSheet, Animated } from 'react-native';
import {
GestureDetector,
GestureHandlerRootView,
usePanGesture,
} from 'react-native-gesture-handler';
import { useSharedValue } from 'react-native-reanimated';
export default function App() {
// highlight-next-line
const shouldRunOnJS = useSharedValue(false);
const panGesture = usePanGesture({
onUpdate: () => {
console.log(
globalThis.__RUNTIME_KIND === 2
? 'Running on UI thread'
: 'Running on JS thread'
);
},
onDeactivate: () => {
shouldRunOnJS.value = !shouldRunOnJS.value;
},
// highlight-next-line
runOnJS: shouldRunOnJS,
});
return (
<GestureHandlerRootView style={styles.container}>
<View style={styles.outerBox}>
<GestureDetector gesture={panGesture}>
<Animated.View style={styles.innerBox} />
</GestureDetector>
</View>
</GestureHandlerRootView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
outerBox: {
backgroundColor: '#b58df1',
width: 150,
height: 150,
},
innerBox: {
width: 100,
height: 100,
backgroundColor: 'blue',
},
});
}/>
---
Packages/Docs Gesture Handler/Docs/Fundamentals/Gesture Animation/Index (packages/docs-gesture-handler/docs/fundamentals/gesture-animation/index.md)
---
id: gesture-animation
title: Your first gesture-driven animation
sidebar_label: Your first gesture-driven animation
sidebar_position: 2
---
import Step, { Divider } from '@site/src/theme/Step';
import Step1 from './\_steps/step1.md';
import Step2 from './\_steps/step2.md';
import Step3 from './\_steps/step3.md';
import Step4 from './\_steps/step4.md';
import Step5 from './\_steps/step5.md';
RNGH3 offers a straightforward way to add gestures to your app. Simply wrap your target view with the GestureDetector component, define your gesture, and pass it in. That’s it!
To see the new API in action, let's build a simple app where you can drag a ball around the screen. To follow along, you'll need both react-native-gesture-handler (to handle gestures) and react-native-reanimated (to handle the animations).
<Step title="Step 1">
<div>Start by defining the basic structure of the application:</div>
<Step1 />
</Step>
<Step title="Step 2">
<div>
Next, define the <a href="https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/glossary#shared-value" target="_blank">SharedValues</a> to track the ball's position and create the animated styles required to position the ball on the screen:
</div>
<Step2 />
</Step>
<Step title="Step 3">
<div>Apply the animated styles to the ball component:</div>
<Step3 />
</Step>
<Step title="Step 4">
<div>
Now, define the <code>Pan</code> gesture logic.
</div>
<Step4 />
</Step>
<Step title="Step 5">
<div>
Finally, wrap the component responsible for rendering the ball with a <code>GestureDetector</code>, and attach the <code>Pan</code> gesture to it:
</div>
<Step5 />
</Step>
The complete implementation is shown below:
import { StyleSheet } from 'react-native';
import {
GestureDetector,
GestureHandlerRootView,
usePanGesture,
} from 'react-native-gesture-handler';
import Animated, {
useAnimatedStyle,
useSharedValue,
withSpring,
} from 'react-native-reanimated';export default function Ball() {
const isPressed = useSharedValue(false);
const offset = useSharedValue({ x: 0, y: 0 });
const gesture = usePanGesture({
onBegin: () => {
isPressed.value = true;
},
onUpdate: (e) => {
offset.value = {
x: offset.value.x + e.changeX,
y: offset.value.y + e.changeY,
};
},
onFinalize: () => {
isPressed.value = false;
},
});
const animatedStyles = useAnimatedStyle(() => {
return {
transform: [
{ translateX: offset.value.x },
{ translateY: offset.value.y },
{ scale: withSpring(isPressed.value ? 1.2 : 1) },
],
backgroundColor: isPressed.value ? 'yellow' : 'blue',
};
});
return (
<GestureHandlerRootView>
<GestureDetector gesture={gesture}>
<Animated.View style={[styles.ball, animatedStyles]} />
</GestureDetector>
</GestureHandlerRootView>
);
}
const styles = StyleSheet.create({
ball: {
width: 100,
height: 100,
borderRadius: 100,
backgroundColor: 'blue',
alignSelf: 'center',
},
});
---
Packages/Docs Gesture Handler/Docs/Fundamentals/Gesture Animation/ Steps/Step1 (packages/docs-gesture-handler/docs/fundamentals/gesture-animation/_steps/step1.md)
import { StyleSheet } from 'react-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import Animated from 'react-native-reanimated';export default function Ball() {
return (
<GestureHandlerRootView>
<Animated.View style={styles.ball} />
</GestureHandlerRootView>
);
}
const styles = StyleSheet.create({
ball: {
width: 100,
height: 100,
borderRadius: 100,
backgroundColor: 'blue',
alignSelf: 'center',
},
});
---
Packages/Docs Gesture Handler/Docs/Fundamentals/Gesture Animation/ Steps/Step2 (packages/docs-gesture-handler/docs/fundamentals/gesture-animation/_steps/step2.md)
import {
useAnimatedStyle,
useSharedValue,
withSpring,
} from 'react-native-reanimated';export default function Ball() {
const isPressed = useSharedValue(false);
const offset = useSharedValue({ x: 0, y: 0 });
const animatedStyles = useAnimatedStyle(() => {
return {
transform: [
{ translateX: offset.value.x },
{ translateY: offset.value.y },
{ scale: withSpring(isPressed.value ? 1.2 : 1) },
],
backgroundColor: isPressed.value ? 'yellow' : 'blue',
};
});
// ...
}
---
Packages/Docs Gesture Handler/Docs/Fundamentals/Gesture Animation/ Steps/Step3 (packages/docs-gesture-handler/docs/fundamentals/gesture-animation/_steps/step3.md)
``jsx {4}
// ...
return (
<GestureHandlerRootView>
<Animated.View style={[styles.ball, animatedStyles]} />
</GestureHandlerRootView>
);
// ...---Packages/Docs Gesture Handler/Docs/Fundamentals/Gesture Animation/ Steps/Step4 (packages/docs-gesture-handler/docs/fundamentals/gesture-animation/_steps/step4.md)
import { usePanGesture } from 'react-native-gesture-handler';
function Ball() {
// ...
const gesture = usePanGesture({
onBegin: () => {
isPressed.value = true;
},
onUpdate: (e) => {
offset.value = {
x: offset.value.x + e.changeX,
y: offset.value.y + e.changeY,
};
},
onFinalize: () => {
isPressed.value = false;
},
});
// ...
}
---Packages/Docs Gesture Handler/Docs/Fundamentals/Gesture Animation/ Steps/Step5 (packages/docs-gesture-handler/docs/fundamentals/gesture-animation/_steps/step5.md)
// ...
return (
<GestureHandlerRootView>
<GestureDetector gesture={gesture}>
<Animated.View style={[styles.ball, animatedStyles]} />
</GestureDetector>
</GestureHandlerRootView>
);
// ...
---GestureStateManagerPackages/Docs Gesture Handler/Docs/Gestures/ Category .Json (packages/docs-gesture-handler/docs/gestures/_category_.json)
{
"label": "Gestures API",
"position": 4,
"link": {
"type": "generated-index"
}
}---
Packages/Docs Gesture Handler/Docs/Gestures/State Manager (packages/docs-gesture-handler/docs/gestures/state-manager.mdx)
---
id: state-manager
title: Gesture state manager
sidebar_label: Gesture state manager
sidebar_position: 10
---RNGH3 allows manually controlling the gestures' lifecycle by using
.handlerTagState management
Manual state management is based on
. There are two ways of manual state control. Some gestures also supportmanualActivationproperty, which blocks their automatic activation, even if they meet their activation criteria.handlerTagInside gesture definition
If you want to manipulate gesture's state in its callbacks, you can get
from event parameter.<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[9, 17]}
src={
import { StyleSheet, View } from 'react-native';
import {
GestureDetector,
GestureHandlerRootView,
GestureStateManager,
useLongPressGesture,
} from 'react-native-gesture-handler';export default function App() {
const longPress = useLongPressGesture({
onTouchesDown: (e) => {
// highlight-next-line
GestureStateManager.activate(e.handlerTag);
},
onActivate: () => {
console.log('LongPress activated!');
},
});return (
<GestureHandlerRootView style={styles.container}>
<GestureDetector gesture={longPress}>
<View style={styles.box} />
</GestureDetector>
</GestureHandlerRootView>
);
}const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'space-around',
},box: {
width: 150,
height: 150,
backgroundColor: 'blue',
},
});}/>
Outside gesture definition
If you want to control gesture lifecycle outside of it, you can use handlerTag
from created gesture object.:::note
The gestures can only be activated after they have begun, that is, after they have received touch events.
:::<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[10, 21]}
src={
import { StyleSheet, View } from 'react-native';
import {
GestureDetector,
GestureHandlerRootView,
GestureStateManager,
useLongPressGesture,
usePanGesture,
} from 'react-native-gesture-handler';export default function App() {
const pan = usePanGesture({
onActivate: () => {
console.log('Pan activated!');
},
});const longPress = useLongPressGesture({
onActivate: () => {
// highlight-next-line
GestureStateManager.activate(pan.handlerTag);
},
});return (
<GestureHandlerRootView style={styles.container}>
<GestureDetector gesture={longPress}>
<View style={styles.box} />
</GestureDetector>
<GestureDetector gesture={pan}>
<View style={styles.box} />
</GestureDetector>
</GestureHandlerRootView>
);
}const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'space-around',
},box: {
width: 150,
height: 150,
backgroundColor: 'blue',
},
});}/>
manualActivation
When manualActivation
property is set totrueon a gesture, it will not activate automatically even if its activation criteria are met. UseGestureStateManagerto manipulate its state manually.In the example below, Pan
gesture will not activate by simply panning on the blue box - using GestureStateManageris necessary.<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[7, 22]}
src={
import { View } from 'react-native';
import {
GestureDetector,
GestureHandlerRootView,
usePanGesture,
} from 'react-native-gesture-handler';export default function App() {
const pan = usePanGesture({
onActivate: () => {
console.log('Pan gesture activated');
},
manualActivation: true,
});return (
<GestureHandlerRootView style={styles.container}>
<GestureDetector gesture={pan}>
<View style={styles.box} />
</GestureDetector>
</GestureHandlerRootView>
);
}const styles = {
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'space-around',
},
box: {
width: 150,
height: 150,
backgroundColor: 'blue',
},
};}/>GestureStateManager
GestureStateManager
provides methods to manipulate gesture's state imperatively.activate
activate: (handlerTag: number) => void;
tsx
deactivate: (handlerTag: number) => void;
If the gesture had activated, it triggers theonDeactivatecallback. It also triggers theonFinalizecallback on the gesture with the specifiedhandlerTag.fail
fail: (handlerTag: number) => void;
TriggersonFinalizecallback on the gesture with the specifiedhandlerTag. If the gesture had activated, it will also triggeronDeactivatecallback.---
Packages/Docs Gesture Handler/Docs/Gestures/Use Fling Gesture (packages/docs-gesture-handler/docs/gestures/use-fling-gesture.mdx)
---
id: use-fling-gesture
title: Fling gesture
sidebar_label: Fling gesture
sidebar_position: 6
---import { webContainer } from '@site/src/utils/getGestureStyles';
import FlingGestureBasic from '@site/static/examples/FlingGestureBasic';
import FlingGestureBasicSrc from '!!raw-loader!@site/static/examples/FlingGestureBasic';
import MouseButtonProp from './\_shared/mouse-button.mdx';The gesture that tracks quick, sufficiently long movement in specified direction.
<div className={webContainer}>
<InteractiveExample
component={<FlingGestureBasic/>}
src={FlingGestureBasicSrc}
disableMarginBottom={true}
/>
</div>import BaseEventData from './\_shared/base-gesture-event-data.mdx';
import BaseGestureConfig from './\_shared/base-gesture-config.mdx';
import BaseGestureCallbacks from './\_shared/base-gesture-callbacks.mdx';
import SharedValueInfo from './\_shared/shared-value-info.md';When the gesture gets activated it will end when the finger is released.
The gesture will fail if the finger is lifted before the gesture could activate.Example
<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[13, 34]}
src={
import { StyleSheet } from 'react-native';
import {
GestureDetector,
GestureHandlerRootView,
Directions,
useFlingGesture,
} from 'react-native-gesture-handler';
import Animated, {
useSharedValue,
useAnimatedStyle,
withTiming,
} from 'react-native-reanimated';export default function App() {
const position = useSharedValue(0);const flingGesture = useFlingGesture({
direction: Directions.RIGHT,
onActivate: () => {
position.value = withTiming(position.value + 10, { duration: 100 });
},
});const animatedStyle = useAnimatedStyle(() => ({
transform: [{ translateX: position.value }],
}));return (
<GestureHandlerRootView style={styles.container}>
<GestureDetector gesture={flingGesture}>
<Animated.View style={[styles.box, animatedStyle]} />
</GestureDetector>
</GestureHandlerRootView>
);
}const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'space-around',
},
box: {
height: 120,
width: 120,
backgroundColor: '#b58df1',
borderRadius: 20,
marginBottom: 30,
},
});}/>Config
<SharedValueInfo />
direction
<CollapsibleCode
label="Show composed types definitions"
expandedLabel="Hide composed types definitions"
lineBounds={[0, 1]}
src={
direction: Directions | SharedValue<Directions>;export const Directions = {
RIGHT: 1,
LEFT: 2,
UP: 4,
DOWN: 8,
} as const;}/>Expresses the allowed direction of movement. Expected values are exported as constants in the Directions
object. It's possible to combine directions using|operator.
import { Directions } from 'react-native-gesture-handler';
// Single direction
const fling = useFlingGesture({ direction: Directions.RIGHT });
// Combined directions
const fling = useFlingGesture({ direction: Directions.RIGHT | Directions.LEFT });
numberOfPointers
numberOfPointers: number | SharedValue<number>;
Determines the exact number of pointers required to handle the fling gesture.<MouseButtonProp />
<BaseGestureConfig />
Callbacks
<BaseGestureCallbacks gesture={"Fling"}/>
Event data
x
x: number;
X coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to theGestureDetector. Expressed in point units.y
y: number;
Y coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to theGestureDetector. Expressed in point units.absoluteX
absoluteX: number;
X coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. The value is expressed in point units. It is recommended to use it instead ofxin cases when the original view can be transformed as an effect of the gesture.absoluteY
absoluteY: number;
Y coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. The value is expressed in point units. It is recommended to use it instead ofyin cases when the original view can be transformed as an effect of the gesture.Hover<BaseEventData />
---
Packages/Docs Gesture Handler/Docs/Gestures/Use Hover Gesture (packages/docs-gesture-handler/docs/gestures/use-hover-gesture.mdx)
---
id: use-hover-gesture
title: Hover gesture
sidebar_label: Hover gesture
sidebar_position: 7
---import { webContainer } from '@site/src/utils/getGestureStyles';
import HoverGestureBasic from '@site/static/examples/HoverGestureBasic';
import HoverGestureBasicSrc from '!!raw-loader!@site/static/examples/HoverGestureBasic';Gesture that can recognize hovering above the view it's attached to.
<div className={webContainer}>
<InteractiveExample
component={<HoverGestureBasic/>}
src={HoverGestureBasicSrc}
disableMarginBottom={true}
/>
</div>import BaseEventData from './\_shared/base-gesture-event-data.mdx';
import BaseGestureConfig from './\_shared/base-gesture-config.mdx';
import BaseGestureCallbacks from './\_shared/base-gesture-callbacks.mdx';
import BaseContinuousGestureCallbacks from './\_shared/base-continuous-gesture-callbacks.mdx';
import SharedValueInfo from './\_shared/shared-value-info.md';The hover effect may be activated by moving a mouse or a stylus over the view.
On iOS additional visual effects may be configured.
:::note
Don't rely ongesture to continue after the mouse button is clicked or the stylus touches the screen. If you want to handle both cases, compose it withPangesture.
:::Example
<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[7, 17]}
src={
import { View, StyleSheet } from 'react-native';
import {
GestureDetector,
GestureHandlerRootView,
useHoverGesture,
} from 'react-native-gesture-handler';export default function App() {
const hoverGesture = useHoverGesture({});return (
<GestureHandlerRootView style={styles.container}>
<GestureDetector gesture={hoverGesture}>
<View style={styles.box} />
</GestureDetector>
</GestureHandlerRootView>
);
}const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'space-around',
},
box: {
height: 120,
width: 120,
backgroundColor: '#b58df1',
borderRadius: 20,
marginBottom: 30,
},
});}/>Config
<SharedValueInfo />
<Badges platforms={['ios']}>
effect [I]
</Badges><CollapsibleCode
label="Show composed types definitions"
expandedLabel="Hide composed types definitions"
lineBounds={[0, 1]}
collapsed={false}
src={
effect: HoverEffect | SharedValue<HoverEffect>;enum HoverEffect {
NONE = 0,
LIFT = 1,
HIGHLIGHT = 2,
}}/>Visual effect applied to the view while the view is hovered. Defaults to HoverEffect.None
.<BaseGestureConfig />
Callbacks
<BaseGestureCallbacks gesture={"Hover"}/>
<BaseContinuousGestureCallbacks gesture={"Hover"}/>Event data
x
x: number;
X coordinate of the current position of the pointer relative to the view attached to theGestureDetector. Expressed in point units.y
y: number;
Y coordinate of the current position of the pointer relative to the view attached to theGestureDetector. Expressed in point units.absoluteX
absoluteX: number;
X coordinate of the current position of the pointer relative to the window. The value is expressed in point units. It is recommended to use it instead ofxin cases when the original view can be transformed as an effect of the gesture.absoluteY
absoluteY: number;
Y coordinate of the current position of the pointer relative to the window. The value is expressed in point units. It is recommended to use it instead ofyin cases when the original view can be transformed as an effect of the gesture.stylusData
stylusData: StylusData;
interface StylusData {
tiltX: number;
tiltY: number;
azimuthAngle: number;
altitudeAngle: number;
pressure: number;
}
Object that contains additional information aboutstylus. It consists of the following fields:tiltX- angle in degrees between the Y-Z plane of the stylus and the screen.tiltY
-- angle in degrees between the X-Z plane of the stylus and the screen.altitudeAngle
-- angle between stylus axis and the X-Y plane of a device screen.azimuthAngle
-- angle between the Y-Z plane and the plane containing both the stylus axis and the Y axis.pressure
-- indicates the normalized pressure of the stylus.<BaseEventData />
---
Packages/Docs Gesture Handler/Docs/Gestures/Use Long Press Gesture (packages/docs-gesture-handler/docs/gestures/use-long-press-gesture.mdx)
---
id: use-long-press-gesture
title: Long press gesture
sidebar_label: Long press gesture
sidebar_position: 3
---import { webContainer } from '@site/src/utils/getGestureStyles';
import LongPressGestureBasic from '@site/static/examples/LongPressGestureBasic';
import LongPressGestureBasicSrc from '!!raw-loader!@site/static/examples/LongPressGestureBasic';import MouseButtonProp from './\_shared/mouse-button.mdx';
Gesture that activates when the corresponding view is pressed for a sufficiently long time.
<div className={webContainer}>
<InteractiveExample
component={<LongPressGestureBasic/>}
src={LongPressGestureBasicSrc}
disableMarginBottom={true}
/>
</div>import BaseEventData from './\_shared/base-gesture-event-data.mdx';
import BaseGestureConfig from './\_shared/base-gesture-config.mdx';
import BaseGestureCallbacks from './\_shared/base-gesture-callbacks.mdx';
import SharedValueInfo from './\_shared/shared-value-info.md';import LongPressExample from '!!raw-loader!./_examples/LongPressExample';
This gesture will deactivate immediately after the finger is released.
The gesture will fail to recognize a touch event if the finger is lifted before the minimum required time or if the finger is moved further than the allowable distance.Example
<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[7, 23]}
src={LongPressExample}/>
Config
<SharedValueInfo />
minDuration
minDuration: number | SharedValue<number>;
Minimum time, expressed in milliseconds, that a finger must remain pressed on the corresponding view. The default value is 500.maxDistance
maxDistance: number | SharedValue<number>;
Maximum distance, expressed in points, that defines how far the finger is allowed to travel during a long press gesture. If the finger travels further than the defined distance and the gesture hasn't yet activated, it will fail to recognize the gesture. The default value is 10.<MouseButtonProp />
<BaseGestureConfig />
Callbacks
<BaseGestureCallbacks gesture={"LongPress"}/>
Event data
x
x: number;
X coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to theGestureDetector.y
y: number;
Y coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to theGestureDetector.absoluteX
absoluteX: number;
X coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. It is recommended to useabsoluteXinstead ofxin cases when the view attached to theGestureDetectorcan be transformed as an effect of the gesture.absoluteY
absoluteY: number;
Y coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. It is recommended to useabsoluteYinstead ofyin cases when the view attached to theGestureDetectorcan be transformed as an effect of the gesture.duration
duration: number;
Duration of the long press (time since the start of the gesture), expressed in milliseconds.Manual<BaseEventData />
---
Packages/Docs Gesture Handler/Docs/Gestures/Use Manual Gesture (packages/docs-gesture-handler/docs/gestures/use-manual-gesture.mdx)
---
id: use-manual-gesture
title: Manual gesture
sidebar_label: Manual gesture
sidebar_position: 9
---import BaseEventData from './\_shared/base-gesture-event-data.mdx';
import BaseGestureConfig from './\_shared/base-gesture-config.mdx';
import BaseGestureCallbacks from './\_shared/base-gesture-callbacks.mdx';
import BaseContinuousGestureCallbacks from './\_shared/base-continuous-gesture-callbacks.mdx';
import SharedValueInfo from './\_shared/shared-value-info.md';import Step, { Divider } from '@site/src/theme/Step';
import Step1 from './\_manual_gesture_steps/step1.md';
import Step2 from './\_manual_gesture_steps/step2.md';
import Step3 from './\_manual_gesture_steps/step3.md';
import Step4 from './\_manual_gesture_steps/step4.md';
import Step5 from './\_manual_gesture_steps/step5.md';
import Step6 from './\_manual_gesture_steps/step6.md';
import Step7 from './\_manual_gesture_steps/step7.md';
A plain gesture that has no specific activation criteria nor event data set. Its state has to be controlled manually using a state manager. It will not fail when all the pointers are lifted from the screen.:::tip
If you need to modify the activation criteria of gestures other than, check their configuration for relevant properties or usemanualActivationto manage their state directly.Animated.View
:::Example
To demonstrate how to make a manual gesture we will make a simple one that tracks all pointers on the screen.
<Step title="Step 1">
First, we need a way to store information about the pointer: whether it should be visible and its position.
<Step1 />
</Step><Step title="Step 2">
We also need a component to mark where a pointer is. In order to accomplish that we will make a component that accepts two shared values: one holding information about the pointer using the interface we just created, the other holding a bool indicating whether the gesture has activated.
In this example when the gesture is not active, the ball representing it will be blue and when it is active the ball will be red and slightly bigger.
<Step2 />
</Step><Step title="Step 3">
Now we have to make a component that will handle the gesture and draw all the pointer indicators. We will store data about pointers in an array and render them inside an.onTouchesDown
<Step3 />
</Step><Step title="Step 4">
We have our components set up and we can finally get to making the gesture! We will start withwhere we need to set position of the pointers and make them visible. We can get this information from the touches property of the event. In this case we will also check how many pointers are on the screen and activate the gesture if there are at least two.onTouchesMove
<Step4 />
</Step><Step title="Step 5">
Next, we will handle pointer movement. Inwe will simply update the position of moved pointers.onTouchesUp
<Step5 />
</Step><Step title="Step 6">
We also need to handle lifting fingers from the screen, which corresponds to. Here we will just hide the pointers that were lifted and end the gesture if there are no more pointers on the screen.onTouchesCancel
Note that we are not handlingas in this very basic case we don't expect it to happen; however, you should clear data about cancelled pointers (most of the time all active ones) when it is called.true
<Step6 />
</Step><Step title="Step 7">
Now that our pointers are being tracked correctly and we have the state management, we can handle activation and ending of the gesture. In our case, we will simply set the active shared value either toorfalse.
<Step7 />
</Step><details>
<summary>Full example code</summary>
/ Detailed source-code truncated for AI context efficiency. /
</details>manualActivation
Modifying existing gestures
While manual gestures open great possibilities, we are aware that reimplementing pinch or rotation from scratch just because you need to activate in specific circumstances or require position of the fingers, would be a waste of time as those gestures are already available. Therefore, you can use touch events with every gesture to extract more detailed information about the gesture than what the basic events alone provide. We also added a
modifier on all continuous gestures, which prevents the gesture it is applied to from activating automatically, giving you full control over its behavior.GestureDetectorConfig
<SharedValueInfo />
<BaseGestureConfig />
Callbacks
<BaseGestureCallbacks gesture={"Manual"}/>
<BaseContinuousGestureCallbacks gesture={"Manual"} />Event data
<BaseEventData />
---
Packages/Docs Gesture Handler/Docs/Gestures/Use Native Gesture (packages/docs-gesture-handler/docs/gestures/use-native-gesture.mdx)
---
id: use-native-gesture
title: Native gesture
sidebar_label: Native gesture
sidebar_position: 8
---import BaseEventData from './\_shared/base-gesture-event-data.mdx';
import BaseGestureConfig from './\_shared/base-gesture-config.mdx';
import BaseGestureCallbacks from './\_shared/base-gesture-callbacks.mdx';
import BaseContinuousGestureCallbacks from './\_shared/base-continuous-gesture-callbacks.mdx';
import SharedValueInfo from './\_shared/shared-value-info.md';A gesture that allows other touch handling components to work within RNGH's gesture system. This streamlines interactions between gestures and the native component, allowing it to form relations with other gestures.
When used, the native component should be the direct child of a
.Native:::danger
Do not usegesture with components exported by React Native Gesture Handler, as they already have it pre-applied. AttachingNativegesture twice will result in undefined behavior.ScrollView
:::Example
This example renders a
with multiple colored rectangles, where each rectangle has a black section. Starting a touch on a black section will disable theScrollViewfor the duration of thePangesture.
<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[15, 54]}
src={
import { View, ScrollView } from 'react-native';
import {
GestureDetector,
GestureHandlerRootView,
useNativeGesture,
usePanGesture,
NativeGesture,
} from 'react-native-gesture-handler';const COLORS = ['red', 'green', 'blue', 'purple', 'orange', 'cyan'];
type RectangleProps = {
color: string;
scrollGesture: NativeGesture;
};function Rectangle({ color, scrollGesture }: RectangleProps) {
const pan = usePanGesture({
block: scrollGesture,
});return (
<>
<View
key={color}
style={{ width: '100%', height: 250, backgroundColor: color }}
/>
<GestureDetector gesture={pan}>
<View style={{ width: '100%', height: 50, backgroundColor: 'black' }} />
</GestureDetector>
</>
);
}export default function App() {
const nativeGesture = useNativeGesture({});return (
<GestureHandlerRootView>
<GestureDetector gesture={nativeGesture}>
<ScrollView style={{ flex: 1 }}>
<View>
{COLORS.map((color) => (
<Rectangle
key={color}
color={color}
scrollGesture={nativeGesture}
/>
))}
</View>
</ScrollView>
</GestureDetector>
</GestureHandlerRootView>
);
}}/>Remarks
- Native
gesture can be used as part of gesture composition and cross-component interactions just like any other gesture. You can use this to block a native component for the duration of the gesture or to make it work alongside a gesture.- Due to platform API limitations, the Native
gesture has restricted functionality onweb. For instance, it cannot be used to block scrolling on aScrollView.Config
<SharedValueInfo />
<Badges platforms={['android']}>
shouldActivateOnStart [A]
</Badges>
shouldActivateOnStart: boolean | SharedValue<boolean>;
Whentrue, the underlying handler will activate unconditionally when it receives any touches.disallowInterruption
disallowInterruption: boolean | SharedValue<boolean>;
Whentrue, this handler cancels all other gesture handlers when it activates.yieldsToContinuousGestures
yieldsToContinuousGestures: boolean | SharedValue<boolean>;
Composes withdisallowInterruption. When both aretrue, this handler still cancels discrete gestures (Tap,LongPress,Fling) on activation but allows continuous gestures (Pan,Pinch,Rotation,Native,Manual,Hover) to interrupt it. No-op whendisallowInterruptionisfalse. Defaults tofalse.<Badges platforms={['android', 'ios']}>
delaysChildPressedState [A][I]
</Badges>
delaysChildPressedState: boolean | SharedValue<boolean>;
Whentrue, the wrapped scrollable container delays displaying the pressed state of its children until it's clear that the gesture is not a scroll. Set it tofalseto display the pressed state immediately. Defaults totrue.delaysContentToucheson the underlyingUIScrollView. On Android it controls whether the container delays the pressed state of its children (seeshouldDelayChildPressedState) — on Android, this requires React Native 0.87 or newer and is a no-op on older versions.<BaseGestureConfig />
Callbacks
<BaseGestureCallbacks gesture={"NativeView"}/>
Event data
pointerInside
pointerInside: boolean;
trueif the gesture was performed inside the containing view,falseotherwise.<BaseEventData />
---
Packages/Docs Gesture Handler/Docs/Gestures/Use Pan Gesture (packages/docs-gesture-handler/docs/gestures/use-pan-gesture.mdx)
---
id: use-pan-gesture
title: Pan gesture
sidebar_label: Pan gesture
sidebar_position: 1
---import { vanishOnMobile, appearOnMobile, webContainer } from '@site/src/utils/getGestureStyles';
import useBaseUrl from '@docusaurus/useBaseUrl';
import PanGestureBasic from '@site/static/examples/PanGestureBasic';
import PanGestureBasicSrc from '!!raw-loader!@site/static/examples/PanGestureBasic';import BaseEventData from './\_shared/base-gesture-event-data.mdx';
import BaseGestureConfig from './\_shared/base-gesture-config.mdx';
import BaseContinuousGestureConfig from './\_shared/base-continuous-gesture-config.md';
import BaseGestureCallbacks from './\_shared/base-gesture-callbacks.mdx';
import BaseContinuousGestureCallbacks from './\_shared/base-continuous-gesture-callbacks.mdx';
import SharedValueInfo from './\_shared/shared-value-info.md';
import MouseButtonProp from './\_shared/mouse-button.mdx';A continuous gesture that can recognize a panning (dragging) gesture and track its movement.
<div className={webContainer}>
<InteractiveExample
component={<PanGestureBasic/>}
src={PanGestureBasicSrc}
disableMarginBottom={true}
/>
</div>
The gesture activates when a finger is placed on the screen and moved some initial distance.Configurations such as a minimum initial distance, specific vertical or horizontal pan detection and number of fingers required for activation (allowing for multifinger swipes) may be specified.
Gesture callback can be used for continuous tracking of the pan gesture. It provides information about the gesture such as its XY translation from the starting point as well as its instantaneous velocity.
Example
<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[12, 35]}
src={
import { StyleSheet } from 'react-native';
import {
GestureDetector,
GestureHandlerRootView,
usePanGesture,
} from 'react-native-gesture-handler';
import Animated, {
useSharedValue,
withTiming,
useAnimatedStyle,
} from 'react-native-reanimated';export default function App() {
const position = useSharedValue(0);const panGesture = usePanGesture({
onUpdate: (e) => {
position.value = e.translationX;
},
onDeactivate: () => {
position.value = withTiming(0, { duration: 100 });
},
});const animatedStyle = useAnimatedStyle(() => ({
transform: [{ translateX: position.value }],
}));return (
<GestureHandlerRootView style={styles.container}>
<GestureDetector gesture={panGesture}>
<Animated.View style={[styles.box, animatedStyle]} />
</GestureDetector>
</GestureHandlerRootView>
);
}const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'space-around',
},
box: {
height: 120,
width: 120,
backgroundColor: '#b58df1',
borderRadius: 20,
marginBottom: 30,
},
});}/>
Multi touch pan handling
If your app relies on multi touch pan handling this section provides some information about how the default behavior differs between platforms and how (if necessary) it can be unified.
The difference in multi touch pan handling lies in the way how translation properties during the event are being calculated.
On iOS the default behavior when more than one finger is placed on the screen is to treat this situation as if only one pointer was placed in the center of mass (average position of all the pointers).
This applies also to many platform native components that handle touch even if not primarily interested in multi touch interactions like for example UIScrollViewcomponent.On Android, the default behavior for native components like scroll view, pager views or drawers is different and hence gesture defaults to that when it comes to pan handling.
The difference is that instead of treating the center of mass of all the fingers placed as a leading pointer it takes the latest placed finger as such.
This behavior can be changed on Android using averageTouchesflag.Note that on both Android and iOS when the additional finger is placed on the screen that translation prop is not affected even though the position of the pointer being tracked might have changed.
Therefore it is safe to rely on translation most of the time as it only reflects the movement that happens regardless of how many fingers are placed on the screen and if that number changes over time.
If you wish to track the "center of mass" virtual pointer and account for its changes when the number of fingers changes you can use relative or absolute position provided in the event (xand yor absoluteXand absoluteY).Config
<SharedValueInfo />
minDistance
minDistance: number | SharedValue<number>;
Minimum distance the finger (or multiple fingers) need to travel before the gesture activates. Expressed in points.minVelocity
minVelocity: number | SharedValue<number>;
Minimum speed the pointer has to reach in order to activate the gesture. Expressed in points per second.minVelocityX
minVelocityX: number | SharedValue<number>;
Minimum speed along X axis the pointer has to reach in order to activate the gesture. Expressed in points per second.minVelocityY
minVelocityY: number | SharedValue<number>;
Minimum speed along Y axis the pointer has to reach in order to activate the gesture. Expressed in points per second.minPointers
minPointers: number | SharedValue<number>;
A number of fingers that is required to be placed before the gesture can activate. Should be higher or equal to 0.maxPointers
maxPointers: number | SharedValue<number>;
When the given number of fingers is placed on the screen and the gesture hasn't yet activated it will fail recognizing the gesture. Should be higher or equal to 0.activateAfterLongPress
activateAfterLongPress: number | SharedValue<number>;
Duration in milliseconds of theLongPressgesture beforePanis allowed to activate. If the finger is moved during that period, the gesture will fail. Should be higher or equal to 0 integer. Default value is 0, meaning noLongPressis required to activate thePan.activeOffsetX
activeOffsetX: number |
SharedValue<number> |
[number | SharedValue<number>, number | SharedValue<number>];
Range along X axis (in points) where fingers travel without activation of gesture. Moving outside of this range implies activation of gesture. Range can be given as an array or a single number.p
If range is set as an array, first value must be lower or equal to 0, and the second one higher or equal to 0.
If only one numberis given a range of(-inf, p)will be used ifpis higher or equal to 0 and(-p, inf)otherwise.activeOffsetY
activeOffsetY: number |
SharedValue<number> |
[number | SharedValue<number>, number | SharedValue<number>];
Range along Y axis (in points) where fingers travel without activation of gesture. Moving outside of this range implies activation of gesture. Range can be given as an array or a single number.p
If range is set as an array, first value must be lower or equal to 0, and the second one higher or equal to 0.
If only one numberis given a range of(-inf, p)will be used ifpis higher or equal to 0 and(-p, inf)otherwise.failOffsetY
failOffsetY: number |
SharedValue<number> |
[number | SharedValue<number>, number | SharedValue<number>];
When the finger moves outside this range (in points) along Y axis and the gesture hasn't yet activated it will fail recognizing the gesture. Range can be given as an array or a single number.p
If range is set as an array, first value must be lower or equal to 0, and the second one higher or equal to 0.
If only one numberis given a range of(-inf, p)will be used ifpis higher or equal to 0 and(-p, inf)otherwise.failOffsetX
failOffsetX: number |
SharedValue<number> |
[number | SharedValue<number>, number | SharedValue<number>];
When the finger moves outside this range (in points) along X axis and the gesture hasn't yet activated it will fail recognizing the gesture. Range can be given as an array or a single number.p
If range is set as an array, first value must be lower or equal to 0, and the second one higher or equal to 0.
If only one numberis given a range of(-inf, p)will be used ifpis higher or equal to 0 and(-p, inf)otherwise.<Badges platforms={['android']}>
averageTouches [A]
</Badges>
averageTouches: boolean | SharedValue<boolean>;
Android, by default, will calculate translation values based on the position of the leading pointer (the first one that was placed on the screen). This modifier allows that behavior to be changed to the one that is default on iOS - the averaged position of all active pointers will be used to calculate the translation values.<Badges platforms={['ios', 'web']}>
enableTrackpadTwoFingerGesture [I][W]
</Badges>enableTrackpadTwoFingerGesture: boolean | SharedValue<boolean>;
Enables two-finger gestures on supported devices, for example iPads with trackpads. If not enabled the gesture will require click + drag, with enableTrackpadTwoFingerGesture swiping with two fingers will also trigger the gesture.<MouseButtonProp />
<BaseContinuousGestureConfig />
<BaseGestureConfig />
Callbacks
<BaseGestureCallbacks gesture={"Pan"}/>
<BaseContinuousGestureCallbacks gesture={"Pan"}/>
Event data
translationX
translationX: number;
Translation of the pan gesture along X axis accumulated over the time of the gesture. The value is expressed in the point units.translationY
translationY: number;
Translation of the pan gesture along Y axis accumulated over the time of the gesture. The value is expressed in the point units.changeX
changeX: number;
The horizontal distance moved since the last event frame. This value represents the immediate incremental change in the X axis, rather than the total accumulated distance.changeY
changeY: number;
The vertical distance moved since the last event frame. This value represents the immediate incremental change in the Y axis, rather than the total accumulated distance.velocityX
velocityX: number;
Velocity of the pan gesture along the X axis in the current moment. The value is expressed in point units per second.velocityY
velocityY: number;
Velocity of the pan gesture along the Y axis in the current moment. The value is expressed in point units per second.x
x: number;
X coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to theGestureDetector. Expressed in point units.y
y: number;
Y coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to theGestureDetector. Expressed in point units.absoluteX
absoluteX: number;
X coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. The value is expressed in point units. It is recommended to use it instead ofxin cases when the original view can be transformed as an effect of the gesture.absoluteY
absoluteY: number;
Y coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. The value is expressed in point units. It is recommended to use it instead ofyin cases when the original view can be transformed as an effect of the gesture.stylusData
<CollapsibleCode
label="Show composed types definitions"
expandedLabel="Hide composed types definitions"
lineBounds={[0, 1]}
collapsed={false}
src={
stylusData: StylusData;interface StylusData {
tiltX: number;
tiltY: number;
azimuthAngle: number;
altitudeAngle: number;
pressure: number;
}}/>Object that contains additional information about stylus
. It consists of the following fields:- tiltX
- angle in degrees between the Y-Z plane of the stylus and the screen.
- tiltY- angle in degrees between the X-Z plane of the stylus and the screen.
- altitudeAngle- angle between stylus axis and the X-Y plane of a device screen.
- azimuthAngle- angle between the Y-Z plane and the plane containing both the stylus axis and the Y axis.
- pressure- indicates the normalized pressure of the stylus.<BaseEventData />
---
Packages/Docs Gesture Handler/Docs/Gestures/Use Pinch Gesture (packages/docs-gesture-handler/docs/gestures/use-pinch-gesture.mdx)
---
id: use-pinch-gesture
title: Pinch gesture
sidebar_label: Pinch gesture
sidebar_position: 5
---import { webContainer } from '@site/src/utils/getGestureStyles';
import PinchGestureBasic from '@site/static/examples/PinchGestureBasic';
import PinchGestureBasicSrc from '!!raw-loader!@site/static/examples/PinchGestureBasicSrc';Gesture that recognizes pinching. It allows for tracking the distance between two fingers and uses that information to scale or zoom your content.
<div className={webContainer}>
<InteractiveExample
component={<PinchGestureBasic/>}
src={PinchGestureBasicSrc}
disableMarginBottom={true}
/>
</div>import BaseEventData from './\_shared/base-gesture-event-data.mdx';
import BaseGestureConfig from './\_shared/base-gesture-config.mdx';
import BaseContinuousGestureConfig from './\_shared/base-continuous-gesture-config.md';
import BaseGestureCallbacks from './\_shared/base-gesture-callbacks.mdx';
import BaseContinuousGestureCallbacks from './\_shared/base-continuous-gesture-callbacks.mdx';
import SharedValueInfo from './\_shared/shared-value-info.md';The gesture activates when fingers are placed on the screen and move away from each other or pull closer together. It provides information about velocity, anchor (focal) point of gesture and scale.
The distance between the fingers is reported as a scale factor. At the beginning of the gesture, the scale factor is 1.0
. As the distance between the two fingers increases, the scale factor increases proportionally.
Similarly, the scale factor decreases as the distance between the fingers decreases.Pinch gestures are used most commonly to change the size of objects or content onscreen.
For example, map views use pinch gestures to change the zoom level of the map.:::note
When implementing pinch based on focal point, make sure to use it after the gesture had activated, i.e. in onActivateoronUpdatecallbacks. Using it inonBeginmay lead to unexpected behavior.
:::Example
<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[11, 31]}
src={
import { StyleSheet } from 'react-native';
import {
GestureDetector,
GestureHandlerRootView,
usePinchGesture,
} from 'react-native-gesture-handler';
import Animated, {
useSharedValue,
useAnimatedStyle,
} from 'react-native-reanimated';export default function App() {
const scale = useSharedValue(1);const pinchGesture = usePinchGesture({
onUpdate: (e) => {
scale.value *= e.scaleChange;
},
});const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }],
}));return (
<GestureHandlerRootView style={styles.container}>
<GestureDetector gesture={pinchGesture}>
<Animated.View style={[styles.box, animatedStyle]} />
</GestureDetector>
</GestureHandlerRootView>
);
}const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'space-around',
},
box: {
height: 120,
width: 120,
backgroundColor: '#b58df1',
borderRadius: 20,
marginBottom: 30,
},
});}/>Config
<SharedValueInfo />
<BaseContinuousGestureConfig />
<BaseGestureConfig />Callbacks
<BaseGestureCallbacks gesture={"Pinch"}/>
<BaseContinuousGestureCallbacks gesture={"Pinch"}/>Event data
scale
scale: number;
The scale factor relative to the points of the two touches in screen coordinates.scaleChange
scaleChange: number;
The incremental change in scale since the last event frame. This value represents the ratio of the current scale to the previous scale, rather than the total accumulated scale of the gesture.velocity
velocity: number;
Velocity of the pinch gesture at the current moment. The value is expressed in scale factor per second.focalX
focalX: number;
Position expressed in points along X axis of center anchor point of gesture.focalY
focalY: number;
Position expressed in points along Y axis of center anchor point of gesture.anchor<BaseEventData />
---
Packages/Docs Gesture Handler/Docs/Gestures/Use Rotation Gesture (packages/docs-gesture-handler/docs/gestures/use-rotation-gesture.mdx)
---
id: use-rotation-gesture
title: Rotation gesture
sidebar_label: Rotation gesture
sidebar_position: 4
---import { webContainer } from '@site/src/utils/getGestureStyles';
import RotationGestureBasic from '@site/static/examples/RotationGestureBasic';
import RotationGestureBasicSrc from '!!raw-loader!@site/static/examples/RotationGestureBasicSrc';Gesture that can recognize rotation and track its movement.
<div className={webContainer}>
<InteractiveExample
component={<RotationGestureBasic/>}
src={RotationGestureBasicSrc}
disableMarginBottom={true}
/>
</div>import BaseEventData from './\_shared/base-gesture-event-data.mdx';
import BaseGestureConfig from './\_shared/base-gesture-config.mdx';
import BaseContinuousGestureConfig from './\_shared/base-continuous-gesture-config.md';
import BaseGestureCallbacks from './\_shared/base-gesture-callbacks.mdx';
import BaseContinuousGestureCallbacks from './\_shared/base-continuous-gesture-callbacks.mdx';
import SharedValueInfo from './\_shared/shared-value-info.md';import RotationExample from '!!raw-loader!./_examples/RotationExample';
The gesture activates when fingers are placed on the screen and rotate around a common point. It provides information such as the amount rotated, the focal point of the rotation (anchor), and its instantaneous velocity.:::note
When implementing rotation based onpoint, make sure to use it after the gesture had activated, i.e. inonActivateoronUpdatecallbacks. Using it inonBeginmay lead to unexpected behavior.
:::Example
<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[11, 31]}
src={RotationExample}/>Config
<SharedValueInfo />
<BaseContinuousGestureConfig />
<BaseGestureConfig />Callbacks
<BaseGestureCallbacks gesture={"Rotation"}/>
<BaseContinuousGestureCallbacks gesture={"Rotation"}/>Event data
rotation
rotation: number;
Amount rotated, expressed in radians, from the gesture's focal point (anchor).rotationChange
rotationChange: number;
The incremental change in rotation since the last event frame. This value represents the difference in radians between the current and previous rotation, rather than the total accumulated rotation of the gesture.velocity
velocity: number;
Instantaneous velocity, expressed in point units per second, of the gesture.anchorX
anchorX: number;
X coordinate, expressed in points, of the gesture's central focal point (anchor).anchorY
anchorY: number;
Y coordinate, expressed in points, of the gesture's central focal point (anchor).minPointers<BaseEventData />
---
Packages/Docs Gesture Handler/Docs/Gestures/Use Tap Gesture (packages/docs-gesture-handler/docs/gestures/use-tap-gesture.mdx)
---
id: use-tap-gesture
title: Tap gesture
sidebar_label: Tap gesture
sidebar_position: 2
---import { vanishOnMobile, appearOnMobile, webContainer } from '@site/src/utils/getGestureStyles';
import useBaseUrl from '@docusaurus/useBaseUrl';
import TapGestureBasic from '@site/static/examples/TapGestureBasic';
import TapGestureBasicSrc from '!!raw-loader!@site/static/examples/TapGestureBasic';import MouseButtonProp from './\_shared/mouse-button.mdx';
A discrete gesture that recognizes taps.
<div className={webContainer}>
<InteractiveExample
component={<TapGestureBasic/>}
src={TapGestureBasicSrc}
disableMarginBottom={true}
/>
</div>import BaseEventData from './\_shared/base-gesture-event-data.mdx';
import BaseGestureConfig from './\_shared/base-gesture-config.mdx';
import BaseGestureCallbacks from './\_shared/base-gesture-callbacks.mdx';
import SharedValueInfo from './\_shared/shared-value-info.md';
Tap gestures detect one or more fingers briefly touching the screen.
The pointers involved in these gestures must not move significantly from their initial touch positions. This can be changed via configuration.
The required number of taps and allowed distance from initial position may be configured.
For example, you might configure tap gesture recognizers to detect single taps, double taps, or triple taps.In order for a gesture to activate, the specified gesture requirements such as
,numberOfTaps,maxDistance,maxDuration, andmaxDelaymust be met.Example
<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[7, 21]}
src={
import { View, StyleSheet } from 'react-native';
import {
GestureDetector,
GestureHandlerRootView,
useTapGesture,
} from 'react-native-gesture-handler';export default function App() {
const singleTap = useTapGesture({
onActivate: () => {
console.log('Single tap!');
},
});return (
<GestureHandlerRootView style={styles.container}>
<GestureDetector gesture={singleTap}>
<View style={styles.box} />
</GestureDetector>
</GestureHandlerRootView>
);
}const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'space-around',
},
box: {
height: 120,
width: 120,
backgroundColor: '#b58df1',
borderRadius: 20,
marginBottom: 30,
},
});}/>Config
<SharedValueInfo />
minPointers
minPointers: number | SharedValue<number>;
Minimum number of pointers (fingers) required to be placed before the gesture activates. Should be a positive integer. The default value is 1.maxDuration
maxDuration: number | SharedValue<number>;
Maximum time, expressed in milliseconds, that defines how fast a finger must be released after a touch. The default value is 500.maxDelay
maxDelay: number | SharedValue<number>;
Maximum time, expressed in milliseconds, that can pass before the next tap — if many taps are required. The default value is 500.numberOfTaps
numberOfTaps: number | SharedValue<number>;
Number of tap gestures required to activate the gesture. The default value is 1.maxDeltaX
maxDeltaX: number | SharedValue<number>;
Maximum distance, expressed in points, that defines how far the finger is allowed to travel along the X axis during a tap gesture. If the finger travels further than the defined distance along the X axis and the gesture hasn't yet activated, it will fail to recognize the gesture.maxDeltaY
maxDeltaY: number | SharedValue<number>;
Maximum distance, expressed in points, that defines how far the finger is allowed to travel along the Y axis during a tap gesture. If the finger travels further than the defined distance along the Y axis and the gesture hasn't yet activated, it will fail to recognize the gesture.maxDistance
maxDistance: number | SharedValue<number>;
Maximum distance, expressed in points, that defines how far the finger is allowed to travel during a tap gesture. If the finger travels further than the defined distance and the gesture hasn't yet activated, it will fail to recognize the gesture.<MouseButtonProp />
<BaseGestureConfig />
Callbacks
<BaseGestureCallbacks gesture={"Tap"}/>
Event data
x
x: number;
X coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to theGestureDetector.y
y: number;
Y coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to theGestureDetector.absoluteX
absoluteX: number;
X coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. It is recommended to useabsoluteXinstead ofxin cases when the view attached to theGestureDetectorcan be transformed as an effect of the gesture.absoluteY
absoluteY: number;
Y coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. It is recommended to useabsoluteYinstead ofyin cases when the view attached to theGestureDetectorcan be transformed as an effect of the gesture.<BaseEventData />
---
Packages/Docs Gesture Handler/Docs/Gestures/ Manual Gesture Steps/Step1 (packages/docs-gesture-handler/docs/gestures/_manual_gesture_steps/step1.md)
type Pointer = {
x: number;
y: number;
visible: boolean;
};
---Packages/Docs Gesture Handler/Docs/Gestures/ Manual Gesture Steps/Step2 (packages/docs-gesture-handler/docs/gestures/_manual_gesture_steps/step2.md)
import { StyleSheet } from 'react-native';
import Animated, {
useAnimatedStyle,
SharedValue,
} from 'react-native-reanimated';
type Pointer = {
x: number;
y: number;
visible: boolean;
};
type PointerElementProps = {
pointer: SharedValue<Pointer>;
active: SharedValue<boolean>;
};
function PointerElement(props: PointerElementProps) {
const animatedStyle = useAnimatedStyle(() => ({
transform: [
{ translateX: props.pointer.value.x },
{ translateY: props.pointer.value.y },
{
scale:
(props.pointer.value.visible ? 1 : 0) *
(props.active.value ? 1.3 : 1),
},
],
backgroundColor: props.active.value ? 'red' : 'blue',
}));
return <Animated.View style={[styles.pointer, animatedStyle]} />;
}
const styles = StyleSheet.create({
pointer: {
width: 60,
height: 60,
borderRadius: 30,
backgroundColor: 'red',
position: 'absolute',
marginStart: -30,
marginTop: -30,
},
});
---Packages/Docs Gesture Handler/Docs/Gestures/ Manual Gesture Steps/Step3 (packages/docs-gesture-handler/docs/gestures/_manual_gesture_steps/step3.md)
import { StyleSheet } from 'react-native';
import {
GestureDetector,
GestureHandlerRootView,
GestureStateManager,
useManualGesture,
} from 'react-native-gesture-handler';
import Animated, {
useAnimatedStyle,
SharedValue,
useSharedValue,
} from 'react-native-reanimated';
...
export default function Example() {
const trackedPointers: SharedValue<Pointer>[] = [];
const active = useSharedValue(false);
for (let i = 0; i < 10; i++) {
trackedPointers[i] = useSharedValue<Pointer>({
x: 0,
y: 0,
visible: false,
});
}
const gesture = useManualGesture({});
return (
<GestureHandlerRootView>
<GestureDetector gesture={gesture}>
<Animated.View style={{ flex: 1 }}>
{trackedPointers.map((pointer, index) => (
<PointerElement pointer={pointer} active={active} key={index} />
))}
</Animated.View>
</GestureDetector>
</GestureHandlerRootView>
);
}
const styles = StyleSheet.create({
pointer: {
width: 60,
height: 60,
borderRadius: 30,
backgroundColor: 'red',
position: 'absolute',
marginStart: -30,
marginTop: -30,
},
});
---Packages/Docs Gesture Handler/Docs/Gestures/ Manual Gesture Steps/Step4 (packages/docs-gesture-handler/docs/gestures/_manual_gesture_steps/step4.md)
const gesture = useManualGesture({
onTouchesDown: (e) => {
for (const touch of e.changedTouches) {
trackedPointers[touch.id].value = {
x: touch.x,
y: touch.y,
visible: true,
};
}
if (e.numberOfTouches >= 2) {
GestureStateManager.activate(e.handlerTag);
}
},
});
---Packages/Docs Gesture Handler/Docs/Gestures/ Manual Gesture Steps/Step5 (packages/docs-gesture-handler/docs/gestures/_manual_gesture_steps/step5.md)
const gesture = useManualGesture({
...
onTouchesMove: (e) => {
for (const touch of e.changedTouches) {
trackedPointers[touch.id].value = {
x: touch.x,
y: touch.y,
visible: true,
};
}
},
});
---Packages/Docs Gesture Handler/Docs/Gestures/ Manual Gesture Steps/Step6 (packages/docs-gesture-handler/docs/gestures/_manual_gesture_steps/step6.md)
const gesture = useManualGesture({
...
onTouchesUp: (e) => {
for (const touch of e.changedTouches) {
trackedPointers[touch.id].value = {
x: touch.x,
y: touch.y,
visible: false,
};
}
if (e.numberOfTouches === 0) {
GestureStateManager.deactivate(e.handlerTag);
}
},
});
---Packages/Docs Gesture Handler/Docs/Gestures/ Manual Gesture Steps/Step7 (packages/docs-gesture-handler/docs/gestures/_manual_gesture_steps/step7.md)
const gesture = useManualGesture({
...
onActivate: () => {
active.value = true;
},
onDeactivate: () => {
active.value = false;
},
---onUpdate: (event: ${props.gesture}HandlerData) => voidPackages/Docs Gesture Handler/Docs/Gestures/ Shared/Base Continuous Gesture Callbacks (packages/docs-gesture-handler/docs/gestures/_shared/base-continuous-gesture-callbacks.mdx)
import CodeBlock from '@theme/CodeBlock';
onUpdate
{
<CodeBlock className="language-ts">
{}
</CodeBlock>
}Set the callback that is being called every time the gesture receives an update while it's active.
---
Packages/Docs Gesture Handler/Docs/Gestures/ Shared/Base Continuous Gesture Config (packages/docs-gesture-handler/docs/gestures/_shared/base-continuous-gesture-config.md)
manualActivation
manualActivation: boolean | SharedValue<boolean>;
Whentrue, the handler will not activate by itself even if its activation criteria are met. Instead, you can manipulate its state usingGestureStateManager. Example usage can be found here.onBegin: (event: ${props.gesture}HandlerData) => void---
Packages/Docs Gesture Handler/Docs/Gestures/ Shared/Base Gesture Callbacks (packages/docs-gesture-handler/docs/gestures/_shared/base-gesture-callbacks.mdx)
import CodeBlock from '@theme/CodeBlock';
onBegin
{
<CodeBlock className="language-ts">
{}onActivate: (event: ${props.gesture}HandlerData) => void
</CodeBlock>
}Set the callback that is being called when given gesture handler starts receiving touches. At the moment of this callback the handler is not yet in an active state and we don't know yet if it will recognize the gesture at all.
onActivate
{
<CodeBlock className="language-ts">
{}onDeactivate: (event: ${props.gesture}HandlerData & { canceled: boolean }) => void
</CodeBlock>
}Set the callback that is being called when the gesture is recognized by the handler and it transitions to the active state.
onDeactivate
{
<CodeBlock className="language-ts">
{}canceled
</CodeBlock>
}Set the callback that is being called when the gesture that was recognized by the handler finishes. It will be called only if the handler was previously in the active state. The event object contains a
property — if the gesture was interrupted,canceledis set totrue. Otherwise it is set tofalse.onFinalize: (event: ${props.gesture}HandlerData & { canceled: boolean }) => voidonFinalize
{
<CodeBlock className="language-ts">
{}canceled
</CodeBlock>
}Set the callback that is being called when the handler finalizes handling gesture - the gesture was recognized and has finished or it failed to recognize. The event object contains a
property — if the gesture failed to activate or was interrupted,canceledis set totrue. Otherwise it is set tofalse.onTouchesDown
onTouchesDown: (event: GestureTouchEvent) => void
Set theonTouchesDowncallback which is called every time a finger is placed on the screen.onTouchesMove
onTouchesMove: (event: GestureTouchEvent) => void
Set theonTouchesMovecallback which is called every time a finger is moved on the screen.onTouchesUp
onTouchesUp: (event: GestureTouchEvent) => void
Set theonTouchesUpcallback which is called every time a finger is lifted from the screen.onTouchesCancel
onTouchesCancel: (event: GestureTouchEvent) => void
Set theonTouchesCancelcallback which is called every time a finger stops being tracked, for example when the gesture finishes.---
Packages/Docs Gesture Handler/Docs/Gestures/ Shared/Base Gesture Config (packages/docs-gesture-handler/docs/gestures/_shared/base-gesture-config.mdx)
enabled
enabled: boolean | SharedValue<boolean>;
Indicates whether the given handler should be analyzing the stream of touch events or not.false
When set to, we can be sure that the handler will never activate.true
If the value gets updated while the handler has already started recognizing a gesture, then the handler will stop processing gestures immediately.
Default value is.shouldCancelWhenOutside
shouldCancelWhenOutside: boolean | SharedValue<boolean>;
Whentrue, the handler will stop recognition whenever the finger leaves the area of the connected view.shouldCancelWhenOutside
Default value of this property is different depending on the handler type.
Most handlers'property defaults tofalseexcept for theLongPress,TapandNative(on Android and web), which default totrue.hitSlop
<CollapsibleCode
label="Show composed types definitions"
expandedLabel="Hide composed types definitions"
lineBounds={[0, 1]}
collapsed={false}
src={
hitSlop: HitSlop | SharedValue<HitSlop>;type HitSlop =
| number
| null
| undefined
| Partial<
Record<
'left' | 'right' | 'top' | 'bottom' | 'vertical' | 'horizontal',
number
>
>
| Record<'width' | 'left', number>
| Record<'width' | 'right', number>
| Record<'height' | 'top', number>
| Record<'height' | 'bottom', number>;}/>This parameter enables control over what part of the connected view area can be used to begin recognizing the gesture.
When a negative number is provided, the bounds of the view will reduce the area by the given number of points in each of the sides evenly.Instead you can pass an object to specify how each boundary side should be reduced by providing different number of points for left
,right,toporbottomsides.horizontal
You can alternatively provideorverticalinstead of specifying directlyleft,rightortopandbottom.width
Finally, the object can also takeandheightattributes.width
Whenis set it is only allowed to specify one of the sidesrightorleft.height
Similarly whenis provided onlytoporbottomcan be set.width
Specifyingorheightis useful if we only want the gesture to activate on the edge of the view. In that case for example we can setleft: 0andwidth: 20which would make it possible for the gesture to be recognized when started no more than 20 points from the left edge.IMPORTANT: Note that this parameter is primarily designed to reduce the area where gesture can activate. Hence it is only supported for all the values (except width
andheight) to be non positive (0 or lower). Although on Android it is supported for the values to also be positive and therefore allow to expand beyond view bounds but not further than the parent view bounds. To achieve this effect on both platforms you can use React Native's View hitSlop property.testID
testID: string;
Sets atestIDproperty for gesture object, allowing for querying for it in tests.<Badges platforms={['ios']}>
cancelsTouchesInView [I]
</Badges>
cancelsTouchesInView: boolean | SharedValue<boolean>;
Accepts a boolean value.true
When, the gesture will cancel touches for native UI components (UIButton,UISwitch, etc) it's attached to upon activation.true
Default value is.react-native-reanimatedrunOnJS
Requires
runOnJS: boolean | SharedValue<boolean>;
If set totrue, callbacks will be executed on JS runtime. Can be changed dynamically throughout gesture lifecycle. Defaults tofalse. For more details, see the runOnJS section.react-native-reanimateddisableReanimated
Requires
disableReanimated: boolean;
If set totrue, the gesture will ignore any interaction withReanimated. This property cannot be changed during the gesture's lifecycle. For more details, see the disableReanimated section.simultaneousWith
simultaneousWith: Gesture | Gesture[]
Adds a gesture that should be recognized simultaneously with this one.GestureDetectorIMPORTANT: Note that this method only marks the relation between gestures, without composing them.
will not recognize other gestures and it needs to be added to another detector in order to be recognized.requireToFail
requireToFail: Gesture | Gesture[]
Adds a relation requiring another gesture to fail, before this one can activate.GestureDetectorIMPORTANT: Note that this method only marks the relation between gestures, without composing them.
will not recognize other gestures and it needs to be added to another detector in order to be recognized.block
block: Gesture | Gesture[]
Adds a relation that makes other gestures wait with activation until this gesture fails (or doesn't start at all).GestureDetectorIMPORTANT: Note that this method only marks the relation between gestures, without composing them.
will not recognize other gestures and it needs to be added to another detector in order to be recognized.useAnimated
useAnimated: boolean;
Setting this property totrueensures that the Animated API functions correctly whenuseNativeDriveris set tofalse. The default value is set tofalse.<Badges platforms={['web']}>
activeCursor [W]
</Badges>
activeCursor: ActiveCursor | SharedValue<ActiveCursor>;
This parameter allows specifying which cursor should be used when the gesture activates. Supports all CSS cursor values (e.g."grab","zoom-in"). Default value is set to"auto".<Badges platforms={['ios', 'android']}>
### cancelsJSResponder [A][I]
</Badges>
cancelsJSResponder?: boolean;
Controls whether activating a Gesture Handler recognizer should cancel React Native JS responders.trueSetting this property to
ensures that the gesture handler cancels any active React Native JS responders in the same root view upon activation. When set tofalse, both Gesture Handler and React Native responder callbacks can run simultaneously. The default value is set totrue.---
Packages/Docs Gesture Handler/Docs/Gestures/ Shared/Base Gesture Event Data (packages/docs-gesture-handler/docs/gestures/_shared/base-gesture-event-data.mdx)
numberOfPointers
numberOfPointers: number;
Represents the number of pointers (fingers) currently placed on the screen.pointerType
<CollapsibleCode
label="Show composed types definitions"
expandedLabel="Hide composed types definitions"
lineBounds={[0, 1]}
collapsed={false}
src={
pointerType: PointerType;enum PointerType {
TOUCH,
STYLUS,
MOUSE,
KEY,
OTHER,
}}/>Indicates the type of pointer device in use.
---
Packages/Docs Gesture Handler/Docs/Gestures/ Shared/Mouse Button (packages/docs-gesture-handler/docs/gestures/_shared/mouse-button.mdx)
<Badges platforms={['android', 'web']}>
mouseButton [A][W]
</Badges><CollapsibleCode
label="Show composed types definitions"
expandedLabel="Hide composed types definitions"
lineBounds={[0, 1]}
collapsed={false}
src={
mouseButton: MouseButton | SharedValue<MouseButton>;enum MouseButton {
LEFT,
RIGHT,
MIDDLE,
BUTTON_4,
BUTTON_5,
ALL,
}}/>Allows users to choose which mouse buttons to respond to. Arguments can be combined using |
operator, e.g.mouseButton(MouseButton.LEFT | MouseButton.RIGHT). Default value is set toMouseButton.LEFT.---
Packages/Docs Gesture Handler/Docs/Gestures/ Shared/Shared Value Info (packages/docs-gesture-handler/docs/gestures/_shared/shared-value-info.md)
Properties supporting SharedValue
can be updated dynamically via Reanimated Shared Values without triggering a re-render.---
Packages/Docs Gesture Handler/Docs/Guides/ Category .Json (packages/docs-gesture-handler/docs/guides/_category_.json)
{
"label": "Guides",
"position": 2,
"link": {
"type": "generated-index"
}
}---
Packages/Docs Gesture Handler/Docs/Guides/Swipe And Scroll (packages/docs-gesture-handler/docs/guides/swipe-and-scroll.md)
---
id: swipe-and-scroll
title: Custom swipeable components inside ScrollView (web)
sidebar_position: 4
---While we recommend using our own ReanimatedSwipeable
component, creating your own version of swipeable gives you more control over its behavior. A common issue here is that after creating your own swipeable component, scroll does not work. In that case, try adding touchActionset to "pan-y", like this:
<GestureDetector gesture={...} ... touchAction="pan-y">
...
</GestureDetector>
---react-native-gesture-handlerPackages/Docs Gesture Handler/Docs/Guides/Testing (packages/docs-gesture-handler/docs/guides/testing.mdx)
---
id: testing
title: Testing with Jest
sidebar_position: 3
---import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';Setup
Jest configuration
In order to use functions provided by Gesture Handler, add
totransformIgnorePatternsin jest config.
transformIgnorePatterns: [
'node_modules/(?!((jest-)?react-native|react-native-gesture-handler)/)',
],
:::notetransformIgnorePatterns
Be careful when adding multiple entries to. SinceJestignores a file if it matches any pattern in the list, splitting negative lookaheads into separate strings often causes them to override each other. It is safer to combine your exceptions into a single regex using the|operator. See the Jest documentation for an example.
:::Mocking native modules
In order to load mocks provided by RNGH, add the following to your jest config:
"setupFiles": ["./node_modules/react-native-gesture-handler/jestSetup.js"]
Example jest config
<Tabs groupId="package-managers">
<TabItem value="js" label="jest.config.js" default>
module.exports = {
preset: '@react-native/jest-preset',
transformIgnorePatterns: [
'node_modules/(?!((jest-)?react-native|react-native-gesture-handler)/)',
],
setupFiles: ['react-native-gesture-handler/jestSetup.js'],
};
</TabItem>
<TabItem value="json" label="package.json">"jest": {
"preset": "@react-native/jest-preset",
"transformIgnorePatterns": [
"node_modules/(?!((jest-)?react-native|react-native-gesture-handler)/)"
],
"setupFiles": [
"react-native-gesture-handler/jestSetup.js"
]
}
</TabItem>createGestureController
</Tabs>Testing Gestures' and Gesture handlers' callbacks
RNGH provides the following APIs for triggering selected handlers:
to imperatively control a gesture lifecycle one step at a time.fireGestureHandler
-to dispatch a complete event stream.getByGestureTestId
-to find a gesture by its test ID.createGestureController
<CollapsibleCode
label="Show composed types definitions"
expandedLabel="Hide composed types definitions"
lineBounds={[0, 3]}
src={
import { createGestureController } from 'react-native-gesture-handler/jest-utils';createGestureController: (componentOrGesture) => GestureController;
export interface GestureController<
TEventPayload extends Record<string, unknown> = Record<string, unknown>,{
begin: (event?: GestureControllerEvent<TEventPayload>) => void;
activate: (event?: GestureControllerEvent<TEventPayload>) => void;
update: (event?: GestureControllerEvent<TEventPayload>) => void;
end: (event?: GestureControllerEvent<TEventPayload>) => void;
fail: (event?: GestureControllerEvent<TEventPayload>) => void;
cancel: (event?: GestureControllerEvent<TEventPayload>) => void;
}export type GestureControllerEvent<
TEventPayload extends Record<string, unknown> = Record<string, unknown>,= Partial<TEventPayload> & {
handlerTag?: never;
nativeEvent?: never;
oldState?: never;
state?: never;
};}/>stateCreates an imperative controller that dispatches gesture lifecycle events one step
at a time. This allows the test to assert application state between lifecycle steps
without manually supplying,oldState, orhandlerTag.componentOrGesturecan be:getByTestId- A Gesture Handler component found using a Jest query such as
.begin()
- A gesture object.
- A gesture test ID string.When a gesture object is passed directly, the event payload type is inferred from
the gesture. Every lifecycle method accepts an optional partial event payload and
fills omitted handler-specific properties with defaults.The controller exposes the following methods:
| Method | Behavior |
| ------------ | ---------------------------------------------------------------------------------------------------------------- |
|| Starts a stream and callsonBegin. If the previous stream finished, the controller resets it before beginning. |activate()
|| Activates a begun stream and callsonActivate. |update()
|| Dispatches an update for an active stream and callsonUpdate. It can be called multiple times. |end()
|| Ends a begun or active stream. CallsonDeactivateif active, thenonFinalizewithcanceled: false. |fail()
|| Fails a begun or active stream. CallsonDeactivateif active, thenonFinalizewithcanceled: true. |cancel()
|| Cancels a begun or active stream. CallsonDeactivateif active, thenonFinalizewithcanceled: true. |begin()Calling
again afterend(),fail(), orcancel()starts another stream with the same controller.state
Calling methods in an invalid order throws an error. State-machine fields such as,oldState,handlerTag, andnativeEventcannot be supplied in event
payloads because the controller manages them internally.
test('updates application state after each gesture step', () => {
const onBegin = jest.fn();
const onActivate = jest.fn();
const onUpdate = jest.fn();
const onDeactivate = jest.fn();
const onFinalize = jest.fn();
const panGesture = renderHook(() =>
usePanGesture({
disableReanimated: true,
onBegin,
onActivate,
onUpdate,
onDeactivate,
onFinalize,
})
).result.current;
const controller = createGestureController(panGesture);
controller.begin();
expect(onBegin).toHaveBeenCalledTimes(1);
controller.activate();
expect(onActivate).toHaveBeenCalledTimes(1);
controller.update({ translationX: 50 });
expect(onUpdate).toHaveBeenCalledWith(
expect.objectContaining({ translationX: 50 })
);
controller.end();
expect(onDeactivate).toHaveBeenCalledTimes(1);
expect(onFinalize).toHaveBeenCalledWith(
expect.objectContaining({ canceled: false })
);
});
:::notecreateGestureControllercontrols lifecycle events directly. It does not generate
pointer input, run platform gesture recognizers, or evaluate relations between
gestures.
:::fireGestureHandler
import { fireGestureHandler } from 'react-native-gesture-handler/jest-utils';
fireGestureHandler: (componentOrGesture, eventList) => void;
Simulates one event stream (i.e. event sequence starting withBEGINstate and endingEND
with one of/FAIL/CANCELstates), calling appropriate callbacks associated with given gesture handler.componentOrGesture-
- Either Gesture Handler component found byJestqueries (e.g.getByTestId) or Gesture found bygetByGestureTestId()eventList-
- Event data passed to appropriate callback. RNGH fills event list if requiredoldState
data is missing using these rules:
-is filled using state of the previous event.BEGINevents useUNDETERMINED
value as previous event.ACTIVE
- Events after firststate can omitstatefield.numberOfTouches
- Handler specific data is filled (e.g.,xfields) withBEGIN
defaults.
- MissingandENDevents are added with data copied from first and laststate
passed event, respectively.
- If first event doesn't havefield, theACTIVEstate is assumed.eventListSome
examples:
const oldStateFilled = [
{ state: State.BEGAN },
{ state: State.ACTIVE },
{ state: State.END },
]; // three events with specified state are fired.
const implicitActiveState = [
{ state: State.BEGAN },
{ state: State.ACTIVE },
{ x: 5 },
{ state: State.END },
]; // 4 events, including two ACTIVE events (second one has overridden additional data).
const implicitBegin = [
{ x: 1, y: 11 },
{ x: 2, y: 12, state: State.FAILED },
]; // 3 events, including implicit BEGAN, one ACTIVE, and one FAILED event with additional data.
const implicitBeginAndEnd = [
{ x: 5, y: 15 },
{ x: 6, y: 16 },
{ x: 7, y: 17 },
]; // 5 events, including 3 ACTIVE events and implicit BEGAN and END events. BEGAN uses first event's additional data, END uses last event's additional data.
const allImplicits = []; // 3 events, one BEGIN, one ACTIVE, one END with defaults.
getByGestureTestId
import { getByGestureTestId } from 'react-native-gesture-handler/jest-utils';
getByGestureTestId: (testID: string) => Gesture;
Returns opaque data type associated with gesture. Gesture is found viatestIDattribute in renderedtestID
components.:::warning
must be unique among components rendered in test.api_v3.test.tsx
:::fireGestureHandler example
for full implementation.
test('Pan gesture', () => {
const onBegin = jest.fn();
const onStart = jest.fn();
const panGesture = renderHook(() =>
usePanGesture({
disableReanimated: true,
onBegin: (e) => onBegin(e),
onActivate: (e) => onStart(e),
})
).result.current;
fireGestureHandler(panGesture, [
{ oldState: State.UNDETERMINED, state: State.BEGAN },
{ oldState: State.BEGAN, state: State.ACTIVE },
{ oldState: State.ACTIVE, state: State.ACTIVE },
{ oldState: State.ACTIVE, state: State.END },
]);
expect(onBegin).toHaveBeenCalledTimes(1);
expect(onStart).toHaveBeenCalledTimes(1);
});
---scalePackages/Docs Gesture Handler/Docs/Guides/Transformations (packages/docs-gesture-handler/docs/guides/transformations.mdx)
---
id: transformations
title: Transforming a view with multiple gestures
sidebar_label: Transforming a view
sidebar_position: 5
---Combining Pan, Pinch and Rotation on a single view — the "photo viewer" interaction where you can drag, zoom and twist an image at the same time — is a common use case. Gesture Handler doesn't ship a dedicated component for it, but you can build it yourself on top of the existing gestures. There are two ideas that make it work; the rest is wiring.
This guide assumes you're using Reanimated to run the transformation on the UI thread. The full example is at the end of this page, and a version of it can be found in the example app.
Use a matrix, not separate transforms
Store the whole transformation as a single affine matrix. It bundles translation, scale and rotation into one value that you compose by multiplying matrices together in whatever order you need, and since the result is itself a matrix you can store it and let each new gesture build on top. That gives you a natural split: the accumulated transform lives in the matrix, while the in-progress gesture stays as the plain
,rotationandtranslationthe callbacks hand you as the fingers move. Each frame, combine the two to draw the view. When the gesture ends, fold its values into the matrix and reset them, so the next gesture picks up where it left off. You don't need to follow the underlying math — the full example has the handful of small helper functions that implement it.scaleWhy not keep separate
,rotationandtranslationvalues and pass them to atransformarray instead? That works for a single gesture, but falls apart once you want each one to build on the last. The array does let you control the order its entries apply in, but there's no clean place to accumulate the running result: to preserve what previous gestures did, you'd have to keep appending an entry for every incremental translation, scale and rotation, and React would rebuild the whole matrix from that ever-growing list on each render.Keep the origin stable
Scaling and rotation pivot around the origin, but the user expects them to pivot around the point between their fingers. To move the pivot, wrap the transform between two translations — shift that point to the origin, apply the scale or rotation, then shift it back. Do this while building the matrix, once for the scale and once for the rotation:
matrix = multiply(matrix, translate(origin.x, origin.y));
matrix = multiply(matrix, scale(scaleValue, scaleValue));
matrix = multiply(matrix, translate(-origin.x, -origin.y));
Capture that pivot once, when the gesture activates, and keep it in a shared value:const pinch = usePinchGesture({
onActivate: (e) => {
origin.value = {
x: -(e.focalX - size.width / 2),
y: -(e.focalY - size.height / 2),
};
},
// ...
});
Set it only on activation and leave it untouched until the gesture ends, so the pivot stays stable — recomputing it every frame, or letting a second simultaneous gesture overwrite it, makes the view jump. The focal point arrives relative to the view rather than its center, which is what the size-based adjustment handles (read the size withonLayout). And when Pinch and Rotation run together, let only the first one set the pivot.useSimultaneousGesturesFull example
Put both ideas together and you have the whole interaction. Each gesture updates its own shared value as the fingers move and folds the result into the stored matrix when it ends;
runsPan,Pinch,Rotationand a double-tap zoom at the same time.<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[123, 264]}
src={
import React, { useState } from 'react';
import { StyleSheet, View } from 'react-native';
import {
GestureDetector,
usePanGesture,
usePinchGesture,
useRotationGesture,
useSimultaneousGestures,
useTapGesture,
} from 'react-native-gesture-handler';
import Animated, {
useAnimatedStyle,
useSharedValue,
} from 'react-native-reanimated';function identity3() {
'worklet';
return [1, 0, 0, 0, 1, 0, 0, 0, 1];
}function multiply3(a: number[], b: number[]) {
'worklet';
return [
a[0] b[0] + a[1] b[3] + a[2] * b[6],
a[0] b[1] + a[1] b[4] + a[2] * b[7],
a[0] b[2] + a[1] b[5] + a[2] * b[8],
a[3] b[0] + a[4] b[3] + a[5] * b[6],
a[3] b[1] + a[4] b[4] + a[5] * b[7],
a[3] b[2] + a[4] b[5] + a[5] * b[8],
a[6] b[0] + a[7] b[3] + a[8] * b[6],
a[6] b[1] + a[7] b[4] + a[8] * b[7],
a[6] b[2] + a[7] b[5] + a[8] * b[8],
];
}function scale3(sx: number, sy: number) {
'worklet';
return [sx, 0, 0, 0, sy, 0, 0, 0, 1];
}function translate3(tx: number, ty: number) {
'worklet';
return [1, 0, 0, 0, 1, 0, tx, ty, 1];
}function rotate3(rad: number) {
'worklet';
const c = Math.cos(rad);
const s = Math.sin(rad);
return [c, -s, 0, s, c, 0, 0, 0, 1];
}function invert2(m: number[]) {
'worklet';
const a = m[0];
const b = m[1];
const c = m[2];
const d = m[3];
const det = a d - b c;if (Math.abs(det) < 1e-6) {
return [1, 0, 0, 1];
}return [d / det, -b / det, -c / det, a / det];
}function toTransformedCoords(
point: { x: number; y: number },
matrix: number[]
) {
'worklet';
const m2 = [matrix[0], matrix[1], matrix[3], matrix[4]];
const inv = invert2(m2);
const x = point.x;
const y = point.y;
const newX = inv[0] x + inv[2] y;
const newY = inv[1] x + inv[3] y;return { x: newX, y: newY };
}function createMatrix(
translation: { x: number; y: number },
scale: number,
rotation: number,
origin: { x: number; y: number }
) {
'worklet';
let matrix = identity3();if (scale !== 1) {
matrix = multiply3(matrix, translate3(origin.x, origin.y));
matrix = multiply3(matrix, scale3(scale, scale));
matrix = multiply3(matrix, translate3(-origin.x, -origin.y));
}
if (rotation !== 0) {
matrix = multiply3(matrix, translate3(origin.x, origin.y));
matrix = multiply3(matrix, rotate3(-rotation));
matrix = multiply3(matrix, translate3(-origin.x, -origin.y));
}if (translation.x !== 0 || translation.y !== 0) {
matrix = multiply3(matrix, translate3(translation.x, translation.y));
}return matrix;
}function applyTransformations(
translation: { x: number; y: number },
scale: number,
rotation: number,
origin: { x: number; y: number },
matrix: number[]
) {
'worklet';
const translationInViewCoords = toTransformedCoords(translation, matrix);
const transform = createMatrix(
translationInViewCoords,
scale,
rotation,
origin
);
return multiply3(transform, matrix);
}function Photo() {
const [size, setSize] = useState({ width: 0, height: 0 });
const translation = useSharedValue({ x: 0, y: 0 });
const origin = useSharedValue({ x: 0, y: 0 });
const scale = useSharedValue(1);
const rotation = useSharedValue(0);
const isRotating = useSharedValue(false);
const isScaling = useSharedValue(false);const transform = useSharedValue(identity3());
const style = useAnimatedStyle(() => {
const matrix = applyTransformations(
translation.value,
scale.value,
rotation.value,
origin.value,
transform.value
);return {
transform: [
{ translateX: matrix[6] },
{ translateY: matrix[7] },
{ scale: Math.hypot(matrix[0], matrix[1]) },
{ rotateZ: \\${Math.atan2(matrix[1], matrix[0])}rad\},
],
};
});const rotationGesture = useRotationGesture({
onActivate: (e) => {
if (!isRotating.value && !isScaling.value) {
origin.value = {
x: -(e.anchorX - size.width / 2),
y: -(e.anchorY - size.height / 2),
};
}
isRotating.value = true;
},
onUpdate: (e) => {
rotation.value += e.rotationChange;
},
onDeactivate: () => {
transform.value = applyTransformations(
translation.value,
scale.value,
rotation.value,
origin.value,
transform.value
);rotation.value = 0;
translation.value = { x: 0, y: 0 };
scale.value = 1;
isRotating.value = false;
},
});const scaleGesture = usePinchGesture({
onActivate: (e) => {
if (!isRotating.value && !isScaling.value) {
origin.value = {
x: -(e.focalX - size.width / 2),
y: -(e.focalY - size.height / 2),
};
}
isScaling.value = true;
},
onUpdate: (e) => {
scale.value *= e.scaleChange;
},
onDeactivate: () => {
transform.value = applyTransformations(
translation.value,
scale.value,
rotation.value,
origin.value,
transform.value
);
rotation.value = 0;
translation.value = { x: 0, y: 0 };
scale.value = 1;
isScaling.value = false;
},
});const panGesture = usePanGesture({
averageTouches: true,
onUpdate: (e) => {
translation.value = {
x: translation.value.x + e.changeX,
y: translation.value.y + e.changeY,
};
},
onDeactivate: () => {
transform.value = applyTransformations(
translation.value,
scale.value,
rotation.value,
origin.value,
transform.value
);rotation.value = 0;
translation.value = { x: 0, y: 0 };
scale.value = 1;
},
});const doubleTapGesture = useTapGesture({
numberOfTaps: 2,
onDeactivate: () => {
scale.value *= 1.25;
},
});const gesture = useSimultaneousGestures(
rotationGesture,
scaleGesture,
panGesture,
doubleTapGesture
);return (
<GestureDetector gesture={gesture}>
<Animated.View
onLayout={({ nativeEvent }) => {
setSize({
width: nativeEvent.layout.width,
height: nativeEvent.layout.height,
});
}}
style={[styles.container, style]}
/>
</GestureDetector>
);
}export default function Example() {
return (
<View style={styles.home}>
<Photo />
</View>
);
}const styles = StyleSheet.create({
home: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
container: {
width: 240,
height: 240,
backgroundColor: '#5b6ef5',
elevation: 8,
borderRadius: 48,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.3,
shadowRadius: 4,
},
});}/>---
Packages/Docs Gesture Handler/Docs/Guides/Troubleshooting (packages/docs-gesture-handler/docs/guides/troubleshooting.mdx)
---
id: troubleshooting
title: Troubleshooting
sidebar_position: 2
---import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';Troubleshooting
Thanks for giving this library a try! We are sorry that you might have encountered issues though. Here is how you can seek help:
1. Search over the issues on GitHub. There is a chance someone had this problem in the past and it has been resolved!
2. When sure your problem hasn't been reported or was reported but the proposed solution doesn't work for you please follow our issue reporting guidelines.
3. You can try seeking help on Software Mansion Discord.
4. If you feel like reading the source code we highly recommend it, as this is by far the best resource and gives you the most up to date insights into how the library works and what might be causing the bug.
5. If you managed to find the solution consider contributing a fix or update our documentation to make this information easier to find for others in the future.Reporting issues
This library is maintained by a very small team. Please keep this in mind when reporting issues, as we may not be able to respond as quickly as you might expect. We strive to address all problems as soon as possible, but our time is often constrained by other issues, features, or projects. To help us understand and address your issue more promptly, you can assist by:
- Making sure the issue description is complete. Please include all the details about your environment (library version, RN version, device OS, etc.).
- It is best to provide an example app that reproduces the issue you are having. Put it up on gist, snack or create a repo on GitHub - it doesn't matter as long as we can easily pull it in, run and see the issue.
- Explain how you run your repro app and what steps to take to reproduce the issue.
- Isolate your issue from other dependencies you might be using and make the repro app as minimal as possible.
- If you have spent some time figuring out the root cause of the problem you can leave a note about your findings so far.
- Do not comment on closed issues. It is very unlikely that we are going to notice your comment in such a case. If the issue has been closed, but the proposed solution doesn't work for you, please open a new one providing all the information necessary and linking to the solution you have tried.It's not a bug, it's a feature
- Changing enabled
prop during a gesture has no effect, only when a gesture starts (that is, a finger touches the screen) theenabledprop is taken into consideration to decide whether to extract (or not) the gesture and provide it with stream of events to analyze.Native
-gesture may not conform to the standard state flow due to platform specific workarounds to incorporate native views into RNGH.Touchables
- Keep in mind that Legacyfrom RNGH are rendering two additional views that may need to be styled separately to achieve desired effect (styleandcontainerStyleprops).GestureHandlerRootView
- In order for the gesture composition to work, all composed gestures must be attached to the same.Multiple instances of Gesture Handler were detected
This error usually happens when in your project there exists more than one instance of Gesture Handler. It can occur when some of your dependencies have installed Gesture Handler inside their own node_modules
instead of using it as a peer dependency. In this case, two different versions of the Gesture Handler JS module try to install the same Native Module.You can resolve this problem manually by modifying your package.json
file. This will depend on the package manager you are using.
<Tabs groupId="package-managers">
<TabItem value="npm" label="NPM">
You can check which libraries are using Gesture Handler, along with its version, with the command:
npm ls react-native-gesture-handler
json
"overrides": {
"react-native-gesture-handler": <Gesture Handler version>
}
After that you need to run your package manager again:npm install
</TabItem>
<TabItem value="yarn" label="YARN">
You can check which libraries are using Gesture Handler, along with its version, with the command:yarn why react-native-gesture-handler
json
"resolutions": {
"react-native-gesture-handler": <Gesture Handler version>
}
After that you need to run your package manager again:yarn
</TabItem>TurboModuleRegistry.getEnforcing(...): 'RNGestureHandlerModule' could not be found
</Tabs>RNGestureHandlerModule could not be found
If you see an error
, this usually happens whenreact-native-gesture-handlerisn't a direct dependency of your app. If it's only pulled in transitively — for example as apeerDependencyof some other library you use — React Native's codegen won't run for it, so the native module is never generated, even though the package is present innode_modules.react-native-gesture-handlerThe fix is to add
directly to your app's own dependencies:<Tabs groupId="package-managers">
<TabItem value="npm" label="NPM">
npm install react-native-gesture-handler
</TabItem>
<TabItem value="yarn" label="YARN">yarn add react-native-gesture-handler
</TabItem>const gesture = Gesture.Pan()
</Tabs>If the issue persists after that, please open a new issue following our issue reporting guidelines and attach a minimal, reproducible example.
---
Packages/Docs Gesture Handler/Docs/Guides/Upgrading To 3 (packages/docs-gesture-handler/docs/guides/upgrading-to-3.mdx)
---
id: upgrading-to-3
title: Upgrading to the new API introduced in Gesture Handler 3
sidebar_position: 1
---:::tip
To make the migration process easier, we have prepared a SKILL that will help your AI assistant to automatically migrate your codebase to the new API.
:::Migrating gestures
import CodeComparison from '@site/src/components/CodeComparison';
The most important change brought by Gesture Handler 3 is the new hook API. Migration is pretty straightforward. Instead of calling builder methods, everything is passed as a configuration object.
<CodeComparison
label1={"RNGH 2"}
label2={"RNGH 3"}
code1={
.onBegin(() => {
console.log('Pan!');
})
.minDistance(25);}const gesture = usePanGesture({
code2={
onBegin: () => {
console.log('Pan!');
},
minDistance: 25,
});}ForceTouch
/>gesture is not available in hook API.Gesture.Pan()<details>
<summary>Full changes</summary>| RNGH2 | RNGH3 |
| ---------------------- | ----------------------- |
||usePanGesture()|Gesture.Tap()
||useTapGesture()|Gesture.LongPress()
||useLongPressGesture()|Gesture.Rotation()
||useRotationGesture()|Gesture.Pinch()
||usePinchGesture()|Gesture.Fling()
||useFlingGesture()|Gesture.Hover()
||useHoverGesture()|Gesture.Native()
||useNativeGesture()|Gesture.Manual()
||useManualGesture()|Gesture.ForceTouch()
|| Not available in hook API |onStart</details>
Renamed callbacks
In Gesture Handler 3 some of the callbacks were renamed, namely:
| RNGH2 | RNGH3 |
| --------- | -------------- |
||onActivate|onEnd
||onDeactivate|onTouchesCancelled
||onTouchesCancel|const gesture = Gesture.Pan()Here is a comparison of the two APIs:
<CodeComparison
label1={"RNGH 2"}
label2={"RNGH 3"}
code1={
.onStart(() => {
console.log('Pan started!');
})
.onEnd(() => {
console.log('Pan ended!');
})
.onTouchesCancelled(() => {
console.log('Pan touches cancelled!');
});}const gesture = usePanGesture({
code2={
onActivate: () => {
console.log('Pan activated!');
},
onDeactivate: () => {
console.log('Pan deactivated!');
},
onTouchesCancel: () => {
console.log('Pan touches cancelled!');
},
});}onEnd
/>canceled instead of success
In RNGH2,
andonFinalizereceived a secondsuccessboolean parameter. In RNGH3, this has been replaced with acanceledproperty on the event object itself. Note that the logic is inverted —canceled: truecorresponds to the oldsuccess: false.const gesture = Gesture.Tap()<CodeComparison
label1={"RNGH 2"}
label2={"RNGH 3"}
code1={
.onEnd((event, success) => {
if (success) {
console.log('Gesture succeeded!');
}
});}const gesture = useTapGesture({
code2={
onDeactivate: (event) => {
if (!event.canceled) {
console.log('Gesture succeeded!');
}
},
});}onChange
/>onChange
callback has been removed, and its functionality has been integrated intoonUpdate. You can now accesschange*properties inonUpdatecallback.const pan = Gesture.Pan().onChange((e) => {<CodeComparison
label1={"RNGH 2"}
label2={"RNGH 3"}
code1={
console.log(e.changeX);
});}const pan = usePanGesture({
code2={
onUpdate: (e) => {
console.log(e.changeX);
},
});}state
/>state & oldState
The
andoldStateproperties are no longer available in event objects. Tracking state changes can now only be accomplished using the appropriate callbacks.GestureEventEvent types
The types of events have been unified for all callbacks. Each event falls into one of two categories:
for gesture callbacks, orGestureTouchEventforTouchEventcallbacks.stateManagerStateManager
In Gesture Handler 3,
is no longer passed toTouchEventcallbacks. Instead, you should use the globalGestureStateManager.const manual = Gesture.Manual().onTouchesDown((e, stateManager ) => {<CodeComparison
label1={"RNGH 2"}
label2={"RNGH 3"}
code1={
stateManager.activate();
});}const manual = useManualGesture({
code2={
onTouchesDown: (e) => {
GestureStateManager.activate(e.handlerTag);
},
});}Gesture
/>Migrating relations
Composed gestures
Previously, composed gestures were created using
object. In Gesture Handler 3, relations are set up using relation hooks.const pan1 = Gesture.Pan();<CodeComparison
label1={"RNGH 2"}
label2={"RNGH 3"}
code1={
const pan2 = Gesture.Pan();const gesture = Gesture.Simultaneous(pan1, pan2);
}const pan1 = usePanGesture({});
code2={
const pan2 = usePanGesture({});const gesture = useSimultaneousGestures(pan1, pan2);
}
/>Full changes are as follows:
| RNGH2 | RNGH3 |
| ----------------------------------------- | --------------------------- |
| Gesture.Race()|useCompetingGestures()|Gesture.Simultaneous()
||useSimultaneousGestures()|Gesture.Exclusive()
||useExclusiveGestures()|Cross components relations properties
Properties used to define cross-components interactions were renamed.
<CodeComparison
label1={"RNGH 2"}
label2={"RNGH 3"}
code1={
const pan1 = Gesture.Pan();
const pan2 =
Gesture.Pan().requireExternalGestureToFail(pan1);}const pan1 = usePanGesture({});
code2={
const pan2 = usePanGesture({
requireToFail: pan1,
});}
/>Full changes are as follows:
| RNGH2 | RNGH3 |
| ----------------------------------------- | --------------------------- |
| gesture.simultaneousWithExternalGesture|gesture.simultaneousWith|gesture.requireExternalGestureToFail
||gesture.requireToFail|gesture.blocksExternalGesture
||gesture.block|
Migrating components relying on view hierarchy
Certain components, such as SVG
, depend on the view hierarchy to function correctly. In Gesture Handler 3,GestureDetectordisrupts these hierarchies. To resolve this issue, two new detectors have been introduced:InterceptingGestureDetectorandVirtualGestureDetector.:::danger Detectors order
VirtualGestureDetectorhas to be a descendant ofInterceptingGestureDetector.
:::Migrating SVG
In Gesture Handler 2, it was possible to use GestureDetector
directly onSVG. In Gesture Handler 3, the correct way to interact withSVGis to useInterceptingGestureDetectorandVirtualGestureDetector.<CodeComparison
label1={"RNGH 2"}
label2={"RNGH 3"}
code1={
<GestureDetector gesture={containerTap}>
<Svg>
<GestureDetector gesture={circleTap}>
<Circle />
</GestureDetector>
</Svg>
</GestureDetector>}
code2={
<InterceptingGestureDetector gesture={containerTap}>
<Svg>
<VirtualGestureDetector gesture={circleTap}>
<Circle />
</VirtualGestureDetector>
</Svg>
</InterceptingGestureDetector>}
/>Old components
Buttons
RNGH3 introduces the Touchable
component — a flexible, unified replacement for all previous button types. While Touchableshares most of the logic with our standard buttons, it offers a more customizable API.To help you migrate, here is the current state of our button components:
- Touchable
- The recommended component. Can be used to replicate both RectButtonandBorderlessButtoneffects by adjusting theunderlayColor,activeOpacity, andanimationDurationprops.RectButton
- To replace, addunderlayColor="black"andanimationDuration={0}(the legacy button switches states instantly).BorderlessButton
- To replace, addactiveOpacity={0.3}andanimationDuration={0}.RectButton
- Android ripple: legacy/BorderlessButtonshow the native theme ripple on Android by default, whileTouchabledisables the ripple unlessandroidRippleis set. To preserve that behavior, setandroidRippleon Android instead of the underlay/opacity/animation props above (don't combine them, or you'll get both a ripple and an underlay animation at once):RectButton
-→androidRipple={{}}BorderlessButton
-→androidRipple={{ borderless: true }}(matches the legacy borderless ripple shape)See the Touchable
docsfor full Platform.selectexamples.- Standard Buttons (Deprecated) - BaseButton
,RectButtonandBorderlessButtonare still available but are now deprecated. They have been internally rewritten using the new Hooks API to resolve long-standing issues.- Legacy Buttons (Deprecated): The original, pre-rewrite versions are still accessible, but have been renamed with a Legacy
prefix (e.g.,LegacyRectButton).Although the legacy JS implementation of the buttons is still available, they also use the new host component internally. Because of that, PureNativeButton
is no longer available in Gesture Handler 3.<details>
<summary>Legacy buttons</summary>| RNGH2 | RNGH3 |
| --------------------- | --------------------------- |
| RawButton|LegacyRawButton|BaseButton
||LegacyBaseButton|RectButton
||LegacyRectButton|BorderlessButton
||LegacyBorderlessButton|PureNativeButton
|| Not available in Gesture Handler 3 |</details>
ReanimatedSwipeable
ReanimatedSwipeable
has been rewritten using the new hook API. Additionally,enabled,hitSlop,enableTrackpadTwoFingerGesture,dragOffsetFromLeftanddragOffsetFromRightprops now acceptSharedValues.dragOffsetFromRight
now accepts negative values. If you were using it with positive values, make sure to change the sign when migrating.Touchables
Touchable
can also be used as a substitute for old, deprecated Touchables.TouchableOpacity
- To replace, addactiveOpacity={0.2}andanimationDuration={{ in: 0, out: 150 }}.TouchableHighlight
- To replace, carryunderlayColorandactiveOpacityover unchanged and addactiveUnderlayOpacity={1}so the underlay is solid. A perfect 1:1 replacement isn't possible: inTouchableHighlightthe container's own background becomes the underlay andactiveOpacitydims just the children on top (the underlay shows through the dimmed children), whileTouchablerenders the underlay as a separate layer between background and children, and itsactiveOpacitydims the whole component. The visual feedback may differ slightly because of the different layering.TouchableNativeFeedback
- To replace, addandroidRipple={{ foreground: true }}— the legacy component defaults touseForeground: true, so{ foreground: true }is the closest match. Dropforegroundonly when the original code setuseForeground={false}; addcolor,radius, orborderlessif it customized thebackgroundprop.TouchableWithoutFeedback
-can be replaced with a plainTouchable.Other components
Other components have also been internally rewritten using the new hook API but are exported under their original names, so no changes are necessary on your part. However, if you need to use the previous implementation for any reason, the old components are also available and are prefixed with Legacy
, e.g.,ScrollViewis now available asLegacyScrollView.<details>
<summary>Legacy components</summary>| RNGH2 | RNGH3 |
| --------------------- | --------------------------- |
| ScrollView|LegacyScrollView|FlatList
||LegacyFlatList|RefreshControl
||LegacyRefreshControl|Switch
||LegacySwitch|TextInput
||LegacyTextInput|DrawerLayoutAndroid
||LegacyDrawerLayoutAndroid|</details>
createNativeWrapper
createNativeWrapper
is deprecated and it is now exported aslegacy_createNativeWrapper.Replaced types
Most of the types, like TapGesture
, are still present in Gesture Handler 3. However, they are now used in new hook API. Types for old API now haveLegacyprefix, e.g.TapGesturebecomesLegacyTapGesture.<details>
<summary>Legacy types</summary>| RNGH2 | RNGH3 |
| ----------------------- | ----------------------------- |
| PanGesture|LegacyPanGesture|TapGesture
||LegacyTapGesture|LongPressGesture
||LegacyLongPressGesture|RotationGesture
||LegacyRotationGesture|PinchGesture
||LegacyPinchGesture|FlingGesture
||LegacyFlingGesture|HoverGesture
||LegacyHoverGesture|NativeGesture
||LegacyNativeGesture|ManualGesture
||LegacyManualGesture|ForceTouchGesture
||LegacyForceTouchGesture|ComposedGesture
| | |
||LegacyComposedGesture|RaceGesture
||LegacyRaceGesture|SimultaneousGesture
||LegacySimultaneousGesture|ExclusiveGesture
||LegacyExclusiveGesture|RawButtonProps
| | |
||LegacyRawButtonProps|BaseButtonProps
||LegacyBaseButtonProps|RectButtonProps
||LegacyRectButtonProps|BorderlessButtonProps
||LegacyBorderlessButtonProps|</details>
---
Packages/Docs Gesture Handler/Docs/Legacy Gestures/ Category .Json (packages/docs-gesture-handler/docs/legacy-gestures/_category_.json)
{
"label": "Legacy Gesture Handler 2",
"position": 8,
"link": {
"type": "generated-index"
}
}---
Packages/Docs Gesture Handler/Docs/Legacy Gestures/ Shared/V2 Info (packages/docs-gesture-handler/docs/legacy-gestures/_shared/v2-info.md)
:::warning
Heads up! This page covers the old builder-based API from Gesture Handler 2. We recommend checking out the newer hook-based API from Gesture Handler 3.
:::================================================
SYMLINK: packages/docs-gesture-handler/docs/legacy-gestures/_shared/base-continuous-gesture-callbacks.md -> base-continuous-gesture-callbacks.md
================================================================================================
SYMLINK: packages/docs-gesture-handler/docs/legacy-gestures/_shared/base-continuous-gesture-config.md -> base-continuous-gesture-config.md
================================================================================================
SYMLINK: packages/docs-gesture-handler/docs/legacy-gestures/_shared/base-gesture-callbacks.md -> base-gesture-callbacks.md
================================================================================================
SYMLINK: packages/docs-gesture-handler/docs/legacy-gestures/_shared/base-gesture-config.md -> base-gesture-config.md
================================================================================================
SYMLINK: packages/docs-gesture-handler/docs/legacy-gestures/_shared/base-gesture-event-data.md -> base-gesture-event-data.md
================================================================================================
SYMLINK: packages/docs-gesture-handler/docs/legacy-gestures/_shared/gesture-detector-functional1.md -> gesture-detector-functional1.md
================================================================================================
SYMLINK: packages/docs-gesture-handler/docs/legacy-gestures/fling-gesture.md -> fling-gesture.md
================================================================================================
SYMLINK: packages/docs-gesture-handler/docs/legacy-gestures/force-touch-gesture.md -> force-touch-gesture.md
================================================================================================
SYMLINK: packages/docs-gesture-handler/docs/legacy-gestures/gesture-composition.md -> gesture-composition.md
================================================================================================
SYMLINK: packages/docs-gesture-handler/docs/legacy-gestures/gesture-detector.md -> gesture-detector.md
================================================================================================
SYMLINK: packages/docs-gesture-handler/docs/legacy-gestures/gesture.md -> gesture.md
================================================================================================
SYMLINK: packages/docs-gesture-handler/docs/legacy-gestures/hover-gesture.md -> hover-gesture.md
================================================================================================
SYMLINK: packages/docs-gesture-handler/docs/legacy-gestures/long-press-gesture.md -> long-press-gesture.md
================================================================================================
SYMLINK: packages/docs-gesture-handler/docs/legacy-gestures/manual-gesture.md -> manual-gesture.md
================================================================================================
SYMLINK: packages/docs-gesture-handler/docs/legacy-gestures/native-gesture.md -> native-gesture.md
================================================================================================
SYMLINK: packages/docs-gesture-handler/docs/legacy-gestures/pan-gesture.md -> pan-gesture.md
================================================================================================
SYMLINK: packages/docs-gesture-handler/docs/legacy-gestures/pinch-gesture.md -> pinch-gesture.md
================================================================================================
SYMLINK: packages/docs-gesture-handler/docs/legacy-gestures/rotation-gesture.md -> rotation-gesture.md
================================================================================================
SYMLINK: packages/docs-gesture-handler/docs/legacy-gestures/state-manager.md -> state-manager.md
================================================================================================
SYMLINK: packages/docs-gesture-handler/docs/legacy-gestures/tap-gesture.md -> tap-gesture.md
================================================================================================
SYMLINK: packages/docs-gesture-handler/docs/legacy-gestures/touch-events.md -> touch-events.md
================================================---
Packages/Docs Gesture Handler/Docs/Under The Hood/ Category .Json (packages/docs-gesture-handler/docs/under-the-hood/_category_.json)
{
"label": "Under the hood",
"position": 7,
"link": {
"type": "generated-index"
}
}---
Packages/Docs Gesture Handler/Docs/Under The Hood/How Does It Work (packages/docs-gesture-handler/docs/under-the-hood/how-does-it-work.md)
---
id: how-does-it-work
title: How does it work?
sidebar_label: How does it work?
---Units
All handler component properties and event attributes that represent onscreen dimensions are expressed in screen density independent units we refer to as "points".
These are the units commonly used in the React Native ecosystem (e.g. in the layout system).
They do not map directly to physical pixels but instead to iOS's points and to dp units on Android.iOS
All gestures are implemented using UIGestureRecognizers. Some of them have been slightly modified to allow for more customization and to conform to the state flow of RNGH. When you assign a gesture configuration to the GestureDetector
, it creates all the required recognizers and assigns them to the child view of the detector. From this point, most of the heavy lifting is handled by the UIKit (with our help to correctly implement interactions between gestures).Android
Unfortunately, Android doesn't provide an easy way of handling gestures, hence most of them were implemented from scratch, including a system for managing how the gestures should interact with each other. Here's a quick overview of how it works:
When you wrap a component with GestureHandlerRootView, it allows for the RNGH to intercept all touch events on that component and process them, deciding whether they should be handled by one of the gesture handlers or passed to the underlying view. Gesture handlers are created right at the moment you declare them in order to initialize all of the necessary handlers natively. Every GestureHandlerRootViewalso has a specific handler to decide whether to pass the touch events or to consume them. It can never activate, only begin, end or be cancelled. When this handler is in theUNDETERMINEDstate, it means that there is no touch in progress; however, when the touch starts it transitions to theBEGANstate. As long as it stays in that state, no touch event is consumed, but as soon as it gets cancelled (meaning that some handler has activated) all incoming touch events get consumed, preventing the underlying view from receiving them.When a pointer touches the screen, the view tree is traversed in order to extract all handlers attached to the views below the finger (including the one attached to the GestureHandlerRootView
) and all extracted handlers transition to theBEGANstate, signalling that the gesture may have begun. The touch events continue to be delivered to all extracted handlers until one of them recognizes the gesture and tries to activate. At this point, the orchestrator checks whether this gesture should wait for any other of the extracted gestures to fail. If it does, it's put to the waiting list, if it doesn't, it gets activated and all other gestures (that are not simultaneous with it) get cancelled. When a gesture handler transitions to a finished state (the gesture recognized by it stops, fails or gets cancelled) the orchestrator checks the waiting handlers. Every one of them that waited for the gesture that just failed tries to activate again (and again the orchestrator checks if it should wait for any of the extracted gestures...).---
Packages/Docs Gesture Handler/Docs/Under The Hood/State (packages/docs-gesture-handler/docs/under-the-hood/state.mdx)
---
id: state
title: Handler State
sidebar_label: Handler State
---import GestureStateFlowExample from '@site/src/examples/GestureStateFlowExample';
Gesture handlers can be treated as "state machines".
At any given time, each handler instance has an assigned state that can change when new touch events occur or can be forced to change by the touch system in certain circumstances.States manage the internal recognition process. You can hook into these transitions using specific gesture callbacks.
| State | Description | Callback |
| :----------------- | :--------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| UNDETERMINED| The default initial state of every handler. | — |
| BEGAN| The handler has started receiving touch data but hasn't yet met the activation criteria. | onBegin|
| ACTIVE| The gesture is recognized and activation criteria are met. | onActivatewhen it first transitions into the ACTIVEstate. <br /><br />onUpdatewhen it has new data about the gesture. |END
|| The user successfully completed the gesture. |onDeactivatewithevent.canceledset tofalse. <br/><br/>onFinalizewithevent.canceledset tofalse. |FAILED
|| The handler failed to recognize the gesture. |onDeactivateif the gesture was inACTIVEstate before, withevent.canceledset totrue. <br/><br/>onFinalizewithevent.canceledset totrue. |CANCELLED
|| The system interrupted the gesture. |onDeactivateif the gesture was inACTIVEstate before, withevent.canceledset totrue. <br/><br/>onFinalizewithevent.canceledset totrue. |State flows
The most typical flow of state is when a gesture picks up on an initial touch event, then recognizes it, then acknowledges its ending and resets itself back to the initial state. Drag or hold the circle below to see how each state machine reacts.
<GestureStateFlowExample />
---
Packages/Docs Gesture Handler/Versioned Docs/Version 1.X/About Handlers (packages/docs-gesture-handler/versioned_docs/version-1.x/about-handlers.md)
---
id: about-handlers
title: About Gesture Handlers
sidebar_label: About Gesture Handlers
---Gesture handlers are the core building blocks of this library.
We use this term to describe elements of the native touch system that the library allows us to instantiate and control from JavaScript using React's Component interface.Each handler type is capable of recognizing one type of gesture (pan, pinch, etc.) and provides gesture-specific information via events (translation, scale, etc.).
Handlers analyze touch stream synchronously in the UI thread. This allows for uninterrupted interactions even when the JavaScript thread is blocked.
Each handler works as an isolated state machine. It takes touch stream as an input and based on it, it can flip between states.
When a gesture starts, based on the position where the finger was placed, a set of handlers that may be interested in recognizing the gesture is selected.
All the touch events (touch down, move, up, or when other fingers are placed or lifted) are delivered to all of the handlers selected initially.
When one gesture becomes active, it cancels all the other gestures (read more about how to influence this process in "Cross handler interactions" section).Gesture handler components do not instantiate a native view in the view hierarchy. Instead, they are kept in the library's own registry and are only connected to native views. When using any of the gesture handler components, it is important for it to have a native view rendered as a child.
Since handler components don't have corresponding views in the hierarchy, the events registered with them are actually hooked into the underlying view.Available gesture handlers
Currently, the library provides the following list of gestures. Their parameters and attributes they provide to gesture events are documented under each gesture page:
- PanGestureHandler
- TapGestureHandler
- LongPressGestureHandler
- RotationGestureHandler
- FlingGestureHandler
- PinchGestureHandler
- ForceTouchGestureHandlerDiscrete vs continuous
We distinguish between two types of gestures: discrete and continuous.
Continuous gesture handlers can be active for a long period of time and will generate a stream of gesture events until the gesture is over.
An example of a continuous handler is PanGestureHandlerthat once activated, will start providing updates about translation and other properties.On the other hand, discrete gesture handlers once activated will not stay in the active state but will end immediately.
LongPressGestureHandleris a discrete handler, as it only detects if the finger is placed for a sufficiently long period of time, it does not track finger movements (as that's the responsibility of PanGestureHandler).Keep in mind that onGestureEvent
is only generated in continuous gesture handlers and shouldn't be used in theTapGestureHandlerand other discrete handlers.Nesting handlers
Handler components can be nested. In any case it is recommended that the innermost handler renders a native view component. There are some limitations that apply when using useNativeDriver
flag. An example of nested handlers:
class Multitap extends Component {
render() {
return (
<LongPressGestureHandler
onHandlerStateChange={this._onLongpress}
minDurationMs={800}>
<TapGestureHandler
onHandlerStateChange={this._onSingleTap}
waitFor={this.doubleTapRef}>
<TapGestureHandler
ref={this.doubleTapRef}
onHandlerStateChange={this._onDoubleTap}
numberOfTaps={2}>
<View style={styles.box} />
</TapGestureHandler>
</TapGestureHandler>
</LongPressGestureHandler>
);
}
}
NativeViewGestureHandlerUsing native components
Gesture handler library exposes a set of components normally available in React Native that are wrapped in
.ScrollView
Here is a list of exposed components:-
FlatList
-Switch
-TextInput
-DrawerLayoutAndroid
-(Android only)ScrollViewIf you want to use other handlers or buttons nested in a
, use thewaitForproperty to define the interaction between a handler andScrollView.useNativeDriverEvents with
Animated.eventBecause handlers do not instantiate native views but instead hook up to their child views, directly nesting two gesture handlers using
is not currently supported.<Animated.View>
To workaround this limitation we recommend placing ancomponent in between the handlers.Instead of doing:
const PanAndRotate = () => (
<PanGestureHandler onGestureEvent={Animated.event({ ... }, { useNativeDriver: true })}>
<RotationGestureHandler onGestureEvent={Animated.event({ ... }, { useNativeDriver: true })}>
<Animated.View style={animatedStyles}/>
</RotationGestureHandler>
</PanGestureHandler>
);
Place an<Animated.View>in between the handlers:
const PanAndRotate = () => (
<PanGestureHandler onGestureEvent={Animated.event({ ... }, { useNativeDriver: true })}>
<Animated.View>
<RotationGestureHandler onGestureEvent={Animated.event({ ... }, { useNativeDriver: true })}>
<Animated.View style={animatedStyles}/>
</RotationGestureHandler>
</Animated.View>
</PanGestureHandler>
);
Another consequence of handlers depending on their native child components is that when using auseNativeDriverflag with anAnimated.event, the child component must be wrapped by anAnimated.APIe.g.<Animated.View>instead of just a<View>:
class Draggable extends Component {
render() {
return (
<PanGestureHandler onGestureEvent={Animated.event({ ... }, { useNativeDriver: true })}>
<Animated.View style={animatedStyles} /> {/ <-- NEEDS TO BE Animated.View /}
</PanGestureHandler>
);
}
};
---Example/Packages/Docs Gesture Handler/Versioned Docs/Version 1.X/Contributing (packages/docs-gesture-handler/versioned_docs/version-1.x/contributing.md)
---
id: contributing
title: Contributing
---If you are interested in the project and want to contribute or support it in other ways don't hesitate to contact me on Twitter!
All PRs are welcome, but talk to us before you start working on something big.
The easiest way to get started with contributing code is by:
- Reviewing the list of open issues and trying to solve the ones that seem approachable to you.
- Updating the documentation whenever you see some information is unclear, missing or out of date.Code is only one way you can contribute. You may want to consider replying on issues if you know how to help.
---
Packages/Docs Gesture Handler/Versioned Docs/Version 1.X/Credits (packages/docs-gesture-handler/versioned_docs/version-1.x/credits.md)
---
id: credits
title: Credits
---This project is supported by amazing people from Expo.io and Software Mansion.
[](https://expo.io)
[](https://swmansion.com)---
Packages/Docs Gesture Handler/Versioned Docs/Version 1.X/Example (packages/docs-gesture-handler/versioned_docs/version-1.x/example.md)
---
id: example
title: Running Example App
---import useBaseUrl from '@docusaurus/useBaseUrl';
import GifGallery from '@site/components/GifGallery'folder in the repo.Example/
It showcases the majority of the Gesture Handler library features.
The app consists of the list of single screen examples presenting the capabilities of the library.
Each example is located under a separate folder under.<GifGallery>
<img src={useBaseUrl("gifs/sampleapp.gif")} width="180" height="320" />
</GifGallery>Running example app on Expo
You can run example app on Expo. Follow instructions under this link to do so. Note that the app published to Expo is not the most up to date version. We publish updates whenever new version of Expo SDK is released. If you wish to try the most up to date version you will have to run example app locally. For that see below 👇
Running example app locally
Before you begin you should follow React Native's setup steps to make sure you have all the tools necessary to build and run React Native apps installed.
The example app is a regular React Native app, so in case of problems or to learn about available commands you may want to check react-native cli documentation.In order to run example app you need to clone the repo first:
git clone [email protected]:software-mansion/react-native-gesture-handler.git
Then go to the library folder:cd react-native-gesture-handler/
Install dependencies of library with the following command:yarn
Then go to theExamplefolder:
cd Example
Install dependencies of example with the following command:yarn
Run development server:yarn start
Finally run one of the commands below in order to build, install and launch the app on Android:react-native run-android
or on iOS:react-native run-ios
You will need to have an Android or iOS device or emulator connected andreact-native-clipackage installed globally.ScrollView---
Packages/Docs Gesture Handler/Versioned Docs/Version 1.X/Getting Started (packages/docs-gesture-handler/versioned_docs/version-1.x/getting-started.md)
---
id: getting-started
title: Getting Started
sidebar_label: Getting Started
slug: /
---Gesture Handler aims to replace React Native's built in touch system called Gesture Responder System.
The motivation for building this library was to address the performance limitations of React Native's Gesture Responder System and to provide more control over the built-in native components that can handle gestures.
We recommend this talk by Krzysztof Magiera in which he explains issues with the responder system.In a nutshell, the library provides:
- A way to use a platform's native touch handling system for recognizing pinch, rotation and pan (besides a few other gestures).
- The ability to define relations between gesture handlers, e.g. when you have a pan handler inyou can make thatScrollViewwait until it knows pan won't recognize.react-native
- Mechanisms to use touchables that run in native thread and follow platform default behavior; e.g. in the event they are in a scrollable component, turning into pressed state is slightly delayed to prevent it from highlighting when you fling.
- The possibility to implement smooth gesture interactions thanks to Animated Native Driver — interactions will be responsive even when the JS thread is overloaded.Installation
Requirements
| version |
version |React.createRef()
| --------- | ---------------------- |
| 1.4.0+ | 0.60.0+ |
| 1.1.0+ | 0.57.2+ |
| <1.1.0 | 0.50.0+ |It may be possible to use newer versions of react-native-gesture-handler on React Native with version <= 0.59 by reverse Jetifying.
Read more on that here https://github.com/mikehardy/jetifier#to-reverse-jetify--convert-node_modules-dependencies-to-support-librariessupport for interactions you need to use v16.3 of React.expo install react-native-gesture-handlerExpo
#### Managed Expo
To use the version of react-native-gesture-handler that is compatible with your managed Expo project, run
.yarnThe Expo SDK incorporates the latest version of react-native-gesture-handler available at the time of each SDK release, so managed Expo apps might not always support all our latest features as soon as they are available.
#### Bare React Native
Since the library uses native support for handling gestures, it requires an extended installation to the norm. If you are starting a new project, you may want to initialize it with expo-cli and use a bare template, they come pre-installed with react-native-gesture-handler.
JS
First, install the library using
:
yarn add react-native-gesture-handler
or withnpmif you prefer:
npm install --save react-native-gesture-handler
After installation, wrap your entry point with<GestureHandlerRootView>.For example:
export default function App() {
return <GestureHandlerRootView>{/ content /}</GestureHandlerRootView>;
}
:::infoshouldCancelWhenOutside
If you use props such as,simultaneousHandlers,waitForetc. with gesture handlers, the handlers need to be mounted under a singleGestureHandlerRootView. So it's important to keep theGestureHandlerRootViewas close to the actual root view as possible.GestureHandlerRootViewNote that
acts like a normalView. So if you want it to fill the screen, you will need to pass{ flex: 1 }like you'll need to do with a normalView. By default, it'll take the size of the content nested inside.
::::::tip
If you're using gesture handler in your component library, you may want to wrap your library's code in the GestureHandlerRootView component. This will avoid extra configuration for the user.
:::#### Linking
Important: You only need to do this step if you're using React Native 0.59 or lower. Since v0.60, linking happens automatically.
react-native link react-native-gesture-handler
MainActivity.javaAndroid
Follow the steps below:
If you use one of the _native navigation libraries_ (e.g.
wix/react-native-navigation),
you should follow this separate guide to get
gesture handler library set up on Android. Ignore the rest of this step – it
only applies to RN apps that use a standard Android project layout.#### Updating
MainActivity.javaUpdate your
file (or wherever you create an instance ofReactActivityDelegate), so that it overrides the method responsible for creatingReactRootViewinstance and then use the root view wrapper provided by this library. Do not forget to importReactActivityDelegate,ReactRootView, andRNGestureHandlerEnabledRootView:
package com.swmansion.gesturehandler.react.example;
import com.facebook.react.ReactActivity;
+ import com.facebook.react.ReactActivityDelegate;
+ import com.facebook.react.ReactRootView;
+ import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;
public class MainActivity extends ReactActivity {
@Override
protected String getMainComponentName() {
return "Example";
}
+ @Override
+ protected ReactActivityDelegate createReactActivityDelegate() {
+ return new ReactActivityDelegate(this, getMainComponentName()) {
+ @Override
+ protected ReactRootView createRootView() {
+ return new RNGestureHandlerEnabledRootView(MainActivity.this);
+ }
+ };
+ }
}
#### Usage with modals on AndroidGestureHandlerRootViewOn Android RNGH does not work by default because modals are not located under React Native Root view in native hierarchy.
To fix that, components need to be wrapped with.For example:
export default function Example() {
return (
<Modal>
<GestureHandlerRootView>
<DraggableBox />
</GestureHandlerRootView>
</Modal>
);
}
iOS
There is no additional configuration required on iOS except what follows in the next steps.
If you're in a CocoaPods project (the default setup since React Native 0.60),
make sure to install pods before you run your app:
cd ios && pod install
For React Native 0.61 or greater, add the library as the first import in your index.js file:import 'react-native-gesture-handler';
ReactRootViewWith wix/react-native-navigation
If you are using a native navigation library like wix/react-native-navigation you need to follow a different setup for your Android app to work properly. The reason is that both native navigation libraries and Gesture Handler library need to use their own special subclasses of
.GestureHandlerRootViewInstead of changing Java code you will need to wrap every screen component using
on the JS side. This can be done for example at the stage when you register your screens. Here's an example:
import { Navigation } from 'react-native-navigation';
import FirstTabScreen from './FirstTabScreen';
import SecondTabScreen from './SecondTabScreen';
import PushedScreen from './PushedScreen';
// register all screens of the app (including internal ones)
export function registerScreens() {
Navigation.registerComponent(
'example.FirstTabScreen',
() => {
return (
<GestureHandlerRootView>
<FirstTabScreen />
</GestureHandlerRootView>
);
},
() => FirstTabScreen
);
Navigation.registerComponent(
'example.SecondTabScreen',
() => {
return (
<GestureHandlerRootView>
<SecondTabScreen />
</GestureHandlerRootView>
);
},
() => SecondTabScreen
);
Navigation.registerComponent(
'example.PushedScreen',
() => {
return (
<GestureHandlerRootView>
<PushedScreen />
</GestureHandlerRootView>
);
},
() => PushedScreen
);
}
You can check out this example project to see this kind of set up in action.GestureHandlerRootViewRemember that you need to wrap each screen that you use in your app with
as with native navigation libraries each screen maps to a separate root view. It will not be enough to wrap the main screen only.package.jsonTesting
In order to load mocks provided by the library add following to your jest config in
:
"setupFiles": ["./node_modules/react-native-gesture-handler/jestSetup.js"]
Example:"jest": {
"preset": "react-native",
"setupFiles": ["./node_modules/react-native-gesture-handler/jestSetup.js"]
}
---React.createRef()Packages/Docs Gesture Handler/Versioned Docs/Version 1.X/Interactions (packages/docs-gesture-handler/versioned_docs/version-1.x/interactions.md)
---
id: interactions
title: Cross handler interactions
sidebar_label: Cross handler interactions
---Gesture handlers can "communicate" with each other to support complex gestures and control how they _activate_ in certain scenarios.
There are two means of achieving that described in the sections below.
In each case, it is necessary to provide a reference of one handler as a property to the other.
Gesture handler relies on ref objects created using, introduced in React 16.3.ACTIVESimultaneous recognition
By default, only one gesture handler is allowed to be in the
state.BEGAN
So when a gesture handler recognizes a gesture it cancels all other handlers in thestate and prevents any new handlers from receiving a stream of touch events as long as it remainsACTIVE.simultaneousHandlersproperty (available for all types of handlers).ACTIVE
This property accepts a ref or an array of refs to other handlers.
Handlers connected in this way will be allowed to remain in thestate at the same time.PinchGestureHandlerUse cases
Simultaneous recognition needs to be used when implementing a photo preview component that supports zooming (scaling) the photo, rotating and panning it while zoomed in.
In this case we would use a,RotationGestureHandlerandPanGestureHandlerthat would have to simultaneously recognize gestures.Example
See the "Scale, rotate & tilt" example from the GestureHandler Example App or view it directly on your phone by visiting our expo demo.
class PinchableBox extends React.Component {
// ...take a look on full implementation in an Example app
render() {
const imagePinch = React.createRef();
const imageRotation = React.createRef();
return (
<RotationGestureHandler
ref={imageRotation}
simultaneousHandlers={imagePinch}
onGestureEvent={this._onRotateGestureEvent}
onHandlerStateChange={this._onRotateHandlerStateChange}>
<Animated.View>
<PinchGestureHandler
ref={imagePinch}
simultaneousHandlers={imageRotation}
onGestureEvent={this._onPinchGestureEvent}
onHandlerStateChange={this._onPinchHandlerStateChange}>
<Animated.View style={styles.container} collapsable={false}>
<Animated.Image
style={[
styles.pinchableImage,
{
/ events-related transformations /
},
]}
/>
</Animated.View>
</PinchGestureHandler>
</Animated.View>
</RotationGestureHandler>
);
}
}
Awaiting other handlers
Use cases
A good example where awaiting is necessary is when we want to have single and double tap handlers registered for one view (a button).
In such a case we need to make single tap handler await a double tap.
Otherwise if we try to perform a double tap the single tap handler will fire just after we hit the button for the first time, consequently cancelling the double tap handler.
Example
See the "Multitap" example from GestureHandler Example App or view it directly on your phone by visiting our expo demo.
const doubleTap = React.createRef();
const PressBox = () => (
<TapGestureHandler
onHandlerStateChange={({ nativeEvent }) =>
nativeEvent.state === State.ACTIVE && Alert.alert('Single tap!')
}
waitFor={doubleTap}>
<TapGestureHandler
ref={doubleTap}
onHandlerStateChange={({ nativeEvent }) =>
nativeEvent.state === State.ACTIVE && Alert.alert("You're so fast")
}
numberOfTaps={2}>
<View style={styles.box} />
</TapGestureHandler>
</TapGestureHandler>
);
---onHandlerStateChangePackages/Docs Gesture Handler/Versioned Docs/Version 1.X/Resources (packages/docs-gesture-handler/versioned_docs/version-1.x/resources.md)
---
id: resources
title: Learning Resources
---Apps
Gesture Handler Example App – official gesture handler "showcase" app.
Gesture Handler Example on Expo – the official app you can install and play with using Expo.
Talks and workshops
Declarative future of gestures and animations in React Native by Krzysztof Magiera - talk that explains motivation behind creating gesture handler library. It also presents react-native-reanimated and how and when it can be used with gesture handler.
React Native workshop with Expo team @ReactEurope 2018 by Brent Vatne – great workshop explaining gesture handler in details and presenting a few exercises that will help get you started.
Living in an async world of React Native by Krzysztof Magiera – talk which highlights some issue with the React Native's touch system Gesture Handler aims to address. Also the motivation for building this library is explained.
React Native Touch & Gesture by Krzysztof Magiera - talk explaining JS responder system limitations and points out some of the core features of Gesture Handler.
---
Packages/Docs Gesture Handler/Versioned Docs/Version 1.X/State (packages/docs-gesture-handler/versioned_docs/version-1.x/state.md)
---
id: state
title: Handler State
sidebar_label: Handler State
---As described in "About Gesture Handlers", gesture handlers can be treated as "state machines".
At any given time, each handler instance has an assigned state that can change when new touch events occur or can be forced to change by the touch system in certain circumstances.A gesture handler can be in one of the six possible states:
- UNDETERMINED
- FAILED
- BEGAN
- CANCELLED
- ACTIVE
- ENDEach state has its own description below.
Accessing state
callback and the destructurednativeEventargument passed to it.nativeEvent
This can be done by comparing the'sstateattribute to one of the constants exported under theStateobject (see example below).
import { State, LongPressGestureHandler } from 'react-native-gesture-handler';
class Demo extends Component {
_handleStateChange = ({ nativeEvent }) => {
if (nativeEvent.state === State.ACTIVE) {
Alert.alert('Longpress');
}
};
render() {
return (
<LongPressGestureHandler onHandlerStateChange={this._handleStateChange}>
<Text style={styles.buttonText}>Longpress me</Text>
</LongPressGestureHandler>
);
}
}
UNDETERMINEDState flows
The most typical flow of state is when a gesture handler picks up on an initial touch event then recognizes it then acknowledges its ending then resets itself back to the initial state.
The flow looks as follows (longer arrows represent that there are possibly more touch events received before the state changes):
->BEGAN------>ACTIVE------>END->UNDETERMINEDUNDETERMINEDAnother possible flow is when a handler receives touches that cause a recognition failure:
->BEGAN------>FAILED->UNDETERMINEDUNDETERMINEDAt last, when a handler does properly recognize the gesture but then is interrupted by the touch system. In that case, the gesture recognition is canceled and the flow looks as follows:
->BEGAN------>ACTIVE------>CANCELLED->UNDETERMINEDmaxDistStates
The section below describes all possible handler states:
UNDETERMINED
This is the initial state of each handler and it goes into this state after it's done recognizing a gesture.
FAILED
A handler received some touches but for some reason didn't recognize them. For example, if a finger travels more distance than a defined
property allows, then the handler won't become active but will fail instead. Afterwards, its state will be reset toUNDETERMINED.CANCELLEDBEGAN
Handler has started receiving touch stream but hasn't yet received enough data to either fail or activate.
CANCELLED
The gesture recognizer has received a signal (possibly new touches or a command from the touch system controller) resulting in the cancellation of a continuous gesture. The gesture's state will become
until it is finally reset to the initial state,UNDETERMINED.ACTIVEACTIVE
Handler has recognized a gesture. It will become and stay in the
state until the gesture finishes (e.g. when user lifts the finger) or gets cancelled by the touch system. Under normal circumstances the state will then turn intoEND. In the case that a gesture is cancelled by the touch system, its state would then becomeCANCELLED.ACTIVE
Learn about discrete and continuous handlers here to understand how long a handler can be kept in thestate.ENDEND
The gesture recognizer has received touches signalling the end of a gesture. Its state will become
until it is reset toUNDETERMINED.TouchableHighlight---
Packages/Docs Gesture Handler/Versioned Docs/Version 1.X/Troubleshooting (packages/docs-gesture-handler/versioned_docs/version-1.x/troubleshooting.md)
---
id: troubleshooting
title: Troubleshooting
---Troubleshooting
Thanks for giving this library a try! We are sorry that you might have encountered issues though. Here is how you can seek help:
1. Search over the issues on Github. There is a chance someone had this problem in the past and it has been resolved!
2. When sure your problem hasn't been reported or was reported but the proposed solution doesn't work for you please follow our issue reporting guidelines.
3. You can try seeking help on Expo Developers Slack channel where we often hang out.
4. If you feel like reading the source code I highly recommend it, as this is by far the best resource and gives you the most up to date insights into how the library works and what might be causing the bug.
5. If you managed to find the solution consider contributing a fix or update our documentation to make this information easier to find for the others in the future.
Reporting issues
This library is maintained by a very small team.
Please be mindful of that when reporting an issue and when it happens that we can't get back to you as soon as you might expect.
We would love to fix all the problems as soon as possible, but often our time is constrained by other issues/features or projects.
To make it easier for us to understand your issue and to be able to approach it sooner you can help by:
- Making sure the issue description is complete. Please include all the details about your environment (library version, RN version, device OS etc).
- It is the best to provide an example app that reproduces the issue you are having. Put it up on gist, snack or create a repo on Github – it doesn't matter as long as we can easily pull it in, run and see the issue.
- Explain how you run your repro app and what steps to take to reproduce the issue.
- Isolate your issue from other dependencies you might be using and make the repro app as minimal as possible.
- If you have spent some time figuring out the root cause of the problem you can leave a note about your findings so far.
- __Do not comment on closed issues__. It is very unlikely that we are going to notice your comment in such a case. If the issue has been closed, but the proposed solution doesn't work for you, please open a new one providing all the information necessary and linking to the solution you have tried.---
Packages/Docs Gesture Handler/Versioned Docs/Version 1.X/Api/Components/Buttons (packages/docs-gesture-handler/versioned_docs/version-1.x/api/components/buttons.mdx)
---
id: buttons
title: Buttons
sidebar_label: Buttons
---import useBaseUrl from '@docusaurus/useBaseUrl';
import GifGallery from '@site/components/GifGallery';<GifGallery>
<img src={useBaseUrl('gifs/samplebutton.gif')} width="280" />
</GifGallery>Gesture handler library provides native components that can act as buttons. These can be treated as a replacement to
orTouchableOpacityfrom RN core. Gesture handler's buttons recognize touches in native which makes the recognition process deterministic, allows for rendering ripples on Android in highly performant way (TouchableNativeFeedbackrequires that touch event does a roundtrip to JS before we can update ripple effect, which makes ripples lag a bit on older phones), and provides native and platform default interaction for buttons that are placed in a scrollable container (in which case the interaction is slightly delayed to prevent button from highlighting when you fling).BaseButtonCurrently Gesture handler library exposes three components that render native touchable elements under the hood:
-
RectButton
-BorderlessButton
-NativeViewGestureHandlerOn top of that all the buttons are wrapped with
and therefore allow for all the common gesture handler properties andNativeViewGestureHandler's extra properties to be applied to them.ViewIMPORTANT: In order to make buttons accessible, you have to wrap your children in a
withaccessibleandaccessibilityRole="button"props.
Example:
// Not accessible:
const NotAccessibleButton = () => (
<RectButton onPress={this._onPress}>
<Text>Foo</Text>
</RectButton>
);
// Accessible:
const AccessibleButton = () => (
<RectButton onPress={this._onPress}>
<View accessible accessibilityRole="button">
<Text>Bar</Text>
</View>
</RectButton>
);
It is applicable for both iOS and Android platform. On iOS, you won't be able to even select the button, on Android you won't be able to click it in accessibility mode.BaseButtonBaseButtonCan be used as a base class if you'd like to implement some custom interaction for when the button is pressed.
Below is a list of properties specific to
component:onActiveStateChangeonPressfunction that gets triggered when button changes from inactive to active and vice versa. It passes active state as a boolean variable as a first parameter for that method.
onPressfunction that gets triggered when the button gets pressed (analogous to
inTouchableHighlightfrom RN core).rippleColor(Android only)exclusivedefines color of native ripple animation used since API level 21.
truedefines if more than one button could be pressed simultaneously. By default set to
.RectButtonBaseButtonThis type of button component should be used when you deal with rectangular elements or blocks of content that can be pressed, for example table rows or buttons with text and icons. This component provides a platform specific interaction, rendering a rectangular ripple on Android or highlighting the background on iOS and on older versions of Android. In addition to the props of
, it accepts the following:RectButtonBelow is a list of properties specific to
component:underlayColoractiveOpacitythis is the background color that will be dimmed when button is in active state.
(iOS only)BorderlessButtonopacity applied to the underlay when button is in active state.
TouchableOpacityThis type of button component should be used with simple icon-only or text-only buttons. The interaction will be different depending on platform: on Android a borderless ripple will be rendered (it means that the ripple will animate into a circle that can span outside of the view bounds), whereas on iOS the button will be dimmed (similar to how
works). In addition to the props ofBaseButton, it accepts the following:BorderlessButtonBelow is a list of properties specific to
component:borderless(Android only)falseset this to
if you want the ripple animation to render only within view bounds.activeOpacity(iOS only)RectButtonopacity applied to the button when it is in an active state.
Design patterns
Components listed here were not designed to behave and look in the same way on both platforms but rather to be used for handling similar behaviour on iOS and Android taking into consideration their design concepts.
If you wish to get specific information about platforms design patterns, visit official Apple docs and Material.io guideline, which widely describe how to implement coherent design.
This library allows to use native components with native feedback in adequate situations.
If you do not wish to implement custom design approach,
andBorderlessButtonseem to be absolutely enough and there's no need to use anything else. In all the remaining cases you can always rely onBaseButtonwhich is a superclass for the other button classes and can be used as a genericTouchablereplacement that can be customized to your needs.RectButtonBelow we list some of the common usecases for button components to be used along with the type of button that should be used according to the platform specific design guidelines.
Lists and action buttons
If you have a list with clickable items or have an action button that need to display as a separate UI block (vs being inlined in a text) you should use
. It changes opacity on click and additionally supports a ripple effect on Android.BorderlessButton<GifGallery>
<img src={useBaseUrl('gifs/androidsettings.gif')} width="280" />
<img src={useBaseUrl('gifs/iossettings.gif')} width="280" />
</GifGallery>To determine emphasis of button it's vital to use fill color or leave it transparent especially on Android.
For medium emphasis you may consider outlined buttons which are used for lower impact than fill buttons.<GifGallery>
<img src={useBaseUrl('gifs/androidbutton.gif')} width="280" />
</GifGallery>Icon or text only buttons
Use
for simple icon-only or text-only buttons. The interaction will be different depending on platform: on Android a borderless ripple will be rendered, whereas on iOS the button will be dimmed.PureNativeButton
It should be used if you wish to handle non-crucial actions and supportive behaviour.<GifGallery>
<img src={useBaseUrl('gifs/androidmail.gif')} width="280" />
<img src={useBaseUrl('gifs/iosmail.gif')} width="280" />
</GifGallery>PureNativeButtonUse a
for accessing the native Component used to build the more complex buttons listed above.
It is normally not recommended to use, but it might be useful if you want to wrap it using Animated or Reanimated.
import {
createNativeWrapper,
PureNativeButton,
} from 'react-native-gesture-handler';
import Animated from 'react-native-reanimated';
const { event, Value, createAnimatedComponent } = Animated;
const AnimatedRawButton = createNativeWrapper(
createAnimatedComponent(PureNativeButton),
{
shouldCancelWhenOutside: false,
shouldActivateOnStart: false,
}
);
export default class App extends React.Component {
constructor(props) {
super(props);
const state = new Value();
this._onGestureEvent = event([
{
nativeEvent: { state },
},
]);
}
render() {
return <AnimatedRawButton onHandlerStateChange={this._onGestureEvent} />;
}
}
---DrawerLayoutPackages/Docs Gesture Handler/Versioned Docs/Version 1.X/Api/Components/Drawer Layout (packages/docs-gesture-handler/versioned_docs/version-1.x/api/components/drawer-layout.mdx)
---
id: drawer-layout
title: Drawer Layout
sidebar_label: DrawerLayout
---import useBaseUrl from '@docusaurus/useBaseUrl';
import GifGallery from '@site/components/GifGallery';This is a cross-platform replacement for React Native's DrawerLayoutAndroid component. It provides a compatible API but allows for the component to be used on both Android and iOS. Please refer to React Native docs for the detailed usage for standard parameters.
Usage:
component isn't exported by default from thereact-native-gesture-handlerpackage. To use it, import it in the following way:
import DrawerLayout from 'react-native-gesture-handler/DrawerLayout';
drawerTypeProperties:
On top of the standard list of parameters DrawerLayout has an additional set of attributes to customize its behavior. Please refer to the list below:
frontpossible values are:
,backorslide(default isfront). It specifies the way the drawer will be displayed. When set tofrontthe drawer will slide in and out along with the gesture and will display on top of the content view. Whenbackis used the drawer displays behind the content view and can be revealed with gesture of pulling the content view to the side. Finallyslideoption makes the drawer appear like it is attached to the side of the content view; when you pull both content view and drawer will follow the gesture.slideType
:front<GifGallery>
<img src={useBaseUrl('gifs/drawer-slide.gif')} width="280" />
</GifGallery>Type
:back<GifGallery>
<img src={useBaseUrl('gifs/drawer-front.gif')} width="280" />
</GifGallery>Type
:edgeWidth<GifGallery>
<img src={useBaseUrl('gifs/drawer-back.gif')} width="280" />
</GifGallery>hideStatusBarnumber, allows for defining how far from the edge of the content view the gesture should activate.
trueboolean, when set to
Drawer component will use StatusBar API to hide the OS status bar whenever the drawer is pulled or when its in an "open" state.statusBarAnimationslidepossible values are:
,noneorfade(defaults toslide). Can be used whenhideStatusBaris set totrueand will select the animation used for hiding/showing the status bar. See StatusBar documentation for more details.overlayColor"black"color (default to
) of a semi-transparent overlay to be displayed on top of the content view when drawer gets open. A solid color should be used as the opacity is added by the Drawer itself and the opacity of the overlay is animated (from 0% to 70%).renderNavigationViewrenderNavigationViewfunction. This attribute is present in the standard implementation already and is one of the required params. The gesture handler version of DrawerLayout makes it possible for the function passed as
to take an Animated value as a parameter that indicates the progress of the drawer opening/closing animation (progress value is 0 when closed and 1 when opened). This can be used by the drawer component to animated its children while the drawer is opening or closing.onDrawerCloseonDrawerOpenfunction. This function is called when the drawer is closed.
onDrawerSlidefunction. This function is called when the drawer is opened.
onDrawerStateChangedfunction. This function is called as a drawer sliding open from touch events. The progress of the drawer opening/closing is passed back as 0 when closed and 1 when opened.
enableTrackpadTwoFingerGesturefunction. This function is called when the status of the drawer changes. Possible values that can be passed back are: 'Idle', 'Dragging', and 'Settling'.
(iOS only)childrenEnables two-finger gestures on supported devices, for example iPads with trackpads. If not enabled the gesture will require click + drag, with enableTrackpadTwoFingerGesture swiping with two fingers will also trigger the gesture.
renderNavigationViewcomponent or function. Children is a component which is rendered by default and is wrapped by drawer. However, it could also be a render function which takes an Animated value as a parameter that indicates the progress of the drawer opening/closing animation (progress value is 0 when closed and 1 when opened) in the same way as the
prop.openDrawer(options)Methods
openDrawercan take an optionaloptionsparameter which is an object, enabling further customization of the open animation.optionshas two optional properties:velocity: number, the initial velocity of the object attached to the spring. Default 0 (object is at rest).speed: number, controls speed of the animation. Default 12.closeDrawer(options)closeDrawercan take an optionaloptionsparameter which is an object, enabling further customization of the close animation.optionshas two optional properties:velocity: number, the initial velocity of the object attached to the spring. Default 0 (object is at rest).speed: number, controls speed of the animation. Default 12.Example:
See the drawer example from GestureHandler Example App or view it directly on your phone by visiting our expo demo.
class Drawerable extends Component {
handleDrawerSlide = (status) => {
// outputs a value between 0 and 1
console.log(status);
};
renderDrawer = () => {
return (
<View>
<Text>I am in the drawer!</Text>
</View>
);
};
render() {
return (
<View style={{ flex: 1 }}>
<DrawerLayout
drawerWidth={200}
drawerPosition={DrawerLayout.positions.Right}
drawerType="front"
drawerBackgroundColor="#ddd"
renderNavigationView={this.renderDrawer}
onDrawerSlide={this.handleDrawerSlide}>
<View>
<Text>Hello, it's me</Text>
</View>
</DrawerLayout>
</View>
);
}
}
---renderLeftActionsPackages/Docs Gesture Handler/Versioned Docs/Version 1.X/Api/Components/Swipeable (packages/docs-gesture-handler/versioned_docs/version-1.x/api/components/swipeable.md)
---
id: swipeable
title: Swipeable
sidebar_label: Swipeable
---import useBaseUrl from '@docusaurus/useBaseUrl';
import GifGallery from '@site/components/GifGallery'<GifGallery>
<img src={useBaseUrl("gifs/sampleswipeable.gif")} height="120" />
</GifGallery>This component allows for implementing swipeable rows or similar interaction. It renders its children within a pannable container that allows for horizontal swiping left and right. While swiping, one of two "action" containers can be shown depending on whether the user swipes left or right (containers can be rendered by
orrenderRightActionsprops).DrawerLayoutUsage:
Similarly to the
,Swipeablecomponent isn't exported by default from thereact-native-gesture-handlerpackage. To use it, import it in the following way:
import Swipeable from 'react-native-gesture-handler/Swipeable';
frictionProperties
leftThresholda number that specifies how much the visual interaction will be delayed compared to the gesture distance. e.g. value of 1 will indicate that the swipeable panel should exactly follow the gesture, 2 means it is going to be two times "slower".
rightThresholddistance from the left edge at which released panel will animate to the open state (or the open panel will animate into the closed state). By default it's a half of the panel's width.
overshootLeftdistance from the right edge at which released panel will animate to the open state (or the open panel will animate into the closed state). By default it's a half of the panel's width.
truea boolean value indicating if the swipeable panel can be pulled further than the left actions panel's width. It is set to
by default as long as the left panel render method is present.overshootRighttruea boolean value indicating if the swipeable panel can be pulled further than the right actions panel's width. It is set to
by default as long as the right panel render method is present.overshootFrictiononSwipeableLeftOpena number that specifies how much the visual interaction will be delayed compared to the gesture distance at overshoot. Default value is 1, which means no friction; for a native feel, try 8 or above.
onSwipeableRightOpenmethod that is called when left action panel gets open.
onSwipeableOpenmethod that is called when right action panel gets open.
onSwipeableClosemethod that is called when action panel gets open (either right or left).
onSwipeableLeftWillOpenmethod that is called when action panel is closed.
onSwipeableRightWillOpenmethod that is called when left action panel starts animating on open.
onSwipeableWillOpenmethod that is called when right action panel starts animating on open.
onSwipeableWillClosemethod that is called when action panel starts animating on open (either right or left).
renderLeftActionsmethod that is called when action panel starts animating on close.
rtlmethod that is expected to return an action panel that is going to be revealed from the left side when user swipes right.
This map describes the values to use as inputRange for extra interpolation:
AnimatedValue: [startValue, endValue]progressAnimatedValue: [0, 1]
dragAnimatedValue: [0, +]To support
flexbox layouts useflexDirectionstyling.renderRightActionsrtlmethod that is expected to return an action panel that is going to be revealed from the right side when user swipes left.
This map describes the values to use as inputRange for extra interpolation:
AnimatedValue: [startValue, endValue]progressAnimatedValue: [0, 1]
dragAnimatedValue: [0, -]To support
flexbox layouts useflexDirectionstyling.containerStyleoverflow: 'hidden'style object for the container (Animated.View), for example to override
.childrenContainerStyleflex: 1style object for the children container (Animated.View), for example to apply
.enableTrackpadTwoFingerGesture(iOS only)SwipeableEnables two-finger gestures on supported devices, for example iPads with trackpads. If not enabled the gesture will require click + drag, with enableTrackpadTwoFingerGesture swiping with two fingers will also trigger the gesture.
Methods
Using reference to
it's possible to trigger some actions on itcloseopenLeftmethod that closes component.
openRightmethod that opens component on left side.
method that opens component on right side.
Example:
See the swipeable example from GestureHandler Example App or view it directly on your phone by visiting our expo demo.
import React, { Component } from 'react';
import { Animated, StyleSheet, View } from 'react-native';
import { RectButton } from 'react-native-gesture-handler';
import Swipeable from 'react-native-gesture-handler/Swipeable';
class AppleStyleSwipeableRow extends Component {
renderLeftActions = (progress, dragX) => {
const trans = dragX.interpolate({
inputRange: [0, 50, 100, 101],
outputRange: [-20, 0, 0, 1],
});
return (
<RectButton style={styles.leftAction} onPress={this.close}>
<Animated.Text
style={[
styles.actionText,
{
transform: [{ translateX: trans }],
},
]}>
Archive
</Animated.Text>
</RectButton>
);
};
render() {
return (
<Swipeable renderLeftActions={this.renderLeftActions}>
<Text>"hello"</Text>
</Swipeable>
);
}
}
---pressRetentionOffsetPackages/Docs Gesture Handler/Versioned Docs/Version 1.X/Api/Components/Touchables (packages/docs-gesture-handler/versioned_docs/version-1.x/api/components/touchables.md)
---
id: touchables
title: Touchables
sidebar_label: Touchables
---Gesture Handler library provides an implementation of RN's touchable components that are based on native buttons and do not rely on the JS responder system utilized by RN. Our touchable implementation follows the same API and aims to be a drop-in replacement for touchables available in React Native.
React Native's touchables API can be found here:
- Touchable Native Feedback
- Touchable Highlight
- Touchable Opacity
- Touchable Without FeedbackAll major touchable properties (except from
) have been adopted and should behave in a similar way as with RN's touchables.ScrollViewThe motivation for using RNGH touchables as a replacement for these imported from React Native is to follow built-in native behavior more closely by utilizing platform native touch system instead of relying on the JS responder system.
These touchables and their feedback behavior are deeply integrated with native
gesture ecosystem and could be connected with other native components (e.g.) and Gesture Handlers easily and in a more predictable way, which
follows native apps' behavior.Our intention was to make the switch for these touchables as simple as possible. In order to use RNGH's touchables, the only thing you need to do is change the library from which you import touchable components.
Example:
import {
TouchableNativeFeedback,
TouchableHighlight,
TouchableOpacity,
TouchableWithoutFeedback,
} from 'react-native';
has to be replaced with:import {
TouchableNativeFeedback,
TouchableHighlight,
TouchableOpacity,
TouchableWithoutFeedback,
} from 'react-native-gesture-handler';
For a comparison of both touchable implementations see our touchables exampleenabled---
Packages/Docs Gesture Handler/Versioned Docs/Version 1.X/Api/Gesture Handlers/Common Gh (packages/docs-gesture-handler/versioned_docs/version-1.x/api/gesture-handlers/common-gh.md)
---
id: common-gh
title: Common handler properties
sidebar_label: Common handler properties
---This page covers the common set of properties all gesture handler components expose.
Units
All handler component properties and event attributes that represent onscreen dimensions are expressed in screen density independent units we refer to as "points".
These are the units commonly used in React Native ecosystem (e.g. in the layout system).
They do not map directly to physical pixels but instead to iOS's points and to dp units on Android.Properties
This section describes properties that can be used with all gesture handler components:
falseAccepts a boolean value.
Indicates whether the given handler should be analyzing stream of touch events or not.
When set towe can be sure that the handler's state will never becomeACTIVE.FAILED
If the value gets updated while the handler has already started recognizing a gesture, then the handler's state will immediately change toorCANCELLED(depending on its current state).true
Default value is.shouldCancelWhenOutsidetrueAccepts a boolean value.
Whenthe handler will cancel or fail recognition (depending on its current state) whenever the finger leaves the area of the connected view.shouldCancelWhenOutside
Default value of this property is different depending on the handler type.
Most handlers'property defaults tofalseexcept for theLongPressGestureHandlerandTapGestureHandlerwhich default totrue.simultaneousHandlersReact.createRef()Accepts a react ref object or an array of refs to other handler components (refs should be created using
). When set, the handler will be allowed to activate even if one or more of the handlers provided by their refs are in anACTIVEstate. It will also prevent the provided handlers from cancelling the current handler when they activate. Read more in the cross handler interaction section.waitForReact.createRef()Accepts a react ref object or an array of refs to other handler components (refs should be created using
). When set the handler will not activate as long as the handlers provided by their refs are in theBEGANstate. Read more in the cross handler interaction section.hitSlopleftThis parameter enables control over what part of the connected view area can be used to begin recognizing the gesture.
When a negative number is provided the bounds of the view will reduce the area by the given number of points in each of the sides evenly.Instead you can pass an object to specify how each boundary side should be reduced by providing different number of points for
,right,toporbottomsides.horizontal
You can alternatively provideorverticalinstead of specifying directlyleft,rightortopandbottom.width
Finally, the object can also takeandheightattributes.width
Whenis set it is only allowed to specify one of the sidesrightorleft.height
Similarly whenis provided onlytoporbottomcan be set.width
Specifyingorheightis useful if we only want the gesture to activate on the edge of the view. In which case for example we can setleft: 0andwidth: 20which would make it possible for the gesture to be recognized when started no more than 20 points from the left edge.widthIMPORTANT: Note that this parameter is primarily designed to reduce the area where gesture can activate. Hence it is only supported for all the values (except
andheight) to be non positive (0 or lower). Although on Android it is supported for the values to also be positive and therefore allow to expand beyond view bounds but not further than the parent view bounds. To achieve this effect on both platforms you can use React Native's View hitSlop property.onGestureEventPinchGestureHandlerTakes a callback that is going to be triggered for each subsequent touch event while the handler is in an ACTIVE state. Event payload depends on the particular handler type. Common set of event data attributes is documented below and handler specific attributes are documented on the corresponding handler pages. E.g. event payload for
containsscaleattribute that represents how the distance between fingers changed since when the gesture started.Animated.eventobject can be used. Also Animated events withuseNativeDriverflag enabled are fully supported.onHandlerStateChangeonGestureEventTakes a callback that is going to be triggered when state of the given handler changes.
including handler specific event attributes some handlers may provide.onHandlerStateChangeIn addition
event payload containsoldStateattribute which represents the state of the handler right before the change.Animated.eventobject can be used. Also Animated events withuseNativeDriverflag enabled are fully supported.onGestureEventEvent data
This section describes the attributes of event object being provided to
andonHandlerStateChangecallbacks:stateStateCurrent state of the handler. Expressed as one of the constants exported under
object by the library. Refer to the section about handler state to learn more about how to use it.numberOfPointersNativeViewGestureHandlerRepresents the number of pointers (fingers) currently placed on the screen.
---
Packages/Docs Gesture Handler/Versioned Docs/Version 1.X/Api/Gesture Handlers/Create Native Wrapper (packages/docs-gesture-handler/versioned_docs/version-1.x/api/gesture-handlers/create-native-wrapper.md)
---
id: create-native-wrapper
title: createNativeWrapper
sidebar_label: createNativeWrapper()
---Creates provided component with NativeViewGestureHandler, allowing it to be part of RNGH's
gesture system.Arguments
Component
The component we want to wrap.
config
FlingGestureHandlerReturns
Wrapped component.
---
Packages/Docs Gesture Handler/Versioned Docs/Version 1.X/Api/Gesture Handlers/Fling Gh (packages/docs-gesture-handler/versioned_docs/version-1.x/api/gesture-handlers/fling-gh.md)
---
id: fling-gh
title: FlingGestureHandler
sidebar_label: Fling
---A discrete gesture handler that activates when the movement is sufficiently long and fast.
Handler gets ACTIVE when movement is sufficiently long and it does not take too much time.
When handler gets activated it will turn into END state when finger is released.
The handler will fail to recognize if the finger is lifted before being activated.
The handler is implemented using UISwipeGestureRecognizer on iOS and from scratch on Android.Properties
See set of properties inherited from base handler class. Below is a list of properties specific to
component:directionExpresses the allowed direction of movement. It's possible to pass one or many directions in one parameter:
direction={Directions.RIGHT | Directions.LEFT}
ordirection={Directions.DOWN}
numberOfPointersFlingGestureHandlerDetermines the exact number of pointers required to handle the fling gesture.
Event data
See set of event attributes from base handler class. Below is a list of gesture event attributes specific to
:xyX coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the handler. Expressed in point units.
absoluteXY coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the handler. Expressed in point units.
xX coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the root view. The value is expressed in point units. It is recommended to use it instead of
in cases when the original view can be transformed as an effect of the gesture.absoluteYyY coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the root view. The value is expressed in point units. It is recommended to use it instead of
in cases when the original view can be transformed as an effect of the gesture.Example
See the fling example from GestureHandler Example App or view it directly on your phone by visiting our expo demo.
const LongPressButton = () => (
<FlingGestureHandler
direction={Directions.RIGHT | Directions.LEFT}
onHandlerStateChange={({ nativeEvent }) => {
if (nativeEvent.state === State.ACTIVE) {
Alert.alert("I'm flinged!");
}
}}>
<View style={styles.box} />
</FlingGestureHandler>
);
---minForcePackages/Docs Gesture Handler/Versioned Docs/Version 1.X/Api/Gesture Handlers/Force Gh (packages/docs-gesture-handler/versioned_docs/version-1.x/api/gesture-handlers/force-gh.md)
---
id: force-gh
title: ForceTouchGestureHandler (iOS only)
sidebar_label: Force touch
---A continuous gesture handler that recognizes force of a touch. It allows for tracking pressure of touch on some iOS devices.
The handler activates when the pressure of a touch is greater than or equal to. It fails if the pressure is greater thanmaxForce.ForceTouchGestureHandler
Gesture callback can be used for continuous tracking of the touch pressure. It provides information for one finger (the first one).At the beginning of the gesture, the pressure factor is 0.0. As the pressure increases, the pressure factor increases proportionally. The maximum pressure is 1.0.
The handler is implemented using custom UIGestureRecognizer on iOS. There's no implementation provided on Android and it simply renders children without any wrappers.
Since this behaviour is only provided on some iOS devices, this handler should not be used for defining any crucial behaviors. Use it only as an additional improvement and make all features accessible without this handler as well.Properties
See set of properties inherited from base handler class. Below is a list of properties specific to
component:minForce[0.0, 1.0]A minimal pressure that is required before handler can activate. Should be a value from range
. Default is0.2.maxForce[0.0, 1.0]A maximal pressure that could be applied for handler. If the pressure is greater, handler fails. Should be a value from range
.feedbackOnActivationForceTouchGestureHandlerBoolean value defining if haptic feedback has to be performed on activation.
Event data
See set of event attributes from base handler class. Below is a list of gesture event attributes specific to
:forceforceTouchAvailableThe pressure of a touch.
Static method
ForceTouchGestureHandlerYou may check if it's possible to use
withForceTouchGestureHandler.forceTouchAvailableExample
See the force touch handler example from GestureHandler Example App or view it directly on your phone by visiting our expo demo.
<ForceTouchGestureHandler
minForce={0}
onGestureEvent={this._onGestureEvent}
onHandlerStateChange={this._onHandlerStateChange}>
<Animated.View
style={[
styles.box,
{ transform: [{ scale: Animated.add(1, this.force) }] },
]}
/>
</ForceTouchGestureHandler>
---LongPressGestureHandlerPackages/Docs Gesture Handler/Versioned Docs/Version 1.X/Api/Gesture Handlers/Longpress Gh (packages/docs-gesture-handler/versioned_docs/version-1.x/api/gesture-handlers/longpress-gh.md)
---
id: longpress-gh
title: LongPressGestureHandler
sidebar_label: Long press
---A discrete gesture handler that activates when the corresponding view is pressed for a sufficiently long time.
This handler's state will turn into END immediately after the finger is released.
The handler will fail to recognize a touch event if the finger is lifted before the minimum required time or if the finger is moved further than the allowable distance.The handler is implemented using UILongPressGestureRecognizer on iOS and LongPressGestureHandler on Android.
Properties
See set of properties inherited from base handler class. Below is a list of properties specific to the
component:minDurationMsmaxDistMinimum time, expressed in milliseconds, that a finger must remain pressed on the corresponding view. The default value is 500.
LongPressGestureHandlerMaximum distance, expressed in points, that defines how far the finger is allowed to travel during a long press gesture. If the finger travels further than the defined distance and the handler hasn't yet activated, it will fail to recognize the gesture. The default value is 10.
Event data
See set of event attributes from base handler class. Below is a list of gesture event attributes specific to the
component:xyX coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the handler.
absoluteXY coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the handler.
absoluteXX coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the root view. It is recommended to use
instead ofxin cases when the view attached to the handler can be transformed as an effect of the gesture.absoluteYabsoluteYY coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the root view. It is recommended to use
instead ofyin cases when the view attached to the handler can be transformed as an effect of the gesture.durationDuration of the long press (time since the start of the event), expressed in milliseconds.
Example
See the multitap example from GestureHandler Example App or view it directly on your phone by visiting our expo demo.
const LongPressButton = () => (
<LongPressGestureHandler
onHandlerStateChange={({ nativeEvent }) => {
if (nativeEvent.state === State.ACTIVE) {
Alert.alert("I'm being pressed for so long");
}
}}
minDurationMs={800}>
<View style={styles.box} />
</LongPressGestureHandler>
);
---createNativeWrapper()Packages/Docs Gesture Handler/Versioned Docs/Version 1.X/Api/Gesture Handlers/Nativeview Gh (packages/docs-gesture-handler/versioned_docs/version-1.x/api/gesture-handlers/nativeview-gh.md)
---
id: nativeview-gh
title: NativeViewGestureHandler
sidebar_label: NativeView
---A gesture handler that allows other touch handling components to participate in
RNGH's gesture system..NativeViewGestureHandlerProperties
See set of properties inherited from base handler class. Below is a list of properties specific to
component:shouldActivateOnStart(Android only)trueWhen
, underlying handler will activate unconditionally when inBEGANorUNDETERMINEDstate.disallowInterruptiontrueWhen
, cancels all other gesture handlers when thisNativeViewGestureHandlerreceives anACTIVEstate event.PanGestureHandler---
Packages/Docs Gesture Handler/Versioned Docs/Version 1.X/Api/Gesture Handlers/Pan Gh (packages/docs-gesture-handler/versioned_docs/version-1.x/api/gesture-handlers/pan-gh.md)
---
id: pan-gh
title: PanGestureHandler
sidebar_label: Pan
---A continuous gesture handler that can recognize a panning (dragging) gesture and track its movement.
The handler activates when a finger is placed on the screen and moved some initial distance.
Configurations such as a minimum initial distance, specific vertical or horizontal pan detection and number of fingers required for activation (allowing for multifinger swipes) may be specified.
Gesture callback can be used for continuous tracking of the pan gesture. It provides information about the gesture such as its XY translation from the starting point as well as its instantaneous velocity.
The handler is implemented using UIPanGestureRecognizer on iOS and PanGestureHandler on Android.
Custom activation criteria
The
component exposes a number of properties that can be used to customize the criteria under which a handler will activate or fail when recognizing a gesture.PanGestureHandlerWhen more than one of such a property is set,
expects all criteria to be met for successful recognition and at most one of the criteria to be overstepped to fail recognition.minDeltaX
For example when bothandminDeltaYare set to 20 we expect the finger to travel by 20 points in both the X and Y axis before the handler activates.maxDeltaX
Another example would be setting bothandmaxDeltaYto 20 andminDistto 23.avgTouches
In such a case, if we move a finger along the X-axis by 20 points and along the Y-axis by 0 points, the handler will fail even though the finger is still within the bounds of translation along Y-axis.Multi touch pan handling
If your app relies on multi touch pan handling, this section provides some information about how the default behavior differs between the platforms and how (if necessary) it can be unified.
The difference in multi touch pan handling lies in the way translation properties during the event are calculated.
On iOS the default behavior when more than one finger is placed on the screen is to treat this situation as if only one pointer was placed in the center of mass (average position of all the pointers).
This applies also to many platform native components that handle touch even if not primarily interested in multi touch interactions, like for example the UIScrollView component.The default behavior for native components like scroll view, pager views or drawers is different and hence gesture handler defaults to that when it comes to pan handling.
The difference is that instead of treating the center of mass of all the fingers placed as a leading pointer it takes the latest placed finger as such.
This behavior can be changed on Android usingflag.xNote that on both Android and iOS when the additional finger is placed on the screen that translation prop is not affected even though the position of the pointer being tracked might have changed.
Therefore it is safe to rely on translation most of the time as it only reflects the movement that happens regardless of how many fingers are placed on the screen and if that number changes over time.
If you wish to track the "center of mass" virtual pointer and account for its changes when the number of finger changes you can use relative or absolute position provided in the event (andyorabsoluteXandabsoluteY).PanGestureHandlerProperties
See set of properties inherited from base handler class. Below is a list of properties specific to
component:minDistminPointersMinimum distance the finger (or multiple fingers) needs to travel before the handler activates. Expressed in points.
maxPointersThe number of fingers that is required to be placed before the handler can activate. Should be an integer greater than or equal to 0.
activeOffsetXWhen the given number of fingers is placed on the screen and the handler hasn't yet activated it will fail recognizing the gesture. Should be an integer greater than or equal to 0.
pRange along the X axis (in points) where fingers travel without activation of the handler. Moving outside of this range implies activation of the handler. Range can be given as an array or a single number.
If the range is set as an array, the first value must be lower or equal to 0, and the second one higher or equal to 0.
If only one numberis given a range of(-inf, p)will be used ifpis higher or equal to 0 and(-p, inf)otherwise.activeOffsetYpRange along the Y axis (in points) where fingers travel without activation of the handler. Moving outside of this range implies activation of the handler. Range can be given as an array or a single number.
If the range is set as an array, the first value must be lower or equal to 0, and the second one higher or equal to 0.
If only one numberis given a range of(-inf, p)will be used ifpis higher or equal to 0 and(-p, inf)otherwise.failOffsetYpWhen the finger moves outside this range (in points) along the Y axis and the handler hasn't yet activated it will fail recognizing the gesture. Range can be given as an array or a single number.
If the range is set as an array, the first value must be lower or equal to 0, and the second one higher or equal to 0.
If only one numberis given a range of(-inf, p)will be used ifpis higher or equal to 0 and(-p, inf)otherwise.failOffsetXpWhen the finger moves outside this range (in points) along the X axis and the handler hasn't yet activated it will fail recognizing the gesture. Range can be given as an array or a single number.
If the range is set as an array, the first value must be lower or equal to 0, and the second one higher or equal to 0.
If only one numberis given a range of(-inf, p)will be used ifpis higher or equal to 0 and(-p, inf)otherwise.avgTouches(Android only)enableTrackpadTwoFingerGesture(iOS only)PanGestureHandlerEnables two-finger gestures on supported devices, for example iPads with trackpads. If not enabled the gesture will require click + drag, with enableTrackpadTwoFingerGesture swiping with two fingers will also trigger the gesture.
Event data
See set of event attributes from base handler class. Below is a list of gesture event attributes specific to
:translationXtranslationYTranslation of the pan gesture along X axis accumulated over the time of the gesture. The value is expressed in the point units.
velocityXTranslation of the pan gesture along Y axis accumulated over the time of the gesture. The value is expressed in the point units.
velocityYVelocity of the pan gesture along the X axis in the current moment. The value is expressed in point units per second.
xVelocity of the pan gesture along the Y axis in the current moment. The value is expressed in point units per second.
yX coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the handler. Expressed in point units.
absoluteXY coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the handler. Expressed in point units.
xX coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the root view. The value is expressed in point units. It is recommended to use it instead of
in cases when the original view can be transformed as an effect of the gesture.absoluteYyY coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the root view. The value is expressed in point units. It is recommended to use it instead of
in cases when the original view can be transformed as an effect of the gesture.Example
See the draggable example from GestureHandler Example App or view it directly on your phone by visiting our expo demo.
const circleRadius = 30;
class Circle extends Component {
_touchX = new Animated.Value(windowWidth / 2 - circleRadius);
_onPanGestureEvent = Animated.event([{ nativeEvent: { x: this._touchX } }], {
useNativeDriver: true,
});
render() {
return (
<PanGestureHandler onGestureEvent={this._onPanGestureEvent}>
<Animated.View
style={{
height: 150,
justifyContent: 'center',
}}>
<Animated.View
style={[
{
backgroundColor: '#42a5f5',
borderRadius: circleRadius,
height: circleRadius * 2,
width: circleRadius * 2,
},
{
transform: [
{
translateX: Animated.add(
this._touchX,
new Animated.Value(-circleRadius)
),
},
],
},
]}
/>
</Animated.View>
</PanGestureHandler>
);
}
}
---PinchGestureHandlerPackages/Docs Gesture Handler/Versioned Docs/Version 1.X/Api/Gesture Handlers/Pinch Gh (packages/docs-gesture-handler/versioned_docs/version-1.x/api/gesture-handlers/pinch-gh.md)
---
id: pinch-gh
title: PinchGestureHandler
sidebar_label: Pinch
---A continuous gesture handler that recognizes a pinch gesture. It allows for tracking the distance between two fingers and using that information to scale or zoom your content.
The handler activates when fingers are placed on the screen and change their position.
Gesture callback can be used for continuous tracking of the pinch gesture. It provides information about velocity, anchor (focal) point of gesture and scale.The distance between the fingers is reported as a scale factor. At the beginning of the gesture, the scale factor is 1.0. As the distance between the two fingers increases, the scale factor increases proportionally.
Similarly, the scale factor decreases as the distance between the fingers decreases.
Pinch gestures are used most commonly to change the size of objects or content onscreen.
For example, map views use pinch gestures to change the zoom level of the map.The handler is implemented using UIPinchGestureRecognizer on iOS and from scratch on Android.
Properties
Properties provided to
do not extend the common set of properties from base handler class.PinchGestureHandlerEvent data
See set of event attributes from base handler class. Below is a list of gesture event attributes specific to
:scalevelocityThe scale factor relative to the points of the two touches in screen coordinates.
focalXVelocity of the pinch gesture at the current moment. The value is expressed in point units per second.
focalYPosition expressed in points along the X axis of the center anchor point of the gesture.
Position expressed in points along the Y axis of the center anchor point of the gesture.
Example
See the scale and rotation example from GestureHandler Example App or view it directly on your phone by visiting our expo demo.
export class PinchableBox extends React.Component {
_baseScale = new Animated.Value(1);
_pinchScale = new Animated.Value(1);
_scale = Animated.multiply(this._baseScale, this._pinchScale);
_lastScale = 1;
_onPinchGestureEvent = Animated.event(
[{ nativeEvent: { scale: this._pinchScale } }],
{ useNativeDriver: USE_NATIVE_DRIVER }
);
_onPinchHandlerStateChange = (event) => {
if (event.nativeEvent.oldState === State.ACTIVE) {
this._lastScale *= event.nativeEvent.scale;
this._baseScale.setValue(this._lastScale);
this._pinchScale.setValue(1);
}
};
render() {
return (
<PinchGestureHandler
onGestureEvent={this._onPinchGestureEvent}
onHandlerStateChange={this._onPinchHandlerStateChange}>
<View style={styles.container} collapsable={false}>
<Animated.Image
style={[
styles.pinchableImage,
{
transform: [{ perspective: 200 }, { scale: this._scale }],
},
]}
/>
</View>
</PinchGestureHandler>
);
}
}
---RotationGestureHandlerPackages/Docs Gesture Handler/Versioned Docs/Version 1.X/Api/Gesture Handlers/Rotation Gh (packages/docs-gesture-handler/versioned_docs/version-1.x/api/gesture-handlers/rotation-gh.md)
---
id: rotation-gh
title: RotationGestureHandler
sidebar_label: Rotation
---A continuous gesture handler that can recognize a rotation gesture and track its movement.
The handler activates when fingers are placed on the screen and change position in a proper way.
Gesture callback can be used for continuous tracking of the rotation gesture. It provides information about the gesture such as the amount rotated, the focal point of the rotation (anchor), and its instantaneous velocity.
The handler is implemented using UIRotationGestureRecognizer on iOS and from scratch on Android.
Properties
Properties provided to
do not extend common set of properties from base handler class.RotationGestureHandlerEvent data
See set of event attributes from base handler class. Below is a list of gesture event attributes specific to
:rotationvelocityAmount rotated, expressed in radians, from the gesture's focal point (anchor).
anchorXInstantaneous velocity, expressed in point units per second, of the gesture.
anchorYX coordinate, expressed in points, of the gesture's central focal point (anchor).
Y coordinate, expressed in points, of the gesture's central focal point (anchor).
Example
See the scale and rotation example from GestureHandler Example App or view it directly on your phone by visiting our expo demo.
class RotableBox extends React.Component {
_rotate = new Animated.Value(0);
_rotateStr = this._rotate.interpolate({
inputRange: [-100, 100],
outputRange: ['-100rad', '100rad'],
});
_lastRotate = 0;
_onRotateGestureEvent = Animated.event(
[{ nativeEvent: { rotation: this._rotate } }],
{ useNativeDriver: USE_NATIVE_DRIVER }
);
_onRotateHandlerStateChange = (event) => {
if (event.nativeEvent.oldState === State.ACTIVE) {
this._lastRotate += event.nativeEvent.rotation;
this._rotate.setOffset(this._lastRotate);
this._rotate.setValue(0);
}
};
render() {
return (
<RotationGestureHandler
onGestureEvent={this._onRotateGestureEvent}
onHandlerStateChange={this._onRotateHandlerStateChange}>
<Animated.Image
style={[
styles.pinchableImage,
{
transform: [{ perspective: 200 }, { rotate: this._rotateStr }],
},
]}
/>
</RotationGestureHandler>
);
}
}
---TapGestureHandlerPackages/Docs Gesture Handler/Versioned Docs/Version 1.X/Api/Gesture Handlers/Tap Gh (packages/docs-gesture-handler/versioned_docs/version-1.x/api/gesture-handlers/tap-gh.md)
---
id: tap-gh
title: TapGestureHandler
sidebar_label: Tap
---A discrete gesture handler that recognizes one or many taps.
Tap gestures detect one or more fingers briefly touching the screen.
The fingers involved in these gestures must not move significantly from their initial touch positions.
The required number of taps and allowed distance from initial position may be configured.
For example, you might configure tap gesture recognizers to detect single taps, double taps, or triple taps.In order for a handler to activate, specified gesture requirements such as minPointers, numberOfTaps, maxDist, maxDurationMs, and maxDelayMs (explained below) must be met. Immediately after the handler activates, it will END.
Properties
See set of properties inherited from base handler class. Below is a list of properties specific to the
component:minPointersmaxDurationMsMinimum number of pointers (fingers) required to be placed before the handler activates. Should be a positive integer. The default value is 1.
maxDelayMsMaximum time, expressed in milliseconds, that defines how fast a finger must be released after a touch. The default value is 500.
numberOfTapsMaximum time, expressed in milliseconds, that can pass before the next tap — if many taps are required. The default value is 500.
maxDeltaXNumber of tap gestures required to activate the handler. The default value is 1.
maxDeltaYMaximum distance, expressed in points, that defines how far the finger is allowed to travel along the X axis during a tap gesture. If the finger travels further than the defined distance along the X axis and the handler hasn't yet activated, it will fail to recognize the gesture.
maxDistMaximum distance, expressed in points, that defines how far the finger is allowed to travel along the Y axis during a tap gesture. If the finger travels further than the defined distance along the Y axis and the handler hasn't yet activated, it will fail to recognize the gesture.
TapGestureHandlerMaximum distance, expressed in points, that defines how far the finger is allowed to travel during a tap gesture. If the finger travels further than the defined distance and the handler hasn't yet activated, it will fail to recognize the gesture.
Event data
See set of event attributes from base handler class. Below is a list of gesture event attributes specific to the
component:xyX coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the handler.
absoluteXY coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the handler.
absoluteXX coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the root view. It is recommended to use
instead ofxin cases when the view attached to the handler can be transformed as an effect of the gesture.absoluteYabsoluteYY coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the root view. It is recommended to use
instead ofyin cases when the view attached to the handler can be transformed as an effect of the gesture.Example
See the multitap example from GestureHandler Example App or view it directly on your phone by visiting our expo demo.
export class PressBox extends Component {
doubleTapRef = React.createRef();
render() {
return (
<TapGestureHandler
onHandlerStateChange={this._onSingleTap}
waitFor={this.doubleTapRef}>
<TapGestureHandler ref={this.doubleTapRef} numberOfTaps={2}>
<View style={styles.box} />
</TapGestureHandler>
</TapGestureHandler>
);
}
}
---TouchableHighlightPackages/Docs Gesture Handler/Versioned Docs/Version 2.X/Components/ Category .Json (packages/docs-gesture-handler/versioned_docs/version-2.x/components/_category_.json)
{
"label": "Components",
"position": 4,
"link": {
"type": "generated-index"
}
}---
Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Components/Buttons (packages/docs-gesture-handler/versioned_docs/version-2.x/components/buttons.mdx)
---
id: buttons
title: Buttons
sidebar_label: Buttons
---import useBaseUrl from '@docusaurus/useBaseUrl';
import GifGallery from '@site/components/GifGallery';<GifGallery>
<img src={useBaseUrl('gifs/samplebutton.gif')} width="280" />
</GifGallery>Gesture handler library provides native components that can act as buttons. These can be treated as a replacement to
orTouchableOpacityfrom RN core. Gesture handler's buttons recognize touches in native which makes the recognition process deterministic, allows for rendering ripples on Android in highly performant way (TouchableNativeFeedbackrequires that touch event does a roundtrip to JS before we can update ripple effect, which makes ripples lag a bit on older phones), and provides native and platform default interaction for buttons that are placed in a scrollable container (in which case the interaction is slightly delayed to prevent button from highlighting when you fling).BaseButtonCurrently Gesture handler library exposes three components that render native touchable elements under the hood:
RectButton
-BorderlessButton
-NativeViewGestureHandlerOn top of that all the buttons are wrapped with
and therefore allow for all the common gesture handler properties andNativeViewGestureHandler's extra properties to be applied to them.ViewIMPORTANT: In order to make buttons accessible, you have to wrap your children in a
withaccessibleandaccessibilityRole="button"props.
Example:
// Not accessible:
const NotAccessibleButton = () => (
<RectButton onPress={this._onPress}>
<Text>Foo</Text>
</RectButton>
);
// Accessible:
const AccessibleButton = () => (
<RectButton onPress={this._onPress}>
<View accessible accessibilityRole="button">
<Text>Bar</Text>
</View>
</RectButton>
);
It is applicable for both iOS and Android platform. On iOS, you won't be able to even select the button, on Android you won't be able to click it in accessibility mode.BaseButtonBaseButtonCan be used as a base class if you'd like to implement some custom interaction for when the button is pressed.
Below is a list of properties specific to
component:onActiveStateChangeonPressfunction that gets triggered when button changes from inactive to active and vice versa. It passes active state as a boolean variable as a first parameter for that method.
onPressfunction that gets triggered when the button gets pressed (analogous to
inTouchableHighlightfrom RN core).onLongPressdelayLongPressfunction that gets triggered when the button gets pressed for at least
milliseconds.rippleColor(Android only)exclusivedefines color of native ripple animation used since API level 21.
truedefines if more than one button could be pressed simultaneously. By default set
.delayLongPressonLongPressdefines the delay, in milliseconds, after which the
callback gets called. By default set to 600.RectButtonBaseButtonThis type of button component should be used when you deal with rectangular elements or blocks of content that can be pressed, for example table rows or buttons with text and icons. This component provides a platform specific interaction, rendering a rectangular ripple on Android or highlighting the background on iOS and on older versions of Android. In addition to the props of
, it accepts the following:RectButtonBelow is a list of properties specific to
component:underlayColoractiveOpacitythis is the background color that will be dimmed when button is in active state.
(iOS only)BorderlessButtonopacity applied to the underlay when button is in active state.
TouchableOpacityThis type of button component should be used with simple icon-only or text-only buttons. The interaction will be different depending on platform: on Android a borderless ripple will be rendered (it means that the ripple will animate into a circle that can span outside of the view bounds), whereas on iOS the button will be dimmed (similar to how
works). In addition to the props ofBaseButton, it accepts the following:BorderlessButtonBelow is a list of properties specific to
component:borderless(Android only)falseset this to
if you want the ripple animation to render only within view bounds.activeOpacity(iOS only)RectButtonopacity applied to the button when it is in an active state.
Design patterns
Components listed here were not designed to behave and look in the same way on both platforms but rather to be used for handling similar behaviour on iOS and Android taking into consideration their design concepts.
If you wish to get specific information about platforms design patterns, visit official Apple docs and Material.io guideline, which widely describe how to implement coherent design.
This library allows to use native components with native feedback in adequate situations.
If you do not wish to implement custom design approach,
andBorderlessButtonseem to be absolutely enough and there's no need to use anything else. In all the remaining cases you can always rely onBaseButtonwhich is a superclass for the other button classes and can be used as a genericTouchablereplacement that can be customized to your needs.RectButtonBelow we list some of the common usecases for button components to be used along with the type of button that should be used according to the platform specific design guidelines.
Lists and action buttons
If you have a list with clickable items or have an action button that need to display as a separate UI block (vs being inlined in a text) you should use
. It changes opacity on click and additionally supports a ripple effect on Android.BorderlessButton<GifGallery>
<img src={useBaseUrl('gifs/androidsettings.gif')} width="280" />
<img src={useBaseUrl('gifs/iossettings.gif')} width="280" />
</GifGallery>To determine emphasis of button it's vital to use fill color or leave it transparent especially on Android.
For medium emphasis you may consider outlined buttons which are used for lower impact than fill buttons.<GifGallery>
<img src={useBaseUrl('gifs/androidbutton.gif')} width="280" />
</GifGallery>Icon or text only buttons
Use
for simple icon-only or text-only buttons. The interaction will be different depending on platform: on Android a borderless ripple will be rendered, whereas on iOS the button will be dimmed.PureNativeButton
It should be used if you wish to handle non-crucial actions and supportive behaviour.<GifGallery>
<img src={useBaseUrl('gifs/androidmail.gif')} width="280" />
<img src={useBaseUrl('gifs/iosmail.gif')} width="280" />
</GifGallery>PureNativeButtonUse a
for accessing the native Component used for building more complex buttons listed above.
It is normally not recommended to use, but it might be useful if we want to wrap it using Animated or Reanimated.
import {
createNativeWrapper,
PureNativeButton,
} from 'react-native-gesture-handler';
import Animated from 'react-native-reanimated';
const { event, Value, createAnimatedComponent } = Animated;
const AnimatedRawButton = createNativeWrapper(
createAnimatedComponent(PureNativeButton),
{
shouldCancelWhenOutside: false,
shouldActivateOnStart: false,
}
);
export default class App extends React.Component {
constructor(props) {
super(props);
const state = new Value();
this._onGestureEvent = event([
{
nativeEvent: { state },
},
]);
}
render() {
return <AnimatedRawButton onHandlerStateChange={this._onGestureEvent} />;
}
}
---PressablePackages/Docs Gesture Handler/Versioned Docs/Version 2.X/Components/Pressable (packages/docs-gesture-handler/versioned_docs/version-2.x/components/pressable.mdx)
---
id: pressable
title: Pressable
sidebar_label: Pressable
---import useBaseUrl from '@docusaurus/useBaseUrl';
import GifGallery from '@site/components/GifGallery';<GifGallery>
<img src={useBaseUrl('gifs/pressable.gif')} width="70%" />
</GifGallery>:::info
This component is a drop-in replacement for thecomponent.Pressable
:::is a component that can detect various stages of tap, press, and hover interactions on any of its children.PressableUsage:
To use
, import it in the following way:
import { Pressable } from 'react-native-gesture-handler';
childrenProperties
styleeither children or a render function that receives a boolean reflecting whether
the component is currently pressed.onPresseither view styles or a function that receives a boolean reflecting whether
the component is currently pressed and returns view styles.onPressOutcalled after
when a single tap gesture is detected.onPressInonPresscalled before
when a touch is engaged.onPressOutonPresscalled before
when a touch is released.onLongPressdelayLongPresscalled immediately after pointer has been down for at least
milliseconds (500ms by default).onLongPressAfter
has been called,onPressOutwill be called as soon as the pointer is lifted andonPresswill not be called at all.cancelabletruewhether a press gesture can be interrupted by a parent gesture such as a scroll event. Defaults to
.onHoverIn(Web only)onHoverOutcalled when pointer is hovering over the element.
(Web only)delayHoverIncalled when pointer stops hovering over the element.
(Web only)onHoverInduration to wait after hover in before calling
.delayHoverOut(Web only)onHoverOutduration to wait after hover out before calling
.delayLongPressonPressInduration (in milliseconds) from
beforeonLongPressis called.disabledPressablewhether the
behavior is disabled.hitSlop(Android & iOS only)onPressInadditional distance outside of the view in which a press is detected and
is triggered.numberAccepts values of type
orRectpressRetentionOffset(Android & iOS only)hitSlopadditional distance outside of the view (or
if present) in which a touch is considered aonPressOut
press beforeis triggered.numberAccepts values of type
orRectandroid_disableSound(Android only)trueif
, doesn't play system sound on touch.android_ripple(Android only)RippleConfigenables the Android ripple effect and configures its color, radius and other parameters.
testOnly_pressedunstable_pressDelayused only for documentation or testing (e.g. snapshot testing).
onPressInduration (in milliseconds) to wait after press down before calling
.GestureHandlerExample:
See the full pressable example from
example app.import GestureStateFlowExample from '@site/src/examples/GestureStateFlowExample';
import { View, Text, StyleSheet } from 'react-native';
import { Pressable } from 'react-native-gesture-handler';
export default function Example() {
return (
<Pressable
style={({ pressed }) => (pressed ? styles.highlight : styles.pressable)}
hitSlop={20}
pressRetentionOffset={20}>
<View style={styles.textWrapper}>
<Text style={styles.text}>Pressable!</Text>
</View>
</Pressable>
);
}
const styles = StyleSheet.create({
pressable: {
width: 120,
height: 120,
backgroundColor: 'mediumpurple',
borderWidth: StyleSheet.hairlineWidth,
},
highlight: {
width: 120,
height: 120,
backgroundColor: 'red',
borderWidth: StyleSheet.hairlineWidth,
},
textWrapper: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
text: {
color: 'black',
},
});
---Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Components/Reanimated Drawer Layout (packages/docs-gesture-handler/versioned_docs/version-2.x/components/reanimated-drawer-layout.mdx)
---
id: reanimated-drawer-layout
title: Reanimated Drawer Layout
sidebar_label: Reanimated Drawer Layout
---
import useBaseUrl from '@docusaurus/useBaseUrl';
Cross-platform replacement for the React Native's DrawerLayoutAndroid component.
For detailed usage of standard parameters, please refer to the React Native docs.
Usage:
To use it, import it in the following way:
import ReanimatedDrawerLayout from 'react-native-gesture-handler/ReanimatedDrawerLayout';
drawerTypeProperties:
DrawerPositionspecifies the way the drawer will be displayed.
Accepts values of theenum. Defaults toFRONT.FRONT-
the drawer will be displayed above the content view.BACK
-the drawer will be displayed below the content view, revealed by sliding away the content view.SLIDE
-the drawer will appear attached to the content view, opening it slides both the drawer and the content view.FRONT|
|BACK|SLIDE|edgeWidth
| ----------------------------------------------------- | ---------------------------------------------------- | ----------------------------------------------------- |
| <img src={useBaseUrl('gifs/new-drawer-front.gif')} /> | <img src={useBaseUrl('gifs/new-drawer-back.gif')} /> | <img src={useBaseUrl('gifs/new-drawer-slide.gif')} /> |hideStatusBarwidth of the invisible, draggable area on the edge of the content view, which can be dragged to open the drawer.
truea boolean value. When set to
, drawer component will use StatusBar API to hide the OS status bar when the drawer is dragged or idle in theopenposition.statusBarAnimationslidea string with possible values:
,noneorfade. Defaults toslide.hideStatusBar
May be used in combination withto select the animation used for hiding the status bar.overlayColor
See StatusBar API docs.opencolor of the background overlay on top of the content window when the drawer is
.rgba(0, 0, 0, 0.7)
This color's opacity animates from 0% to 100% as the drawer transitions from closed to open. Defaults to.renderNavigationViewprogressa renderer function for the drawer component, provided with a
parameter.progress-
-SharedValuethat indicates the progress of drawer opening/closing animation.0
- equalswhen thedraweris closed and1when thedraweris openeddrawer
- can be used by thecomponent to animated its children while thedraweris opening or closingonDrawerCloseonDrawerOpena function which is called when the drawer has been closed.
onDrawerSlidea function which is called when the drawer has been opened.
progressa function which is called when drawer is moving or animating, provided with a
parameter.progress-
-SharedValuethat indicates the progress of drawer opening/closing animation.0
- equalswhen thedraweris closed and1when thedraweris openeddrawer
- can be used by thecomponent to animated its children while thedraweris opening or closingonDrawerStateChangednewStatea function which is called when the status of the drawer changes. It takes two arguments:
-
- interaction state of the drawer. It can be one of the following:DrawerState.IDLE
-DrawerState.DRAGGING
-DrawerState.SETTLING
-drawerWillShow
--truewhendrawerstarted animating toopenposition,falseotherwise.enableTrackpadTwoFingerGesture(iOS only)enableTrackpadTwoFingerGestureenables two-finger gestures on supported devices, for example iPads with trackpads.
If not enabled, the gesture will require click + drag, withswiping with two fingers will also trigger the gesture.childrenchildreneither a component that's rendered in the content view or a function.
Ifis a function, it is provided with aprogressparameter.progress-
-SharedValuethat indicates the progress of drawer opening/closing animation.0
- equalswhen thedraweris closed and1when thedraweris openeddrawer
- can be used by thecomponent to animated its children while thedraweris opening or closingmouseButton(value: MouseButton)(Web & Android only)MouseButtonallows users to choose which mouse button should handler respond to.
The enumconsists of the following predefined fields:LEFT-
RIGHT
-MIDDLE
-BUTTON_4
-BUTTON_5
-ALL
-|Arguments can be combined using
operator, e.g.mouseButton(MouseButton.LEFT | MouseButton.RIGHT). Defaults toMouseButton.LEFT.enableContextMenu(value: boolean)(Web only)falsespecifies whether context menu should be enabled after clicking on underlying view with right mouse button. Defaults to
.openDrawer(options)Methods
openDraweraccepts an optionaloptionsparameter, which is an object with the following optional properties:initialVelocity-
- the initial velocity of the object attached to the spring. Defaults to0.animationSpeed
-- controls speed of the animation. Defaults to1.closeDrawer(options)closeDraweraccepts an optionaloptionsparameter, which is an object with the following optional properties:initialVelocity-
- initial velocity of the object attached to the spring. Defaults to0.animationSpeed
-- controls speed of the animation. Defaults to1.Example:
See the reanimated drawer layout example from GestureHandler example app.
import React, { useRef } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import ReanimatedDrawerLayout, {
DrawerType,
DrawerPosition,
DrawerLayoutMethods,
} from 'react-native-gesture-handler/ReanimatedDrawerLayout';
const DrawerPage = () => {
return (
<View style={styles.drawerContainer}>
<Text>Lorem ipsum</Text>
</View>
);
};
export default function ReanimatedDrawerExample() {
const drawerRef = useRef < DrawerLayoutMethods > null;
const tapGesture = Gesture.Tap()
.runOnJS(true)
.onStart(() => drawerRef.current?.openDrawer());
return (
<ReanimatedDrawerLayout
ref={drawerRef}
renderNavigationView={() => <DrawerPage />}
drawerPosition={DrawerPosition.LEFT}
drawerType={DrawerType.FRONT}>
<View style={styles.innerContainer}>
<GestureDetector gesture={tapGesture}>
<View style={styles.box}>
<Text>Open drawer</Text>
</View>
</GestureDetector>
</View>
</ReanimatedDrawerLayout>
);
}
const styles = StyleSheet.create({
drawerContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'pink',
},
innerContainer: {
flex: 1,
backgroundColor: 'white',
alignItems: 'center',
justifyContent: 'center',
gap: 20,
},
box: {
padding: 20,
backgroundColor: 'pink',
},
});
---SwipeablePackages/Docs Gesture Handler/Versioned Docs/Version 2.X/Components/Reanimated Swipeable (packages/docs-gesture-handler/versioned_docs/version-2.x/components/reanimated_swipeable.md)
---
id: reanimated_swipeable
title: Reanimated Swipeable
sidebar_label: Reanimated Swipeable
---import useBaseUrl from '@docusaurus/useBaseUrl';
import GifGallery from '@site/components/GifGallery'<GifGallery>
<img src={useBaseUrl("gifs/sampleswipeable.gif")} height="120" />
</GifGallery>:::info
This component is a drop-in replacement for thecomponent, rewritten usingReanimated.Swipeable
:::Reanimated
allows for implementing swipeable rows or similar interaction. It renders its children within a pannable container and allows for horizontal swiping left and right. While swiping, one of two "action" containers can be shown depending on whether the user swipes left or right (containers can be rendered byrenderLeftActionsorrenderRightActionsprops).Usage:
To use it, import it in the following way:
import Swipeable from 'react-native-gesture-handler/ReanimatedSwipeable';
frictionProperties
1a number that specifies how much the visual interaction will be delayed compared to the gesture distance.
e.g. value ofwill indicate that the swipeable panel should exactly follow the gesture,2means it is going to be two times "slower".leftThresholdrightThresholddistance from the left edge at which released panel will animate to the open state (or the open panel will animate into the closed state). By default it's a half of the panel's width.
dragOffsetFromLeftEdgedistance from the right edge at which released panel will animate to the open state (or the open panel will animate into the closed state). By default it's a half of the panel's width.
10distance that the panel must be dragged from the left edge to be considered a swipe. The default value is
.dragOffsetFromRightEdge10distance that the panel must be dragged from the right edge to be considered a swipe. The default value is
.overshootLefttruea boolean value indicating if the swipeable panel can be pulled further than the left actions panel's width. It is set to
by default as long as the left panel render function is present.overshootRighttruea boolean value indicating if the swipeable panel can be pulled further than the right actions panel's width. It is set to
by default as long as the right panel render function is present.overshootFriction1a number that specifies how much the visual interaction will be delayed compared to the gesture distance at overshoot. Default value is
, it means no friction, for a native feel, try8or above.onSwipeableOpenswipeablea function that is called when
is opened (either right or left).onSwipeableClose
Receives swipe direction as an argument.swipeablea function that is called when
is closed.onSwipeableWillOpen
Receives swipe direction as an argument.swipeablea function that is called when
starts animating on open (either right or left).onSwipeableWillClose
Receives swipe direction as an argument.swipeablea function that is called when
starts animating on close.onSwipeableOpenStartDrag
Receives swipe direction as an argument.swipablea function that is called when a user starts to drag the
to open.onSwipeableCloseStartDrag
Receives swipe direction as an argument.swipablea function that is called when a user starts to drag the
to close.renderLeftActions
Receives swipe direction as an argument.progressa function that returns a component which will be rendered under the swipeable after swiping it to the right.
The function receives the following arguments:-
- aSharedValuerepresenting swiping progress relative to the width of the returned element.0
- Equalswhenswipeableis closed,1whenswipeableis opened.Infinity
- When the element overshoots it's opened position the value tends towards.translation
-- a horizontal offset of theswipeablerelative to its closed position.swipeableMethods
-- provides an object exposing the methods listed here.ReactNodeThis function must return a
.rtlTo support
flexbox layouts useflexDirectionstyling.renderRightActionsprogressa function that returns a component which will be rendered under the swipeable after swiping it to the left.
The function receives the following arguments:-
- aSharedValuerepresenting swiping progress relative to the width of the returned element.0
- Equalswhenswipeableis closed,1whenswipeableis opened.Infinity
- When the element overshoots it's opened position the value tends towards.translation
-- a horizontal offset of theswipeablerelative to its closed position.swipeableMethods
-- provides an object exposing the methods listed here.ReactNodeThis function must return a
.rtlTo support
flexbox layouts useflexDirectionstyling.containerStyleAnimated.Viewstyle object for the container (
), for example to overrideoverflow: 'hidden'.childrenContainerStyleAnimated.Viewstyle object for the children container (
), for example to applyflex: 1.simultaneousWithExternalGestureA gesture configuration to be recognized simultaneously with the swipeable gesture. This is useful for allowing other gestures to work simultaneously with swipeable gesture handler.
For example, to enable a pan gesture alongside the swipeable gesture:
const panGesture = Gesture.Pan();
<GestureDetector gesture={panGesture}>
<ReanimatedSwipeable simultaneousWithExternalGesture={panGesture} />
</GestureDetector>
More details can be found in the gesture composition documentation.enableTrackpadTwoFingerGesture(iOS only)enableTrackpadTwoFingerGestureEnables two-finger gestures on supported devices, for example iPads with trackpads.
If not enabled the gesture will require click + drag, withswiping with two fingers will also trigger the gesture.mouseButton(value: MouseButton)(Web & Android only)MouseButtonAllows users to choose which mouse button should handler respond to. The enum
consists of the following predefined fields:LEFT-
RIGHT
-MIDDLE
-BUTTON_4
-BUTTON_5
-ALL
-|Arguments can be combined using
operator, e.g.mouseButton(MouseButton.LEFT | MouseButton.RIGHT). Default value is set toMouseButton.LEFT.enableContextMenu(value: boolean)(Web only)falseSpecifies whether context menu should be enabled after clicking on underlying view with right mouse button. Default value is set to
.SwipeableMethods
Using reference to
it's possible to trigger some actions on itcloseopenLefta method that closes component.
openRighta method that opens component on left side.
reseta method that opens component on right side.
Swipeablea method that resets the swiping states of this
component.closeUnlike method
, this method does not trigger any animation.Example:
For a more in-depth presentation of differences between the new and the legacy implementations,
see swipeable example from GestureHandler Example App.
import React from 'react';
import { Text, StyleSheet } from 'react-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import ReanimatedSwipeable from 'react-native-gesture-handler/ReanimatedSwipeable';
import Reanimated, {
SharedValue,
useAnimatedStyle,
} from 'react-native-reanimated';
function RightAction(prog: SharedValue<number>, drag: SharedValue<number>) {
const styleAnimation = useAnimatedStyle(() => {
console.log('showRightProgress:', prog.value);
console.log('appliedTranslation:', drag.value);
return {
transform: [{ translateX: drag.value + 50 }],
};
});
return (
<Reanimated.View style={styleAnimation}>
<Text style={styles.rightAction}>Text</Text>
</Reanimated.View>
);
}
export default function Example() {
return (
<GestureHandlerRootView>
<ReanimatedSwipeable
containerStyle={styles.swipeable}
friction={2}
enableTrackpadTwoFingerGesture
rightThreshold={40}
renderRightActions={RightAction}>
<Text>Swipe me!</Text>
</ReanimatedSwipeable>
</GestureHandlerRootView>
);
}
const styles = StyleSheet.create({
rightAction: { width: 50, height: 50, backgroundColor: 'purple' },
separator: {
width: '100%',
borderTopWidth: 1,
},
swipeable: {
height: 50,
backgroundColor: 'papayawhip',
alignItems: 'center',
},
});
---pressRetentionOffsetPackages/Docs Gesture Handler/Versioned Docs/Version 2.X/Components/Touchables (packages/docs-gesture-handler/versioned_docs/version-2.x/components/touchables.md)
---
id: touchables
title: Touchables
sidebar_label: Touchables
---:::warning
Touchables will be removed in the future version of Gesture Handler. Use Pressable instead.
:::Gesture Handler library provides an implementation of RN's touchable components that are based on native buttons and does not rely on JS responder system utilized by RN. Our touchable implementation follows the same API and aims to be a drop-in replacement for touchables available in React Native.
React Native's touchables API can be found here:
- Touchable Native Feedback
- Touchable Highlight
- Touchable Opacity
- Touchable Without FeedbackAll major touchable properties (except from
) have been adopted and should behave in a similar way as with RN's touchables.ScrollViewThe motivation for using RNGH touchables as a replacement for these imported from React Native is to follow built-in native behavior more closely by utilizing platform native touch system instead of relying on the JS responder system.
These touchables and their feedback behavior are deeply integrated with native
gesture ecosystem and could be connected with other native components (e.g.) and Gesture Handlers easily and in a more predictable way, whichuseNativeAnimations
follows native apps' behavior.Our intention was to make switch for these touchables as simple as possible. In order to use RNGH's touchables the only thing you need to do is to change the library from which you import touchable components.
:::info
Gesture Handler's TouchableOpacity uses native driver for animations by default. If this causes problems for you, you can setprop to false.
:::Example:
import {
TouchableNativeFeedback,
TouchableHighlight,
TouchableOpacity,
TouchableWithoutFeedback,
} from 'react-native';
has to be replaced with:import {
TouchableNativeFeedback,
TouchableHighlight,
TouchableOpacity,
TouchableWithoutFeedback,
} from 'react-native-gesture-handler';
For a comparison of both touchable implementations see our touchables exampleRace---
Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Fundamentals/ Category .Json (packages/docs-gesture-handler/versioned_docs/version-2.x/fundamentals/_category_.json)
{
"label": "Fundamentals",
"position": 1,
"link": {
"type": "generated-index"
}
}---
Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Fundamentals/Gesture Composition (packages/docs-gesture-handler/versioned_docs/version-2.x/fundamentals/gesture-composition.md)
---
id: gesture-composition
title: Gesture composition & interactions
sidebar_label: Gesture composition & interactions
sidebar_position: 3
---Composing gestures is much simpler in RNGH2, you don't need to create a ref for every gesture that depends on another one. Instead you can use
,SimultaneousandExclusivemethods provided by theGestureobject.simultaneousHandlersRace
Only one of the provided gestures can become active at the same time. The first gesture to become active will cancel the rest of the gestures. It accepts variable number of arguments.
It is the equivalent to having more than one gesture handler without definingandwaitForprops.RaceFor example, let's say that you have a component that you want to make draggable but you also want to show additional options on long press. Presumably you would not want the component to move after the long press activates. You can accomplish this using
:useSharedValueNote: theanduseAnimatedStyleare part ofreact-native-reanimated.
/ Detailed source-code truncated for AI context efficiency. /
simultaneousHandlersSimultaneous
All of the provided gestures can activate at the same time. Activation of one will not cancel the other.
It is the equivalent to having some gesture handlers, each withprop set to the other handlers.SimultaneousFor example, if you want to make a gallery app, you might want user to be able to zoom, rotate and pan around photos. You can do it with
:useSharedValueNote: theanduseAnimatedStyleare part ofreact-native-reanimated.
/ Detailed source-code truncated for AI context efficiency. /
waitForExclusive
Only one of the provided gestures can become active, with the first one having a higher priority than the second one (if both gestures are still possible, the second one will wait for the first one to fail before it activates), second one having a higher priority than the third one, and so on.
It is equivalent to having some gesture handlers where the second one has theprop set to the first handler, third one has thewaitForprop set to the first and the second one, and so on.ExclusiveFor example, if you want to make a component that responds to single tap as well as to a double tap, you can accomplish that using
:useSharedValueNote: theanduseAnimatedStyleare part ofreact-native-reanimated.
import { GestureDetector, Gesture } from 'react-native-gesture-handler';
function App() {
const singleTap = Gesture.Tap().onEnd((_event, success) => {
if (success) {
console.log('single tap!');
}
});
const doubleTap = Gesture.Tap()
.numberOfTaps(2)
.onEnd((_event, success) => {
if (success) {
console.log('double tap!');
}
});
const taps = Gesture.Exclusive(doubleTap, singleTap);
return (
<GestureDetector gesture={taps}>
<Component />
</GestureDetector>
);
}
GestureDetectorCross-component interactions
You may have noticed that gesture composition described above requires you to mount all of the composed gestures under a single
, effectively attaching them to the same underlying component. You can customize how gestures interact with each other across multiple components in a couple of ways:requireExternalGestureToFailrequireExternalGestureToFail
allows delaying activation of the handler until all handlers passed as arguments to this method fail (or don't begin at all).For example, you may want to have two nested components, both of them can be tapped by the user to trigger different actions: outer view requires one tap, but the inner one requires 2 taps. If you don't want the first tap on the inner view to activate the outer handler, you must make the outer gesture wait until the inner one fails:
import React from 'react';
import { View, StyleSheet } from 'react-native';
import {
GestureDetector,
Gesture,
GestureHandlerRootView,
} from 'react-native-gesture-handler';
export default function Example() {
const innerTap = Gesture.Tap()
.numberOfTaps(2)
.onStart(() => {
console.log('inner tap');
});
const outerTap = Gesture.Tap()
.onStart(() => {
console.log('outer tap');
})
.requireExternalGestureToFail(innerTap);
return (
<GestureHandlerRootView style={styles.container}>
<GestureDetector gesture={outerTap}>
<View style={styles.outer}>
<GestureDetector gesture={innerTap}>
<View style={styles.inner} />
</GestureDetector>
</View>
</GestureDetector>
</GestureHandlerRootView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
outer: {
width: 250,
height: 250,
backgroundColor: 'lightblue',
},
inner: {
width: 100,
height: 100,
backgroundColor: 'blue',
alignSelf: 'center',
},
});
blocksExternalGestureblocksExternalGesture
works similarly torequireExternalGestureToFailbut the direction of the relation is reversed - instead of being a one-to-many relation, it's many-to-one. It's especially useful for making lists where theScrollViewcomponent needs to wait for every gesture underneath it. All that is required to do is to pass a ref, for example:
/ Detailed source-code truncated for AI context efficiency. /
simultaneousWithExternalGesturesimultaneousWithExternalGesture
allows gestures across different components to be recognized simultaneously. For example, you may want to have two nested views, both with tap gesture attached. Both of them require one tap, but tapping the inner one should also activate the gesture attached to the outer view:
import React from 'react';
import { View, StyleSheet } from 'react-native';
import {
GestureDetector,
Gesture,
GestureHandlerRootView,
} from 'react-native-gesture-handler';
export default function Example() {
const innerTap = Gesture.Tap()
.onStart(() => {
console.log('inner tap');
});
const outerTap = Gesture.Tap()
.onStart(() => {
console.log('outer tap');
})
.simultaneousWithExternalGesture(innerTap);
return (
<GestureHandlerRootView style={styles.container}>
<GestureDetector gesture={outerTap}>
<View style={styles.outer}>
<GestureDetector gesture={innerTap}>
<View style={styles.inner} />
</GestureDetector>
</View>
</GestureDetector>
</GestureHandlerRootView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
outer: {
width: 250,
height: 250,
backgroundColor: 'lightblue',
},
inner: {
width: 100,
height: 100,
backgroundColor: 'blue',
alignSelf: 'center',
},
});
---react-native-gesture-handlerPackages/Docs Gesture Handler/Versioned Docs/Version 2.X/Fundamentals/Installation (packages/docs-gesture-handler/versioned_docs/version-2.x/fundamentals/installation.md)
---
id: installation
title: Installation
sidebar_position: 2
---import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';Requirements
supports the three latest minor releases ofreact-native.react-native| version |
version |react-native-reanimated
| ------- | ---------------------- |
| 2.28.0+ | 0.79.0+ |
| 2.26.0+ | 0.78.0+ |
| 2.25.0+ | 0.76.0+ |
| 2.24.0+ | 0.75.0+ |
| 2.21.0+ | 0.74.0+ |
| 2.18.0+ | 0.73.0+ |
| 2.16.0+ | 0.68.0+ |
| 2.14.0+ | 0.67.0+ |
| 2.10.0+ | 0.64.0+ |
| 2.0.0+ | 0.63.0+ |In order to fully utilize the touch events you also need to use
2.3.0 or newer.react-native-gesture-handlerSetting up
is pretty straightforward:1. Start with installing the package from npm:
<Tabs groupId="package-managers">
<TabItem value="expo" label="EXPO" default>
npx expo install react-native-gesture-handler
</TabItem>
<TabItem value="npm" label="NPM">npm install react-native-gesture-handler
</TabItem>
<TabItem value="yarn" label="YARN">yarn add react-native-gesture-handler
</TabItem>GestureHandlerRootView
</Tabs>2. Wrap your app with
component
import { GestureHandlerRootView } from 'react-native-gesture-handler';
export default function App() {
return (
<GestureHandlerRootView>
<ActualApp />
</GestureHandlerRootView>
);
}
If you don't provide anything to thestylesprop, it will default toflex: 1. If you want to customize the styling of the root view, don't forget to also includeflex: 1in the custom style, otherwise your app won't render anything. KeepGestureHandlerRootViewas close to the actual root of the app as possible. It's the entry point for all gestures and all gesture relations. The gestures won't be recognized outside of the root view, and relations only work between gestures mounted under the same root view.GestureHandlerRootViewIf you're unsure if one of your dependencies already renders
on its own, don't worry and add one at the root anyway. In case of nested root views, Gesture Handler will only use the top-most one and ignore the nested ones.GestureHandlerRootView:::tip
If you're using gesture handler in your component library, you may want to wrap your library's code in thecomponent. This will avoid extra configuration for the user.<GestureHandlerRootView>
::::::tip
If you're having trouble with gestures not working when inside a component provided by a third-party library, even though you've wrapped the entry point with, you can try adding another<GestureHandlerRootView unstable_forceActive>closer to the place the gestures are defined. This way, you can prevent Android from canceling relevant gestures when one of the native views tries to grab lock for delivering touch events.
:::3. Platform specific setup
When using an Expo development build, run prebuild to update the native code in the ios and android directories.
npx expo prebuild
#### AndroidGestureHandlerRootViewSetting up Gesture Handler on Android doesn't require any more steps. Keep in mind that if you want to use gestures in Modals you need to wrap Modal's content with
:
import { Modal } from 'react-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
export function CustomModal({ children, ...rest }) {
return (
<Modal {...rest}>
<GestureHandlerRootView>{children}</GestureHandlerRootView>
</Modal>
);
}
##### KotlinkotlinVersionGesture Handler on Android is implemented in Kotlin. If you need to set a specific Kotlin version to be used by the library, set the
ext property inandroid/build.gradlefile and RNGH will use that version:
buildscript {
ext {
kotlinVersion = "1.6.21"
}
}
#### iOSWhile developing for iOS, make sure to install pods first before running the app:
cd ios && pod install && cd ..
#### WebGestureHandlerRootViewThere is no additional configuration required for the web.
#### With wix/react-native-navigation
If you are using a native navigation library like wix/react-native-navigation you need to make sure that every screen is wrapped with
. This can be done for example at the stage when you register your screens. Here's an example:
import { Navigation } from 'react-native-navigation';
import FirstTabScreen from './FirstTabScreen';
import SecondTabScreen from './SecondTabScreen';
import PushedScreen from './PushedScreen';
// register all screens of the app (including internal ones)
export function registerScreens() {
Navigation.registerComponent(
'example.FirstTabScreen',
() => {
return (
<GestureHandlerRootView>
<FirstTabScreen />
</GestureHandlerRootView>
);
},
() => FirstTabScreen
);
Navigation.registerComponent(
'example.SecondTabScreen',
() => {
return (
<GestureHandlerRootView>
<SecondTabScreen />
</GestureHandlerRootView>
);
},
() => SecondTabScreen
);
Navigation.registerComponent(
'example.PushedScreen',
() => {
return (
<GestureHandlerRootView>
<PushedScreen />
</GestureHandlerRootView>
);
},
() => PushedScreen
);
}
You can check out this example project to see this kind of set up in action.GestureHandlerRootViewRemember that you need to wrap each screen that you use in your app with
as with native navigation libraries each screen maps to a separate root view. It will not be enough to wrap the main screen only.react-native-reanimated---
Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Fundamentals/Introduction (packages/docs-gesture-handler/versioned_docs/version-2.x/fundamentals/introduction.md)
---
id: introduction
title: Introduction
sidebar_label: Introduction
sidebar_position: 1
slug: /
---Gesture Handler provides a declarative API exposing the native platform's touch and gesture system to React Native. It's designed to be a replacement of React Native's built in touch system called Gesture Responder System. Using native touch handling allows to address the performance limitations of React Native's Gesture Responder System. It also provides more control over the platform's native components that can handle gestures on their own. If you want to learn more, we recommend this talk by Krzysztof Magiera in which he explains issues with the responder system.
The main benefits of using React Native Gesture Handler are:
- A way to use a platform's native touch handling system for recognizing gestures (like pinch, rotation, pan and a few others).
- The ability to define relations between gestures to ensure gestures, and possibly native components, will not conflict with each other.
- Mechanisms to use touchable components that run in native thread and follow platform default behavior; e.g. in the event they are in a scrollable component, turning into pressed state is slightly delayed to prevent it from highlighting when you fling.
- Close integration withto process touch events on the UI thread.maxDist
- Support for different input devices like touch screens, pens and mice.
- Ability to include any native component into the Gesture Handler's touch system, making it work alongside your gestures.:::info
We recommend using Reanimated to implement gesture-driven animations with Gesture Handler. Its more advanced features rely heavily on worklets and the UI runtime provided by Reanimated.
:::Learning resources
Apps
Gesture Handler Example App – official gesture handler "showcase" app.
Talks and workshops
Declarative future of gestures and animations in React Native by Krzysztof Magiera - talk that explains motivation behind creating gesture handler library. It also presents react-native-reanimated and how and when it can be used with gesture handler.
React Native workshop with Expo team @ReactEurope 2018 by Brent Vatne – great workshop explaining gesture handler in details and presenting a few exercises that will help get you started.
Living in an async world of React Native by Krzysztof Magiera – talk which highlights some issue with the React Native's touch system Gesture Handler aims to address. Also the motivation for building this library is explained.
React Native Touch & Gesture by Krzysztof Magiera - talk explaining JS responder system limitations and points out some of the core features of Gesture Handler.
Contributing
If you are interested in the project and want to contribute or support it in other ways don't hesitate to contact anyone from the team on Twitter or Bluesky (links below)!
All PRs are welcome, but talk to us before you start working on something big.
The easiest way to get started with contributing code is by:
- Reviewing the list of open issues and trying to solve the one that seem approachable to you.
- Updating the documentation whenever you see some information is unclear, missing or out of date.Code is only one way how you can contribute. You may want to consider replying on issues if you know how to help.
Community
We are very proud of the community that has been build around this package. We really appreciate all your help regardless of whether it is a pull request, issue report, helping others by commenting on existing issues or posting some demo or video tutorial on social media.
If you've build something with this library you'd like to share, please contact us as we'd love to help share it with others.Gesture Handler Team 🚀
<div className="community-holder-container">
<div className="community-holder-container-item">
<div className="community-imageHolder">
<img src="https://ca.slack-edge.com/T03Q9AMJJ-U02700KC6J1-0c9e18c89e71-512" />
</div>
<div>Jakub Piasecki</div>
<div><a href="https://twitter.com/breskin67">@breskin67</a></div>
<div><a href="https://bsky.app/profile/jpiasecki.com">@jpiasecki.com</a></div>
</div><div className="community-holder-container-item">
<div className="community-imageHolder">
<img src="https://ca.slack-edge.com/T03Q9AMJJ-U03N3HU2C0M-60a31c54a7d5-512" />
</div>
<div>Michał Bert</div>
<div><a href="https://x.com/Michal3870">@Michal3870</a></div>
</div><div className="community-holder-container-item">
<div className="community-imageHolder">
<img src="https://ca.slack-edge.com/T03Q9AMJJ-U06MQFHEY3V-6535cb89fc75-192" />
</div>
<div>Ignacy Łątka</div>
<div><a href="https://x.com/latekvo">@latekvo</a></div>
</div><div className="community-holder-container-item">
<div className="community-imageHolder">
<img src="https://ca.slack-edge.com/T03Q9AMJJ-U0F40CATS-d0a2e7559a1b-512" />
</div>
<div>Krzysztof Magiera</div>
<div><a href="https://twitter.com/kzzzf">@kzzzf</a></div>
<div><a href="https://bsky.app/profile/kzzzf.bsky.social">@kzzzf.bsky.social</a></div>
</div></div>
Sponsors
We really appreciate our sponsors! Thanks to them we can develop our library and make the react-native world a better place. Special thanks for:
<div className="community-holder-container">
<div className="community-holder-container-item">
<a href="https://expo.dev">
<div className="community-imageHolder">
<img className="community-imageHolder" src="https://avatars2.githubusercontent.com/u/12504344?v=3&s=100" />
</div>
<div>Expo</div>
</a>
</div></div>
---
Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Fundamentals/States Events (packages/docs-gesture-handler/versioned_docs/version-2.x/fundamentals/states-events.mdx)
---
id: states-events
title: Gesture states & events
sidebar_label: Gesture states & events
sidebar_position: 4
---Every gesture can be treated as "state machine".
At any given time, each handler instance has an assigned state that can change when new touch events occur or can be forced to change by the touch system in certain circumstances.A gesture can be in one of the six possible states:
- #### UNDETERMINED
This is the initial state of each gesture recognizer and it goes into this state after it's done recognizing a gesture.
- #### FAILED
A gesture recognizer received some touches but for some reason didn't recognize them. For example, if a finger travels more distance than a defined
property allows, then the gesture won't become active but will fail instead. Afterwards, its state will be reset toUNDETERMINED.CANCELLED- #### BEGAN
Gesture recognizer has started receiving touch stream but hasn't yet received enough data to either fail or activate.
- #### CANCELLED
The gesture recognizer has received a signal (possibly new touches or a command from the touch system controller) resulting in the cancellation of a continuous gesture. The gesture's state will become
until it is finally reset to the initial state,UNDETERMINED.ACTIVE- #### ACTIVE
Recognizer has recognized a gesture. It will become and stay in the
state until the gesture finishes (e.g. when user lifts the finger) or gets cancelled by the touch system. Under normal circumstances the state will then turn intoEND. In the case that a gesture is cancelled by the touch system, its state would then becomeCANCELLED.END- #### END
The gesture recognizer has received touches signalling the end of a gesture. Its state will become
until it is reset toUNDETERMINED.StateChangeEventState flows
The most typical flow of state is when a gesture picks up on an initial touch event, then recognizes it, then acknowledges its ending and resets itself back to the initial state.
The flow looks as follows:
import GestureStateFlowExample from '@site/src/examples/GestureStateFlowExampleLegacy';
<InteractiveExample
component={<GestureStateFlowExample />}
label="Drag or long-press the circle"
larger={true}
/>Events
There are three types of events in RNGH2:
,GestureEventandPointerEvent. TheStateChangeEventis sent every time a gesture moves to a different state, whileGestureEventis sent every time a gesture is updated. The first two carry a gesture-specific data and astateproperty, indicating the current state of the gesture.StateChangeEventalso carries aoldStateproperty indicating the previous state of the gesture.PointerEventcarries information about raw touch events, like touching the screen or moving the finger. These events are handled internally before they are passed along to the correct callbacks:onBeginBEGANstate.onStartACTIVEstate.onEndACTIVEstate to theEND,FAILED, orCANCELLEDstate. If the gesture transitions to theENDstate, thesuccessargument is set totrueotherwise it is set tofalse.onFinalizeEND,FAILED, orCANCELLEDstate. If the gesture transitions to theENDstate, thesuccessargument is set totrueotherwise it is set tofalse. If the gesture transitions from theACTIVEstate, it will be called afteronEnd.onUpdateACTIVEIs called every time a gesture is updated while it is in the
state.onPointerDownonPointerMoveIs called when new pointers are placed on the screen. It may carry information about more than one pointer because the events are batched.
onPointerUpIs called when pointers are moved on the screen. It may carry information about more than one pointer because the events are batched.
onPointerCancelledIs called when pointers are lifted from the screen. It may carry information about more than one pointer because the events are batched.
PanGestureHandlerIs called when there will be no more information about this pointer. It may be called because the gesture has ended or was interrupted. It may carry information about more than one pointer because the events are batched.
---
Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gesture Handlers/ Category .Json (packages/docs-gesture-handler/versioned_docs/version-2.x/gesture-handlers/_category_.json)
{
"label": "Gesture handlers (legacy)",
"position": 6,
"link": {
"type": "generated-index"
}
}---
Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gesture Handlers/About Handlers (packages/docs-gesture-handler/versioned_docs/version-2.x/gesture-handlers/about-handlers.md)
---
id: about-handlers
title: About Gesture Handlers
sidebar_label: About Gesture Handlers
sidebar_position: 1
---:::warning
The old API will be removed in the future version of Gesture Handler. Please migrate to gestures API instead. Check out our upgrading guide for more information.
:::Gesture handlers are the core building blocks of this library.
We use this term to describe elements of the native touch system that the library allows us to instantiate and control from JavaScript using React's Component interface.Each handler type is capable of recognizing one type of gesture (pan, pinch, etc.) and provides gesture-specific information via events (translation, scale, etc.).
Handlers analyze the touch stream synchronously in the UI thread. This allows for uninterrupted interactions even when the JavaScript thread is blocked.
Each handler works as an isolated state machine. It takes a touch stream as an input and based on it, it can flip between states.
When a gesture starts, based on the position where the finger was placed, a set of handlers that may be interested in recognizing the gesture is selected.
All the touch events (touch down, move, up, or when other fingers are placed or lifted) are delivered to all of the handlers selected initially.
When one gesture becomes active, it cancels all the other gestures (read more about how to influence this process in "Cross handler interactions" section).Gesture handler components do not instantiate a native view in the view hierarchy. Instead, they are kept in the library's own registry and are only connected to native views. When using any of the gesture handler components, it is important for it to have a native view rendered as a child.
Since handler components don't have corresponding views in the hierarchy, the events registered with them are actually hooked into the underlying view.Available gesture handlers
Currently, the library provides the following list of gestures. Their parameters and attributes they provide to gesture events are documented under each gesture page:
TapGestureHandler
-LongPressGestureHandler
-RotationGestureHandler
-FlingGestureHandler
-PinchGestureHandler
-ForceTouchGestureHandler
-PanGestureHandlerDiscrete vs continuous
We distinguish between two types of gestures: discrete and continuous.
Continuous gesture handlers can be active for a long period of time and will generate a stream of gesture events until the gesture is over.
An example of a continuous handler isthat once activated, will start providing updates about translation and other properties.LongPressGestureHandlerOn the other hand, discrete gesture handlers once activated will not stay in the active state but will end immediately.
is a discrete handler, as it only detects if the finger is placed for a sufficiently long period of time, it does not track finger movements (as that's the responsibility ofPanGestureHandler).onGestureEventKeep in mind that
is only generated by continuous gesture handlers and shouldn't be used in theTapGestureHandlerand other discrete handlers.useNativeDriverNesting handlers
Handler components can be nested. In any case, it is recommended that the innermost handler renders a native view component. There are some limitations that apply when using
flag. An example of nested handlers:
class Multitap extends Component {
render() {
return (
<LongPressGestureHandler
onHandlerStateChange={this._onLongpress}
minDurationMs={800}>
<TapGestureHandler
onHandlerStateChange={this._onSingleTap}
waitFor={this.doubleTapRef}>
<TapGestureHandler
ref={this.doubleTapRef}
onHandlerStateChange={this._onDoubleTap}
numberOfTaps={2}>
<View style={styles.box} />
</TapGestureHandler>
</TapGestureHandler>
</LongPressGestureHandler>
);
}
}
NativeViewGestureHandlerUsing native components
Gesture handler library exposes a set of components normally available in React Native that are wrapped in
.ScrollView
Here is a list of exposed components:-
FlatList
-Switch
-TextInput
-DrawerLayoutAndroid
-(Android only)ScrollViewIf you want to use other handlers or buttons nested in a
, use thewaitForproperty to define interaction between a handler andScrollView.useNativeDriverEvents with
Animated.eventBecause handlers do not instantiate native views but instead hook up to their child views, directly nesting two gesture handlers using
is not currently supported.<Animated.View>
To workaround this limitation we recommend placing ancomponent in between the handlers.Instead of doing:
const PanAndRotate = () => (
<PanGestureHandler onGestureEvent={Animated.event({ ... }, { useNativeDriver: true })}>
<RotationGestureHandler onGestureEvent={Animated.event({ ... }, { useNativeDriver: true })}>
<Animated.View style={animatedStyles}/>
</RotationGestureHandler>
</PanGestureHandler>
);
Place an<Animated.View>in between the handlers:
const PanAndRotate = () => (
<PanGestureHandler onGestureEvent={Animated.event({ ... }, { useNativeDriver: true })}>
<Animated.View>
<RotationGestureHandler onGestureEvent={Animated.event({ ... }, { useNativeDriver: true })}>
<Animated.View style={animatedStyles}/>
</RotationGestureHandler>
</Animated.View>
</PanGestureHandler>
);
Another consequence of handlers depending on their native child components is that when using auseNativeDriverflag with anAnimated.event, the child component must be wrapped by anAnimated.API, e.g.<Animated.View>instead of just a<View>:
class Draggable extends Component {
render() {
return (
<PanGestureHandler onGestureEvent={Animated.event({ ... }, { useNativeDriver: true })}>
<Animated.View style={animatedStyles} /> {/ <-- NEEDS TO BE Animated.View /}
</PanGestureHandler>
);
}
};
---enabledPackages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gesture Handlers/Common Gh (packages/docs-gesture-handler/versioned_docs/version-2.x/gesture-handlers/common-gh.md)
---
id: common-gh
title: Common handler properties
sidebar_label: Common handler properties
sidebar_position: 4
---:::warning
The old API will be removed in the future version of Gesture Handler. Please migrate to gestures API instead. Check out our upgrading guide for more information.
:::This page covers the common set of properties all gesture handler components expose.
Units
All handler component properties and event attributes that represent onscreen dimensions are expressed in screen density independent units we refer to as "points".
These are the units commonly used in React Native ecosystem (e.g. in the layout system).
They do not map directly to physical pixels but instead to iOS's points and to dp units on Android.Properties
This section describes properties that can be used with all gesture handler components:
falseAccepts a boolean value.
Indicates whether the given handler should be analyzing stream of touch events or not.
When set towe can be sure that the handler's state will never becomeACTIVE.FAILED
If the value gets updated while the handler has already started recognizing a gesture, then the handler's state will immediately change toorCANCELLED(depending on its current state).true
Default value is.shouldCancelWhenOutsidetrueAccepts a boolean value.
Whenthe handler will cancel or fail recognition (depending on its current state) whenever the finger leaves the area of the connected view.shouldCancelWhenOutside
Default value of this property is different depending on the handler type.
Most handlers'property defaults tofalseexcept for theLongPressGestureHandlerandTapGestureHandlerwhich default totrue.cancelsTouchesInView(iOS only)trueAccepts a boolean value.
When, the handler will cancel touches for native UI components (UIButton,UISwitch, etc) it's attached to when it becomesACTIVE.true
Default value is.simultaneousHandlersReact.createRef()Accepts a react ref object or an array of refs to other handler components (refs should be created using
). When set, the handler will be allowed to activate even if one or more of the handlers provided by their refs are in anACTIVEstate. It will also prevent the provided handlers from cancelling the current handler when they activate. Read more in the cross handler interaction section.waitForReact.createRef()Accepts a react ref object or an array of refs to other handler components (refs should be created using
). When set, the handler will not activate as long as the handlers provided by their refs are in theBEGANstate. Read more in the cross handler interaction section.hitSlopleftThis parameter enables control over what part of the connected view area can be used to begin recognizing the gesture.
When a negative number is provided the bounds of the view will reduce the area by the given number of points in each of the sides evenly.Instead, you can pass an object to specify how each boundary side should be reduced by providing a different number of points for
,right,toporbottomsides.horizontal
You can alternatively provideorverticalinstead of specifying directlyleft,rightortopandbottom.width
Finally, the object can also takeandheightattributes.width
Whenis set it is only allowed to specify one of the sidesrightorleft.height
Similarly whenis provided onlytoporbottomcan be set.width
Specifyingorheightis useful if we only want the gesture to activate on the edge of the view. In which case for example we can setleft: 0andwidth: 20which would make it possible for the gesture to be recognized when started no more than 20 points from the left edge.widthIMPORTANT: Note that this parameter is primarily designed to reduce the area where gesture can activate. Hence it is only supported for all the values (except
andheight) to be non positive (0 or lower). Although on Android it is supported for the values to also be positive and therefore allow to expand beyond view bounds but not further than the parent view bounds. To achieve this effect on both platforms you can use React Native's View hitSlop property.userSelect(Web only)userSelectThis parameter allows to specify which
property should be applied to underlying view. Possible values are"none" | "auto" | "text". Default value is set to"none".activeCursor(Web only)"grab"This parameter allows to specify which cursor should be used when gesture activates. Supports all CSS cursor values (e.g.
,"zoom-in"). Default value is set to"auto".onGestureEventPinchGestureHandlerTakes a callback that is going to be triggered for each subsequent touch event while the handler is in an ACTIVE state. Event payload depends on the particular handler type. Common set of event data attributes is documented below and handler specific attributes are documented on the corresponding handler pages. E.g. event payload for
contains ascaleattribute that represents how the distance between fingers changed since the gesture started.Animated.eventobject can be used. Also Animated events withuseNativeDriverflag enabled are fully supported.onHandlerStateChangeonGestureEventTakes a callback that is going to be triggered when state of the given handler changes.
The event payload contains the same payload as in the case of
including handler specific event attributes some handlers may provide.onHandlerStateChangeIn addition
event payload containsoldStateattribute which represents the state of the handler right before the change.Animated.eventobject can be used. Also Animated events withuseNativeDriverflag enabled are fully supported.onGestureEventEvent data
This section describes the attributes of event object being provided to
andonHandlerStateChangecallbacks:stateStateCurrent state of the handler. Expressed as one of the constants exported under
object by the library. Refer to the section about handler state to learn more about how to use it.numberOfPointersNativeViewGestureHandlerRepresents the number of pointers (fingers) currently placed on the screen.
---
Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gesture Handlers/Create Native Wrapper (packages/docs-gesture-handler/versioned_docs/version-2.x/gesture-handlers/create-native-wrapper.md)
---
id: create-native-wrapper
title: createNativeWrapper
sidebar_label: createNativeWrapper()
sidebar_position: 13
---:::warning
The old API will be removed in the future version of Gesture Handler. Please migrate to gestures API instead. Check out our upgrading guide for more information.
:::Creates provided component with NativeViewGestureHandler, allowing it to be part of RNGH's
gesture system.Arguments
Component
The component we want to wrap.
config
FlingGestureHandlerReturns
Wrapped component.
---
Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gesture Handlers/Fling Gh (packages/docs-gesture-handler/versioned_docs/version-2.x/gesture-handlers/fling-gh.md)
---
id: fling-gh
title: FlingGestureHandler
sidebar_label: Fling
sidebar_position: 9
---:::warning
The old API will be removed in the future version of Gesture Handler. Please migrate to gestures API instead. Check out our upgrading guide for more information.
:::A discrete gesture handler that activates when the movement is sufficiently long and fast.
Handler gets ACTIVE when movement is sufficiently long and it does not take too much time.
When handler gets activated it will turn into END state when finger is released.
The handler will fail to recognize if the finger is lifted before being activated.
The handler is implemented using UISwipeGestureRecognizer on iOS and from scratch on Android.Properties
See set of properties inherited from base handler class. Below is a list of properties specific to
component:directionExpresses the allowed direction of movement. It's possible to pass one or many directions in one parameter:
direction={Directions.RIGHT | Directions.LEFT}
ordirection={Directions.DOWN}
numberOfPointersFlingGestureHandlerDetermines the exact number of pointers required to handle the fling gesture.
Event data
See set of event attributes from base handler class. Below is a list of gesture event attributes specific to
:xyX coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the handler. Expressed in point units.
absoluteXY coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the handler. Expressed in point units.
xX coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. The value is expressed in point units. It is recommended to use it instead of
in cases when the original view can be transformed as an effect of the gesture.absoluteYyY coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. The value is expressed in point units. It is recommended to use it instead of
in cases when the original view can be transformed as an effect of the gesture.Example
See the fling example from Gesture Handler Example App.
const LongPressButton = () => (
<FlingGestureHandler
direction={Directions.RIGHT | Directions.LEFT}
onHandlerStateChange={({ nativeEvent }) => {
if (nativeEvent.state === State.ACTIVE) {
Alert.alert("I'm flinged!");
}
}}>
<View style={styles.box} />
</FlingGestureHandler>
);
---minForcePackages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gesture Handlers/Force Gh (packages/docs-gesture-handler/versioned_docs/version-2.x/gesture-handlers/force-gh.md)
---
id: force-gh
title: ForceTouchGestureHandler (iOS only)
sidebar_label: Force touch
sidebar_position: 11
---:::warning
The old API will be removed in the future version of Gesture Handler. Please migrate to gestures API instead. Check out our upgrading guide for more information.
:::A continuous gesture handler that recognizes force of a touch. It allows for tracking pressure of touch on some iOS devices.
The handler activates when pressure of touch is greater or equal to. It fails if pressure is greater thanmaxForce.ForceTouchGestureHandler
Gesture callback can be used for continuous tracking of the touch pressure. It provides information for one finger (the first one).At the beginning of the gesture, the pressure factor is 0.0. As the pressure increases, the pressure factor increases proportionally. The maximum pressure is 1.0.
The handler is implemented using custom UIGestureRecognizer on iOS. There's no implementation provided on Android and it simply renders children without any wrappers.
Since this behaviour is only provided on some iOS devices, this handler should not be used for defining any crucial behaviors. Use it only as an additional improvement and make all features to be accessed without this handler as well.Properties
See set of properties inherited from base handler class. Below is a list of properties specific to
component:minForce[0.0, 1.0]A minimal pressure that is required before handler can activate. Should be a value from range
. Default is0.2.maxForce[0.0, 1.0]A maximal pressure that could be applied for handler. If the pressure is greater, handler fails. Should be a value from range
.feedbackOnActivationForceTouchGestureHandlerBoolean value defining if haptic feedback has to be performed on activation.
Event data
See set of event attributes from base handler class. Below is a list of gesture event attributes specific to
:forceforceTouchAvailableThe pressure of a touch.
Static method
ForceTouchGestureHandlerYou may check if it's possible to use
withForceTouchGestureHandler.forceTouchAvailableExample
See the force touch handler example from Gesture Handler Example App.
<ForceTouchGestureHandler
minForce={0}
onGestureEvent={this._onGestureEvent}
onHandlerStateChange={this._onHandlerStateChange}>
<Animated.View
style={[
styles.box,
{ transform: [{ scale: Animated.add(1, this.force) }] },
]}
/>
</ForceTouchGestureHandler>
---React.createRef()Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gesture Handlers/Interactions (packages/docs-gesture-handler/versioned_docs/version-2.x/gesture-handlers/interactions.md)
---
id: interactions
title: Cross handler interactions
sidebar_label: Cross handler interactions
sidebar_position: 3
---:::warning
The old API will be removed in the future version of Gesture Handler. Please migrate to gestures API instead. Check out our upgrading guide for more information.
:::Gesture handlers can "communicate" with each other to support complex gestures and control how they _activate_ in certain scenarios.
There are two means of achieving that described in the sections below.
In each case, it is necessary to provide a reference of one handler as a property to the other.
Gesture handler relies on ref objects created using, introduced in React 16.3.ACTIVESimultaneous recognition
By default, only one gesture handler is allowed to be in the
state.BEGAN
So when a gesture handler recognizes a gesture it cancels all other handlers in thestate and prevents any new handlers from receiving a stream of touch events as long as it remainsACTIVE.simultaneousHandlersproperty (available for all types of handlers).ACTIVE
This property accepts a ref or an array of refs to other handlers.
Handlers connected in this way will be allowed to remain in thestate at the same time.PinchGestureHandlerUse cases
Simultaneous recognition needs to be used when implementing a photo preview component that supports zooming (scaling) the photo, rotating and panning it while zoomed in.
In this case we would use a,RotationGestureHandlerandPanGestureHandlerthat would have to simultaneously recognize gestures.Example
See the "Scale, rotate & tilt" example from the GestureHandler Example App or view it directly on your phone by visiting our expo demo.
class PinchableBox extends React.Component {
// ...take a look on full implementation in an Example app
render() {
const imagePinch = React.createRef();
const imageRotation = React.createRef();
return (
<RotationGestureHandler
ref={imageRotation}
simultaneousHandlers={imagePinch}
onGestureEvent={this._onRotateGestureEvent}
onHandlerStateChange={this._onRotateHandlerStateChange}>
<Animated.View>
<PinchGestureHandler
ref={imagePinch}
simultaneousHandlers={imageRotation}
onGestureEvent={this._onPinchGestureEvent}
onHandlerStateChange={this._onPinchHandlerStateChange}>
<Animated.View style={styles.container} collapsable={false}>
<Animated.Image
style={[
styles.pinchableImage,
{
/ events-related transformations /
},
]}
/>
</Animated.View>
</PinchGestureHandler>
</Animated.View>
</RotationGestureHandler>
);
}
}
Awaiting other handlers
Use cases
A good example where awaiting is necessary is when we want to have single and double tap handlers registered for one view (a button).
In such a case we need to make single tap handler await a double tap.
Otherwise if we try to perform a double tap the single tap handler will fire just after we hit the button for the first time, consequently cancelling the double tap handler.
Example
See the "Multitap" example from GestureHandler Example App or view it directly on your phone by visiting our expo demo.
const doubleTap = React.createRef();
const PressBox = () => (
<TapGestureHandler
onHandlerStateChange={({ nativeEvent }) =>
nativeEvent.state === State.ACTIVE && Alert.alert('Single tap!')
}
waitFor={doubleTap}>
<TapGestureHandler
ref={doubleTap}
onHandlerStateChange={({ nativeEvent }) =>
nativeEvent.state === State.ACTIVE && Alert.alert("You're so fast")
}
numberOfTaps={2}>
<View style={styles.box} />
</TapGestureHandler>
</TapGestureHandler>
);
---LongPressGestureHandlerPackages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gesture Handlers/Longpress Gh (packages/docs-gesture-handler/versioned_docs/version-2.x/gesture-handlers/longpress-gh.md)
---
id: longpress-gh
title: LongPressGestureHandler
sidebar_label: Long press
sidebar_position: 7
---:::warning
The old API will be removed in the future version of Gesture Handler. Please migrate to gestures API instead. Check out our upgrading guide for more information.
:::A discrete gesture handler that activates when the corresponding view is pressed for a sufficiently long time.
This handler's state will turn into END immediately after the finger is released.
The handler will fail to recognize a touch event if the finger is lifted before the minimum required time or if the finger is moved further than the allowable distance.The handler is implemented using UILongPressGestureRecognizer on iOS and LongPressGestureHandler on Android.
Properties
See set of properties inherited from base handler class. Below is a list of properties specific to the
component:minDurationMsmaxDistMinimum time, expressed in milliseconds, that a finger must remain pressed on the corresponding view. The default value is 500.
LongPressGestureHandlerMaximum distance, expressed in points, that defines how far the finger is allowed to travel during a long press gesture. If the finger travels further than the defined distance and the handler hasn't yet activated, it will fail to recognize the gesture. The default value is 10.
Event data
See set of event attributes from base handler class. Below is a list of gesture event attributes specific to the
component:xyX coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the handler.
absoluteXY coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the handler.
absoluteXX coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. It is recommended to use
instead ofxin cases when the view attached to the handler can be transformed as an effect of the gesture.absoluteYabsoluteYY coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. It is recommended to use
instead ofyin cases when the view attached to the handler can be transformed as an effect of the gesture.durationDuration of the long press (time since the start of the event), expressed in milliseconds.
Example
See the multitap example from GestureHandler Example App.
const LongPressButton = () => (
<LongPressGestureHandler
onHandlerStateChange={({ nativeEvent }) => {
if (nativeEvent.state === State.ACTIVE) {
Alert.alert("I'm being pressed for so long");
}
}}
minDurationMs={800}>
<View style={styles.box} />
</LongPressGestureHandler>
);
---createNativeWrapper()Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gesture Handlers/Nativeview Gh (packages/docs-gesture-handler/versioned_docs/version-2.x/gesture-handlers/nativeview-gh.md)
---
id: nativeview-gh
title: NativeViewGestureHandler
sidebar_label: NativeView
sidebar_position: 12
---:::warning
The old API will be removed in the future version of Gesture Handler. Please migrate to gestures API instead. Check out our upgrading guide for more information.
:::A gesture handler that allows other touch handling components to participate in
RNGH's gesture system..NativeViewGestureHandlerProperties
See set of properties inherited from base handler class. Below is a list of properties specific to
component:shouldActivateOnStart(Android only)trueWhen
, underlying handler will activate unconditionally when inBEGANorUNDETERMINEDstate.disallowInterruptiontrueWhen
, cancels all other gesture handlers when thisNativeViewGestureHandlerreceives anACTIVEstate event.PanGestureHandler---
Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gesture Handlers/Pan Gh (packages/docs-gesture-handler/versioned_docs/version-2.x/gesture-handlers/pan-gh.md)
---
id: pan-gh
title: PanGestureHandler
sidebar_label: Pan
sidebar_position: 5
---:::warning
The old API will be removed in the future version of Gesture Handler. Please migrate to gestures API instead. Check out our upgrading guide for more information.
:::A continuous gesture handler that can recognize a panning (dragging) gesture and track its movement.
The handler activates when a finger is placed on the screen and moved some initial distance.
Configurations such as a minimum initial distance, specific vertical or horizontal pan detection and number of fingers required for activation (allowing for multifinger swipes) may be specified.
Gesture callback can be used for continuous tracking of the pan gesture. It provides information about the gesture such as its XY translation from the starting point as well as its instantaneous velocity.
The handler is implemented using UIPanGestureRecognizer on iOS and PanGestureHandler on Android.
Custom activation criteria
The
component exposes a number of properties that can be used to customize the criteria under which a handler will activate or fail when recognizing a gesture.PanGestureHandlerWhen more than one of such a property is set,
expects all criteria to be met for successful recognition and at most one of the criteria to be overstepped to fail recognition.minDeltaX
For example when bothandminDeltaYare set to 20 we expect the finger to travel by 20 points in both the X and Y axis before the handler activates.maxDeltaX
Another example would be setting bothandmaxDeltaYto 20 andminDistto 23.avgTouches
In such a case, if we move a finger along the X-axis by 20 points and along the Y-axis by 0 points, the handler will fail even though the finger is still within the bounds of translation along Y-axis.Multi touch pan handling
If your app relies on multi touch pan handling this section provides some information about how the default behavior differs between platforms and how (if necessary) it can be unified.
The difference in multi touch pan handling lies in the way translation properties during the event are being calculated.
On iOS the default behavior when more than one finger is placed on the screen is to treat this situation as if only one pointer was placed in the center of mass (average position of all the pointers).
This applies also to many platform native components that handle touch even if not primarily interested in multi touch interactions, like for example the UIScrollView component.The default behavior for native components like scroll view, pager views or drawers is different and hence gesture handler defaults to that when it comes to pan handling.
The difference is that instead of treating the center of mass of all the fingers placed as a leading pointer it takes the latest placed finger as such.
This behavior can be changed on Android usingflag.xNote that on both Android and iOS when the additional finger is placed on the screen that translation prop is not affected even though the position of the pointer being tracked might have changed.
Therefore it is safe to rely on translation most of the time as it only reflects the movement that happens regardless of how many fingers are placed on the screen and if that number changes over time.
If you wish to track the "center of mass" virtual pointer and account for its changes when the number of finger changes you can use relative or absolute position provided in the event (andyorabsoluteXandabsoluteY).PanGestureHandlerProperties
See set of properties inherited from base handler class. Below is a list of properties specific to
component:minDistminVelocityMinimum distance the finger (or multiple finger) need to travel before the handler activates. Expressed in points.
minVelocityXMinimum speed the pointer has to reach in order for the handler to activate. Expressed in points per second.
minVelocityYMinimum speed along X axis the pointer has to reach in order for the handler to activate. Expressed in points per second.
minPointersMinimum speed along Y axis the pointer has to reach in order for the handler to activate. Expressed in points per second.
maxPointersA number of fingers that is required to be placed before handler can activate. Should be an integer greater than or equal to 0.
activeOffsetXWhen the given number of fingers is placed on the screen and handler hasn't yet activated it will fail recognizing the gesture. Should be an integer greater than or equal to 0.
pRange along X axis (in points) where fingers travels without activation of handler. Moving outside of this range implies activation of handler. Range can be given as an array or a single number.
If range is set as an array, first value must be lower or equal to 0, and the second one higher or equal to 0.
If only one numberis given a range of(-inf, p)will be used ifpis higher or equal to 0 and(-p, inf)otherwise.activeOffsetYpRange along Y axis (in points) where fingers travels without activation of handler. Moving outside of this range implies activation of handler. Range can be given as an array or a single number.
If range is set as an array, first value must be lower or equal to 0, and the second one higher or equal to 0.
If only one numberis given a range of(-inf, p)will be used ifpis higher or equal to 0 and(-p, inf)otherwise.failOffsetYpWhen the finger moves outside this range (in points) along Y axis and handler hasn't yet activated it will fail recognizing the gesture. Range can be given as an array or a single number.
If range is set as an array, first value must be lower or equal to 0, and the second one higher or equal to 0.
If only one numberis given a range of(-inf, p)will be used ifpis higher or equal to 0 and(-p, inf)otherwise.failOffsetXpWhen the finger moves outside this range (in points) along X axis and handler hasn't yet activated it will fail recognizing the gesture. Range can be given as an array or a single number.
If range is set as an array, first value must be lower or equal to 0, and the second one higher or equal to 0.
If only one numberis given a range of(-inf, p)will be used ifpis higher or equal to 0 and(-p, inf)otherwise.maxDeltaXmaxDeltaX={N}This method is deprecated but supported for backward compatibility. Instead of usingyou can dofailOffsetX={[-N, N]}.maxDeltaYWhen the finger travels the given distance expressed in points along X axis and handler hasn't yet activated it will fail recognizing the gesture.
maxDeltaY={N}This method is deprecated but supported for backward compatibility. Instead of usingyou can dofailOffsetY={[-N, N]}.minOffsetXWhen the finger travels the given distance expressed in points along Y axis and handler hasn't yet activated it will fail recognizing the gesture.
minOffsetX={N}This method is deprecated but supported for backward compatibility. Instead of usingyou can doactiveOffsetX={N}.minDeltaXMinimum distance along X (in points) axis the finger (or multiple finger) need to travel before the handler activates. If set to a lower or equal to 0 value we expect the finger to travel "left" by the given distance. When set to a higher or equal to 0 number the handler will activate on a movement to the "right". If you wish for the movement direction to be ignored use
instead.minOffsetYminOffsetY={N}This method is deprecated but supported for backward compatibility. Instead of usingyou can doactiveOffsetY={N}.minDeltaYMinimum distance along Y (in points) axis the finger (or multiple finger) need to travel before the handler activates. If set to a lower or equal to 0 value we expect the finger to travel "up" by the given distance. When set to a higher or equal to 0 number the handler will activate on a movement to the "bottom". If you wish for the movement direction to be ignored use
instead.minDeltaXminDeltaX={N}This method is deprecated but supported for backward compatibility. Instead of usingyou can doactiveOffsetX={[-N, N]}.minoffsetxMinimum distance along X (in points) axis the finger (or multiple finger) need to travel (left or right) before the handler activates. Unlike
this parameter accepts only non-lower or equal to 0 numbers that represents the distance in point units. If you want for the handler to activate for the movement in one particular direction useminOffsetXinstead.minDeltaYminDeltaY={N}This method is deprecated but supported for backward compatibility. Instead of usingyou can doactiveOffsetY={[-N, N]}.minOffsetYMinimum distance along Y (in points) axis the finger (or multiple finger) need to travel (top or bottom) before the handler activates. Unlike
this parameter accepts only non-lower or equal to 0 numbers that represents the distance in point units. If you want for the handler to activate for the movement in one particular direction useminOffsetYinstead.avgTouches(Android only)enableTrackpadTwoFingerGestureAndroid, by default, will calculate translation values based on the position of the leading pointer (the first one that was placed on the screen). This prop allows that behavior to be changed to the one that is default on iOS - the averaged position of all active pointers will be used to calculate the translation values.
(iOS only)PanGestureHandlerEnables two-finger gestures on supported devices, for example iPads with trackpads. If not enabled the gesture will require click + drag, with enableTrackpadTwoFingerGesture swiping with two fingers will also trigger the gesture.
Event data
See set of event attributes from base handler class. Below is a list of gesture event attributes specific to
:translationXtranslationYTranslation of the pan gesture along X axis accumulated over the time of the gesture. The value is expressed in the point units.
velocityXTranslation of the pan gesture along Y axis accumulated over the time of the gesture. The value is expressed in the point units.
velocityYVelocity of the pan gesture along the X axis in the current moment. The value is expressed in point units per second.
xVelocity of the pan gesture along the Y axis in the current moment. The value is expressed in point units per second.
yX coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the handler. Expressed in point units.
absoluteXY coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the handler. Expressed in point units.
xX coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. The value is expressed in point units. It is recommended to use it instead of
in cases when the original view can be transformed as an effect of the gesture.absoluteYyY coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. The value is expressed in point units. It is recommended to use it instead of
in cases when the original view can be transformed as an effect of the gesture.Example
See the draggable example from Gesture Handler Example App.
import React, { Component } from 'react';
import { Animated, Dimensions } from 'react-native';
import {
GestureHandlerRootView,
PanGestureHandler,
} from 'react-native-gesture-handler';
const { width } = Dimensions.get('screen');
const circleRadius = 30;
class Circle extends Component {
_touchX = new Animated.Value(width / 2 - circleRadius);
_onPanGestureEvent = Animated.event([{ nativeEvent: { x: this._touchX } }], {
useNativeDriver: true,
});
render() {
return (
<GestureHandlerRootView>
<PanGestureHandler onGestureEvent={this._onPanGestureEvent}>
<Animated.View
style={{
height: 150,
justifyContent: 'center',
}}>
<Animated.View
style={[
{
backgroundColor: '#42a5f5',
borderRadius: circleRadius,
height: circleRadius * 2,
width: circleRadius * 2,
},
{
transform: [
{
translateX: Animated.add(
this._touchX,
new Animated.Value(-circleRadius)
),
},
],
},
]}
/>
</Animated.View>
</PanGestureHandler>
</GestureHandlerRootView>
);
}
}
export default function App() {
return <Circle />;
}
---PinchGestureHandlerPackages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gesture Handlers/Pinch Gh (packages/docs-gesture-handler/versioned_docs/version-2.x/gesture-handlers/pinch-gh.md)
---
id: pinch-gh
title: PinchGestureHandler
sidebar_label: Pinch
sidebar_position: 10
---:::warning
The old API will be removed in the future version of Gesture Handler. Please migrate to gestures API instead. Check out our upgrading guide for more information.
:::A continuous gesture handler that recognizes pinch gesture. It allows for tracking the distance between two fingers and use that information to scale or zoom your content.
The handler activates when fingers are placed on the screen and change their position.
Gesture callback can be used for continuous tracking of the pinch gesture. It provides information about velocity, anchor (focal) point of gesture and scale.The distance between the fingers is reported as a scale factor. At the beginning of the gesture, the scale factor is 1.0. As the distance between the two fingers increases, the scale factor increases proportionally.
Similarly, the scale factor decreases as the distance between the fingers decreases.
Pinch gestures are used most commonly to change the size of objects or content onscreen.
For example, map views use pinch gestures to change the zoom level of the map.The handler is implemented using UIPinchGestureRecognizer on iOS and from scratch on Android.
Properties
Properties provided to
do not extend common set of properties from base handler class.PinchGestureHandlerEvent data
See set of event attributes from base handler class. Below is a list of gesture event attributes specific to
:scalevelocityThe scale factor relative to the points of the two touches in screen coordinates.
focalXVelocity of the pinch gesture at the current moment. The value is expressed in scale factor per second.
focalYPosition expressed in points along X axis of center anchor point of the gesture.
Position expressed in points along Y axis of center anchor point of the gesture.
Example
See the scale and rotation example from Gesture Handler Example App.
export class PinchableBox extends React.Component {
_baseScale = new Animated.Value(1);
_pinchScale = new Animated.Value(1);
_scale = Animated.multiply(this._baseScale, this._pinchScale);
_lastScale = 1;
_onPinchGestureEvent = Animated.event(
[{ nativeEvent: { scale: this._pinchScale } }],
{ useNativeDriver: USE_NATIVE_DRIVER }
);
_onPinchHandlerStateChange = (event) => {
if (event.nativeEvent.oldState === State.ACTIVE) {
this._lastScale *= event.nativeEvent.scale;
this._baseScale.setValue(this._lastScale);
this._pinchScale.setValue(1);
}
};
render() {
return (
<PinchGestureHandler
onGestureEvent={this._onPinchGestureEvent}
onHandlerStateChange={this._onPinchHandlerStateChange}>
<View style={styles.container} collapsable={false}>
<Animated.Image
style={[
styles.pinchableImage,
{
transform: [{ perspective: 200 }, { scale: this._scale }],
},
]}
/>
</View>
</PinchGestureHandler>
);
}
}
---RotationGestureHandlerPackages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gesture Handlers/Rotation Gh (packages/docs-gesture-handler/versioned_docs/version-2.x/gesture-handlers/rotation-gh.md)
---
id: rotation-gh
title: RotationGestureHandler
sidebar_label: Rotation
sidebar_position: 8
---:::warning
The old API will be removed in the future version of Gesture Handler. Please migrate to gestures API instead. Check out our upgrading guide for more information.
:::A continuous gesture handler that can recognize a rotation gesture and track its movement.
The handler activates when fingers are placed on the screen and change position in a proper way.
Gesture callback can be used for continuous tracking of the rotation gesture. It provides information about the gesture such as the amount rotated, the focal point of the rotation (anchor), and its instantaneous velocity.
The handler is implemented using UIRotationGestureRecognizer on iOS and from scratch on Android.
Properties
Properties provided to
do not extend common set of properties from base handler class.RotationGestureHandlerEvent data
See set of event attributes from base handler class. Below is a list of gesture event attributes specific to
:rotationvelocityAmount rotated, expressed in radians, from the gesture's focal point (anchor).
anchorXInstantaneous velocity, expressed in point units per second, of the gesture.
anchorYX coordinate, expressed in points, of the gesture's central focal point (anchor).
Y coordinate, expressed in points, of the gesture's central focal point (anchor).
Example
See the scale and rotation example from Gesture Handler Example App.
class RotableBox extends React.Component {
_rotate = new Animated.Value(0);
_rotateStr = this._rotate.interpolate({
inputRange: [-100, 100],
outputRange: ['-100rad', '100rad'],
});
_lastRotate = 0;
_onRotateGestureEvent = Animated.event(
[{ nativeEvent: { rotation: this._rotate } }],
{ useNativeDriver: USE_NATIVE_DRIVER }
);
_onRotateHandlerStateChange = (event) => {
if (event.nativeEvent.oldState === State.ACTIVE) {
this._lastRotate += event.nativeEvent.rotation;
this._rotate.setOffset(this._lastRotate);
this._rotate.setValue(0);
}
};
render() {
return (
<RotationGestureHandler
onGestureEvent={this._onRotateGestureEvent}
onHandlerStateChange={this._onRotateHandlerStateChange}>
<Animated.Image
style={[
styles.pinchableImage,
{
transform: [{ perspective: 200 }, { rotate: this._rotateStr }],
},
]}
/>
</RotationGestureHandler>
);
}
}
---TapGestureHandlerPackages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gesture Handlers/Tap Gh (packages/docs-gesture-handler/versioned_docs/version-2.x/gesture-handlers/tap-gh.md)
---
id: tap-gh
title: TapGestureHandler
sidebar_label: Tap
sidebar_position: 6
---:::warning
The old API will be removed in the future version of Gesture Handler. Please migrate to gestures API instead. Check out our upgrading guide for more information.
:::A discrete gesture handler that recognizes one or many taps.
Tap gestures detect one or more fingers briefly touching the screen.
The fingers involved in these gestures must not move significantly from their initial touch positions.
The required number of taps and allowed distance from initial position may be configured.
For example, you might configure tap gesture recognizers to detect single taps, double taps, or triple taps.In order for a handler to activate, specified gesture requirements such as minPointers, numberOfTaps, maxDist, maxDurationMs, and maxDelayMs (explained below) must be met. Immediately after the handler activates, it will END.
Properties
See set of properties inherited from base handler class. Below is a list of properties specific to the
component:minPointersmaxDurationMsMinimum number of pointers (fingers) required to be placed before the handler activates. Should be a positive integer. The default value is 1.
maxDelayMsMaximum time, expressed in milliseconds, that defines how fast a finger must be released after a touch. The default value is 500.
numberOfTapsMaximum time, expressed in milliseconds, that can pass before the next tap — if many taps are required. The default value is 500.
maxDeltaXNumber of tap gestures required to activate the handler. The default value is 1.
maxDeltaYMaximum distance, expressed in points, that defines how far the finger is allowed to travel along the X axis during a tap gesture. If the finger travels further than the defined distance along the X axis and the handler hasn't yet activated, it will fail to recognize the gesture.
maxDistMaximum distance, expressed in points, that defines how far the finger is allowed to travel along the Y axis during a tap gesture. If the finger travels further than the defined distance along the Y axis and the handler hasn't yet activated, it will fail to recognize the gesture.
TapGestureHandlerMaximum distance, expressed in points, that defines how far the finger is allowed to travel during a tap gesture. If the finger travels further than the defined distance and the handler hasn't yet activated, it will fail to recognize the gesture.
Event data
See set of event attributes from base handler class. Below is a list of gesture event attributes specific to the
component:xyX coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the handler.
absoluteXY coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the handler.
absoluteXX coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. It is recommended to use
instead ofxin cases when the view attached to the handler can be transformed as an effect of the gesture.absoluteYabsoluteYY coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. It is recommended to use
instead ofyin cases when the view attached to the handler can be transformed as an effect of the gesture.Example
See the multitap example from GestureHandler Example App.
export class PressBox extends Component {
doubleTapRef = React.createRef();
render() {
return (
<TapGestureHandler
onHandlerStateChange={this._onSingleTap}
waitFor={this.doubleTapRef}>
<TapGestureHandler ref={this.doubleTapRef} numberOfTaps={2}>
<View style={styles.box} />
</TapGestureHandler>
</TapGestureHandler>
);
}
}
---RacePackages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gestures/ Category .Json (packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/_category_.json)
{
"label": "Gestures",
"position": 3,
"link": {
"type": "generated-index"
}
}---
Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gestures/Composed Gestures (packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/composed-gestures.md)
---
id: composed-gestures
title: Composed gestures
sidebar_label: Composed gestures
sidebar_position: 13
---Composed gestures (
,Simultaneous,Exclusive) provide a simple way of building relations between gestures. See Gesture Composition for more details.Reference
import { GestureDetector, Gesture } from 'react-native-gesture-handler';
function App() {
const pan = Gesture.Pan();
const longPress = Gesture.LongPress();
// highlight-next-line
const composed = Gesture.Race(pan, longPress);
return (
<GestureDetector gesture={composed}>
<Animated.View />
</GestureDetector>
);
}
---Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gestures/Fling Gesture (packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/fling-gesture.md)
---
id: fling-gesture
title: Fling gesture
sidebar_label: Fling gesture
sidebar_position: 8
---
import { webContainer } from '@site/src/utils/getGestureStyles';
import FlingGestureBasic from '../examples/FlingGestureBasic';
import FlingGestureBasicSrc from '!!raw-loader!../examples/FlingGestureBasic';
A discrete gesture that activates when the movement is sufficiently long and fast.
<div className={webContainer}>
<InteractiveExample
component={<FlingGestureBasic/>}
src={FlingGestureBasicSrc}
disableMarginBottom={true}
/>
</div>
import BaseEventData from './\_shared/base-gesture-event-data.md';
import BaseEventConfig from './\_shared/base-gesture-config.md';
import BaseEventCallbacks from './\_shared/base-gesture-callbacks.md';
Gesture gets ACTIVE when movement is sufficiently long and it does not take too much time.
When gesture gets activated it will turn into END state when finger is released.
The gesture will fail to recognize if the finger is lifted before being activated.
<samp id="FlingGestureBasicSrc">Fling Gesture</samp>
Example
import { StyleSheet } from 'react-native';
import {
Gesture,
GestureDetector,
Directions,
} from 'react-native-gesture-handler';
import Animated, {
useSharedValue,
useAnimatedStyle,
withTiming,
} from 'react-native-reanimated';
export default function App() {
const position = useSharedValue(0);
// highlight-next-line
const flingGesture = Gesture.Fling()
.direction(Directions.RIGHT)
.onStart((e) => {
position.value = withTiming(position.value + 10, { duration: 100 });
});
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ translateX: position.value }],
}));
return (
<GestureDetector gesture={flingGesture}>
<Animated.View style={[styles.box, animatedStyle]} />
</GestureDetector>
);
}
const styles = StyleSheet.create({
box: {
height: 120,
width: 120,
backgroundColor: '#b58df1',
borderRadius: 20,
marginBottom: 30,
},
});
FlingGestureConfig
Properties specific to
:direction(value: Directions)DirectionsExpresses the allowed direction of movement. Expected values are exported as constants in the
object. It's possible to pass one or many directions in one parameter:
import { Directions } from 'react-native-gesture-handler';
fling.direction(Directions.RIGHT | Directions.LEFT);
orfling.direction(Directions.DOWN);
numberOfPointers(value: number)mouseButton(value: MouseButton)Determines the exact number of pointers required to handle the fling gesture.
(Web & Android only)MouseButtonAllows users to choose which mouse button should handler respond to. The enum
consists of the following predefined fields:LEFT-
RIGHT
-MIDDLE
-BUTTON_4
-BUTTON_5
-ALL
-|Arguments can be combined using
operator, e.g.mouseButton(MouseButton.LEFT | MouseButton.RIGHT). Default value is set toMouseButton.LEFT.FlingGesture<BaseEventConfig />
Callbacks
<BaseEventCallbacks />
Event data
Event attributes specific to
:xGestureDetectorX coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the
. Expressed in point units.yGestureDetectorY coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the
. Expressed in point units.absoluteXxX coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. The value is expressed in point units. It is recommended to use it instead of
in cases when the original view can be transformed as an effect of the gesture.absoluteYyY coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. The value is expressed in point units. It is recommended to use it instead of
in cases when the original view can be transformed as an effect of the gesture.minForce<BaseEventData />
---
Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gestures/Force Touch Gesture (packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/force-touch-gesture.md)
---
id: force-touch-gesture
title: Force touch gesture (iOS only)
sidebar_label: Force touch gesture
sidebar_position: 10
---:::warning
ForceTouch gesture is deprecated and will be removed in the future version of Gesture Handler.
:::import BaseEventData from './\_shared/base-gesture-event-data.md';
import BaseEventConfig from './\_shared/base-gesture-config.md';
import BaseContinuousEventConfig from './\_shared/base-continuous-gesture-config.md';
import BaseEventCallbacks from './\_shared/base-gesture-callbacks.md';
import BaseContinuousEventCallbacks from './\_shared/base-continuous-gesture-callbacks.md';A continuous gesture that recognizes force of a touch. It allows for tracking pressure of touch on some iOS devices.
The gesture activates when pressure of touch is greater than or equal to. It fails if pressure is greater thanmaxForce.
Gesture callback can be used for continuous tracking of the touch pressure. It provides information for one finger (the first one).At the beginning of the gesture, the pressure factor is 0.0. As the pressure increases, the pressure factor increases proportionally. The maximum pressure is 1.0.
There's no implementation provided on Android and it simply renders children without any wrappers.
Since this behavior is only provided on some iOS devices, this gesture should not be used for defining any crucial behaviors. Use it only as an additional improvement and make all features accessible without this gesture as well.Reference
import { GestureDetector, Gesture } from 'react-native-gesture-handler';
function App() {
// highlight-next-line
const forceTouch = Gesture.ForceTouch();
return (
<GestureDetector gesture={forceTouch}>
<View />
</GestureDetector>
);
}
ForceTouchGestureConfig
Properties specific to
:minForce(value: number)[0.0, 1.0]A minimal pressure that is required before gesture can activate. Should be a value from range
. Default is0.2.maxForce(value: number)[0.0, 1.0]A maximal pressure that could be applied for gesture. If the pressure is greater, gesture fails. Should be a value from range
.feedbackOnActivation(value: boolean)ForceTouchGestureValue defining if haptic feedback has to be performed on activation.
<BaseEventConfig />
<BaseContinuousEventConfig />Callbacks
<BaseEventCallbacks />
<BaseContinuousEventCallbacks />Event data
Event attributes specific to
:forceGestureDetectorThe pressure of a touch.
<BaseEventData />
---
Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gestures/Gesture Detector (packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/gesture-detector.md)
---
id: gesture-detector
title: GestureDetector
sidebar_label: Gesture detector
sidebar_position: 1
---import FunctionalComponents from './\_shared/gesture-detector-functional1.md';
is the main component of the RNGH2. It is responsible for creating and updating native gesture handlers based on the config of the provided gesture. The most significant difference between it and old gesture handlers is that theGestureDetectorcan recognize more than one gesture at a time thanks to gesture composition. Keep in mind thatGestureDetectoris not compatible with the Animated API, nor with Reanimated 1.Reference
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
function App() {
const tap = Gesture.Tap();
return (
// highlight-next-line
<GestureDetector gesture={tap}>
<Animated.View />
// highlight-next-line
</GestureDetector>
);
}
gestureProperties
TapA gesture object containing the configuration and callbacks. Can be any of the base gestures (
,Pan,LongPress,Fling,Pinch,Rotation,ForceTouch) or anyComposedGesture(Race,Simultaneous,Exclusive).userSelect:::info
GestureDetector will decide whether to use Reanimated to process provided gestures based on callbacks they have. If any of the callbacks is a worklet, tools provided by Reanimated will be utilized, bringing the ability to handle gestures synchronously.Starting with Reanimated 2.3.0 Gesture Handler will provide a StateManager in the touch events that allows for managing the state of the gesture.
:::(Web only)userSelectThis parameter allows to specify which
property should be applied to underlying view. Possible values are"none" | "auto" | "text". Default value is set to"none".touchAction(Web only)touchActionThis parameter allows to specify which
property should be applied to underlying view. Supports all CSStouch-actionvalues (e.g."none","pan-y"). Default value is set to"none".enableContextMenu(value: boolean)(Web only)falseSpecifies whether context menu should be enabled after clicking on underlying view with right mouse button. Default value is set to
.Remarks
- Gesture Detector will use first native view in its subtree to recognize gestures, however if this view is used only to group its children it may get automatically collapsed. Consider this example:
<FunctionalComponents />
If we were to remove the collapsable prop from the View, the gesture would stop working because it would be attached to a view that is not present in the view hierarchy. Gesture Detector adds this prop automatically to its direct child but it's impossible to do automatically for more complex view trees.- Using the same instance of a gesture across multiple Gesture Detectors is not possible. Have a look at the code below:
export default function Example() {
const pan = Gesture.Pan();
return (
<View>
<GestureDetector gesture={pan}>
<View>
<GestureDetector gesture={pan}>
{' '}
{/ Don't do this! /}
<View />
</GestureDetector>
</View>
</GestureDetector>
</View>
);
}
This example will throw an error, because we try to use the same instance ofPanin two different Gesture Detectors.Gesture---
Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gestures/Gesture (packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/gesture.md)
---
id: gesture
title: Gesture
sidebar_label: Gesture
sidebar_position: 2
---is the object that allows you to create and compose gestures.Reference
import { GestureDetector, Gesture } from 'react-native-gesture-handler';
function App() {
// highlight-next-line
const tap = Gesture.Tap();
return (
<GestureDetector gesture={tap}>
<Animated.View />
</GestureDetector>
);
}
TapGestureGesture.Tap()
with its default config and no callbacks.PanGestureGesture.Pan()
with its default config and no callbacks.LongPressGestureGesture.LongPress()
with its default config and no callbacks.FlingGestureGesture.Fling()
with its default config and no callbacks.PinchGestureGesture.Pinch()
with its default config and no callbacks.RotationGestureGesture.Rotation()
with its default config and no callbacks.HoverGestureGesture.Hover()
with its default config and no callbacks.ForceTouchGestureGesture.ForceTouch()
with its default config and no callbacks.ManualGestureGesture.Manual()
with its default config and no callbacks.NativeGestureGesture.Native()
with its default config and no callbacks.BEGANGesture.Race(gesture1, gesture2, gesture3, ...): ComposedGesture
Creates a gesture composed of those provided as arguments. Only one of those can become active and there are no restrictions to the activation of the gesture. The first one to activate will cancel all the others.
Gesture.Simultaneous(gesture1, gesture2, gesture3, ...): ComposedGesture
Creates a gesture composed of those provided as arguments. All of them can become active without cancelling the others.
Gesture.Exclusive(gesture1, gesture2, gesture3, ...): ComposedGesture
Creates a gesture composed of those provided as arguments. Only one of them can become active, but the first one has a higher priority than the second one, the second one has a higher priority than the third one, and so on. When all gestures are in the
state and the activation criteria for the second one is met, instead of activating it will wait until the first one fails (and only then it will activate) or until the first one activates (and then the second one will get cancelled). It is useful when you want to compose gestures with similar activation criteria (e.g. single and double tap at the same component, without Exclusive the single tap would activate every time user taps thus cancelling the double tap).useMemoRemarks
- Consider wrapping your gesture configurations with
, as it will reduce the amount of work Gesture Handler has to do under the hood when updating gestures. For example:
import React from 'react';
function App() {
const gesture = React.useMemo(
() =>
Gesture.Tap().onStart(() => {
console.log('Number of taps:', tapNumber + 1);
setTapNumber((value) => value + 1);
}),
[tapNumber, setTapNumber]
);
// ...
}
---Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gestures/Hover Gesture (packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/hover-gesture.md)
---
id: hover-gesture
title: Hover gesture
sidebar_label: Hover gesture
sidebar_position: 9
---
import { webContainer } from '@site/src/utils/getGestureStyles';
import HoverGestureBasic from '../examples/HoverGestureBasic';
import HoverGestureBasicSrc from '!!raw-loader!../examples/HoverGestureBasic';
A continuous gesture that can recognize hovering above the view it's attached to.
<div className={webContainer}>
<InteractiveExample
component={<HoverGestureBasic/>}
src={HoverGestureBasicSrc}
disableMarginBottom={true}
/>
</div>
import BaseEventData from './\_shared/base-gesture-event-data.md';
import BaseEventConfig from './\_shared/base-gesture-config.md';
import BaseEventCallbacks from './\_shared/base-gesture-callbacks.md';
import BaseContinuousEventCallbacks from './\_shared/base-continuous-gesture-callbacks.md';
The hover effect may be activated by moving a mouse or a stylus over the view.
On iOS additional visual effects may be configured.
<samp id="HoverGestureBasic">Hover Gesture</samp>
Reference
import { GestureDetector, Gesture } from 'react-native-gesture-handler';
function App() {
// highlight-next-line
const hover = Gesture.Hover();
return (
<GestureDetector gesture={hover}>
<View />
</GestureDetector>
);
}
HoverRemarks
- Don't rely on
gesture to continue after the mouse button is clicked or the stylus touches the screen. If you want to handle both cases, compose it withPangesture.HoverGestureConfig
Properties specific to
:effect(effect: HoverEffect)(iOS only)
import { HoverEffect } from 'react-native-gesture-handler';
Visual effect applied to the view while the view is hovered. The possible values are:HoverEffect.None-
HoverEffect.Lift
-HoverEffect.Highlight
-HoverEffect.NoneDefaults to
HoverGesture<BaseEventConfig />
Callbacks
<BaseEventCallbacks />
<BaseContinuousEventCallbacks />Event data
Event attributes specific to
:xGestureDetectorX coordinate of the current position of the pointer relative to the view attached to the
. Expressed in point units.yGestureDetectorY coordinate of the current position of the pointer relative to the view attached to the
. Expressed in point units.absoluteXxX coordinate of the current position of the pointer relative to the window. The value is expressed in point units. It is recommended to use it instead of
in cases when the original view can be transformed as an effect of the gesture.absoluteYyY coordinate of the current position of the pointer relative to the window. The value is expressed in point units. It is recommended to use it instead of
in cases when the original view can be transformed as an effect of the gesture.stylusDatastylusObject that contains additional information about
. It consists of the following fields:tiltX- angle in degrees between the Y-Z plane of the stylus and the screen.tiltY
-- angle in degrees between the X-Z plane of the stylus and the screen.altitudeAngle
-- angle between stylus axis and the X-Y plane of a device screen.azimuthAngle
-- angle between the Y-Z plane and the plane containing both the stylus axis and the Y axis.pressure
-- indicates the normalized pressure of the stylus.<BaseEventData />
---
Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gestures/Long Press Gesture (packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/long-press-gesture.md)
---
id: long-press-gesture
title: Long press gesture
sidebar_label: Long press gesture
sidebar_position: 5
---import { webContainer } from '@site/src/utils/getGestureStyles';
import LongPressGestureBasic from '../examples/LongPressGestureBasic';
import LongPressGestureBasicSrc from '!!raw-loader!../examples/LongPressGestureBasic';A discrete gesture that activates when the corresponding view is pressed for a sufficiently long time.
<div className={webContainer}>
<InteractiveExample
component={<LongPressGestureBasic/>}
src={LongPressGestureBasicSrc}
disableMarginBottom={true}
/>
</div>import BaseEventData from './\_shared/base-gesture-event-data.md';
import BaseEventConfig from './\_shared/base-gesture-config.md';
import BaseEventCallbacks from './\_shared/base-gesture-callbacks.md';This gesture's state will turn into END immediately after the finger is released.
The gesture will fail to recognize a touch event if the finger is lifted before the minimum required time or if the finger is moved further than the allowable distance.<samp id="LongPressGestureBasic">Long Press Gesture</samp>
Example
import { View, StyleSheet } from 'react-native';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
export default function App() {
// highlight-next-line
const longPressGesture = Gesture.LongPress().onEnd((e, success) => {
if (success) {
console.log(Long pressed for ${e.duration} ms!);
}
});
return (
<GestureDetector gesture={longPressGesture}>
<View style={styles.box} />
</GestureDetector>
);
}
const styles = StyleSheet.create({
box: {
height: 120,
width: 120,
backgroundColor: '#b58df1',
borderRadius: 20,
marginBottom: 30,
},
});
LongPressGestureConfig
Properties specific to
:minDuration(value: number)maxDistance(value: number)Minimum time, expressed in milliseconds, that a finger must remain pressed on the corresponding view. The default value is 500.
mouseButton(value: MouseButton)Maximum distance, expressed in points, that defines how far the finger is allowed to travel during a long press gesture. If the finger travels further than the defined distance and the gesture hasn't yet activated, it will fail to recognize the gesture. The default value is 10.
(Web & Android only)MouseButtonAllows users to choose which mouse button should handler respond to. The enum
consists of the following predefined fields:LEFT-
RIGHT
-MIDDLE
-BUTTON_4
-BUTTON_5
-ALL
-|Arguments can be combined using
operator, e.g.mouseButton(MouseButton.LEFT | MouseButton.RIGHT). Default value is set toMouseButton.LEFT.LongPressGesture<BaseEventConfig />
Callbacks
<BaseEventCallbacks />
Event data
Event attributes specific to
:xGestureDetectorX coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the
.yGestureDetectorY coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the
.absoluteXabsoluteXX coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. It is recommended to use
instead ofxin cases when the view attached to theGestureDetectorcan be transformed as an effect of the gesture.absoluteYabsoluteYY coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. It is recommended to use
instead ofyin cases when the view attached to theGestureDetectorcan be transformed as an effect of the gesture.durationDuration of the long press (time since the start of the gesture), expressed in milliseconds.
<BaseEventData />
---
Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gestures/Manual Gesture (packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/manual-gesture.md)
---
id: manual-gesture
title: Manual gesture
sidebar_label: Manual gesture
sidebar_position: 12
---import BaseEventData from './\_shared/base-gesture-event-data.md';
import BaseEventConfig from './\_shared/base-gesture-config.md';
import BaseEventCallbacks from './\_shared/base-gesture-callbacks.md';
import BaseContinuousEventCallbacks from './\_shared/base-continuous-gesture-callbacks.md';A plain gesture that has no specific activation criteria nor event data set. Its state has to be controlled manually using a state manager. It will not fail when all the pointers are lifted from the screen.
Reference
import { GestureDetector, Gesture } from 'react-native-gesture-handler';
function App() {
// highlight-next-line
const manual = Gesture.Manual();
return (
<GestureDetector gesture={manual}>
<Animated.View />
</GestureDetector>
);
}
GestureDetectorConfig
<BaseEventConfig />
Callbacks
<BaseEventCallbacks />
<BaseContinuousEventCallbacks />Event data
<BaseEventData />
---
Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gestures/Native Gesture (packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/native-gesture.md)
---
id: native-gesture
title: Native gesture
sidebar_label: Native gesture
sidebar_position: 11
---import BaseEventData from './\_shared/base-gesture-event-data.md';
import BaseEventConfig from './\_shared/base-gesture-config.md';
import BaseEventCallbacks from './\_shared/base-gesture-callbacks.md';
import BaseContinuousEventCallbacks from './\_shared/base-continuous-gesture-callbacks.md';A gesture that allows other touch handling components to work within RNGH's gesture system. This streamlines interactions between gestures and the native component, allowing it to form relations with other gestures.
When used, the native component should be the direct child of a
.ScrollViewExample
This example renders a
with multiple colored rectangles, where each rectangle has a black section. Starting a touch on a black section will disable theScrollViewfor the duration of thePangesture.
import { View, ScrollView } from 'react-native';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
const COLORS = ['red', 'green', 'blue', 'purple', 'orange', 'cyan'];
export default function App() {
// highlight-next-line
const native = Gesture.Native();
return (
<GestureDetector gesture={native}>
<ScrollView style={{ flex: 1 }}>
<ScrollableContent scrollGesture={native} />
</ScrollView>
</GestureDetector>
);
}
function ScrollableContent({ scrollGesture }) {
return (
<View>
{COLORS.map((color) => (
<Rectangle key={color} color={color} scrollGesture={scrollGesture} />
))}
</View>
);
}
function Rectangle({ color, scrollGesture }) {
const pan = Gesture.Pan().blocksExternalGesture(scrollGesture);
return (
<View
key={color}
style={{ width: '100%', height: 250, backgroundColor: color }}>
<GestureDetector gesture={pan}>
<View style={{ width: '100%', height: 50, backgroundColor: 'black' }} />
</GestureDetector>
</View>
);
}
NativeRemarks
-
gesture can be used as part of gesture composition and cross-component interactions just like any other gesture. You can use this to block a native component for the duration of the gesture or to make it work alongside a gesture.Native:::danger
Do not usegesture with components exported by React Native Gesture Handler. Those come with a native gesture handler preapplied. Attaching a native gesture twice will likely result in the components not working as intended.NativeGesture
:::Config
Properties specific to
:shouldActivateOnStart(value: boolean)(Android only)trueWhen
, underlying handler will activate unconditionally when it receives any touches inBEGANorUNDETERMINEDstate.disallowInterruption(value: boolean)trueWhen
, cancels all other gesture handlers when thisNativeViewGestureHandlerchanges its state toACTIVE.NativeGesture<BaseEventConfig />
Callbacks
<BaseEventCallbacks />
Event data
Event attributes specific to
:pointerInsideTrue if gesture was performed inside of containing view, false otherwise.
<BaseEventData />
---
Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gestures/Pan Gesture (packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/pan-gesture.md)
---
id: pan-gesture
title: Pan gesture
sidebar_label: Pan gesture
sidebar_position: 3
---import { webContainer } from '@site/src/utils/getGestureStyles';
import PanGestureBasic from '../examples/PanGestureBasic';
import PanGestureBasicSrc from '!!raw-loader!../examples/PanGestureBasic';A continuous gesture that can recognize a panning (dragging) gesture and track its movement.
<div className={webContainer}>
<InteractiveExample
component={<PanGestureBasic/>}
src={PanGestureBasicSrc}
disableMarginBottom={true}
/>
</div>import BaseEventData from './\_shared/base-gesture-event-data.md';
import BaseEventConfig from './\_shared/base-gesture-config.md';
import BaseContinuousEventConfig from './\_shared/base-continuous-gesture-config.md';
import BaseEventCallbacks from './\_shared/base-gesture-callbacks.md';
import BaseContinuousEventCallbacks from './\_shared/base-continuous-gesture-callbacks.md';The gesture activates when a finger is placed on the screen and moved some initial distance.
Configurations such as a minimum initial distance, specific vertical or horizontal pan detection and number of fingers required for activation (allowing for multifinger swipes) may be specified.
Gesture callback can be used for continuous tracking of the pan gesture. It provides information about the gesture such as its XY translation from the starting point as well as its instantaneous velocity.
<samp id="PanGestureBasicSrc">Pan Gesture</samp>
Example
import { StyleSheet } from 'react-native';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import Animated, {
useSharedValue,
withTiming,
useAnimatedStyle,
} from 'react-native-reanimated';
const END_POSITION = 200;
export default function App() {
const onLeft = useSharedValue(true);
const position = useSharedValue(0);
// highlight-next-line
const panGesture = Gesture.Pan()
.onUpdate((e) => {
if (onLeft.value) {
position.value = e.translationX;
} else {
position.value = END_POSITION + e.translationX;
}
})
.onEnd((e) => {
if (position.value > END_POSITION / 2) {
position.value = withTiming(END_POSITION, { duration: 100 });
onLeft.value = false;
} else {
position.value = withTiming(0, { duration: 100 });
onLeft.value = true;
}
});
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ translateX: position.value }],
}));
return (
// highlight-next-line
<GestureDetector gesture={panGesture}>
<Animated.View style={[styles.box, animatedStyle]} />
</GestureDetector>
);
}
const styles = StyleSheet.create({
box: {
height: 120,
width: 120,
backgroundColor: '#b58df1',
borderRadius: 20,
marginBottom: 30,
},
});
averageTouchesMulti touch pan handling
If your app relies on multi touch pan handling this section provides some information on how the default behavior differs between platforms and how (if necessary) it can be unified.
The difference in multi touch pan handling lies in the way translation properties during the event are being calculated.
On iOS the default behavior when more than one finger is placed on the screen is to treat this situation as if only one pointer was placed in the center of mass (average position of all the pointers).
This applies also to many platform native components that handle touch even if not primarily interested in multi touch interactions like for example UIScrollView component.On Android, the default behavior for native components like scroll view, pager views or drawers is different and hence gesture defaults to that when it comes to pan handling.
The difference is that instead of treating the center of mass of all the fingers placed as a leading pointer it takes the latest placed finger as such.
This behavior can be changed on Android usingflag.xNote that on both Android and iOS when the additional finger is placed on the screen that translation prop is not affected even though the position of the pointer being tracked might have changed.
Therefore it is safe to rely on translation most of the time as it only reflects the movement that happens regardless of how many fingers are placed on the screen and if that number changes over time.
If you wish to track the "center of mass" virtual pointer and account for its changes when the number of finger changes you can use relative or absolute position provided in the event (andyorabsoluteXandabsoluteY).PanGestureConfig
Properties specific to
:minDistance(value: number)minVelocity(value: number)Minimum distance the finger (or multiple fingers) need to travel before the gesture activates. Expressed in points.
minVelocityX(value: number)Minimum speed the pointer has to reach in order for the gesture to activate. Expressed in points per second.
minVelocityY(value: number)Minimum speed along X axis the pointer has to reach in order for the gesture to activate. Expressed in points per second.
minPointers(value: number)Minimum speed along Y axis the pointer has to reach in order for the gesture to activate. Expressed in points per second.
maxPointers(value: number)A number of fingers that is required to be placed before the gesture can activate. Should be an integer greater than or equal to 0.
activateAfterLongPress(duration: number)When the given number of fingers is placed on the screen and gesture hasn't yet activated it will fail recognizing the gesture. Should be an integer greater than or equal to 0.
LongPressDuration in milliseconds of the
gesture beforePanis allowed to activate. If the finger is moved during that period, the gesture will fail. Should be an integer greater than or equal to 0. Default value is 0, meaning noLongPressis required to activate thePan.activeOffsetX(value: number | number[])pRange along X axis (in points) where fingers travel without activation of gesture. Moving outside of this range implies activation of gesture. Range can be given as an array or a single number.
If range is set as an array, the first value must be lower or equal to 0 and the second one higher or equal to 0.
If only one numberis given a range of(-inf, p)will be used ifpis higher or equal to 0 and(-p, inf)otherwise.activeOffsetY(value: number | number[])pRange along Y axis (in points) where fingers travel without activation of gesture. Moving outside of this range implies activation of gesture. Range can be given as an array or a single number.
If range is set as an array, the first value must be lower or equal to 0 and the second one higher or equal to 0.
If only one numberis given a range of(-inf, p)will be used ifpis higher or equal to 0 and(-p, inf)otherwise.failOffsetY(value: number | number[])pWhen the finger moves outside this range (in points) along Y axis and gesture hasn't yet activated it will fail recognizing the gesture. Range can be given as an array or a single number.
If range is set as an array, the first value must be lower or equal to 0 and the second one higher or equal to 0.
If only one numberis given a range of(-inf, p)will be used ifpis higher or equal to 0 and(-p, inf)otherwise.failOffsetX(value: number | number[])pWhen the finger moves outside this range (in points) along X axis and gesture hasn't yet activated it will fail recognizing the gesture. Range can be given as an array or a single number.
If range is set as an array, the first value must be lower or equal to 0 and the second one higher or equal to 0.
If only one numberis given a range of(-inf, p)will be used ifpis higher or equal to 0 and(-p, inf)otherwise.averageTouches(value: boolean)(Android only)enableTrackpadTwoFingerGesture(value: boolean)Android, by default, will calculate translation values based on the position of the leading pointer (the first one that was placed on the screen). This modifier allows that behavior to be changed to the one that is default on iOS - the averaged position of all active pointers will be used to calculate the translation values.
(iOS only)mouseButton(value: MouseButton)Enables two-finger gestures on supported devices, for example iPads with trackpads. If not enabled the gesture will require click + drag, with enableTrackpadTwoFingerGesture swiping with two fingers will also trigger the gesture.
(Web & Android only)MouseButtonAllows users to choose which mouse button should handler respond to. The enum
consists of the following predefined fields:LEFT-
RIGHT
-MIDDLE
-BUTTON_4
-BUTTON_5
-ALL
-|Arguments can be combined using
operator, e.g.mouseButton(MouseButton.LEFT | MouseButton.RIGHT). Default value is set toMouseButton.LEFT.PanGesture<BaseEventConfig />
<BaseContinuousEventConfig />Callbacks
<BaseEventCallbacks />
<BaseContinuousEventCallbacks />Event data
Event attributes specific to
:translationXtranslationYTranslation of the pan gesture along X axis accumulated over the time of the gesture. The value is expressed in the point units.
velocityXTranslation of the pan gesture along Y axis accumulated over the time of the gesture. The value is expressed in the point units.
velocityYVelocity of the pan gesture along the X axis in the current moment. The value is expressed in point units per second.
xVelocity of the pan gesture along the Y axis in the current moment. The value is expressed in point units per second.
GestureDetectorX coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the
. Expressed in point units.yGestureDetectorY coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the
. Expressed in point units.absoluteXxX coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. The value is expressed in point units. It is recommended to use it instead of
in cases when the original view can be transformed as an effect of the gesture.absoluteYyY coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. The value is expressed in point units. It is recommended to use it instead of
in cases when the original view can be transformed as an effect of the gesture.stylusDatastylusObject that contains additional information about
. It consists of the following fields:tiltX- angle in degrees between the Y-Z plane of the stylus and the screen.tiltY
-- angle in degrees between the X-Z plane of the stylus and the screen.altitudeAngle
-- angle between stylus axis and the X-Y plane of a device screen.azimuthAngle
-- angle between the Y-Z plane and the plane containing both the stylus axis and the Y axis.pressure
-- indicates the normalized pressure of the stylus.<BaseEventData />
---
Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gestures/Pinch Gesture (packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/pinch-gesture.md)
---
id: pinch-gesture
title: Pinch gesture
sidebar_label: Pinch gesture
sidebar_position: 7
---import { webContainer } from '@site/src/utils/getGestureStyles';
import PinchGestureBasic from '../examples/PinchGestureBasic';
import PinchGestureBasicSrc from '!!raw-loader!../examples/PinchGestureBasicSrc';A continuous gesture that recognizes a pinch gesture. It allows for tracking the distance between two fingers and using that information to scale or zoom your content.
<div className={webContainer}>
<InteractiveExample
component={<PinchGestureBasic/>}
src={PinchGestureBasicSrc}
disableMarginBottom={true}
/>
</div>import BaseEventData from './\_shared/base-gesture-event-data.md';
import BaseEventConfig from './\_shared/base-gesture-config.md';
import BaseContinuousEventConfig from './\_shared/base-continuous-gesture-config.md';
import BaseEventCallbacks from './\_shared/base-gesture-callbacks.md';
import BaseContinuousEventCallbacks from './\_shared/base-continuous-gesture-callbacks.md';The gesture activates when fingers are placed on the screen and change their position.
Gesture callback can be used for continuous tracking of the pinch gesture. It provides information about velocity, anchor (focal) point of gesture and scale.The distance between the fingers is reported as a scale factor. At the beginning of the gesture, the scale factor is 1.0. As the distance between the two fingers increases, the scale factor increases proportionally.
Similarly, the scale factor decreases as the distance between the fingers decreases.
Pinch gestures are used most commonly to change the size of objects or content onscreen.
For example, map views use pinch gestures to change the zoom level of the map.<samp id="PinchGestureBasicSrc">Pinch Gesture</samp>
Example
import { StyleSheet } from 'react-native';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import Animated, {
useSharedValue,
useAnimatedStyle,
} from 'react-native-reanimated';
export default function App() {
const scale = useSharedValue(1);
const savedScale = useSharedValue(1);
// highlight-next-line
const pinchGesture = Gesture.Pinch()
.onUpdate((e) => {
scale.value = savedScale.value * e.scale;
})
.onEnd(() => {
savedScale.value = scale.value;
});
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }],
}));
return (
<GestureDetector gesture={pinchGesture}>
<Animated.View style={[styles.box, animatedStyle]} />
</GestureDetector>
);
}
const styles = StyleSheet.create({
box: {
height: 120,
width: 120,
backgroundColor: '#b58df1',
borderRadius: 20,
marginBottom: 30,
},
});
focalRemarks
- When implementing pinch based on
point, make sure to use it after gesture has activated, i.e. inonStart,onUpdateoronChangecallbacks. Using it inonBeganmay lead to unexpected behavior.PinchGestureConfig
<BaseEventConfig />
<BaseContinuousEventConfig />Callbacks
<BaseEventCallbacks />
<BaseContinuousEventCallbacks />Event data
Event attributes specific to
:scalevelocityThe scale factor relative to the points of the two touches in screen coordinates.
focalXVelocity of the pinch gesture at the current moment. The value is expressed in scale factor per second.
focalYPosition expressed in points along the X axis of the center anchor point of the gesture.
Position expressed in points along the Y axis of the center anchor point of the gesture.
<BaseEventData />
---
Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gestures/Rotation Gesture (packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/rotation-gesture.md)
---
id: rotation-gesture
title: Rotation gesture
sidebar_label: Rotation gesture
sidebar_position: 6
---import { webContainer } from '@site/src/utils/getGestureStyles';
import RotationGestureBasic from '../examples/RotationGestureBasic';
import RotationGestureBasicSrc from '!!raw-loader!../examples/RotationGestureBasicSrc';A continuous gesture that can recognize a rotation gesture and track its movement.
<div className={webContainer}>
<InteractiveExample
component={<RotationGestureBasic/>}
src={RotationGestureBasicSrc}
disableMarginBottom={true}
/>
</div>import BaseEventData from './\_shared/base-gesture-event-data.md';
import BaseEventConfig from './\_shared/base-gesture-config.md';
import BaseContinuousEventConfig from './\_shared/base-continuous-gesture-config.md';
import BaseEventCallbacks from './\_shared/base-gesture-callbacks.md';
import BaseContinuousEventCallbacks from './\_shared/base-continuous-gesture-callbacks.md';The gesture activates when fingers are placed on the screen and change position in a proper way.
Gesture callback can be used for continuous tracking of the rotation gesture. It provides information about the gesture such as the amount rotated, the focal point of the rotation (anchor), and its instantaneous velocity.
<samp id="RotationGestureBasicSrc">Rotation Gesture</samp>
Example
import { StyleSheet } from 'react-native';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import Animated, {
useSharedValue,
useAnimatedStyle,
} from 'react-native-reanimated';
export default function App() {
const rotation = useSharedValue(1);
const savedRotation = useSharedValue(1);
// highlight-next-line
const rotationGesture = Gesture.Rotation()
.onUpdate((e) => {
rotation.value = savedRotation.value + e.rotation;
})
.onEnd(() => {
savedRotation.value = rotation.value;
});
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ rotateZ: ${(rotation.value / Math.PI) * 180}deg }],
}));
return (
<GestureDetector gesture={rotationGesture}>
<Animated.View style={[styles.box, animatedStyle]} />
</GestureDetector>
);
}
const styles = StyleSheet.create({
box: {
height: 120,
width: 120,
backgroundColor: '#b58df1',
borderRadius: 20,
marginBottom: 30,
},
});
anchorRemarks
- When implementing rotation based on
point, make sure to use it after gesture has activated, i.e. inonStart,onUpdateoronChangecallbacks. Using it inonBeganmay lead to unexpected behavior.RotationGestureConfig
<BaseEventConfig />
<BaseContinuousEventConfig />Callbacks
<BaseEventCallbacks />
<BaseContinuousEventCallbacks />Event data
Event attributes specific to
:rotationvelocityAmount rotated, expressed in radians, from the gesture's focal point (anchor).
anchorXInstantaneous velocity, expressed in point units per second, of the gesture.
anchorYX coordinate, expressed in points, of the gesture's central focal point (anchor).
GestureStateManagerY coordinate, expressed in points, of the gesture's central focal point (anchor).
<BaseEventData />
---
Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gestures/State Manager (packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/state-manager.md)
---
id: state-manager
title: Gesture state manager
sidebar_label: Gesture state manager
sidebar_position: 15
---allows you to manually control the state of the gestures. Please note thatreact-native-reanimatedis required to use it, since it allows for synchronously executing methods in worklets.begin()Methods
BEGANstate. This method will have no effect if the gesture has already activated or finished.activate()ACTIVEstate. This method will have no effect if the handler is already active, or has finished.exclusive
If the gesture iswith another one, the activation will be delayed until the gesture with higher priority fails.end()ENDstate. This method will have no effect if the handler has already finished.fail()FAILEDstate. This method will have no effect if the handler has already finished.---
Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gestures/Tap Gesture (packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/tap-gesture.md)
---
id: tap-gesture
title: Tap gesture
sidebar_label: Tap gesture
sidebar_position: 4
---import { webContainer } from '@site/src/utils/getGestureStyles';
import TapGestureBasic from '../examples/TapGestureBasic';
import TapGestureBasicSrc from '!!raw-loader!../examples/TapGestureBasic';A discrete gesture that recognizes one or many taps.
<div className={webContainer}>
<InteractiveExample
component={<TapGestureBasic/>}
src={TapGestureBasicSrc}
disableMarginBottom={true}
/>
</div>import BaseEventData from './\_shared/base-gesture-event-data.md';
import BaseEventConfig from './\_shared/base-gesture-config.md';
import BaseEventCallbacks from './\_shared/base-gesture-callbacks.md';Tap gestures detect one or more fingers briefly touching the screen.
The fingers involved in these gestures must not move significantly from their initial touch positions.
The required number of taps and allowed distance from initial position may be configured.
For example, you might configure tap gesture recognizers to detect single taps, double taps, or triple taps.In order for a gesture to activate, specified gesture requirements such as minPointers, numberOfTaps, maxDist, maxDuration, and maxDelayMs (explained below) must be met. Immediately after the gesture activates, it will end.
<samp id="TapGestureBasic">Tap Gesture</samp>
Example
import { View, StyleSheet } from 'react-native';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
export default function App() {
// highlight-next-line
const singleTap = Gesture.Tap()
.maxDuration(250)
.onStart(() => {
console.log('Single tap!');
});
// highlight-next-line
const doubleTap = Gesture.Tap()
.maxDuration(250)
.numberOfTaps(2)
.onStart(() => {
console.log('Double tap!');
});
return (
<GestureDetector gesture={Gesture.Exclusive(doubleTap, singleTap)}>
<View style={styles.box} />
</GestureDetector>
);
}
const styles = StyleSheet.create({
box: {
height: 120,
width: 120,
backgroundColor: '#b58df1',
borderRadius: 20,
marginBottom: 30,
},
});
TapGestureConfig
Properties specific to
:minPointers(value: number)maxDuration(value: number)Minimum number of pointers (fingers) required to be placed before the gesture activates. Should be a positive integer. The default value is 1.
maxDelay(value: number)Maximum time, expressed in milliseconds, that defines how fast a finger must be released after a touch. The default value is 500.
numberOfTaps(value: number)Maximum time, expressed in milliseconds, that can pass before the next tap — if many taps are required. The default value is 500.
maxDeltaX(value: number)Number of tap gestures required to activate the gesture. The default value is 1.
maxDeltaY(value: number)Maximum distance, expressed in points, that defines how far the finger is allowed to travel along the X axis during a tap gesture. If the finger travels further than the defined distance along the X axis and the gesture hasn't yet activated, it will fail to recognize the gesture.
maxDistance(value: number)Maximum distance, expressed in points, that defines how far the finger is allowed to travel along the Y axis during a tap gesture. If the finger travels further than the defined distance along the Y axis and the gesture hasn't yet activated, it will fail to recognize the gesture.
mouseButton(value: MouseButton)Maximum distance, expressed in points, that defines how far the finger is allowed to travel during a tap gesture. If the finger travels further than the defined distance and the gesture hasn't yet activated, it will fail to recognize the gesture.
(Web & Android only)MouseButtonAllows users to choose which mouse button should handler respond to. The enum
consists of the following predefined fields:LEFT-
RIGHT
-MIDDLE
-BUTTON_4
-BUTTON_5
-ALL
-|Arguments can be combined using
operator, e.g.mouseButton(MouseButton.LEFT | MouseButton.RIGHT). Default value is set toMouseButton.LEFT.TapGesture<BaseEventConfig />
Callbacks
<BaseEventCallbacks />
Event data
Event attributes specific to
:xGestureDetectorX coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the
.yGestureDetectorY coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the
.absoluteXabsoluteXX coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. It is recommended to use
instead ofxin cases when the view attached to theGestureDetectorcan be transformed as an effect of the gesture.absoluteYabsoluteYY coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. It is recommended to use
instead ofyin cases when the view attached to theGestureDetectorcan be transformed as an effect of the gesture.eventType<BaseEventData />
---
Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gestures/Touch Events (packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/touch-events.md)
---
id: touch-events
title: Touch events
sidebar_label: Touch events
sidebar_position: 14
---Touch event attributes:
changedTouchesType of the current event - whether the finger was placed on the screen, moved, lifted or cancelled.
allTouchesAn array of objects where every object represents a single touch. Contains information only about the touches that were affected by the event i.e. those that were placed down, moved, lifted or cancelled.
numberOfTouchesAn array of objects where every object represents a single touch. Contains information about all active touches.
touchesNumber representing the count of currently active touches.
:::caution
Don't rely on the order of items in theas it may change during the gesture, instead use theidattribute to track individual touches across events.id
:::PointerData attributes:
xA number representing the id of the touch. It may be used to track the touch between events as the id will not change while it is being tracked.
GestureDetectorX coordinate of the current position of the touch relative to the view attached to the
. Expressed in point units.yGestureDetectorY coordinate of the current position of the touch relative to the view attached to the
. Expressed in point units.absoluteXxX coordinate of the current position of the touch relative to the window. The value is expressed in point units. It is recommended to use it instead of
in cases when the original view can be transformed as an effect of the gesture.absoluteYyY coordinate of the current position of the touch relative to the window. The value is expressed in point units. It is recommended to use it instead of
in cases when the original view can be transformed as an effect of the gesture.onUpdate(callback)---
Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gestures/ Shared/Base Continuous Gesture Callbacks (packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/_shared/base-continuous-gesture-callbacks.md)
Callbacks common to all continuous gestures:
onChange(callback)Set the callback that is being called every time the gesture receives an update while it's active.
manualActivation(value: boolean)Set the callback that is being called every time the gesture receives an update while it's active. This callback will receive information about change in value in relation to the last received event.
---
Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gestures/ Shared/Base Continuous Gesture Config (packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/_shared/base-continuous-gesture-config.md)
Properties common to all continuous gestures:
trueWhen
, the handler will not activate by itself even if its activation criteria are met. Instead, you can manipulate its state using state manager.onBegin(callback)---
Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gestures/ Shared/Base Gesture Callbacks (packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/_shared/base-gesture-callbacks.md)
Callbacks common to all gestures:
onStart(callback)Set the callback that is being called when the given gesture handler starts receiving touches. At the moment of this callback the handler is not yet in an active state and we don't know yet if it will recognize the gesture at all.
onEnd(callback)Set the callback that is being called when the gesture is recognized by the handler and it transitions to the active state.
onFinalize(callback)Set the callback that is being called when the gesture that was recognized by the handler finishes. It will be called only if the handler was previously in the active state.
onTouchesDown(callback)Set the callback that is being called when the handler finalizes handling the gesture - the gesture was recognized and has finished or it failed to recognize.
onTouchesDownSet the
callback which is called every time a finger is placed on the screen.onTouchesMove(callback)onTouchesMoveSet the
callback which is called every time a finger is moved on the screen.onTouchesUp(callback)onTouchesUpSet the
callback which is called every time a finger is lifted from the screen.onTouchesCancelled(callback)onTouchesCancelledSet the
callback which is called every time a finger stops being tracked, for example when the gesture finishes.enabled(value: boolean)---
Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gestures/ Shared/Base Gesture Config (packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/_shared/base-gesture-config.md)
Properties common to all gestures:
falseIndicates whether the given handler should be analyzing the stream of touch events or not.
When set to, we can be sure that the handler's state will never becomeACTIVE.FAILED
If the value gets updated while the handler has already started recognizing a gesture, then the handler's state will immediately change toorCANCELLED(depending on its current state).true
Default value is.shouldCancelWhenOutside(value: boolean)trueWhen
, the handler will cancel or fail recognition (depending on its current state) whenever the finger leaves the area of the connected view.shouldCancelWhenOutside
Default value of this property is different depending on the handler type.
Most handlers'property defaults tofalseexcept for theLongPressGesture,TapGestureandNativeGesture(on Android and web) which default totrue.hitSlop(settings)leftThis parameter enables control over what part of the connected view area can be used to begin recognizing the gesture.
When a negative number is provided, the bounds of the view will reduce the area by the given number of points in each of the sides evenly.Instead you can pass an object to specify how each boundary side should be reduced by providing different number of points for
,right,toporbottomsides.horizontal
You can alternatively provideorverticalinstead of specifying directlyleft,rightortopandbottom.width
Finally, the object can also takeandheightattributes.width
Whenis set it is only allowed to specify one of the sidesrightorleft.height
Similarly whenis provided onlytoporbottomcan be set.width
Specifyingorheightis useful if we only want the gesture to activate on the edge of the view. In which case for example we can setleft: 0andwidth: 20which would make it possible for the gesture to be recognized when started no more than 20 points from the left edge.widthIMPORTANT: Note that this parameter is primarily designed to reduce the area where gesture can activate. Hence it is only supported for all the values (except
andheight) to be non positive (0 or lower). Although on Android it is supported for the values to also be positive and therefore allow to expand beyond view bounds but not further than the parent view bounds. To achieve this effect on both platforms you can use React Native's View hitSlop property.withRef(ref)withTestId(testID)Sets a ref to the gesture object, allowing for interoperability with the old
API.testIDSets a
property for gesture object, allowing for querying for it in tests.cancelsTouchesInView(value)(iOS only)trueAccepts a boolean value.
When, the gesture will cancel touches for native UI components (UIButton,UISwitch, etc) it's attached to when it becomesACTIVE.true
Default value is.runOnJS(value: boolean)react-native-reanimatedWhen
is installed, the callbacks passed to the gestures are automatically workletized and run on the UI thread when called. This option allows for changing this behavior: whentrue, all the callbacks will be run on the JS thread instead of the UI thread, regardless of whether they are worklets or not.false
Defaults to.simultaneousWithExternalGesture(otherGesture1, otherGesture2, ...)GestureDetectorAdds a gesture that should be recognized simultaneously with this one.
IMPORTANT: Note that this method only marks the relation between gestures, without composing them.
will not recognize theotherGesturesand it needs to be added to another detector in order to be recognized.requireExternalGestureToFail(otherGesture1, otherGesture2, ...)blocksExternalGesture(otherGesture1, otherGesture2, ...)Adds a relation requiring another gesture to fail, before this one can activate.
GestureDetectorAdds a relation that makes other gestures wait with activation until this gesture fails (or doesn't start at all).
IMPORTANT: Note that this method only marks the relation between gestures, without composing them.
will not recognize theotherGesturesand it needs to be added to another detector in order to be recognized.activeCursor(value)(Web only)"grab"This parameter allows specifying which cursor should be used when the gesture activates. Supports all CSS cursor values (e.g.
,"zoom-in"). Default value is set to"auto".state---
Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gestures/ Shared/Base Gesture Event Data (packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/_shared/base-gesture-event-data.md)
Event attributes common to all gestures:
StateCurrent state of the handler. Expressed as one of the constants exported under
object by the library.numberOfPointerspointerTypeRepresents the number of pointers (fingers) currently placed on the screen.
PointerTypeIndicates the type of pointer device in use. This value is represented by the
enum, which includes the following fields:TOUCH-
- represents fingerSTYLUS
-- represents stylus or digital penMOUSE
-- represents computer mouseKEY
-- represents keyboardOTHER
-- represents unknown device type that is not relevant---
Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gestures/ Shared/Gesture Detector Functional1 (packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/_shared/gesture-detector-functional1.md)
export default function Example() {
const tap = Gesture.Tap().onStart(() => {
console.log('tap');
});
return (
<GestureDetector gesture={tap}>
<FunctionalComponent>
<View style={styles.box} />
</FunctionalComponent>
</GestureDetector>
);
}
function FunctionalComponent(props) {
return <View collapsable={false}>{props.children}</View>;
}
---MainActivity.javaPackages/Docs Gesture Handler/Versioned Docs/Version 2.X/Guides/ Category .Json (packages/docs-gesture-handler/versioned_docs/version-2.x/guides/_category_.json)
{
"label": "Guides",
"position": 2,
"link": {
"type": "generated-index"
}
}---
Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Guides/Migrating Off Rnghenabledroot (packages/docs-gesture-handler/versioned_docs/version-2.x/guides/migrating-off-rnghenabledroot.md)
---
id: migrating-off-rnghenabledroot
title: Migrating off RNGHEnabledRootView
---Update
MainActivity.javaUpdate your
file (or wherever you create an instance ofReactActivityDelegate), so that it no longer overrides the method responsible for creatingReactRootViewinstance, or modify it so that it no longer usesRNGestureHandlerEnabledRootView. Do not forget to remove import forRNGestureHandlerEnabledRootView:
package com.swmansion.gesturehandler.react.example;
import com.facebook.react.ReactActivity;
- import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;
public class MainActivity extends ReactActivity {
- @Override
- protected ReactActivityDelegate createReactActivityDelegate() {
- return new ReactActivityDelegate(this, getMainComponentName()) {
- @Override
- protected ReactRootView createRootView() {
- return new RNGestureHandlerEnabledRootView(MainActivity.this);
- }
- };
- }
}
GestureHandlerRootViewCheck if your app works correctly
Some libraries (for example React Navigation) already use
as a wrapper to enable gesture interactions. In that case you don't have to add one yourself. If gestures in your app work as expected after removingRNGestureHandlerEnabledRootViewyou can skip the next step.RNGestureHandlerEnabledRootViewUpdate your JS code
Instead of using
wrap your entry point with<GestureHandlerRootView>, for example:
export default function App() {
return (
<GestureHandlerRootView style={{ flex: 1 }}>
{/ content /}
</GestureHandlerRootView>
);
}
:::infoGestureHandlerRootView
Note thatacts like a normalView. So if you want it to fill the screen, you will need to pass{ flex: 1 }like you'll need to do with a normal View. By default, it'll take the size of the content nested inside.ReanimatedSwipeable
:::---
Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Guides/Swipe And Scroll (packages/docs-gesture-handler/versioned_docs/version-2.x/guides/swipe-and-scroll.md)
---
id: swipe-and-scroll
title: Custom swipeable components inside ScrollView (web)
sidebar_position: 5
---component, creating your own version of swipeable gives you more control over its behavior. A common issue here is that after creating your own swipeable component, scroll does not work. In that case, try addingtouchActionset to"pan-y", like this:
<GestureDetector gesture={...} ... touchAction="pan-y">
...
</GestureDetector>
---package.jsonPackages/Docs Gesture Handler/Versioned Docs/Version 2.X/Guides/Testing (packages/docs-gesture-handler/versioned_docs/version-2.x/guides/testing.md)
---
id: testing
title: Testing with Jest
sidebar_position: 4
---Mocking native modules
In order to load mocks provided by RNGH add the following to your jest config in
:
"setupFiles": ["./node_modules/react-native-gesture-handler/jestSetup.js"]
Example:"jest": {
"preset": "react-native",
"setupFiles": ["./node_modules/react-native-gesture-handler/jestSetup.js"]
}
fireGestureHandler(gestureOrHandler, eventList)Testing Gestures' and Gesture handlers' callbacks
RNGH provides an API for triggering selected handlers:
getByGestureTestId(testID)
-BEGINfireGestureHandler(gestureOrHandler, eventList)
Simulates one event stream (i.e. event sequence starting with
state and endingEND
with one of/FAIL/CANCELstates), calling appropriate callbacks associated with given gesture handler.gestureOrHandlerArguments
####
getByTestIdRepresents either:
1. Gesture handler component found by Jest queries (e.g.
)getByGestureTestId()
2. Gesture found byeventList####
oldStateEvent data passed to appropriate callback. RNGH fills event list if required
data is missing using these rules:1.
is filled using state of the previous event.BEGINevents useUNDETERMINED
value as previous event.ACTIVE
2. Events after firststate can omitstatefield.numberOfTouches
3. Handler specific data is filled (e.g.,xfields) withBEGIN
defaults.
4. MissingandENDevents are added with data copied from first and laststate
passed event, respectively.
5. If the first event doesn't have afield, theACTIVEstate is assumed.Some examples:
const oldStateFilled = [
{ state: State.BEGAN },
{ state: State.ACTIVE },
{ state: State.END },
]; // three events with specified state are fired.
const implicitActiveState = [
{ state: State.BEGAN },
{ state: State.ACTIVE },
{ x: 5 },
{ state: State.END },
]; // 4 events, including two ACTIVE events (second one has overridden additional data).
const implicitBegin = [
{ x: 1, y: 11 },
{ x: 2, y: 12, state: State.FAILED },
]; // 3 events, including implicit BEGAN, one ACTIVE, and one FAILED event with additional data.
const implicitBeginAndEnd = [
{ x: 5, y: 15 },
{ x: 6, y: 16 },
{ x: 7, y: 17 },
]; // 5 events, including 3 ACTIVE events and implicit BEGAN and END events. BEGAN uses first event's additional data, END uses last event's additional data.
const allImplicits = []; // 3 events, one BEGIN, one ACTIVE, one END with defaults.
Events.test.tsxExample
Extracted from RNGH tests, check
for full implementation.
it('sends events with additional data to handlers', () => {
const panHandlers = mockedEventHandlers();
render(<SingleHandler handlers={panHandlers} treatStartAsUpdate />);
fireGestureHandler<PanGesture>(getByGestureTestId('pan'), [
{ state: State.BEGAN, translationX: 0 },
{ state: State.ACTIVE, translationX: 10 },
{ translationX: 20 },
{ translationX: 20 },
{ state: State.END, translationX: 30 },
]);
expect(panHandlers.active).toHaveBeenCalledTimes(3);
expect(panHandlers.active).toHaveBeenLastCalledWith(
expect.objectContaining({ translationX: 20 })
);
});
testIDgetByGestureTestId(testID)
Returns opaque data type associated with gesture. Gesture is found via
attribute in renderedwithTestID
components (seemethod).testIDArguments
####
testIDString identifying gesture.
Notes
must be unique among components rendered in test.fireGestureHandlerExample
See above example for
.enabled---
Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Guides/Troubleshooting (packages/docs-gesture-handler/versioned_docs/version-2.x/guides/troubleshooting.md)
---
id: troubleshooting
title: Troubleshooting
sidebar_position: 3
---Troubleshooting
Thanks for giving this library a try! We are sorry that you might have encountered issues though. Here is how you can seek help:
1. Search over the issues on Github. There is a chance someone had this problem in the past and it has been resolved!
2. When sure your problem hasn't been reported or was reported but the proposed solution doesn't work for you please follow our issue reporting guidelines.
3. You can try seeking help on Expo Developers Discord where we often hang out.
4. If you feel like reading the source code I highly recommend it, as this is by far the best resource and gives you the most up to date insights into how the library works and what might be causing the bug.
5. If you managed to find the solution consider contributing a fix or update our documentation to make this information easier to find for the others in the future.Reporting issues
This library is maintained by a very small team.
Please be mindful of that when reporting an issue and when it happens that we can't get back to you as soon as you might expect.
We would love to fix all the problems as soon as possible, but often our time is constrained by other issues/features or projects.
To make it easier for us to understand your issue and to be able to approach it sooner you can help by:- Making sure the issue description is complete. Please include all the details about your environment (library version, RN version, device OS etc).
- It is the best to provide an example app that reproduces the issue you are having. Put it up on gist, snack or create a repo on Github – it doesn't matter as long as we can easily pull it in, run and see the issue.
- Explain how you run your repro app and what steps to take to reproduce the issue.
- Isolate your issue from other dependencies you might be using and make the repro app as minimal as possible.
- If you have spent some time figuring out the root cause of the problem you can leave a note about your findings so far.
- Do not comment on closed issues. It is very unlikely that we are going to notice your comment in such a case. If the issue has been closed, but the proposed solution doesn't work for you, please open a new one providing all the information necessary and linking to the solution you have tried.It's not a bug, it's a feature
- Changing
prop during a gesture has no effect, only when a gesture starts (that is a finger touches the screen) theenabledprop is taken into consideration to decide whether to extract (or not) the gesture and provide it with stream of events to analyze.Native
-gesture may not conform to the standard state flow due to platform specific workarounds to incorporate native views into RNGH.Touchables
- Keep in mind thatfrom RNGH are rendering two additional views that may need to be styled separately to achieve desired effect (styleandcontainerStyleprops).GestureHandlerRootView
- In order for the gesture composition to work, all composed gestures must be attached to the same.node_modulesMultiple instances of Gesture Handler were detected
This error usually happens when in your project there exists more than one instance of Gesture Handler. It can occur when some of your dependencies have installed Gesture Handler inside their own
instead of using it as a peer dependency. In this case two different versions of Gesture Handler JS module try to install the same Native Module. You can resolve this problem manually by modifying yourpackage.jsonfile.You can check which libraries are using Gesture Handler, for example, with the command:
npm ls react-native-gesture-handler
yarn why react-native-gesture-handler
json
"resolutions": {
"react-native-gesture-handler": <Gesture Handler version>
}
json
"overrides": {
"react-native-gesture-handler": <Gesture Handler version>
}
After that you need to run your package manager again.yarn
ornpm install
'worklet';Automatic workletization of gesture callbacks
Reanimated's Babel plugin is setup in a way that automatically marks callbacks passed to gestures in the configuration chain as worklets. This means that as long as all your callbacks are defined in a single chain, you don't need to add a
directive at the beginning of the functions. Here is an example that will be automatically workletized:
const gesture = Gesture.Tap().onBegin(() => {
console.log(_WORKLET);
});
And here are some examples that won't:const gesture = Gesture.Tap();
gesture.onBegin(() => {
console.log(_WORKLET);
});
const callback = () => {
console.log(_WORKLET);
};
const gesture = Gesture.Tap().onBegin(callback);
const callback = () => {
console.log(_WORKLET);
};
const gesture = Gesture.Tap();
gesture.onBegin(callback);
In the above cases, you should add a"worklet";directive at the beginning of the callbacks, like so:
const callback = () => {
// highlight-next-line
'worklet';
console.log(_WORKLET);
};
const gesture = Gesture.Tap().onBegin(callback);
const callback = () => {
// highlight-next-line
'worklet';
console.log(_WORKLET);
};
const gesture = Gesture.Tap();
gesture.onBegin(callback);
---RNGestureHandlerEnabledRootViewPackages/Docs Gesture Handler/Versioned Docs/Version 2.X/Guides/Upgrading To 2 (packages/docs-gesture-handler/versioned_docs/version-2.x/guides/upgrading-to-2.md)
---
id: upgrading-to-2
title: Upgrading to the new API introduced in Gesture Handler 2
---Make sure to migrate off the
(Android only)createRootViewGesture Handler 1 required you to override
to return an instance ofRNGestureHandlerEnabledRootView. This class has been the cause of many hard-to-debug and hard-to-fix crashes and was deprecated in version 2.0, and subsequently removed in version 2.4. If you are still using it, check out migrating off RNGHEnabledRootView guide.GestureDetectorUpgrading to the new API
The most important change brought by the Gesture Handler 2 is the new Gesture API, along with the
component. It makes declaring gestures easier, as it handles much of the work under the hood and reduces the amount of necessary boilerplate code. Instead of a separate component for every type of gesture, theGestureDetectorcomponent is used to attach gestures to the underlying view based on the configuration object passed to it. The configuration objects are created using theGestureobject, here is a simple example:
const tapGesture = Gesture.Tap().onStart(() => {
console.log('Tap!');
});
...
return (
<GestureDetector gesture={tapGesture}>
<View />
</GestureDetector>
);
As you can see, there are noonGestureEventandonHandlerStateChangecallbacks, instead the state machine is handled under the hood and relevant callbacks are called for specific transitions or events:onBegin-
- called when the gesture transitions to theBEGANstate, which in most cases is when the gesture starts processing the touch stream - when the finger first touches the viewonStart
-- called when the activation criteria for the gesture are met and it transitions fromBEGANtoACTIVEstateonUpdate
-- replacesonGestureEvent, called every time the gesture sends a new event while it's in theACTIVEstateonChange
-- if defined, called just afteronUpdate, the events passed to it are the same as the ones passed toonUpdatebut they also containchangevalues which hold the change in value they represent since the last event (i.e. in case of thePangesture, the event will also containchangeXandchangeYproperties)onEnd
-- called when the gesture transitions from theACTIVEstate to either ofEND,FAILEDorCANCELLED- you can tell whether the gesture finished due to user interaction or because of other reason (like getting cancelled by the system, or failure criteria) using the second value passed to theonEndcallback alongside the eventonFinalize
-- called when the gesture transitions into either ofEND,FAILEDorCANCELLEDstate, if the gesture wasACTIVE,onEndwill be called first (similarly toonEndyou can determine the reason for finishing using the second argument)onEndThe difference between
andonFinalizeis that theonEndwill be called only if the gesture wasACTIVE, whileonFinalizewill be called if the gesture hasBEGAN. This means that you can useonEndto clean up afteronStart, andonFinalizeto clean up afteronBegin(or bothonBeginandonStart).Configuring the gestures
The new gesture objects are configured in the builder-like pattern. Instead of properties, each gesture provides methods that allow for its customization. In most cases the names of the methods are the same as the relevant props, or at least very similar. For example:
return (
<TapGestureHandler
numberOfTaps={2}
maxDurationMs={500}
maxDelayMs={500}
maxDist={10}
onHandlerStateChange={({ nativeEvent }) => {
if (nativeEvent.state === State.ACTIVE) {
console.log('Tap!');
}
}}>
<View />
</TapGestureHandler>
);
Would have the same effect as:const tapGesture = Gesture.Tap()
.numberOfTaps(2)
.maxDuration(500)
.maxDelay(500)
.maxDistance(10)
.onStart(() => {
console.log('Tap!');
});
return (
<GestureDetector gesture={tapGesture}>
<View />
</GestureDetector>
);
You can check the modifiers available to specific gestures in the API Reference under Gestures.Animated.ViewUsing multiple gestures on the same view
Using the gesture handler components, if you wanted to have multiple gestures on one view, you would have to stack them on top of each other and, in case you wanted to use animations, add an
after each handler, resulting in a deep component tree, for example:
return (
<TapGestureHandler ... >
<Animated.View>
<PanGestureHandler ... >
<Animated.View>
<PinchGestureHandler ... >
<YourView />
</PinchGestureHandler>
</Animated.View>
</PanGestureHandler>
</Animated.View>
</TapGestureHandler>
);
With theGestureDetectoryou can use the Gesture Composition API to stack the gestures onto one view:
const tapGesture = Gesture.Tap();
const panGesture = Gesture.Pan();
const pinchGesture = Gesture.Pinch();
return (
<GestureDetector gesture={Gesture.Race(tapGesture, panGesture, pinchGesture)}>
<YourView />
</GestureDetector>
);
Similarly, you can useGesture.Simultaneousto replace stacked gesture handlers that should be able to recognize gestures simultaneously, andGesture.Exclusiveto replace stacked gesture handlers that require failure of others.waitForReplacing
andsimultaneousHandlerssimultaneousHandlers')" title="Copy section prompt for LLMs"> Copy SectionsimultaneousWithExternalGestureIf you want to make relations between the gestures attached to the same view, you should use the Gesture Composition API described above. However, if you want to make a relation between gestures attached to different views, or between gesture and an old gesture handler, you should use
instead ofsimultaneousHandlers, andrequireExternalGestureToFailinstead ofwaitFor. In case you need a ref object to pass to an old gesture handler, you can set it to the gesture using.withRef(refObject)modifier.manualActivation---
Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Guides/Manual Gestures/Index (packages/docs-gesture-handler/versioned_docs/version-2.x/guides/manual-gestures/index.md)
---
id: manual-gestures
title: Manual gestures
sidebar_label: Manual gestures
sidebar_position: 2
---import Step, { Divider } from '@site/src/theme/Step';
import Step1 from './\_steps/step1.md';
import Step2 from './\_steps/step2.md';
import Step3 from './\_steps/step3.md';
import Step4 from './\_steps/step4.md';
import Step5 from './\_steps/step5.md';
import Step6 from './\_steps/step6.md';
import Step7 from './\_steps/step7.md';RNGH2 finally brings one of the most requested features: manual gestures and touch events. To demonstrate how to make a manual gesture we will make a simple one that tracks all pointers on the screen.
<Step title="Step 1">
First, we need a way to store information about the pointer: whether it should be visible and its position.
<Step1 />
</Step><Step title="Step 2">
We also need a component to mark where a pointer is. In order to accomplish that we will make a component that accepts two shared values: one holding information about the pointer using the interface we just created, the other holding a bool indicating whether the gesture has activated.
In this example when the gesture is not active, the ball representing it will be blue and when it is active the ball will be red and slightly bigger.
<Step2 />
</Step><Step title="Step 3">
Now we have to make a component that will handle the gesture and draw all the pointer indicators. We will store data about pointers in an array of size 12, as that is the maximum number of touches that RNGH will track, and render them inside an Animated.View.
<Step3 />
</Step><Step title="Step 4">
We have our components set up and we can finally get to making the gesture! We will start with onTouchesDown where we need to set the position of the pointers and make them visible. We can get this information from the touches property of the event. In this case we will also check how many pointers are on the screen and activate the gesture if there are at least two.
<Step4 />
</Step><Step title="Step 5">
Next, we will handle pointer movement. In onTouchesMove we will simply update the position of moved pointers.
<Step5 />
</Step><Step title="Step 6">
We also need to handle lifting fingers from the screen, which corresponds to onTouchesUp. Here we will just hide the pointers that were lifted and end the gesture if there are no more pointers on the screen.
Note that we are not handling onTouchesCancelled as in this very basic case we don't expect it to happen, however you should clear data about cancelled pointers (most of the time all active ones) when it is called.
<Step6 />
</Step><Step title="Step 7">
Now that our pointers are being tracked correctly and we have the state management, we can handle activation and ending of the gesture. In our case, we will simply set the active shared value either to true or false.
<Step7 />
</Step>And that's all! As you can see using manual gestures is really easy but as you can imagine, manual gestures are a powerful tool that makes it possible to accomplish things that were previously impossible with RNGH.
Modifying existing gestures
While manual gestures open great possibilities we are aware that reimplementing pinch or rotation from scratch just because you need to activate in specific circumstances or require position of the fingers, would be a waste of time as those gestures are already available. Therefore, you can use touch events with every gesture to extract more detailed information about the gesture than what the basic events alone provide. We also added a
modifier on all continuous gestures, which prevents the gesture it is applied to from activating automatically, giving you full control over its behavior.manualActivationThis functionality makes another highly requested feature possible: drag after long press. Simply set
totrueon aPanGestureand useStateManagerto fail the gesture if the user attempts to drag the component sooner than the duration of the long press.---
Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Guides/Manual Gestures/ Steps/Step1 (packages/docs-gesture-handler/versioned_docs/version-2.x/guides/manual-gestures/_steps/step1.md)
interface Pointer {
visible: boolean;
x: number;
y: number;
}
---Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Guides/Manual Gestures/ Steps/Step2 (packages/docs-gesture-handler/versioned_docs/version-2.x/guides/manual-gestures/_steps/step2.md)
import { StyleSheet } from 'react-native';
import Animated, {
useAnimatedStyle,
useSharedValue,
} from 'react-native-reanimated';
function PointerElement(props: {
pointer: Animated.SharedValue<Pointer>,
active: Animated.SharedValue<boolean>,
}) {
const animatedStyle = useAnimatedStyle(() => ({
transform: [
{ translateX: props.pointer.value.x },
{ translateY: props.pointer.value.y },
{
scale:
(props.pointer.value.visible ? 1 : 0) *
(props.active.value ? 1.3 : 1),
},
],
backgroundColor: props.active.value ? 'red' : 'blue',
}));
return <Animated.View style={[styles.pointer, animatedStyle]} />;
}
// ...
const styles = StyleSheet.create({
pointer: {
width: 60,
height: 60,
borderRadius: 30,
backgroundColor: 'red',
position: 'absolute',
marginStart: -30,
marginTop: -30,
},
});
---Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Guides/Manual Gestures/ Steps/Step3 (packages/docs-gesture-handler/versioned_docs/version-2.x/guides/manual-gestures/_steps/step3.md)
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
export default function Example() {
const trackedPointers: Animated.SharedValue<Pointer>[] = [];
const active = useSharedValue(false);
for (let i = 0; i < 12; i++) {
trackedPointers[i] =
useSharedValue <
Pointer >
{
visible: false,
x: 0,
y: 0,
};
}
const gesture = Gesture.Manual();
return (
<GestureDetector gesture={gesture}>
<Animated.View style={{ flex: 1 }}>
{trackedPointers.map((pointer, index) => (
<PointerElement pointer={pointer} active={active} key={index} />
))}
</Animated.View>
</GestureDetector>
);
}
---Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Guides/Manual Gestures/ Steps/Step4 (packages/docs-gesture-handler/versioned_docs/version-2.x/guides/manual-gestures/_steps/step4.md)
const gesture = Gesture.Manual().onTouchesDown((e, manager) => {
for (const touch of e.changedTouches) {
trackedPointers[touch.id].value = {
visible: true,
x: touch.x,
y: touch.y,
};
}
if (e.numberOfTouches >= 2) {
manager.activate();
}
});
---Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Guides/Manual Gestures/ Steps/Step5 (packages/docs-gesture-handler/versioned_docs/version-2.x/guides/manual-gestures/_steps/step5.md)
const gesture = Gesture.Manual()
...
.onTouchesMove((e, _manager) => {
for (const touch of e.changedTouches) {
trackedPointers[touch.id].value = {
visible: true,
x: touch.x,
y: touch.y,
};
}
})
---Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Guides/Manual Gestures/ Steps/Step6 (packages/docs-gesture-handler/versioned_docs/version-2.x/guides/manual-gestures/_steps/step6.md)
const gesture = Gesture.Manual()
...
.onTouchesUp((e, manager) => {
for (const touch of e.changedTouches) {
trackedPointers[touch.id].value = {
visible: false,
x: touch.x,
y: touch.y,
};
}
if (e.numberOfTouches === 0) {
manager.end();
}
})
---Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Guides/Manual Gestures/ Steps/Step7 (packages/docs-gesture-handler/versioned_docs/version-2.x/guides/manual-gestures/_steps/step7.md)
const gesture = Gesture.Manual()
...
.onStart(() => {
active.value = true;
})
.onEnd(() => {
active.value = false;
});
---GestureDetectorPackages/Docs Gesture Handler/Versioned Docs/Version 2.X/Guides/Quickstart/Index (packages/docs-gesture-handler/versioned_docs/version-2.x/guides/quickstart/index.md)
---
id: quickstart
title: Quick start
sidebar_label: Quick start
sidebar_position: 1
---import Step, { Divider } from '@site/src/theme/Step';
import Step1 from './\_steps/step1.md';
import Step2 from './\_steps/step2.md';
import Step3 from './\_steps/step3.md';
import Step4 from './\_steps/step4.md';
import Step5 from './\_steps/step5.md';RNGH2 provides a much simpler way to add gestures to your app. All you need to do is wrap the view that you want your gesture to work on with
, define the gesture and pass it to detector. That's all!react-native-gesture-handlerTo demonstrate how you would use the new API, let's make a simple app where you can drag a ball around. You will need to add
(for gestures) andreact-native-reanimated(for animations) modules.start<Step title="Step 1">
<div>First let's define styles we will need to make the app:</div>
<Step1 />
</Step><Step title="Step 2">
<div>Then we can start writing our <code>Ball</code> component:</div>
<Step2 />
</Step><Step title="Step 3">
<div>
We also need to define{' '}
<a href="https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/glossary#shared-value">
shared values
</a>{' '}
to keep track of the ball position and create animated styles in order to be
able to position the ball on the screen:
</div>
<Step3 />
</Step><Step title="Step 4">
<div>And add it to the ball's styles:</div>
<Step4 />
</Step><Step title="Step 5">
<div>
The only thing left is to define the pan gesture and assign it to the
detector:
</div>
<Step5 />
</Step>Note the
shared value. We need it to store the position of the ball at the moment we grab it to be able to correctly position it later, because we only have access to translation relative to the starting point of the gesture.BallNow you can just add
component to some view in the app and see the results! (Or you can just check the code here and see it in action in the Example app.)---
Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Guides/Quickstart/ Steps/Step1 (packages/docs-gesture-handler/versioned_docs/version-2.x/guides/quickstart/_steps/step1.md)
import { StyleSheet } from 'react-native';
const styles = StyleSheet.create({
ball: {
width: 100,
height: 100,
borderRadius: 100,
backgroundColor: 'blue',
alignSelf: 'center',
},
});
---Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Guides/Quickstart/ Steps/Step2 (packages/docs-gesture-handler/versioned_docs/version-2.x/guides/quickstart/_steps/step2.md)
import { GestureDetector } from 'react-native-gesture-handler';
import Animated from 'react-native-reanimated';
function Ball() {
return (
<GestureDetector>
<Animated.View style={[styles.ball]} />
</GestureDetector>
);
}
---Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Guides/Quickstart/ Steps/Step3 (packages/docs-gesture-handler/versioned_docs/version-2.x/guides/quickstart/_steps/step3.md)
import {
useSharedValue,
useAnimatedStyle,
withSpring,
} from 'react-native-reanimated';
function Ball() {
const isPressed = useSharedValue(false);
const offset = useSharedValue({ x: 0, y: 0 });
const animatedStyles = useAnimatedStyle(() => {
return {
transform: [
{ translateX: offset.value.x },
{ translateY: offset.value.y },
{ scale: withSpring(isPressed.value ? 1.2 : 1) },
],
backgroundColor: isPressed.value ? 'yellow' : 'blue',
};
});
// ...
}
---Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Guides/Quickstart/ Steps/Step4 (packages/docs-gesture-handler/versioned_docs/version-2.x/guides/quickstart/_steps/step4.md)
// ...
return (
<GestureDetector>
<Animated.View style={[styles.ball, animatedStyles]} />
</GestureDetector>
);
// ...
---Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Guides/Quickstart/ Steps/Step5 (packages/docs-gesture-handler/versioned_docs/version-2.x/guides/quickstart/_steps/step5.md)
import { Gesture } from 'react-native-gesture-handler';
function Ball() {
// ...
const start = useSharedValue({ x: 0, y: 0 });
const gesture = Gesture.Pan()
.onBegin(() => {
isPressed.value = true;
})
.onUpdate((e) => {
offset.value = {
x: e.translationX + start.value.x,
y: e.translationY + start.value.y,
};
})
.onEnd(() => {
start.value = {
x: offset.value.x,
y: offset.value.y,
};
})
.onFinalize(() => {
isPressed.value = false;
});
// ...
}
// ...
return (
<GestureDetector gesture={gesture}>
<Animated.View style={[styles.ball, animatedStyles]} />
</GestureDetector>
);
// ...
---GestureDetectorPackages/Docs Gesture Handler/Versioned Docs/Version 2.X/Under The Hood/ Category .Json (packages/docs-gesture-handler/versioned_docs/version-2.x/under-the-hood/_category_.json)
{
"label": "Under the hood",
"position": 5,
"link": {
"type": "generated-index"
}
}---
Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Under The Hood/How Does It Work (packages/docs-gesture-handler/versioned_docs/version-2.x/under-the-hood/how-does-it-work.md)
---
id: how-does-it-work
title: How does it work?
sidebar_label: How does it work?
---Units
All handler component properties and event attributes that represent onscreen dimensions are expressed in screen density independent units we refer to as "points".
These are the units commonly used in React Native ecosystem (e.g. in the layout system).
They do not map directly to physical pixels but instead to iOS's points and to dp units on Android.iOS
All gestures are implemented using UIGestureRecognizers, some of them have been slightly modified to allow for more customization and to conform to the state flow of RNGH. When you assign a gesture configuration to the
, it creates all the required recognizers and assigns them to the child view of the detector. From this point most of the heavy lifting is handled by the UIKit (with our help to correctly implement interactions between gestures).GestureHandlerRootViewAndroid
Unfortunately, Android doesn't provide an easy way of handling gestures hence most of them were implemented from scratch, including a system for managing how the gestures should interact with each other. Here's a quick overview of how it works:
When you wrap a component withit allows for the RNGH to intercept all touch events on that component and process them, deciding whether they should be handled by one of the gesture handlers or passed to the underlying view. Gesture handlers are created when you assign a gesture configuration to theGestureDetector, it initializes all of the necessary handlers natively. EveryGestureHandlerRootViewalso has a specific handler to decide whether to pass the touch events or to consume them. It can never activate, only begin, end or be cancelled. When this handler is in theUNDETERMINEDstate it means that there is no touch in progress, however when the touch starts it transitions to theBEGANstate. As long as it stays in that state, no touch event is consumed, but as soon as it gets cancelled (meaning that some handler has activated) all incoming touch events get consumed, preventing underlying view from receiving them.GestureHandlerRootViewWhen a pointer touches the screen the view tree is traversed in order to extract all handlers attached to the views below the finger (including the one attached to the
) and all extracted handlers transition to theBEGANstate, signalling that the gesture may have begun. The touch events continue to be delivered to all extracted handlers until one of them recognizes the gesture and tries to activate. At this point the orchestrator checks whether this gesture should wait for any other of the extracted gestures to fail. If it does, it's put to the waiting list, if it doesn't, it gets activated and all other gestures (that are not simultaneous with it) get cancelled. When a gesture handler transitions to a finished state (the gesture recognized by it stops, it fails or gets cancelled) the orchestrator checks the waiting handlers. Every one of them that waited for the gesture that just failed tries to activate again (and again the orchestrator checks if it should wait for any of the extracted gestures...).onHandlerStateChange---
Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Under The Hood/State (packages/docs-gesture-handler/versioned_docs/version-2.x/under-the-hood/state.md)
---
id: state
title: Handler State
sidebar_label: Handler State
---As described in "About Gesture Handlers", gesture handlers can be treated as "state machines".
At any given time, each handler instance has an assigned state that can change when new touch events occur or can be forced to change by the touch system in certain circumstances.A gesture handler can be in one of the six possible states:
- Accessing state
- State flows
- States
- UNDETERMINED
- FAILED
- BEGAN
- CANCELLED
- ACTIVE
- ENDEach state has its own description below.
Accessing state
callback and the destructurednativeEventargument passed to it.nativeEvent
This can be done by comparing the'sstateattribute to one of the constants exported under theStateobject (see example below).
import { State, LongPressGestureHandler } from 'react-native-gesture-handler';
class Demo extends Component {
_handleStateChange = ({ nativeEvent }) => {
if (nativeEvent.state === State.ACTIVE) {
Alert.alert('Longpress');
}
};
render() {
return (
<LongPressGestureHandler onHandlerStateChange={this._handleStateChange}>
<Text style={styles.buttonText}>Longpress me</Text>
</LongPressGestureHandler>
);
}
}
UNDETERMINEDState flows
The most typical flow of state is when a gesture handler picks up on an initial touch event then recognizes it then acknowledges its ending then resets itself back to the initial state.
The flow looks as follows (longer arrows represent that there are possibly more touch events received before the state changes):
->BEGAN------>ACTIVE------>END->UNDETERMINEDUNDETERMINEDAnother possible flow is when a handler receives touches that cause a recognition failure:
->BEGAN------>FAILED->UNDETERMINEDUNDETERMINEDAt last, when a handler does properly recognize the gesture but then is interrupted by the touch system. In that case, the gesture recognition is canceled and the flow looks as follows:
->BEGAN------>ACTIVE------>CANCELLED->UNDETERMINEDmaxDistStates
The section below describes all possible handler states:
UNDETERMINED
This is the initial state of each handler and it goes into this state after it's done recognizing a gesture.
FAILED
A handler received some touches but for some reason didn't recognize them. For example, if a finger travels more distance than a defined
property allows, then the handler won't become active but will fail instead. Afterwards, its state will be reset toUNDETERMINED.CANCELLEDBEGAN
Handler has started receiving touch stream but hasn't yet received enough data to either fail or activate.
CANCELLED
The gesture recognizer has received a signal (possibly new touches or a command from the touch system controller) resulting in the cancellation of a continuous gesture. The gesture's state will become
until it is finally reset to the initial state,UNDETERMINED.ACTIVEACTIVE
Handler has recognized a gesture. It will become and stay in the
state until the gesture finishes (e.g. when user lifts the finger) or gets cancelled by the touch system. Under normal circumstances the state will then turn intoEND. In the case that a gesture is cancelled by the touch system, its state would then becomeCANCELLED.ACTIVE
Learn about discrete and continuous handlers here to understand how long a handler can be kept in thestate.ENDEND
The gesture recognizer has received touches signalling the end of a gesture. Its state will become
until it is reset toUNDETERMINED.react-native-gesture-handler---
Packages/Docs Gesture Handler/Versioned Sidebars/Version 1.X Sidebars.Json (packages/docs-gesture-handler/versioned_sidebars/version-1.x-sidebars.json)
{
"docs": [
{
"collapsed": true,
"type": "category",
"label": "Basics",
"items": [
{
"type": "doc",
"id": "getting-started"
},
{
"type": "doc",
"id": "about-handlers"
},
{
"type": "doc",
"id": "state"
},
{
"type": "doc",
"id": "interactions"
},
{
"type": "doc",
"id": "example"
}
]
},
{
"collapsed": true,
"type": "category",
"label": "API reference",
"items": [
{
"collapsed": true,
"type": "category",
"label": "Gesture handlers",
"items": [
{
"type": "doc",
"id": "api/gesture-handlers/common-gh"
},
{
"type": "doc",
"id": "api/gesture-handlers/pan-gh"
},
{
"type": "doc",
"id": "api/gesture-handlers/tap-gh"
},
{
"type": "doc",
"id": "api/gesture-handlers/longpress-gh"
},
{
"type": "doc",
"id": "api/gesture-handlers/rotation-gh"
},
{
"type": "doc",
"id": "api/gesture-handlers/fling-gh"
},
{
"type": "doc",
"id": "api/gesture-handlers/pinch-gh"
},
{
"type": "doc",
"id": "api/gesture-handlers/force-gh"
},
{
"type": "doc",
"id": "api/gesture-handlers/nativeview-gh"
},
{
"type": "doc",
"id": "api/gesture-handlers/create-native-wrapper"
}
]
},
{
"collapsed": true,
"type": "category",
"label": "Components",
"items": [
{
"type": "doc",
"id": "api/components/buttons"
},
{
"type": "doc",
"id": "api/components/swipeable"
},
{
"type": "doc",
"id": "api/components/touchables"
},
{
"type": "doc",
"id": "api/components/drawer-layout"
}
]
}
]
},
{
"collapsed": true,
"type": "category",
"label": "Other",
"items": [
{
"type": "doc",
"id": "contributing"
},
{
"type": "doc",
"id": "troubleshooting"
},
{
"type": "doc",
"id": "resources"
},
{
"type": "doc",
"id": "credits"
}
]
}
]
}---
Packages/Docs Gesture Handler/Versioned Sidebars/Version 2.X Sidebars.Json (packages/docs-gesture-handler/versioned_sidebars/version-2.x-sidebars.json)
{
"tutorialSidebar": [
{
"type": "autogenerated",
"dirName": "."
}
]
}---
Packages/React Native Gesture Handler/Compatibility.Json (packages/react-native-gesture-handler/compatibility.json)
{
"3.2.x": {
"react-native": ["0.83", "0.84", "0.85", "0.86", "0.87"]
},
"3.1.x": {
"react-native": ["0.83", "0.84", "0.85", "0.86"]
},
"3.0.x": {
"react-native": ["0.82", "0.83", "0.84", "0.85", "0.86"]
}
}---
Packages/React Native Gesture Handler/Package.Json (packages/react-native-gesture-handler/package.json)
{
"name": "react-native-gesture-handler",
"version": "2.29.0",
"description": "Declarative API exposing native platform touch and gesture system to React Native",
"scripts": {
"test": "jest",
"build": "yarn tsc -p tsconfig.build.json && bob build",
"ts-check": "yarn tsc --noEmit && yarn tsc -p __typetests__ --noEmit",
"format-js": "prettier --write --list-different './src//*.{js,jsx,ts,tsx}'",
"format:js": "yarn format-js",
"format:android": "node ../../scripts/format-android.js",
"format:apple": "FORMAT_GLOB_PATTERN=\"../packages/react-native-gesture-handler/apple//*.{h,m,mm,cpp}\" node ../../scripts/format-cpp.js",
"format:cpp": "FORMAT_GLOB_PATTERN=\"../packages/react-native-gesture-handler/{shared,android/src}//*.{h,cpp}\" node ../../scripts/format-cpp.js",
"lint-js": "eslint --ext '.js,.ts,.tsx' src/ && yarn prettier --check './src//*.{js,jsx,ts,tsx}'",
"lint:js": "yarn lint-js",
"lint:android": "./android/gradlew -p android spotlessCheck -q",
"circular-dependency-check": "yarn madge --extensions js,ts,tsx --circular src",
"clean": "rm -rf node_modules android/build android/.cxx",
"prepack": "cp ../../README.md ./README.md",
"postpack": "rm ./README.md"
},
"react-native": "src/index.ts",
"main": "lib/module/index.js",
"module": "lib/module/index.js",
"types": "lib/typescript/index.d.ts",
"files": [
"src",
"lib",
"!/__tests__",
"!/__fixtures__",
"!/__mocks__",
"android/build.gradle",
"android/gradle.properties",
"android/CMakeLists.txt",
"android/src/main/AndroidManifest.xml",
"android/src/main/java/",
"android/src/main/jni/",
"android/common/src/main/java/",
"android/reanimated/src/main/java/",
"android/noreanimated/src/main/java/",
"android/svg",
"android/nosvg",
"apple/",
"shared/",
"scripts/",
"ReanimatedSwipeable/",
"jest-utils/",
"ReanimatedDrawerLayout/",
"README.md",
"jestSetup.js",
"RNGestureHandler.podspec",
"react-native.config.js"
],
"repository": {
"type": "git",
"url": "git+https://github.com/software-mansion/react-native-gesture-handler.git"
},
"author": {
"email": "[email protected]",
"name": "Krzysztof Magiera"
},
"license": "MIT",
"readmeFilename": "README.md",
"bugs": {
"url": "https://github.com/software-mansion/react-native-gesture-handler/issues"
},
"homepage": "https://docs.swmansion.com/react-native-gesture-handler/",
"dependencies": {
"@types/react-test-renderer": "^19.1.0",
"invariant": "^2.2.4"
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@babel/preset-env": "^7.25.3",
"@babel/preset-typescript": "^7.12.7",
"@react-native/babel-preset": "0.87.0",
"@react-native/jest-preset": "0.87.0",
"@testing-library/react-native": "^12.5.1",
"@types/invariant": "^2.2.37",
"@types/jest": "^29.5.12",
"@types/react": "^19.2.0",
"@typescript-eslint/eslint-plugin": "^6.9.0",
"@typescript-eslint/parser": "^6.9.0",
"babel-plugin-module-resolver": "^5.0.2",
"clang-format": "^1.8.0",
"eslint": "^8.57.0",
"eslint-config-satya164": "3.3.0",
"eslint-import-resolver-babel-module": "^5.2.0",
"eslint-plugin-jest": "27.4.3",
"eslint-plugin-prettier": "^5.0.1",
"eslint-plugin-react": "^7.37.5",
"husky": "^8.0.1",
"jest": "^29.7.0",
"lint-staged": "^12.3.2",
"madge": "^6.1.0",
"prettier": "3.3.3",
"react": "19.2.3",
"react-native": "0.87.0",
"react-native-builder-bob": "^0.40.13",
"react-native-reanimated": "4.6.0-nightly-20260811-248dee712",
"react-native-worklets": "patch:react-native-worklets@npm%3A0.12.0-nightly-20260810-fb9cb5596#~/.yarn/patches/react-native-worklets-npm-0.12.0-nightly-20260810-fb9cb5596-f08a72b88e.patch",
"react-test-renderer": "19.2.3",
"typescript": "~6.0.3"
},
"peerDependencies": {
"react": "*",
"react-native": "*"
},
"react-native-builder-bob": {
"source": "src",
"output": "lib",
"targets": [
"module",
[
"typescript",
{
"project": "tsconfig.build.json"
}
]
]
},
"codegenConfig": {
"name": "rngesturehandler_codegen",
"type": "all",
"jsSrcsDir": "./src/specs",
"android": {
"javaPackageName": "com.swmansion.gesturehandler"
},
"ios": {
"componentProvider": {
"RNGestureHandlerButton": "RNGestureHandlerButtonComponentView",
"RNGestureHandlerDetector": "RNGestureHandlerDetector"
}
}
},
"packageManager": "[email protected]"
}---
Packages/React Native Gesture Handler/Tsconfig.Build.Json (packages/react-native-gesture-handler/tsconfig.build.json)
{
"extends": "./tsconfig.json",
"compilerOptions": {
"rootDir": "./src"
},
"exclude": ["/.test.ts", "/.test.tsx"]
}---
Packages/React Native Gesture Handler/Tsconfig.Json (packages/react-native-gesture-handler/tsconfig.json)
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "lib/typescript",
"exactOptionalPropertyTypes": true,
"paths": {
"react-native-gesture-handler": ["./src"]
},
"types": ["./src/global.d.ts", "jest"]
},
"include": ["src//.ts", "src//.tsx", "jestSetup.js"]
}---
Packages/React Native Gesture Handler/ Typetests /Tsconfig.Json (packages/react-native-gesture-handler/__typetests__/tsconfig.json)
{
"extends": "../tsconfig.json",
"compilerOptions": {
"types": ["../src/global.d.ts", "jest"],
"rootDir": "..",
"noUnusedLocals": false
},
"include": ["."]
}---
Packages/React Native Gesture Handler/Android/CMakeLists (packages/react-native-gesture-handler/android/CMakeLists.txt)
cmake_minimum_required(VERSION 3.13)
project(GestureHandlerCodegen)set(CMAKE_VERBOSE_MAKEFILE on)
set(CMAKE_CXX_STANDARD 20)file(GLOB_RECURSE rn_gesture_handler_SRCS CONFIGURE_DEPENDS ../shared/shadowNodes/*.cpp)
file(GLOB_RECURSE rn_gesture_handler_codegen_SRCS CONFIGURE_DEPENDS ./build/generated/source/codegen/jni/*.cpp)add_library(
react_codegen_rngesturehandler_codegen
SHARED
${rn_gesture_handler_SRCS}
${rn_gesture_handler_codegen_SRCS}
)if(ReactAndroid_VERSION_MINOR GREATER_EQUAL 80)
target_compile_reactnative_options(react_codegen_rngesturehandler_codegen PRIVATE)
endif()target_include_directories(react_codegen_rngesturehandler_codegen PUBLIC ../shared/shadowNodes)
target_include_directories(react_codegen_rngesturehandler_codegen PUBLIC ./build/generated/source/codegen/jni)target_link_libraries(
react_codegen_rngesturehandler_codegen
fbjni
jsi
reactnative
)---
Packages/React Native Gesture Handler/Android/Src/Main/Jni/CMakeLists (packages/react-native-gesture-handler/android/src/main/jni/CMakeLists.txt)
project(GestureHandler)
cmake_minimum_required(VERSION 3.9.0)string(
APPEND
CMAKE_CXX_FLAGS
" -DREACT_NATIVE_MINOR_VERSION=${REACT_NATIVE_MINOR_VERSION}")set(CMAKE_VERBOSE_MAKEFILE ON)
if(${REACT_NATIVE_MINOR_VERSION} GREATER_EQUAL 73)
set(CMAKE_CXX_STANDARD 20)
else()
set(CMAKE_CXX_STANDARD 17)
endif()set(PACKAGE_NAME "gesturehandler")
set(RNGH_DIR "${CMAKE_SOURCE_DIR}/../../../../")
set(REACT_ANDROID_DIR "${REACT_NATIVE_DIR}/ReactAndroid")file(GLOB_RECURSE gesture_handler_SRCS CONFIGURE_DEPENDS ./*.cpp)
file(GLOB_RECURSE gesture_handler_shared_SRCS CONFIGURE_DEPENDS "${RNGH_DIR}/shared/runtime/*.cpp")include(${REACT_ANDROID_DIR}/cmake-utils/folly-flags.cmake)
add_compile_options(${folly_FLAGS})add_library(${PACKAGE_NAME}
SHARED
${gesture_handler_SRCS}
${gesture_handler_shared_SRCS}
)target_include_directories(
${PACKAGE_NAME}
PUBLIC
"${CMAKE_SOURCE_DIR}"
"${RNGH_DIR}/shared/runtime"
PRIVATE
"${REACT_NATIVE_DIR}/ReactCommon"
)if(ReactAndroid_VERSION_MINOR GREATER_EQUAL 80)
target_compile_reactnative_options(${LIB_TARGET_NAME} PRIVATE)
endif()find_package(ReactAndroid REQUIRED CONFIG)
find_package(fbjni REQUIRED CONFIG)target_link_libraries(
${PACKAGE_NAME}
ReactAndroid::reactnative
ReactAndroid::jsi
fbjni::fbjni
)if(RNGH_USE_WORKLETS)
find_package(react-native-worklets REQUIRED CONFIG)
target_compile_definitions(${PACKAGE_NAME} PRIVATE RNGH_USE_WORKLETS=1)
target_link_libraries(${PACKAGE_NAME} react-native-worklets::worklets)
endif()---
Packages/React Native Gesture Handler/Jest Utils/Package.Json (packages/react-native-gesture-handler/jest-utils/package.json)
{
"main": "../lib/module/jestUtils/index",
"module": "../lib/module/jestUtils/index",
"react-native": "../src/jestUtils/index",
"types": "../lib/typescript/jestUtils/index.d.ts"
}---
Packages/React Native Gesture Handler/ReanimatedDrawerLayout/Package.Json (packages/react-native-gesture-handler/ReanimatedDrawerLayout/package.json)
{
"main": "../lib/module/components/ReanimatedDrawerLayout",
"module": "../lib/module/components/ReanimatedDrawerLayout",
"react-native": "../src/components/ReanimatedDrawerLayout",
"types": "../lib/typescript/components/ReanimatedDrawerLayout.d.ts"
}---
Packages/React Native Gesture Handler/ReanimatedSwipeable/Package.Json (packages/react-native-gesture-handler/ReanimatedSwipeable/package.json)
{
"main": "../lib/module/components/ReanimatedSwipeable/",
"module": "../lib/module/components/ReanimatedSwipeable/",
"react-native": "../src/components/ReanimatedSwipeable/",
"types": "../lib/typescript/components/ReanimatedSwipeable/"
}---
Skills/Gesture Handler 3 Migration/SKILL (skills/gesture-handler-3-migration/SKILL.md)
---
name: gesture-handler-3-migration
description: Migrates files containing React Native components which use the React Native Gesture Handler 2 API to Gesture Handler 3.
---Migrate to Gesture Handler 3
This skill scans React Native components that use the Gesture Handler builder-based API and updates them to use the new hook-based API. It also updates related types and components to adapt to the new version.
When to Use
- Updating the usage of components imported from
Gesture.X()
- Upgrading to Gesture Handler 3
- Migrating to the new hook-based gesture APIInstructions
Use the instructions below to correctly replace all legacy APIs with the modern ones.
1. Identify all imports from 'react-native-gesture-handler'
2. For eachcall, replace with correspondinguseXGesture()hookGesture
3. Replaceimport with imports for the used hooksGesture.X()
4. Convert builder method chains to configuration objects
5. Update callback names (onStart → onActivate, etc.)
6. Replace composed gestures with relation hooks. Keep rules of hooks in mind
7. Update GestureDetector usage if SVG is involved to Intercepting/Virtual GestureDetector
8. Update usage of compoenent imported from 'react-native-gesture-handler' according to "Legacy components" sectionMigrating gestures
All hook gestures have their counterparts in the builder API:
becomesuseXGesture(config). The methods are now config object fields with the same name as the relevant builder methods, unless specified otherwise.Gesture.ForceTouchThe exception to thait is
which DOES NOT have a counterpart in the hook API.onStart#### Callback changes
In Gesture Handler 3 some of the callbacks were renamed, namely:
-
->onActivateonEnd
-->onDeactivateonTouchesCancelled
-->onTouchesCancelonDeactivateThe
andonFinalizecallbacks no longer receive a seconddidSucceed/successboolean parameter. Instead, the event object now contains acanceledproperty. Note that the logic is inverted —canceled: truecorresponds to the oldsuccess: false.
// Old (RNGH2)
.onEnd((event, success) => {
if (success) { / gesture succeeded / }
})
// New (RNGH3)
onDeactivate: (event) => {
if (!event.canceled) { / gesture succeeded / }
}
In the hooks APIonChangeis no longer available. Instead thechangeproperties were moved to the event available insideonUpdate.usePanGesture()All callbacks of a gesture are now using the same type:
-
->PanGestureEventuseTapGesture()
-->TapGestureEventuseLongPressGesture()
-->LongPressGestureEventuseRotationGesture()
-->RotationGestureEventusePinchGesture()
-->PinchGestureEventuseFlingGesture()
-->FlingGestureEventuseHoverGesture()
-->HoverGestureEventuseNativeGesture()
-->RotationGestureEventuseManualGesture()
-->ManualGestureEventonTouchesDownThe exception to this is touch events:
-
onTouchesUp
-onTouchesMove
-onTouchesCancel
-GestureTouchEventWhere each callback receives
regardless of the hook used.stateManager#### StateManager
In Gesture Handler 3,
is no longer passed toTouchEventcallbacks. Instead, you should use the globalGestureStateManager.GestureStateManagerprovides methods for imperative state management:handlerTag- .activate(handlerTag: number)
- .deactivate(handlerTag: number) (.end() in the old API)
- .fail(handlerTag: number)can be obtained in two ways:gesture.handlerTag1. From the gesture object returned by the hook (
)event.handlerTag
2. From the event inside callback ()Gesture.Simultaneous(gesture1, gesture2);Callback definitions CANNOT reference the gesture that's being defined. In this scenario use events to get access to the handler tag.
Remove GestureStateManager.begin() as gestures must now automatically enter the BEGAN state via touch events before they can be activated through the GestureStateManager.
Migrating relations
#### Composed gestures
becomesuseSimultaneousGestures(pan1, pan2);Gesture.Race()All relations from the old API and their counterparts in the new one:
-
->useCompetingGestures()Gesture.Simultaneous()
-->useSimultaneousGestures()Gesture.Exclusive()
-->useExclusiveGestures().simultaneousWithExternalGesture#### Cross components relations properties
Properties used to define cross-components interactions were renamed:
-
->simultaneousWith:.requireExternalGestureToFail
-->requireToFail:.blocksExternalGesture
-->block:GestureDetectorGestureDetector
The
is a key component ofreact-native-gesture-handler. It supports gestures created either using the hooks API or the builder pattern (but those cannot be mixed, it's either or).'worklet';Don't use the same instance of a gesture across multiple Gesture Detectors as it will lead to an undefined behavior.
Integration with Reanimated
Worklets' Babel plugin is setup in a way that automatically marks callbacks passed to gestures in the configuration chain as worklets. This means that you don't need to add a
directive at the beginning of the functions.This will not be workletized because the callback is defined outside of the gesture object:
const callback = () => {
console.log(_WORKLET);
};
const gesture = useTapGesture({
onBegin: callback,
});
The callback wrapped by any other higher order function will not be workletized:const gesture = useTapGesture({
onBegin: useCallback(() => {
console.log(_WORKLET);
}, []),
});
In the above cases, you should add a"worklet";directive as the first line of the callback.ReanimatedDisabling Reanimated
Gestures created with the hook API have
integration enabled by default (if it's installed), meaning all callbacks are executed on the UI thread.runOnJS#### runOnJS
The
property allows you to dynamically control whether callbacks are executed on the JS thread or the UI thread. When set totrue, callbacks will run on the JS thread. Setting it tofalsewill execute them on the UI thread. Default value isfalse.SVGMigrating components relying on view hierarchy
Certain components, such as
, depend on the view hierarchy to function correctly. In Gesture Handler 3,GestureDetectordisrupts these hierarchies. To resolve this issue, two new detectors have been introduced:InterceptingGestureDetectorandVirtualGestureDetector.InterceptingGestureDetectorfunctions similarly to theGestureDetector, but it can also act as a proxy forVirtualGestureDetectorwithin its component subtree. Because it can be used solely to establish the context for virtual detectors, thegestureproperty is optional.VirtualGestureDetectoris similar to theGestureDetectorfrom RNGH2. Because it is not a host component, it does not interfere with the host view hierarchy. This allows you to attach gestures without disrupting functionality that depends on it.VirtualGestureDetectorWarning:
has to be a descendant ofInterceptingGestureDetector.GestureDetector#### Migrating SVG
In Gesture Handler 2 it was possible to use
directly onSVG. In Gesture Handler 3, the correct way to interact withSVGis to useInterceptingGestureDetectorandVirtualGestureDetector.waitForLegacy components
When the code using the component relies on the APIs that are no longer available on the components in Gesture Handler 3 (like
,simultaneousWith,blocksHandler,onHandlerStateChange,onGestureEventprops), it cannot be easily migrated in isolation. In this case update the imports to the Legacy version of the component, and inform the user that the dependencies need to be migrated first.LegacyIf the migration is possible, use the ask questions tool to clarify the user intent unless clearly stated beforehand: should the components be using the new implementation (no
prefix when imported), or should they revert to the old implementation (Legacyprefix when imported)?LegacyDon't suggest replacing buttons from Gesture Handler with components from React Native and vice versa.
The implementation of buttons has been updated, resolving most button-related issues. They have also been internally rewritten to utilize the new hook API. The legacy JS implementations of button components are still accessible but have been renamed with the prefix
, e.g.,RectButtonis now available asLegacyRectButton. Those still use the new native component under the hood.PureNativeButtonhas been removed. If encountered, inform the user that it has been removed and let them decide how to handle that case. They can achieve similar functionality with other buttons.dragOffsetFromRightReanimatedSwipeable prop
now accepts negative values. If it was used with positive values, make sure to change the sign.LegacyOther components have also been internally rewritten using the new hook API but are exported under their original names, so no changes are necessary on your part. However, if you need to use the previous implementation for any reason, the legacy components are also available and are prefixed with
, e.g.,ScrollViewis now available asLegacyScrollView.TouchableRename all instances of createNativeWrapper to legacy_createNativeWrapper. This includes both the import statements and the function calls.
#### Migrating to
TouchableIn Gesture Handler 3 the
component replaces both the old buttons (BaseButton,RectButton,BorderlessButton) and the legacy core-style touchables (TouchableOpacity,TouchableHighlight,TouchableWithoutFeedback,TouchableNativeFeedback). It is a single component whose visual feedback is controlled entirely through props — pick the right combination instead of picking a different component.onPress(event)The props you will use when migrating:
-
— fired on a successful tap. Note: the callback signature changed; the oldBaseButton.onPressreceived(pointerInside: boolean),Touchable.onPressreceives a gesture event object instead.onPressIn(event)
-/onPressOut(event)— fired when the pointer first touches and when it is released or leaves the component.onLongPress()
-— fired after the press is held fordelayLongPressmilliseconds (default600). When a long press fires, the subsequent release does not callonPress.disabled
-— replaces the oldenabledprop (note the inverted sense). Defaults tofalse.cancelOnLeave
-— whether the press is cancelled when the pointer leaves the component bounds. Defaults totrue. Use this to replaceshouldCancelWhenOutsidefrom raw buttons.activeOpacity
-— opacity applied to the component itself while pressed (mirrorsTouchableOpacity). Defaults to1(no opacity change).underlayColor
-+activeUnderlayOpacity— color and opacity of the underlay shown while pressed (mirrorsTouchableHighlight/RectButton).underlayColordefaults to'transparent'andactiveUnderlayOpacityto0.105.androidRipple
-— Android ripple config ({ color?, radius?, borderless?, foreground? }). When omitted, no native ripple is rendered. Use this to replaceTouchableNativeFeedback.animationDuration
-— press/hover animation timing in milliseconds. Pass a single number to apply it to every phase, or{ in, out }(optionally withtap/hover/longPressoverrides). Defaults to50in /100out.hitSlop
-,testID,style,children— same as before.BaseButton##### Replacing Gesture Handler buttons
| Old component | Replace with (iOS / cross-platform default) |
| ----------------- | --------------------------------------------------------- |
||<Touchable />(default props) |RectButton
||<Touchable underlayColor="black" animationDuration={0} />|BorderlessButton
||<Touchable activeOpacity={0.3} animationDuration={0} />|RectButtonAndroid ripple: legacy
/BorderlessButtonuse the native theme ripple on Android, whileTouchabledisables the ripple unlessandroidRippleis set. To preserve the legacy Android feedback, setandroidRippleon Android instead ofunderlayColor/activeOpacity/animationDuration(don't combine them — the ripple is the visual feedback on Android). The two configs are different:RectButton-
→androidRipple={{}}BorderlessButton
-→androidRipple={{ borderless: true }}(matches the legacy borderless ripple shape)Platform.selectUse
to apply different props per platform. Example forRectButton:
import { Platform } from 'react-native';
<Touchable
{...Platform.select({
android: { androidRipple: {} },
default: { underlayColor: 'black', animationDuration: 0 },
})}
/>
`
##### Replacing legacy Touchables
| Old component | Replace with |
| --------------------------- | ---------------------------------------------------------------------------- |
| TouchableOpacity | <Touchable activeOpacity={0.2} animationDuration={{ in: 0, out: 150 }} /> |TouchableHighlight
| | <Touchable underlayColor={...} activeUnderlayOpacity={1} activeOpacity={...} /> — closest approximation only (not 1:1, see note below) |TouchableWithoutFeedback
| | <Touchable /> (plain, no visual feedback props) |TouchableNativeFeedback
| | <Touchable androidRipple={{ foreground: true }} /> (legacy default draws the ripple in the foreground; drop foreground if the original code passed useForeground={false}) |
For TouchableNativeFeedback, androidRipple must be set explicitly — without it no ripple is rendered. The legacy component defaults to useForeground: true, so { foreground: true } is the closest default replacement; omit foreground only when the original code set useForeground={false}. Add color, radius, or borderless if the original code customized the background prop.
For TouchableHighlight, a perfect 1:1 replacement is not possible — in the legacy component the container's own background becomes the underlay (solid underlayColor) and activeOpacity dims just the children on top, so the underlay shows through the dimmed children. Touchable instead has a separate underlay layer between the background and children, and its activeOpacity dims the whole component (background + underlay + children together). The closest approximation: carry underlayColor and activeOpacity over unchanged, and add activeUnderlayOpacity={1} so the underlay layer is rendered solid. Inform the user that the visual feedback may differ from the legacy component because of the different layering.
Do not swap Gesture Handler buttons/touchables for React Native core components or vice versa during migration — keep them within react-native-gesture-handler.
Replaced types
Most of the types used in the builder API, like TapGesture, are still present in Gesture Handler 3. However, they are now used in new hook API. Types for builder API now have Legacy prefix, e.g. TapGesture becomes LegacyTapGesture.
---
.Github/ISSUE TEMPLATE/Bug Report.Yml (.github/ISSUE_TEMPLATE/bug-report.yml)
name: Bug report
description: Report an issue with Gesture Handler here.
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!
Before you proceed:
- Make sure to check whether there are similar issues in the repository
- Make sure to clean cache in your project. Depending on your setup this could be done by:
- yarn start --reset-cache ornpm run start -- --reset-cache
- orexpo start --clear
-
- type: markdown
attributes:
value: |
## Required information
- type: textarea
id: description
attributes:
label: Description
description: Please provide a clear, concise and descriptive explanation of what the bug is. Include screenshots or a video if needed. Tell us what were you expecting to happen instead of what is happening now.
validations:
required: true
- type: textarea
id: steps-to-reproduce
attributes:
label: Steps to reproduce
description: Provide a detailed list of steps that reproduce the issue.
placeholder: |
1.
2.
3.
validations:
required: true
- type: input
id: repro
attributes:
label: A link to a Gist, an Expo Snack or a link to a repository based on this template that reproduces the bug.
description: |
Please provide code snippet, a Snack or a link to a repository on GitHub under your username that reproduces the issue.
Here are some tips for providing a minimal example: https://stackoverflow.com/help/mcve.
Issues without a reproduction are likely to stale.
placeholder: Link to a Snack or a GitHub repository
validations:
required: true
- type: input
id: gesture-handler-version
attributes:
label: Gesture Handler version
description: What version of react-native-gesture-handler are you using?
placeholder: 2.25.0
validations:
required: true
- type: input
id: react-native-version
attributes:
label: React Native version
description: What version of react-native are you using?
placeholder: 0.79.0
validations:
required: true
- type: dropdown
id: platforms
attributes:
label: Platforms
description: On what platform your application is running on?
multiple: true
options:
- Android
- iOS
- Web
- MacOS
validations:
required: true
- type: markdown
attributes:
value: |
## Additonal information
Providing as much information as possible greatly helps us with reproducting the issues.
- type: dropdown
id: runtime
attributes:
label: JavaScript runtime
description: What runtime is your application using?
options:
- Hermes
- JSC
- V8
- type: dropdown
id: workflow
attributes:
label: Workflow
description: How your application is managed? Not sure? Read this part of Expo documentation.
options:
- React Native (without Expo)
- Using Expo Go
- Using Expo Prebuild or an Expo development build
- type: dropdown
id: architecture
attributes:
label: Architecture
description: What React Native architecture your application is running on? Currently, the New Architecture is enabled by default for every new React Native project.
options:
- New Architecture (Fabric)
- Old Architecture (Paper)
- type: dropdown
id: build-type
attributes:
label: Build type
description: What mode your application is running?
options:
- Debug mode
- Release mode
- type: dropdown
id: emulator
attributes:
label: Device
description: How are you running your application?
options:
- iOS simulator
- Android emulator
- Real device
- type: input
id: device-model
attributes:
label: Device model
description: What device you are experiencing this problem on? Specify full device name along with the version of the operating system it's running.
placeholder: ex. Samsung Galaxy A22 (Android 12)
- type: dropdown
id: acknowledgements
attributes:
label: Acknowledgements
description: I searched for similar issues in the repository.
options:
- 'Yes'
validations:
required: true
---
.Github/ISSUE TEMPLATE/Config.Yml (.github/ISSUE_TEMPLATE/config.yml)
blank_issues_enabled: false
contact_links:
- name: Question
url: https://github.com/software-mansion/react-native-gesture-handler/discussions/categories/q-a
about: Please ask and answer questions here.
---
.Github/Workflows/Android Basic.Yml (.github/workflows/android-basic.yml)
name: Build Android (basic-example)
on:
pull_request:
paths:
- .github/workflows/android-basic.yml
- .github/workflows/android-build.yml
- packages/react-native-gesture-handler/package.json
- packages/react-native-gesture-handler/android/
- packages/react-native-gesture-handler/shared/
- packages/react-native-gesture-handler/src/specs/
- apps/basic-example/
- '!apps/basic-example/ios/'
- '!apps/basic-example/Gemfile*'
- rnrepo.config.json
- yarn.lock
push:
branches:
- main
workflow_dispatch:
concurrency:
group: android-basic-${{ github.ref }}
cancel-in-progress: true
jobs:
basic-example:
if: github.repository == 'software-mansion/react-native-gesture-handler'
uses: ./.github/workflows/android-build.yml
with:
app: basic-example
artifact-name: android-apk-basic-example
abi: x86_64
---
.Github/Workflows/Android Build.Yml (.github/workflows/android-build.yml)
name: Build Android app
Reusable: builds one example app in Release and uploads the APK.
on:
workflow_call:
inputs:
app:
description: Directory under apps/ holding the example app to build.
required: true
type: string
artifact-name:
description: Name to upload the built APK under.
required: true
type: string
abi:
description: ABI to build. Must match the emulator the e2e job installs the APK on.
required: true
type: string
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: checkout
uses: actions/checkout@v4
- name: Use Java 17
uses: actions/setup-java@v4
with:
distribution: oracle
java-version: 17
- name: Use Node.js 24
uses: actions/setup-node@v6
with:
node-version: 24
cache: yarn
- name: Install node dependencies
working-directory: apps/${{ inputs.app }}
run: yarn install --immutable
- name: Build app in Release mode
working-directory: apps/${{ inputs.app }}/android
run: ./gradlew :app:assembleRelease --console=plain -PreactNativeArchitectures=${{ inputs.abi }}
- name: Upload APK artifact
uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-name }}
path: apps/${{ inputs.app }}/android/app/build/outputs/apk/release/*.apk
if-no-files-found: error
compression-level: 0
retention-days: 3
---
.Github/Workflows/Android.Yml (.github/workflows/android.yml)
name: Build Android (expo-example)
on:
pull_request:
paths:
- .github/workflows/android.yml
- .github/workflows/android-build.yml
- packages/react-native-gesture-handler/package.json
- packages/react-native-gesture-handler/android/
- packages/react-native-gesture-handler/shared/
- packages/react-native-gesture-handler/src/
- apps/expo-example/
- apps/common-app/
- rnrepo.config.json
- yarn.lock
push:
branches:
- main
workflow_dispatch:
concurrency:
group: android-${{ github.ref }}
cancel-in-progress: true
jobs:
expo-example:
if: github.repository == 'software-mansion/react-native-gesture-handler'
uses: ./.github/workflows/android-build.yml
with:
app: expo-example
artifact-name: android-apk-expo-example
abi: x86_64
---
.Github/Workflows/Close When Stale.Yml (.github/workflows/close-when-stale.yml)
name: Check for stale issues
on:
schedule:
- cron: '37 21 *' # at 21:37 every day
issues:
types: [edited]
issue_comment:
types: [created, edited]
workflow_dispatch:
jobs:
main:
if: github.repository == 'software-mansion/react-native-gesture-handler'
runs-on: ubuntu-latest
steps:
- name: Checkout Actions
uses: actions/checkout@v4
with:
repository: software-mansion-labs/swmansion-bot
ref: stable
- uses: actions/cache@v4
with:
path: '/node_modules'
key: ${{ runner.os }}-modules-${{ hashFiles('/yarn.lock') }}
- name: Install Actions
run: yarn install
- name: Close when stale
uses: ./close-when-stale
with:
close-when-stale-label: Close when stale
days-to-close: 20
---
.Github/Workflows/Docs Check.Yml (.github/workflows/docs-check.yml)
name: Check documentation
on:
pull_request:
paths:
- packages/docs-gesture-handler/
workflow_dispatch:
jobs:
check:
if: github.repository == 'software-mansion/react-native-gesture-handler'
runs-on: ubuntu-latest
env:
WORKING_DIRECTORY: packages/docs-gesture-handler
concurrency:
group: docs-check-${{ github.ref }}
cancel-in-progress: true
steps:
- name: checkout
uses: actions/checkout@v4
- name: Use Node.js 24
uses: actions/setup-node@v6
with:
node-version: 24
- name: Install node dependencies
working-directory: ${{ env.WORKING_DIRECTORY }}
run: yarn
- name: Generate docs
working-directory: ${{ env.WORKING_DIRECTORY }}
run: yarn build
---
.Github/Workflows/Ios Basic.Yml (.github/workflows/ios-basic.yml)
name: Build iOS (basic-example)
on:
pull_request:
paths:
- .github/workflows/ios-basic.yml
- .github/workflows/ios-build.yml
- packages/react-native-gesture-handler/package.json
- packages/react-native-gesture-handler/RNGestureHandler.podspec
- packages/react-native-gesture-handler/apple/
- packages/react-native-gesture-handler/shared/
- packages/react-native-gesture-handler/src/specs/
- apps/basic-example/
- '!apps/basic-example/android/'
- rnrepo.config.json
- yarn.lock
push:
branches:
- main
workflow_dispatch:
concurrency:
group: ios-basic-${{ github.ref }}
cancel-in-progress: true
jobs:
basic-example:
if: github.repository == 'software-mansion/react-native-gesture-handler'
uses: ./.github/workflows/ios-build.yml
with:
app: basic-example
scheme: BasicExample
artifact-name: ios-app-basic-example
xcode-version: '26.4.1'
---
.Github/Workflows/Ios Build.Yml (.github/workflows/ios-build.yml)
name: Build iOS app
Reusable: builds one example app for the simulator in Release and uploads the
.app bundle.
on:
workflow_call:
inputs:
app:
description: Directory under apps/ holding the example app to build.
required: true
type: string
scheme:
description: Xcode scheme of the app (also the workspace name).
required: true
type: string
artifact-name:
description: Name to upload the packaged .app bundle under.
required: true
type: string
xcode-version:
description: Xcode version to build with.
required: true
type: string
jobs:
build:
runs-on: macos-26
timeout-minutes: 60
steps:
- name: checkout
uses: actions/checkout@v4
- name: Select Xcode
env:
XCODE_VERSION: ${{ inputs.xcode-version }}
run: |
XCODE_APP="/Applications/Xcode_${XCODE_VERSION}.app"
if [ ! -d "$XCODE_APP" ]; then
echo "Xcode ${XCODE_VERSION} is not installed on this runner. Available:" >&2
ls -d /Applications/Xcode*.app >&2
exit 1
fi
echo "Using $XCODE_APP"
echo "DEVELOPER_DIR=$XCODE_APP/Contents/Developer" >> "$GITHUB_ENV"
DEVELOPER_DIR="$XCODE_APP/Contents/Developer" xcodebuild -version
- name: Use Node.js 24
uses: actions/setup-node@v6
with:
node-version: 24
cache: yarn
- name: Install node dependencies
working-directory: apps/${{ inputs.app }}
run: yarn install --immutable
# expo-example runs expo prebuild` on postinstall, which installs its pods.
- name: Install pods
if: ${{ inputs.app == 'basic-example' }}
working-directory: apps/${{ inputs.app }}/ios
run: bundle install && NO_FLIPPER=1 bundle exec pod install
- name: Build app in Release mode
working-directory: apps/${{ inputs.app }}/ios
run: |
xcodebuild \
-workspace ${{ inputs.scheme }}.xcworkspace \
-scheme ${{ inputs.scheme }} \
-configuration Release \
-sdk iphonesimulator \
-destination 'generic/platform=iOS Simulator' \
-derivedDataPath build \
CODE_SIGNING_ALLOWED=NO \
build
# Artifact zips drop POSIX permissions and symlinks, which would leave the
# .app with a non-executable binary — tar the bundle to keep it installable.
- name: Package app bundle
working-directory: apps/${{ inputs.app }}/ios
run: |
APP_PATH=$(find build/Build/Products/Release-iphonesimulator -maxdepth 1 -name '*.app' -type d | head -1)
if [ -z "$APP_PATH" ]; then
echo "Could not find built .app" >&2
exit 1
fi
echo "Packaging $APP_PATH"
mkdir -p "$RUNNER_TEMP/artifact"
tar -czf "$RUNNER_TEMP/artifact/app.tar.gz" \
-C "$(dirname "$APP_PATH")" "$(basename "$APP_PATH")"
- name: Upload app artifact
uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-name }}
path: ${{ runner.temp }}/artifact/app.tar.gz
if-no-files-found: error
compression-level: 0
retention-days: 3
---
.Github/Workflows/Ios.Yml (.github/workflows/ios.yml)
name: Build iOS (expo-example)
on:
pull_request:
paths:
- .github/workflows/ios.yml
- .github/workflows/ios-build.yml
- packages/react-native-gesture-handler/package.json
- packages/react-native-gesture-handler/RNGestureHandler.podspec
- packages/react-native-gesture-handler/apple/
- packages/react-native-gesture-handler/shared/
- packages/react-native-gesture-handler/src/
- apps/expo-example/
- apps/common-app/
- rnrepo.config.json
- yarn.lock
push:
branches:
- main
workflow_dispatch:
concurrency:
group: ios-${{ github.ref }}
cancel-in-progress: true
jobs:
expo-example:
if: github.repository == 'software-mansion/react-native-gesture-handler'
uses: ./.github/workflows/ios-build.yml
with:
app: expo-example
scheme: ExpoExample
artifact-name: ios-app-expo-example
xcode-version: '26.4.1'
---
.Github/Workflows/Kotlin Lint.Yml (.github/workflows/kotlin-lint.yml)
name: Kotlin Lint
on:
pull_request:
paths:
- packages/react-native-gesture-handler/android/
push:
branches:
- main
workflow_dispatch:
jobs:
check:
if: github.repository == 'software-mansion/react-native-gesture-handler'
runs-on: ubuntu-latest
env:
WORKING_DIRECTORY: packages/react-native-gesture-handler
concurrency:
group: kotlin-lint-${{ github.ref }}
cancel-in-progress: true
steps:
- name: checkout
uses: actions/checkout@v4
- name: Use Java 17
uses: actions/setup-java@v4
with:
distribution: oracle
java-version: 17
- name: Use Node.js 24
uses: actions/setup-node@v6
with:
node-version: 24
cache: yarn
- uses: actions/cache@v4
with:
path: '/node_modules'
key: ${{ runner.os }}-modules-${{ hashFiles('/yarn.lock') }}
- name: Install node dependencies
run: yarn install --immutable
- name: Restore build from cache
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
${{ env.WORKING_DIRECTORY }}/android/build
${{ env.WORKING_DIRECTORY }}/android/.gradle
key: ${{ runner.os }}-kotlin-lint-gradle-${{ hashFiles('${{ env.WORKING_DIRECTORY }}//.gradle', '${{ env.WORKING_DIRECTORY }}//gradle-wrapper.properties', '${{ env.WORKING_DIRECTORY }}/android/build.gradle') }}
- name: Lint
run: yarn workspace react-native-gesture-handler lint:android
---
.Github/Workflows/Macos Build.Yml (.github/workflows/macos-build.yml)
name: Test macOS build
on:
pull_request:
paths:
- .github/workflows/macos-build.yml
- packages/react-native-gesture-handler/RNGestureHandler.podspec
- packages/react-native-gesture-handler/apple/
- apps/macos-example/macos/
push:
branches:
- main
workflow_dispatch:
jobs:
build:
if: github.repository == 'software-mansion/react-native-gesture-handler'
runs-on: macos-15-xlarge
env:
WORKING_DIRECTORY: apps/macos-example
concurrency:
group: macos-${{ github.ref }}
cancel-in-progress: true
steps:
- name: checkout
uses: actions/checkout@v4
- name: Use Node.js 22
uses: actions/setup-node@v6
with:
node-version: 22
cache: yarn
- name: Install node dependencies
working-directory: ${{ env.WORKING_DIRECTORY }}
run: yarn
- name: Install pods
working-directory: ${{ env.WORKING_DIRECTORY }}/macos
run: bundle install && bundle exec pod install
- name: Build app
working-directory: ${{ env.WORKING_DIRECTORY }}
run: npx react-native-macos run-macos
---
.Github/Workflows/Main.Yml (.github/workflows/main.yml)
name: Publish to GitHub Pages
on:
push:
branches:
- main
jobs:
publish:
if: github.repository == 'software-mansion/react-native-gesture-handler'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Check out
uses: actions/checkout@v6
- name: Generate docs
run: >-
git config --local user.email "[email protected]"
&& git config --local user.name "GitHub Action"
&& cd packages/docs-gesture-handler
&& yarn
&& yarn build
- name: Publish generated content to GitHub Pages
uses: JamesIves/github-pages-deploy-action@v4
with:
folder: packages/docs-gesture-handler/build
branch: gh-pages
token: ${{ secrets.GITHUB_TOKEN }}
---
.Github/Workflows/Needs More Info.Yml (.github/workflows/needs-more-info.yml)
name: Check issue template
on:
issues:
types: [opened, edited]
jobs:
main:
if: ${{ github.repository == 'software-mansion/react-native-gesture-handler' && !contains(github.event.issue.labels.*.name, 'Maintainer issue') }}
runs-on: ubuntu-latest
concurrency:
group: needs-more-info-${{ github.event.issue.number }}
cancel-in-progress: true
steps:
- name: Checkout Actions
uses: actions/checkout@v4
with:
repository: software-mansion-labs/swmansion-bot
ref: stable
- uses: actions/cache@v4
with:
path: '/node_modules'
key: ${{ runner.os }}-modules-${{ hashFiles('/yarn.lock') }}
- name: Install Actions
run: yarn install
- name: Needs More Info
uses: ./needs-more-info
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
needs-more-info-label: 'Missing info'
required-sections: 'Description;Steps to reproduce;A link to a Gist, an Expo Snack or a link to a repository based on this template that reproduces the bug.;Gesture Handler version;React Native version;Platforms'
needs-more-info-response: "Hey! 👋 \n\nIt looks like you've omitted a few important sections from the issue template."
# This action also appends something like: "Please complete X, Y and Z sections." to the response.
# Code responsible for this can be found here: https://github.com/software-mansion-labs/swmansion-bot/blob/main/needs-more-info/MissingSectionsFormatter.js
---
.Github/Workflows/Needs Repro.Yml (.github/workflows/needs-repro.yml)
name: Check for reproduction
on:
issues:
types: [opened, edited]
jobs:
main:
if: ${{ github.repository == 'software-mansion/react-native-gesture-handler' && !contains(github.event.issue.labels.*.name, 'Maintainer issue') }}
runs-on: ubuntu-latest
concurrency:
group: needs-repro-${{ github.event.issue.number }}
cancel-in-progress: true
steps:
- name: Checkout Actions
uses: actions/checkout@v4
with:
repository: software-mansion-labs/swmansion-bot
ref: stable
- uses: actions/cache@v4
with:
path: '/node_modules'
key: ${{ runner.os }}-modules-${{ hashFiles('/yarn.lock') }}
- name: Install Actions
run: yarn install
- name: Needs Repro
uses: ./needs-repro
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
needs-repro-label: 'Missing repro'
needs-repro-response: "Hey! 👋 \n\nThe issue doesn't seem to contain a minimal reproduction.\n\nCould you provide a snack or a link to a GitHub repository under your username that reproduces the problem?"
repro-provided-label: 'Repro provided'
check-issues-only-created-after: 2022-02-01
---
.Github/Workflows/Platforms.Yml (.github/workflows/platforms.yml)
name: Check for platforms
on:
issues:
types: [opened, edited]
jobs:
main:
if: ${{ github.repository == 'software-mansion/react-native-gesture-handler' && !contains(github.event.issue.labels.*.name, 'Maintainer issue') }}
runs-on: ubuntu-latest
concurrency:
group: platforms-${{ github.event.issue.number }}
cancel-in-progress: true
steps:
- name: Checkout Actions
uses: actions/checkout@v4
with:
repository: software-mansion-labs/swmansion-bot
ref: stable
- uses: actions/cache@v4
with:
path: '/node_modules'
key: ${{ runner.os }}-modules-${{ hashFiles('/yarn.lock') }}
- name: Install Actions
run: yarn install
- name: Platforms
uses: ./platforms
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
platforms-section-header: Platforms
platforms-comma-separated: true
platforms-with-labels: '{"Android": "Platform: Android", "iOS": "Platform: iOS", "Web": "Platform: Web", "MacOS": "Platform: MacOS"}'
---
.Github/Workflows/Publish Release.Yml (.github/workflows/publish-release.yml)
name: Publish release to npm
on:
# For nightly releases
schedule:
- cron: '27 23 *' # at 23:27 every day
# For manual releases
workflow_dispatch:
inputs:
release-type:
description: Type of release to publish.
type: choice
options:
- stable
- nightly
- beta
- rc
default: stable
version:
description: Specific version to publish (usually inferred from x.y-stable branch name).
type: string
required: false
default: ''
dry-run:
description: Whether to perform a dry run of the publish.
type: boolean
default: true
jobs:
npm-build:
if: github.repository == 'software-mansion/react-native-gesture-handler'
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write # for OIDC
concurrency:
group: publish-${{ github.ref }}
cancel-in-progress: false
steps:
- name: Check out
uses: actions/checkout@v4
- name: Set up environment
shell: bash
run: |
echo "YARN_ENABLE_HARDENED_MODE=0" >> $GITHUB_ENV
- name: Setup Node
uses: actions/setup-node@v6
with:
node-version: 24
cache: 'yarn'
registry-url: https://registry.npmjs.org/
- name: Publish manual release
if: ${{ github.event_name == 'workflow_dispatch' }}
uses: software-mansion/npm-package-publish@273bbdd5df5d28ae2de6c3ecd4a6f8067e4ff370
with:
package-name: 'react-native-gesture-handler'
package-json-path: 'packages/react-native-gesture-handler/package.json'
install-dependencies-command: 'yarn install --immutable'
release-type: ${{ inputs.release-type }}
version: ${{ inputs.version }}
dry-run: ${{ inputs.dry-run }}
- name: Publish automatic nightly release
if: ${{ github.event_name == 'schedule' }}
uses: software-mansion/npm-package-publish@273bbdd5df5d28ae2de6c3ecd4a6f8067e4ff370
with:
package-name: 'react-native-gesture-handler'
package-json-path: 'packages/react-native-gesture-handler/package.json'
install-dependencies-command: 'yarn install --immutable'
release-type: 'nightly'
dry-run: false
---
.Github/Workflows/Rngh Api V3.Yml (.github/workflows/rngh-api-v3.yml)
name: Test Gesture Handler 3 API
on:
pull_request:
paths:
- packages/react-native-gesture-handler/src/v3/
- packages/react-native-gesture-handler/src/__tests__/RelationsTraversal.test.tsx
- packages/react-native-gesture-handler/src/__tests__/API_V3.test.tsx
push:
branches:
- main
workflow_dispatch:
jobs:
check:
if: github.repository == 'software-mansion/react-native-gesture-handler'
runs-on: ubuntu-latest
concurrency:
group: rngh-api-v3-${{ github.ref }}
cancel-in-progress: true
steps:
- name: checkout
uses: actions/checkout@v4
- name: Use Node.js 24
uses: actions/setup-node@v6
with:
node-version: 24
cache: yarn
- name: Install node dependencies
run: yarn --immutable
- name: Run tests
working-directory: packages/react-native-gesture-handler
run: yarn test RelationsTraversal API_V3
---
.Github/Workflows/Run Jest Tests.Yml (.github/workflows/run-jest-tests.yml)
name: Run Jest tests
on:
pull_request:
paths:
- '/*.js'
- '/*.jsx'
- '/*.ts'
- '/*.tsx'
push:
branches:
- main
workflow_dispatch:
jobs:
build:
if: github.repository == 'software-mansion/react-native-gesture-handler'
runs-on: ubuntu-latest
concurrency:
group: jest-${{ github.ref }}
cancel-in-progress: true
steps:
- name: checkout
uses: actions/checkout@v4
- name: Use Node.js 24
uses: actions/setup-node@v6
with:
node-version: 24
cache: yarn
- name: Install node dependencies
run: yarn --immutable
- name: Run jest package tests
run: yarn workspace react-native-gesture-handler test
---
.Github/Workflows/Static Example Apps Checks.Yml (.github/workflows/static-example-apps-checks.yml)
name: Test TypeScript and Lint
on:
pull_request:
paths:
- apps/basic-example/
- apps/common-app/
push:
branches:
- main
workflow_dispatch:
jobs:
check:
if: github.repository == 'software-mansion/react-native-gesture-handler'
runs-on: ubuntu-latest
strategy:
matrix:
working-directory: [apps/basic-example, apps/common-app]
concurrency:
group: typescript-${{ matrix.working-directory }}-${{ github.ref }}
cancel-in-progress: true
steps:
- name: checkout
uses: actions/checkout@v4
- name: Use Node.js 24
uses: actions/setup-node@v6
with:
node-version: 24
cache: yarn
- name: Install node dependencies
run: yarn --immutable
- name: Check types
working-directory: ${{ matrix.working-directory }}
run: yarn ts-check
- name: Lint
working-directory: ${{ matrix.working-directory }}
run: yarn lint-js
---
.Github/Workflows/Static Root Checks.Yml (.github/workflows/static-root-checks.yml)
name: Test TypeScript and Lint
on:
pull_request:
paths:
- packages/react-native-gesture-handler/src/
- packages/react-native-gesture-handler/*
push:
branches:
- main
workflow_dispatch:
jobs:
check:
if: github.repository == 'software-mansion/react-native-gesture-handler'
runs-on: ubuntu-latest
concurrency:
group: static-root-${{ github.ref }}
cancel-in-progress: true
steps:
- name: checkout
uses: actions/checkout@v4
- name: Use Node.js 24
uses: actions/setup-node@v6
with:
node-version: 24
cache: yarn
- name: Install node dependencies
run: yarn --immutable
- name: Check types
run: yarn workspace react-native-gesture-handler ts-check
- name: Lint
run: yarn workspace react-native-gesture-handler lint-js
- name: Check for circular dependencies
run: yarn workspace react-native-gesture-handler circular-dependency-check
---
.Github/Workflows/Yarn Validation.Yml (.github/workflows/yarn-validation.yml)
name: Yarn validation
on:
pull_request:
paths:
- .github/workflows/yarn-validation.yml
- '/package.json'
- '/yarn.lock'
merge_group:
branches:
- main
push:
branches:
- main
paths:
- .github/workflows/yarn-validation.yml
- '/package.json'
- '/yarn.lock'
workflow_call:
workflow_dispatch:
jobs:
check:
if: github.repository == 'software-mansion/react-native-gesture-handler'
runs-on: ubuntu-latest
env:
YARN_ENABLE_HARDENED_MODE: 1
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v6
- name: Install root dependencies
run: yarn install
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v46
with:
files: |
/package.json
/yarn.lock
files_ignore: |
packages/react-native-gesture-handler/ReanimatedDrawerLayout/package.json
packages/react-native-gesture-handler/ReanimatedSwipeable/package.json
packages/react-native-gesture-handler/jest-utils/package.json
- name: Get list of changed directories
id: changed-dirs
run: |
ECHOLIST=()
for CHANGE in ${{ steps.changed-files.outputs.all_changed_files }}
do
DIR=$(dirname "$CHANGE")
ECHOLIST+=("$DIR")
done
ECHOLIST=($(echo "${ECHOLIST[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' '))
echo "dirList=${ECHOLIST[*]}" >> $GITHUB_OUTPUT
- name: Perform yarn install in changed directories
working-directory: ${{ github.workspace }}
run: |
for DIR in ${{ steps.changed-dirs.outputs.dirList }}
do
echo "Yarn install in $DIR"
echo $(pwd)
cd "$DIR" && yarn install --immutable && cd ${{ github.workspace }}
done
---