Skip to content

Commit 44c2430

Browse files
committed
Merge branch 'main' into @jpiasecki/fix-borderless-ripple-animation
2 parents 3ad3ca2 + 5587435 commit 44c2430

43 files changed

Lines changed: 1758 additions & 1161 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.yarn/releases/yarn-4.13.0.cjs

Lines changed: 940 additions & 0 deletions
Large diffs are not rendered by default.

.yarn/releases/yarn-4.7.0.cjs

Lines changed: 0 additions & 935 deletions
This file was deleted.

.yarnrc.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1-
yarnPath: .yarn/releases/yarn-4.7.0.cjs
21
nodeLinker: node-modules
2+
3+
yarnPath: .yarn/releases/yarn-4.13.0.cjs

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,5 @@
3333
"prettier": "3.3.3",
3434
"typescript": "~5.8.3"
3535
},
36-
"packageManager": "yarn@4.7.0"
36+
"packageManager": "yarn@4.13.0"
3737
}

packages/docs-gesture-handler/docs/fundamentals/state-manager.mdx

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ const styles = StyleSheet.create({
7070

7171
If you want to control gesture lifecycle outside of it, you can use `handlerTag` from created gesture object.
7272

73+
:::note
74+
The gestures can only be activated after it has begun, that is, after it has received received touch events.
75+
:::
76+
7377
<CollapsibleCode
7478
label="Show full example"
7579
expandedLabel="Hide full example"
@@ -179,14 +183,6 @@ const styles = {
179183

180184
`GestureStateManager` provides methods to manipulate gesture's state imperatively.
181185

182-
### begin
183-
184-
```tsx
185-
begin: (handlerTag: number) => void;
186-
```
187-
188-
Triggers [`onBegin`](/docs/fundamentals/callbacks-events#onbegin) callback on gesture with specified `handlerTag`.
189-
190186
### activate
191187

192188
```tsx

packages/docs-gesture-handler/src/components/TopPromoRotator/index.tsx

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -34,21 +34,6 @@ const PROMOS: readonly Promo[] = [
3434
</>
3535
),
3636
},
37-
{
38-
key: 'paradise',
39-
href: 'https://paradise.swmansion.com?origin=swmansion_bar',
40-
bg: '#FFF4C0',
41-
buttonLabel: 'Learn more',
42-
label: (
43-
<>
44-
<strong>React Native Paradise</strong>
45-
<span className={styles.hiddenOnMobile}>
46-
{' '}
47-
- a week of advanced RN workshops in Croatia!
48-
</span>
49-
</>
50-
),
51-
},
5237
];
5338

5439
// bump when adding promos so users who dismissed banner see the new one
@@ -72,13 +57,16 @@ export default function TopPromoRotator({ onClose }: Props) {
7257
'script[src*="www.googletagmanager.com/gtm.js?id=GTM-WV2G3SQL"]'
7358
);
7459

75-
if (existingScript) return;
60+
if (existingScript) {
61+
return;
62+
}
7663

7764
(function (w: Window, d: Document, s: string, l: string, i: string) {
7865
w.dataLayer = w.dataLayer || [];
66+
// prettier-ignore
7967
w.dataLayer.push({
8068
'gtm.start': new Date().getTime(),
81-
event: 'gtm.js',
69+
'event': 'gtm.js',
8270
});
8371
const f = d.getElementsByTagName(s)[0] as HTMLScriptElement;
8472
const j = d.createElement(s) as HTMLScriptElement;
@@ -91,7 +79,7 @@ export default function TopPromoRotator({ onClose }: Props) {
9179

9280
useEffect(() => {
9381
const id = window.setInterval(() => {
94-
setIndex(i => (i + 1) % promos.length);
82+
setIndex((i) => (i + 1) % promos.length);
9583
}, 5_000);
9684

9785
return () => window.clearInterval(id);
@@ -115,7 +103,7 @@ export default function TopPromoRotator({ onClose }: Props) {
115103
transform: translateY,
116104
transition: 'transform 700ms cubic-bezier(0.22, 1, 0.36, 1)',
117105
}}>
118-
{promos.map(p => (
106+
{promos.map((p) => (
119107
<a
120108
key={p.key}
121109
href={p.href}

packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.kt

Lines changed: 142 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package com.swmansion.gesturehandler.react
22

3+
import android.animation.Animator
4+
import android.animation.AnimatorSet
5+
import android.animation.ObjectAnimator
36
import android.annotation.SuppressLint
47
import android.annotation.TargetApi
58
import android.content.Context
@@ -19,10 +22,10 @@ import android.util.TypedValue
1922
import android.view.KeyEvent
2023
import android.view.MotionEvent
2124
import android.view.View
22-
import android.view.View.OnClickListener
2325
import android.view.ViewGroup
2426
import android.view.accessibility.AccessibilityNodeInfo
2527
import androidx.core.view.children
28+
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator
2629
import com.facebook.react.R
2730
import com.facebook.react.module.annotations.ReactModule
2831
import com.facebook.react.uimanager.PixelUtil
@@ -133,6 +136,46 @@ class RNGestureHandlerButtonViewManager :
133136
view.isSoundEffectsEnabled = !touchSoundDisabled
134137
}
135138

139+
@ReactProp(name = "animationDuration")
140+
override fun setAnimationDuration(view: ButtonViewGroup, animationDuration: Int) {
141+
view.animationDuration = animationDuration
142+
}
143+
144+
@ReactProp(name = "defaultOpacity")
145+
override fun setDefaultOpacity(view: ButtonViewGroup, defaultOpacity: Float) {
146+
view.defaultOpacity = defaultOpacity
147+
}
148+
149+
@ReactProp(name = "activeOpacity")
150+
override fun setActiveOpacity(view: ButtonViewGroup, targetOpacity: Float) {
151+
view.activeOpacity = targetOpacity
152+
}
153+
154+
@ReactProp(name = "defaultScale")
155+
override fun setDefaultScale(view: ButtonViewGroup, defaultScale: Float) {
156+
view.defaultScale = defaultScale
157+
}
158+
159+
@ReactProp(name = "activeScale")
160+
override fun setActiveScale(view: ButtonViewGroup, activeScale: Float) {
161+
view.activeScale = activeScale
162+
}
163+
164+
@ReactProp(name = "underlayColor")
165+
override fun setUnderlayColor(view: ButtonViewGroup, underlayColor: Int?) {
166+
view.underlayColor = underlayColor
167+
}
168+
169+
@ReactProp(name = "defaultUnderlayOpacity")
170+
override fun setDefaultUnderlayOpacity(view: ButtonViewGroup, defaultUnderlayOpacity: Float) {
171+
view.defaultUnderlayOpacity = defaultUnderlayOpacity
172+
}
173+
174+
@ReactProp(name = "activeUnderlayOpacity")
175+
override fun setActiveUnderlayOpacity(view: ButtonViewGroup, activeUnderlayOpacity: Float) {
176+
view.activeUnderlayOpacity = activeUnderlayOpacity
177+
}
178+
136179
@ReactProp(name = ViewProps.POINTER_EVENTS)
137180
override fun setPointerEvents(view: ButtonViewGroup, pointerEvents: String?) {
138181
view.pointerEvents = when (pointerEvents) {
@@ -212,6 +255,20 @@ class RNGestureHandlerButtonViewManager :
212255
borderBottomRightRadius != 0f
213256

214257
var exclusive = true
258+
var animationDuration: Int = 100
259+
var activeOpacity: Float = 1.0f
260+
var defaultOpacity: Float = 1.0f
261+
var activeScale: Float = 1.0f
262+
var defaultScale: Float = 1.0f
263+
var underlayColor: Int? = null
264+
set(color) = withBackgroundUpdate {
265+
field = color
266+
}
267+
var activeUnderlayOpacity: Float = 0f
268+
var defaultUnderlayOpacity: Float = 0f
269+
set(value) = withBackgroundUpdate {
270+
field = value
271+
}
215272

216273
override var pointerEvents: PointerEvents = PointerEvents.AUTO
217274

@@ -220,6 +277,8 @@ class RNGestureHandlerButtonViewManager :
220277
private var lastEventTime = -1L
221278
private var lastAction = -1
222279
private var receivedKeyEvent = false
280+
private var currentAnimator: AnimatorSet? = null
281+
private var underlayDrawable: PaintDrawable? = null
223282

224283
var isTouched = false
225284

@@ -331,7 +390,73 @@ class RNGestureHandlerButtonViewManager :
331390
return false
332391
}
333392

334-
private fun updateBackgroundColor(backgroundColor: Int, borderDrawable: Drawable, selectable: Drawable?) {
393+
private fun applyStartAnimationState() {
394+
(parent as? ViewGroup)?.let {
395+
if (activeOpacity != 1.0f || defaultOpacity != 1.0f) {
396+
it.alpha = defaultOpacity
397+
}
398+
if (activeScale != 1.0f || defaultScale != 1.0f) {
399+
it.scaleX = defaultScale
400+
it.scaleY = defaultScale
401+
}
402+
}
403+
underlayDrawable?.alpha = (defaultUnderlayOpacity * 255).toInt()
404+
}
405+
406+
private fun animateTo(opacity: Float, scale: Float, underlayOpacity: Float) {
407+
val hasOpacity = activeOpacity != 1.0f || defaultOpacity != 1.0f
408+
val hasScale = activeScale != 1.0f || defaultScale != 1.0f
409+
val hasUnderlay = activeUnderlayOpacity != defaultUnderlayOpacity && underlayDrawable != null
410+
if (!hasOpacity && !hasScale && !hasUnderlay) {
411+
return
412+
}
413+
414+
currentAnimator?.cancel()
415+
val animators = ArrayList<Animator>()
416+
if (hasOpacity || hasScale) {
417+
val parent = this.parent as? ViewGroup ?: return
418+
if (hasOpacity) {
419+
animators.add(ObjectAnimator.ofFloat(parent, "alpha", opacity))
420+
}
421+
if (hasScale) {
422+
animators.add(ObjectAnimator.ofFloat(parent, "scaleX", scale))
423+
animators.add(ObjectAnimator.ofFloat(parent, "scaleY", scale))
424+
}
425+
}
426+
if (hasUnderlay) {
427+
animators.add(ObjectAnimator.ofInt(underlayDrawable!!, "alpha", (underlayOpacity * 255).toInt()))
428+
}
429+
currentAnimator = AnimatorSet().apply {
430+
playTogether(animators)
431+
duration = animationDuration.toLong()
432+
interpolator = LinearOutSlowInInterpolator()
433+
start()
434+
}
435+
}
436+
437+
private fun animatePressIn() {
438+
animateTo(activeOpacity, activeScale, activeUnderlayOpacity)
439+
}
440+
441+
private fun animatePressOut() {
442+
animateTo(defaultOpacity, defaultScale, defaultUnderlayOpacity)
443+
}
444+
445+
private fun createUnderlayDrawable(): PaintDrawable {
446+
val drawable = PaintDrawable(underlayColor ?: Color.BLACK)
447+
if (hasBorderRadii) {
448+
drawable.setCornerRadii(buildBorderRadii())
449+
}
450+
drawable.alpha = (defaultUnderlayOpacity * 255).toInt()
451+
return drawable
452+
}
453+
454+
private fun updateBackgroundColor(
455+
backgroundColor: Int,
456+
underlay: Drawable,
457+
borderDrawable: Drawable,
458+
selectable: Drawable?,
459+
) {
335460
val colorDrawable = PaintDrawable(backgroundColor)
336461

337462
if (hasBorderRadii) {
@@ -340,9 +465,9 @@ class RNGestureHandlerButtonViewManager :
340465

341466
val layerDrawable = LayerDrawable(
342467
if (selectable != null) {
343-
arrayOf(colorDrawable, selectable, borderDrawable)
468+
arrayOf(colorDrawable, underlay, selectable, borderDrawable)
344469
} else {
345-
arrayOf(colorDrawable, borderDrawable)
470+
arrayOf(colorDrawable, underlay, borderDrawable)
346471
},
347472
)
348473
background = layerDrawable
@@ -365,6 +490,8 @@ class RNGestureHandlerButtonViewManager :
365490

366491
val selectable = createSelectableDrawable()
367492
val borderDrawable = createBorderDrawable()
493+
val underlay = createUnderlayDrawable()
494+
underlayDrawable = underlay
368495

369496
if (hasBorderRadii && selectable is RippleDrawable) {
370497
val mask = PaintDrawable(Color.WHITE)
@@ -375,13 +502,15 @@ class RNGestureHandlerButtonViewManager :
375502
if (useDrawableOnForeground && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
376503
foreground = selectable
377504
if (buttonBackgroundColor != Color.TRANSPARENT) {
378-
updateBackgroundColor(buttonBackgroundColor, borderDrawable, null)
505+
updateBackgroundColor(buttonBackgroundColor, underlay, borderDrawable, null)
379506
}
380507
} else if (buttonBackgroundColor == Color.TRANSPARENT && rippleColor == null) {
381-
background = LayerDrawable(arrayOf(selectable, borderDrawable))
508+
background = LayerDrawable(arrayOf(underlay, selectable, borderDrawable))
382509
} else {
383-
updateBackgroundColor(buttonBackgroundColor, borderDrawable, selectable)
510+
updateBackgroundColor(buttonBackgroundColor, underlay, borderDrawable, selectable)
384511
}
512+
513+
applyStartAnimationState()
385514
}
386515

387516
private fun createBorderDrawable(): Drawable {
@@ -540,6 +669,12 @@ class RNGestureHandlerButtonViewManager :
540669
// is null or non-exclusive, assuming it doesn't have pressed children
541670
isTouched = pressed
542671
super.setPressed(pressed)
672+
673+
if (pressed) {
674+
animatePressIn()
675+
} else {
676+
animatePressOut()
677+
}
543678
}
544679

545680
if (!pressed && touchResponder === this) {

packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerModule.kt

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -145,17 +145,11 @@ class RNGestureHandlerModule(reactContext: ReactApplicationContext?) :
145145
UiThreadUtil.assertOnUiThread()
146146

147147
registry.getHandler(handlerTag)?.let { handler ->
148-
if (handler.state == GestureHandler.STATE_UNDETERMINED) {
149-
handler.forceReinitializeDuringOnHandle = true
150-
151-
// When going from UNDETERMINED to ACTIVE, force going through BEGAN to preserve
152-
// the correct state flow
153-
if (newState == GestureHandler.STATE_ACTIVE) {
154-
handler.begin()
148+
if (newState == GestureHandler.STATE_ACTIVE) {
149+
if (handler.state != GestureHandler.STATE_BEGAN) {
150+
// We don't allow activation of gestures which haven't received any touches
151+
return
155152
}
156-
}
157-
158-
if (newState == GestureHandler.STATE_ACTIVE || newState == GestureHandler.STATE_BEGAN) {
159153
handler.recordHandlerIfNotPresent()
160154
}
161155

packages/react-native-gesture-handler/apple/Handlers/RNHoverHandler.m

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -220,21 +220,29 @@ - (void)mouseEntered:(NSEvent *)event
220220
{
221221
[self sendEventsInState:RNGestureHandlerStateBegan
222222
forViewWithTag:_view.reactTag
223-
withExtraData:[RNGestureHandlerEventExtraData forPointerInside:YES withPointerType:_pointerType]];
223+
withExtraData:[RNGestureHandlerEventExtraData forPointerInside:YES
224+
withNumberOfTouches:1
225+
withPointerType:_pointerType]];
224226
[self sendEventsInState:RNGestureHandlerStateActive
225227
forViewWithTag:_view.reactTag
226-
withExtraData:[RNGestureHandlerEventExtraData forPointerInside:YES withPointerType:_pointerType]];
228+
withExtraData:[RNGestureHandlerEventExtraData forPointerInside:YES
229+
withNumberOfTouches:1
230+
withPointerType:_pointerType]];
227231
}
228232

229233
- (void)mouseExited:(NSEvent *)theEvent
230234
{
231235
[self sendEventsInState:RNGestureHandlerStateEnd
232236
forViewWithTag:_view.reactTag
233-
withExtraData:[RNGestureHandlerEventExtraData forPointerInside:YES withPointerType:_pointerType]];
237+
withExtraData:[RNGestureHandlerEventExtraData forPointerInside:NO
238+
withNumberOfTouches:1
239+
withPointerType:_pointerType]];
234240

235241
[self sendEventsInState:RNGestureHandlerStateUndetermined
236242
forViewWithTag:_view.reactTag
237-
withExtraData:[RNGestureHandlerEventExtraData forPointerInside:YES withPointerType:_pointerType]];
243+
withExtraData:[RNGestureHandlerEventExtraData forPointerInside:NO
244+
withNumberOfTouches:1
245+
withPointerType:_pointerType]];
238246
}
239247

240248
@end

0 commit comments

Comments
 (0)