11package com.swmansion.gesturehandler.react
22
3+ import android.animation.Animator
4+ import android.animation.AnimatorSet
5+ import android.animation.ObjectAnimator
36import android.annotation.SuppressLint
47import android.annotation.TargetApi
58import android.content.Context
@@ -19,10 +22,10 @@ import android.util.TypedValue
1922import android.view.KeyEvent
2023import android.view.MotionEvent
2124import android.view.View
22- import android.view.View.OnClickListener
2325import android.view.ViewGroup
2426import android.view.accessibility.AccessibilityNodeInfo
2527import androidx.core.view.children
28+ import androidx.interpolator.view.animation.LinearOutSlowInInterpolator
2629import com.facebook.react.R
2730import com.facebook.react.module.annotations.ReactModule
2831import 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 ) {
0 commit comments