98oktay / react-native-axis-pad

A virtual joystick for touch capable mobile devices.
21 stars 6 forks source link

Incompatible with Expo #6

Open ChrisCavs opened 2 years ago

ChrisCavs commented 2 years ago

Simply installing and importing package will break Expo metro bundler. Considering the popularity of Expo you might want to investigate why there's a conflict

itzAamir commented 2 years ago

I also tried using it with Expo but everytime I open my app its just stuck on splash screen.

gothaw commented 1 year ago

I have the same issue.

TheThreshCarry commented 1 year ago

The issue is that i clashes with the expo renderer ( react native screens ) upon setup, the problem is in the StyleSheet.create() in the AxisPadStyle.js, what i did is i rewrote the entire code in to this ( also removed the .getNodes() which don't work on react native ), i also made the handler size relative to the size variable, so now to set the size of the axis pad, just add the size prop ( which was not documented )

import React, { Component } from 'react';
import { Animated, Easing } from 'react-native';

export default class AxisPad extends Component {
    constructor(props) {
        super(props);
        this.anim_cx = new Animated.Value(0);
        this.anim_cy = new Animated.Value(0);
        this.anim_px = new Animated.Value(0);
        this.anim_py = new Animated.Value(0);

        this.state = {
            identifier: 0,
            cx: 0,
            cy: 0,
            sx: 0,
            sy: 0,
            px: 0,
            py: 0,
            dx: 0,
            dy: 0,
            width: props.size ? props.size : 300,
            handler: props.size ? props.size/3 : 100,
            step: props.step ? props.step : 0
        };

        this.onTouchStart = this.onTouchStart.bind(this);
        this.onTouchEnd = this.onTouchEnd.bind(this);
        this.onTouchCancel = this.onTouchCancel.bind(this);
        this.onTouchMove = this.onTouchMove.bind(this);
        this.limitter = this.limitter.bind(this);
        this.sendValue = this.sendValue.bind(this);
        this.centerPosition = this.centerPosition.bind(this);
        this.animate = this.animate.bind(this);
        this.centerAnimate = this.centerAnimate.bind(this);
        this.setPosition = this.setPosition.bind(this);
    }

    centerAnimate() {
        Animated.timing(
            this.anim_cx,
            {
                toValue: this.state.cx,
                duration: 300,
                easing: Easing.elastic(),
                useNativeDriver: true
            }
        ).start();
        Animated.timing(
            this.anim_cy,
            {
                toValue: this.state.cy,
                duration: 300,
                easing: Easing.elastic(),
                useNativeDriver: true
            }
        ).start();
    }

    animate() {
        Animated.timing(
            this.anim_px,
            {
                toValue: this.state.px,
                duration: 50,
                easing: Easing.out(Easing.exp),
                useNativeDriver: true
            }
        ).start();
        Animated.timing(
            this.anim_py,
            {
                toValue: this.state.py,
                duration: 50,
                easing: Easing.out(Easing.exp),
                useNativeDriver: true
            }
        ).start()
    }

    limitter(input) {
        const { width, step } = this.state;
        const minimised = input / width * 2
        const stepped = (x) => step ? Math.floor(x / step) * step : x;
        const limited = (x) => Math.min(1, Math.max(-1, x));
        return stepped(limited(minimised)) * width / 2;
    }

    sendValue(x, y) {
        const { width } = this.state;
        this.props.onValue && this.props.onValue({ x: x / width * 2, y: y / width * 2 });
    }

    centerPosition(pageX, pageY) {
        this.handlerElement.measure((fx, fy, width, height, px, py) => {
            const cx = pageX - px - width / 2
            const cy = pageY - py - height / 2
            this.setState({
                cx,
                cy
            }, this.centerAnimate);
        })
    }

    setPosition(pageX, pageY, after) {
        let { dx, dy } = this.state;
        this.wrapperElement.measure((fx, fy, width, height, px, py) => {
            const cx = px + width / 2;
            const cy = py + height / 2;
            this.setState({
                sx: cx,
                sy: cy,
                px: this.props.lockX ? 0 : pageX - cx,
                py: this.props.lockY ? 0 : pageY - cy
            }, after);
        })
    }

    getTouchPoint(touches, identifier) {
        let touchItem = null;

        touches.map((item) => {
            if (item.identifier === identifier) {
                touchItem = item;
            }
        });

        return touchItem;
    }

    onTouchStart(evt) {

        const identifier = evt.nativeEvent.identifier;
        const touchItem = this.getTouchPoint(evt.nativeEvent.touches, identifier);
        console.log(evt.nativeEvent.touches, identifier, touchItem);

        if (typeof identifier === "number" && touchItem) {
            const { pageX, pageY } = touchItem;

            if (this.props.autoCenter) {
                this.centerPosition(pageX, pageY);
                this.sendValue(this.state.px, this.state.py);
                this.setState({
                    identifier,
                    sx: pageX,
                    sy: pageY
                });
            } else {
                this.setPosition(pageX, pageY, () => {
                    this.sendValue(this.state.px, this.state.py);
                    this.animate();
                })
            }
        }
    }

    onTouchMove(evt) {
        const touchItem = this.getTouchPoint(evt.nativeEvent.touches, this.state.identifier);
        if (touchItem) {
            const { pageX, pageY } = touchItem;

            let px = this.props.lockX ? 0 : pageX - this.state.sx + this.state.dx;
            let py = this.props.lockY ? 0 : pageY - this.state.sy + this.state.dy;

            px = this.props.lockX ? 0 : this.limitter(px);
            py = this.props.lockY ? 0 : this.limitter(py);
            this.sendValue(px, py);
            this.setState({ px, py }, this.animate);
        }
    }

    onTouchEnd() {
        let { px, py } = this.state;
        let dx, dy;
        if (this.props.resetOnRelease) {
            px = 0;
            py = 0;
        }
        if (this.props.autoCenter) {
            dx = px;
            dy = py;
        } else {
            dx = 0;
            dy = 0;
        }
        this.sendValue(px, py);
        this.setState({
            cx: 0,
            cy: 0,
            px,
            py,
            dx,
            dy
        }, () => {
            this.centerAnimate();
            this.animate();
        });
    }

    onTouchCancel() {
        this.setState({
            cx: 0,
            cy: 0
        }, this.centerAnimate);
    }

    render() {
        return (
            <Animated.View onTouchStart={this.onTouchStart}
                onTouchEnd={this.onTouchEnd}
                onTouchCancel={this.onTouchCancel}
                onTouchMove={this.onTouchMove}
                style={[AxisPadStyle.wrapper, this.props.wrapperStyle ? this.props.wrapperStyle : {}, {
                    width: this.state.width,
                    height: this.state.width,
                    transform: [{
                        translateX: this.anim_cx,
                    }, {
                        translateY: this.anim_cy
                    }]
                }]}
                ref={view => { this.wrapperElement = view; }}>
                <Animated.View
                    ref={view => { this.handlerElement = view; }}
                    style={[AxisPadStyle.handler, this.props.handlerStyle ? this.props.handlerStyle : {}, {
                        width: this.state.handler,
                        height: this.state.handler,
                        transform: [{
                            translateX: this.state.px,
                        }, {
                            translateY: this.state.py
                        }]
                    }]}>
                    {this.props.children}
                </Animated.View>
            </Animated.View>
        )
    }
}

const AxisPadStyle =  {
    wrapper: {
        width: 300,
        height: 300,
        borderRadius: 300,
        justifyContent: 'center',
        alignItems: 'center',
        backgroundColor: '#00000033',
    },
    handler: {
        width: "60%",
        height: "60%",
        justifyContent: 'center',
        alignItems: 'center',
        borderRadius: 300,
        backgroundColor: '#00000066'
    }
};