React-native: TouchableOpacity button is not working with absolute position + transform animation

Created on 21 Apr 2020  路  6Comments  路  Source: facebook/react-native

Description

I am trying to implement a Floating action button using React-Native. I was able to implement this in iOS, But when it comes to android it's not working. The issue is I cannot press the button. I know that position:'absolute' is not working very well in Android.

React Native version:

Run react-native info in your terminal and copy the results here.
System:
OS: macOS Mojave 10.14.6
CPU: (4) x64 Intel(R) Core(TM) i5-4278U CPU @ 2.60GHz
Memory: 26.49 MB / 8.00 GB
Shell: 3.2.57 - /bin/bash
Binaries:
Node: 10.16.2 - /usr/local/bin/node
Yarn: 1.10.1 - /usr/local/bin/yarn
npm: 6.9.0 - /usr/local/bin/npm
Watchman: 4.9.0 - /usr/local/bin/watchman
Managers:
CocoaPods: 1.7.5 - /usr/local/bin/pod
SDKs:
iOS SDK:
Platforms: iOS 13.2, DriverKit 19.0, macOS 10.15, tvOS 13.2, watchOS 6.1
Android SDK: Not Found
IDEs:
Android Studio: Not Found
Xcode: 11.2.1/11B500 - /usr/bin/xcodebuild

npmPackages:
@react-native-community/cli: Not Found
react: 16.11.0 => 16.11.0
react-native: 0.62.1 => 0.62.1

Expected Results

I want to show an Alert message in Android when user clicks on the button

Snack, code example, screenshot, or link to a repository:

snack Link : https://snack.expo.io/z88E3rOGm

import React, { useState } from 'react';
import {
    View,
    Text,
    TouchableWithoutFeedback,
    Animated,
    Image,
    StyleSheet,
    Alert,
    TouchableOpacity,
} from 'react-native';

const DrinksFAB = () => {
    const [animation] = useState(new Animated.Value(0));

    const labelPositionInterploate = animation.interpolate({
        inputRange: [0, 1],
        outputRange: [-30, -70],
    });

    const opacityInterpolate = animation.interpolate({
        inputRange: [0, 0.8, 1],
        outputRange: [0, 0, 8],
    });

    const labelStyle = {
        opacity: opacityInterpolate,
        transform: [
            {
                translateX: labelPositionInterploate,
            },
        ],
    };

    let _open = 0;
    const toggleOpen = () => {
        const toValue = _open ? 0 : 1;

        Animated.timing(animation, {
            useNativeDriver: true,
            toValue: toValue,
            duration: 200,
        }).start();
        _open = !_open;
    };
    let range = 0;
    return (
        <>
            {[1,2].map((i) => {
                range = -70 + range;
                return (
                    <TouchableOpacity
                        style={[styles.button]}
                        onPress={() => {
                            Alert.alert('hey');
                            //Alert.alert('hey');
                        }}
                    >
                        <>
                            <Animated.View
                                style={[
                                    styles.button,
                                    styles.other,
                                    {
                                        transform: [
                                            {
                                                scale: animation,
                                            },
                                            {
                                                translateY: animation.interpolate(
                                                    {
                                                        inputRange: [0, 1],
                                                        outputRange: [0, range],
                                                    },
                                                ),
                                            },
                                        ],
                                    },
                                ]}
                            >
                                <Animated.Text
                                    style={[styles.label, labelStyle]}
                                >
                                    Coffee
                                </Animated.Text>
                            </Animated.View>
                        </>
                    </TouchableOpacity>
                );
            })}

            <TouchableOpacity
                onPress={toggleOpen}
                style={[
                    styles.button,
                    styles.pay,
                ]}
            >

                <Text>Pay</Text>
            </TouchableOpacity>
        </>
    );
};

export default DrinksFAB;

const styles = StyleSheet.create({
    button: {
        width: 60,
        height: 60,
        alignItems: 'center',
        justifyContent: 'center',
        shadowColor: '#333',
        shadowOpacity: 0.1,
        shadowOffset: { x: 2, y: 0 },
        shadowRadius: 2,
        borderRadius: 30,
        position: 'absolute',
        bottom: 20,
        right: 20,
        backgroundColor: '#ffff',
    },
    pay: {
        backgroundColor: '#00B15E',
    },
    other: {
        bottom: 5,
        right: 0,
        backgroundColor: '#e2e2e2',
    },
    label: {
        flex: 1,
        flexWrap: 'nowrap',
        color: '#000',
        position: 'absolute',
        fontSize: 14,
    },
});

Animated Button TouchableOpacity Platform Disparity

Most helpful comment

I'm having exact same problem with react-native version 0.61.5
I had to use TouchableNativeFeedback as a workaround for android.

All 6 comments

I'm having exact same problem with react-native version 0.61.5
I had to use TouchableNativeFeedback as a workaround for android.

@Aashu-Dubey can you share the code sample

@amilaDulanjana I wouldn't be able to share the code sample
but a better alternative I found that would work on both platform is to use "TouchableHighlight" with underlayColor="transparent" and activeOpacity={0.2}
then It would react same as TouchableOpacity
but I had to put its child View's complete style (or maybe only position and transform related style is needed) to TouchableHighlight for it to work.

Having the same issue - at first I thought it was just transforms but my opacity animation kills any touches to the button. Works fine on iOS.

My FadeView component:

const FadeInView = props => {
  const fadeAnim = useRef(new Animated.Value(0)).current;
  useEffect(() => {
    Animated.timing(fadeAnim, {
      toValue: 1,
      duration: props.duration || 1000,
      useNativeDriver: true,
      delay: props.delay || 0,
    }).start();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return (
    <Animated.View style={{...props.style, opacity: fadeAnim}}>
      {props.children}
    </Animated.View>
  );
};

So what I have now is a few Buttons in a View (absolute positioned view) that's wrapped by the FadeView. Only happens with absolute positioned things.

I was having this issue on Android using TouchableHighlight, TouchableWithoutFeedback, TouchableOpacity and TouchableNativeFeedback.

My solution is, set hitSlop and zIndex:

<TouchableWithoutFeedback {...} hitSlop={{ left: 20, top: 20, right: 20, bottom: 20 }} style={{ zIndex: 99999 }}>
</TouchableWithoutFeedback>

Experiencing same issue on iOS with 63.3, no matter if using onEnd from View or the Touchable components and even the new Pressable.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

oney picture oney  路  3Comments

axelg12 picture axelg12  路  3Comments

janmonschke picture janmonschke  路  3Comments

grabbou picture grabbou  路  3Comments

josev55 picture josev55  路  3Comments