Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ import {

const UNDERLAY_PROPS = {
underlayColor: 'red',
activeUnderlayOpacity: 0.5,
animationDuration: 200,
activeUnderlayOpacity: 0.2,
activeScale: 0.9,
pressAndHoldAnimationDuration: 200,
tapAnimationDuration: 100,
rippleColor: 'transparent',
} as const;

Expand Down
4 changes: 2 additions & 2 deletions packages/docs-gesture-handler/docs/guides/troubleshooting.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import TabItem from '@theme/TabItem';

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](https://github.com/software-mansion/react-native-gesture-handler/issues). There is a chance someone had this problem in the past and it has been resolved!
1. Search over the [issues on GitHub](https://github.com/software-mansion/react-native-gesture-handler/issues). 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](#reporting-issues).
3. You can try seeking help on [Software Mansion Discord](https://discord.com/invite/VemJ4df8Yr).
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.
Expand All @@ -22,7 +22,7 @@ Thanks for giving this library a try! We are sorry that you might have encounter
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 the best to provide an example app that reproduces the issue you are having. Put it up on [gist](https://gist.github.com/), [snack](https://snack.expo.io) or create a repo on Github – it doesn't matter as long as we can easily pull it in, run and see the issue.
- It is the best to provide an example app that reproduces the issue you are having. Put it up on [gist](https://gist.github.com/), [snack](https://snack.expo.io) 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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import OldAPIInfo from './\_shared/v2-info.md'

import FunctionalComponents from './\_shared/gesture-detector-functional1.md';

`GestureDetector` is the main component of the RNGH2. It is responsible for creating and updating native gesture handlers based on the config of provided gesture. The most significant difference between it and old gesture handlers is that the `GestureDetector` can recognize more than one gesture at the time thanks to gesture composition. Keep in mind that `GestureDetector` is not compatible with the [Animated API](https://reactnative.dev/docs/animated), nor with [Reanimated 1](https://docs.swmansion.com/react-native-reanimated/docs/1.x/).
`GestureDetector` 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 the `GestureDetector` can recognize more than one gesture at the time thanks to gesture composition. Keep in mind that `GestureDetector` is not compatible with the [Animated API](https://reactnative.dev/docs/animated), nor with [Reanimated 1](https://docs.swmansion.com/react-native-reanimated/docs/1.x/).

## Reference

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,15 +136,15 @@ Minimum distance the finger (or multiple finger) need to travel before the gestu

### `minPointers(value: number)`

A number of fingers that is required to be placed before gesture can [activate](/docs/2.x/fundamentals/states-events#active). Should be a higher or equal to 0 integer.
A number of fingers that is required to be placed before the gesture can [activate](/docs/2.x/fundamentals/states-events#active). Should be an integer greater than or equal to 0.

### `maxPointers(value: number)`

When the given number of fingers is placed on the screen and gesture hasn't yet [activated](/docs/2.x/fundamentals/states-events#active) it will fail recognizing the gesture. Should be a higher or equal to 0 integer.
When the given number of fingers is placed on the screen and gesture hasn't yet [activated](/docs/2.x/fundamentals/states-events#active) it will fail recognizing the gesture. Should be an integer greater than or equal to 0.

### `activateAfterLongPress(duration: number)`

Duration in milliseconds of the `LongPress` gesture before `Pan` is allowed to [activate](/docs/2.x/fundamentals/states-events#active). If the finger is moved during that period, the gesture will [fail](/docs/2.x/fundamentals/states-events#failed). Should be a higher or equal to 0 integer. Default value is 0, meaning no `LongPress` is required to [activate](/docs/2.x/fundamentals/states-events#active) the `Pan`.
Duration in milliseconds of the `LongPress` gesture before `Pan` is allowed to [activate](/docs/2.x/fundamentals/states-events#active). If the finger is moved during that period, the gesture will [fail](/docs/2.x/fundamentals/states-events#failed). Should be an integer greater than or equal to 0. Default value is 0, meaning no `LongPress` is required to [activate](/docs/2.x/fundamentals/states-events#active) the `Pan`.

### `activeOffsetX(value: number | number[])`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,11 @@ Minimum distance the finger (or multiple finger) need to travel before the handl

### `minPointers`

A number of fingers that is required to be placed before handler can [activate](/docs/2.x/under-the-hood/state#active). Should be a higher or equal to 0 integer.
A number of fingers that is required to be placed before handler can [activate](/docs/2.x/under-the-hood/state#active). Should be an integer greater than or equal to 0.

### `maxPointers`

When the given number of fingers is placed on the screen and handler hasn't yet [activated](/docs/2.x/under-the-hood/state#active) it will fail recognizing the gesture. Should be a higher or equal to 0 integer.
When the given number of fingers is placed on the screen and handler hasn't yet [activated](/docs/2.x/under-the-hood/state#active) it will fail recognizing the gesture. Should be an integer greater than or equal to 0.

### `activeOffsetX`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,15 +132,15 @@ Minimum distance the finger (or multiple finger) need to travel before the gestu

### `minPointers(value: number)`

A number of fingers that is required to be placed before gesture can [activate](/docs/2.x/fundamentals/states-events#active). Should be a higher or equal to 0 integer.
A number of fingers that is required to be placed before the gesture can [activate](/docs/2.x/fundamentals/states-events#active). Should be an integer greater than or equal to 0.

### `maxPointers(value: number)`

When the given number of fingers is placed on the screen and gesture hasn't yet [activated](/docs/2.x/fundamentals/states-events#active) it will fail recognizing the gesture. Should be a higher or equal to 0 integer.
When the given number of fingers is placed on the screen and gesture hasn't yet [activated](/docs/2.x/fundamentals/states-events#active) it will fail recognizing the gesture. Should be an integer greater than or equal to 0.

### `activateAfterLongPress(duration: number)`

Duration in milliseconds of the `LongPress` gesture before `Pan` is allowed to [activate](/docs/2.x/fundamentals/states-events#active). If the finger is moved during that period, the gesture will [fail](/docs/2.x/fundamentals/states-events#failed). Should be a higher or equal to 0 integer. Default value is 0, meaning no `LongPress` is required to [activate](/docs/2.x/fundamentals/states-events#active) the `Pan`.
Duration in milliseconds of the `LongPress` gesture before `Pan` is allowed to [activate](/docs/2.x/fundamentals/states-events#active). If the finger is moved during that period, the gesture will [fail](/docs/2.x/fundamentals/states-events#failed). Should be an integer greater than or equal to 0. Default value is 0, meaning no `LongPress` is required to [activate](/docs/2.x/fundamentals/states-events#active) the `Pan`.

### `activeOffsetX(value: number | number[])`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import android.graphics.drawable.RippleDrawable
import android.graphics.drawable.ShapeDrawable
import android.graphics.drawable.shapes.RectShape
import android.os.Build
import android.os.SystemClock
import android.util.TypedValue
import android.view.KeyEvent
import android.view.MotionEvent
Expand Down Expand Up @@ -266,9 +267,14 @@ class RNGestureHandlerButtonViewManager :
view.isSoundEffectsEnabled = !touchSoundDisabled
}

@ReactProp(name = "animationDuration")
override fun setAnimationDuration(view: ButtonViewGroup, animationDuration: Int) {
view.animationDuration = animationDuration
@ReactProp(name = "pressAndHoldAnimationDuration")
override fun setPressAndHoldAnimationDuration(view: ButtonViewGroup, pressAndHoldAnimationDuration: Int) {
view.pressAndHoldAnimationDuration = pressAndHoldAnimationDuration
}

@ReactProp(name = "tapAnimationDuration")
override fun setTapAnimationDuration(view: ButtonViewGroup, tapAnimationDuration: Int) {
view.tapAnimationDuration = if (tapAnimationDuration > 0) tapAnimationDuration else 0
}

@ReactProp(name = "defaultOpacity")
Expand Down Expand Up @@ -346,7 +352,9 @@ class RNGestureHandlerButtonViewManager :
var useBorderlessDrawable = false

var exclusive = true
var animationDuration: Int = 100
var tapAnimationDuration: Int = 100
var pressAndHoldAnimationDuration: Int = -1
get() = if (field < 0) tapAnimationDuration else field
var activeOpacity: Float = 1.0f
var defaultOpacity: Float = 1.0f
var activeScale: Float = 1.0f
Expand All @@ -369,6 +377,8 @@ class RNGestureHandlerButtonViewManager :
private var receivedKeyEvent = false
private var currentAnimator: AnimatorSet? = null
private var underlayDrawable: PaintDrawable? = null
private var pressInTimestamp = 0L
private var pendingPressOut: Runnable? = null

// When non-null the ripple is drawn in dispatchDraw (above background, below children).
// When null the ripple lives on the foreground drawable instead.
Expand Down Expand Up @@ -487,7 +497,7 @@ class RNGestureHandlerButtonViewManager :
underlayDrawable?.alpha = (defaultUnderlayOpacity * 255).toInt()
}

private fun animateTo(opacity: Float, scale: Float, underlayOpacity: Float) {
private fun animateTo(opacity: Float, scale: Float, underlayOpacity: Float, durationMs: Long) {
val hasOpacity = activeOpacity != 1.0f || defaultOpacity != 1.0f
val hasScale = activeScale != 1.0f || defaultScale != 1.0f
val hasUnderlay = activeUnderlayOpacity != defaultUnderlayOpacity && underlayDrawable != null
Expand All @@ -509,18 +519,43 @@ class RNGestureHandlerButtonViewManager :
}
currentAnimator = AnimatorSet().apply {
playTogether(animators)
duration = animationDuration.toLong()
duration = durationMs
interpolator = LinearOutSlowInInterpolator()
start()
}
}

private fun animatePressIn() {
animateTo(activeOpacity, activeScale, activeUnderlayOpacity)
pendingPressOut?.let {
handler.removeCallbacks(it)
pendingPressOut = null
}
pressInTimestamp = SystemClock.uptimeMillis()
animateTo(activeOpacity, activeScale, activeUnderlayOpacity, pressAndHoldAnimationDuration.toLong())
}

private fun animatePressOut() {
animateTo(defaultOpacity, defaultScale, defaultUnderlayOpacity)
pendingPressOut?.let { handler.removeCallbacks(it) }
val pressAndHoldMs = pressAndHoldAnimationDuration.toLong()
val tapMs = tapAnimationDuration.toLong()
val elapsed = SystemClock.uptimeMillis() - pressInTimestamp

if (elapsed >= pressAndHoldMs) {
animateTo(defaultOpacity, defaultScale, defaultUnderlayOpacity, pressAndHoldMs)
// elapsed * 2 to ensure there is at least half of the tapAnimationDuration left for the animation to play
} else if (elapsed * 2 >= tapMs) {
animateTo(defaultOpacity, defaultScale, defaultUnderlayOpacity, elapsed)
} else {
val remaining = tapMs - elapsed
animateTo(activeOpacity, activeScale, activeUnderlayOpacity, remaining)

val runnable = Runnable {
pendingPressOut = null
animateTo(defaultOpacity, defaultScale, defaultUnderlayOpacity, tapMs)
}
pendingPressOut = runnable
handler.postDelayed(runnable, remaining)
}
}

private fun createUnderlayDrawable(): PaintDrawable {
Expand Down Expand Up @@ -630,6 +665,14 @@ class RNGestureHandlerButtonViewManager :
return drawable
}

override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
pendingPressOut?.let { handler.removeCallbacks(it) }
pendingPressOut = null
currentAnimator?.cancel()
currentAnimator = null
}

override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
needBackgroundUpdate = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
@property (nonatomic) BOOL userEnabled;
@property (nonatomic, assign) RNGestureHandlerPointerEvents pointerEvents;

@property (nonatomic, assign) NSInteger animationDuration;
@property (nonatomic, assign) NSInteger pressAndHoldAnimationDuration;
@property (nonatomic, assign) NSInteger tapAnimationDuration;
@property (nonatomic, assign) CGFloat activeOpacity;
@property (nonatomic, assign) CGFloat defaultOpacity;
@property (nonatomic, assign) CGFloat activeScale;
Expand Down
Loading
Loading