This commit is contained in:
Yamozha
2021-04-02 02:24:13 +03:00
parent c23950b545
commit 7256d79e2c
31493 changed files with 3036630 additions and 0 deletions

View File

@ -0,0 +1,3 @@
import RNGestureHandlerModule from './RNGestureHandlerModule';
export default RNGestureHandlerModule.Direction;

View File

@ -0,0 +1,542 @@
// @flow
// This component is based on RN's DrawerLayoutAndroid API
//
// It perhaps deserves to be put in a separate repo, but since it relies
// on react-native-gesture-handler library which isn't very popular at the
// moment I decided to keep it here for the time being. It will allow us
// to move faster and fix issues that may arise in gesture handler library
// that could be found when using the drawer component
import React, { Component } from 'react';
import invariant from 'invariant';
import {
Animated,
StyleSheet,
View,
Keyboard,
StatusBar,
I18nManager,
} from 'react-native';
import { PanGestureHandler, TapGestureHandler, State } from './GestureHandler';
const DRAG_TOSS = 0.05;
const IDLE = 'Idle';
const DRAGGING = 'Dragging';
const SETTLING = 'Settling';
export type PropType = {
children: any,
drawerBackgroundColor?: string,
drawerPosition: 'left' | 'right',
drawerLockMode?: 'unlocked' | 'locked-closed' | 'locked-open',
drawerWidth: number,
keyboardDismissMode?: 'none' | 'on-drag',
onDrawerClose?: Function,
onDrawerOpen?: Function,
onDrawerStateChanged?: Function,
renderNavigationView: (progressAnimatedValue: any) => any,
useNativeAnimations: boolean,
// brand new properties
drawerType: 'front' | 'back' | 'slide',
edgeWidth: number,
minSwipeDistance: number,
hideStatusBar?: boolean,
statusBarAnimation?: 'slide' | 'none' | 'fade',
overlayColor: string,
drawerContainerStyle?: any,
contentContainerStyle?: any,
onGestureRef?: Function,
// Properties not yet supported
// onDrawerSlide?: Function
};
export type StateType = {
dragX: any,
touchX: any,
drawerTranslation: any,
containerWidth: number,
};
export type EventType = {
stopPropagation: Function,
};
export type DrawerMovementOptionType = {
velocity?: number,
};
export default class DrawerLayout extends Component<PropType, StateType> {
static defaultProps = {
drawerWidth: 200,
drawerPosition: 'left',
useNativeAnimations: true,
drawerType: 'front',
edgeWidth: 20,
minSwipeDistance: 3,
overlayColor: 'rgba(0, 0, 0, 0.7)',
drawerLockMode: 'unlocked',
};
static positions = {
Left: 'left',
Right: 'right',
};
_openValue: ?Animated.Interpolation;
_onGestureEvent: ?Animated.Event;
_accessibilityIsModalView = React.createRef();
_pointerEventsView = React.createRef();
_panGestureHandler = React.createRef();
_drawerShown = false;
constructor(props: PropType, context: any) {
super(props, context);
const dragX = new Animated.Value(0);
const touchX = new Animated.Value(0);
const drawerTranslation = new Animated.Value(0);
this.state = {
dragX,
touchX,
drawerTranslation,
containerWidth: 0,
};
this._updateAnimatedEvent(props, this.state);
}
UNSAFE_componentWillUpdate(props: PropType, state: StateType) {
if (
this.props.drawerPosition !== props.drawerPosition ||
this.props.drawerWidth !== props.drawerWidth ||
this.props.drawerType !== props.drawerType ||
this.state.containerWidth !== state.containerWidth
) {
this._updateAnimatedEvent(props, state);
}
}
_updateAnimatedEvent = (props: PropType, state: StateType) => {
// Event definition is based on
const { drawerPosition, drawerWidth, drawerType } = props;
const {
dragX: dragXValue,
touchX: touchXValue,
drawerTranslation,
containerWidth,
} = state;
let dragX = dragXValue;
let touchX = touchXValue;
if (drawerPosition !== 'left') {
// Most of the code is written in a way to handle left-side drawer.
// In order to handle right-side drawer the only thing we need to
// do is to reverse events coming from gesture handler in a way they
// emulate left-side drawer gestures. E.g. dragX is simply -dragX, and
// touchX is calulcated by subtracing real touchX from the width of the
// container (such that when touch happens at the right edge the value
// is simply 0)
dragX = Animated.multiply(new Animated.Value(-1), dragXValue);
touchX = Animated.add(
new Animated.Value(containerWidth),
Animated.multiply(new Animated.Value(-1), touchXValue)
);
touchXValue.setValue(containerWidth);
} else {
touchXValue.setValue(0);
}
// While closing the drawer when user starts gesture outside of its area (in greyed
// out part of the window), we want the drawer to follow only once finger reaches the
// edge of the drawer.
// E.g. on the diagram below drawer is illustrate by X signs and the greyed out area by
// dots. The touch gesture starts at '*' and moves left, touch path is indicated by
// an arrow pointing left
// 1) +---------------+ 2) +---------------+ 3) +---------------+ 4) +---------------+
// |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........|
// |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........|
// |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........|
// |XXXXXXXX|......| |XXXXXXXX|.<-*..| |XXXXXXXX|<--*..| |XXXXX|<-----*..|
// |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........|
// |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........|
// |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........|
// +---------------+ +---------------+ +---------------+ +---------------+
//
// For the above to work properly we define animated value that will keep start position
// of the gesture. Then we use that value to calculate how much we need to subtract from
// the dragX. If the gesture started on the greyed out area we take the distance from the
// edge of the drawer to the start position. Otherwise we don't subtract at all and the
// drawer be pulled back as soon as you start the pan.
//
// This is used only when drawerType is "front"
//
let translationX = dragX;
if (drawerType === 'front') {
const startPositionX = Animated.add(
touchX,
Animated.multiply(new Animated.Value(-1), dragX)
);
const dragOffsetFromOnStartPosition = startPositionX.interpolate({
inputRange: [drawerWidth - 1, drawerWidth, drawerWidth + 1],
outputRange: [0, 0, 1],
});
translationX = Animated.add(dragX, dragOffsetFromOnStartPosition);
}
this._openValue = Animated.add(translationX, drawerTranslation).interpolate(
{
inputRange: [0, drawerWidth],
outputRange: [0, 1],
extrapolate: 'clamp',
}
);
this._onGestureEvent = Animated.event(
[{ nativeEvent: { translationX: dragXValue, x: touchXValue } }],
{ useNativeDriver: props.useNativeAnimations }
);
};
_handleContainerLayout = ({ nativeEvent }) => {
this.setState({ containerWidth: nativeEvent.layout.width });
};
_emitStateChanged = (newState: string, drawerWillShow: boolean) => {
this.props.onDrawerStateChanged &&
this.props.onDrawerStateChanged(newState, drawerWillShow);
};
_openingHandlerStateChange = ({ nativeEvent }) => {
if (nativeEvent.oldState === State.ACTIVE) {
this._handleRelease(nativeEvent);
} else if (nativeEvent.state === State.ACTIVE) {
this._emitStateChanged(DRAGGING, false);
if (this.props.keyboardDismissMode === 'on-drag') {
Keyboard.dismiss();
}
if (this.props.hideStatusBar) {
StatusBar.setHidden(true, this.props.statusBarAnimation || 'slide');
}
}
};
_onTapHandlerStateChange = ({ nativeEvent }) => {
if (
this._drawerShown &&
nativeEvent.oldState === State.ACTIVE &&
this.props.drawerLockMode !== 'locked-open'
) {
this.closeDrawer();
}
};
_handleRelease = nativeEvent => {
const { drawerWidth, drawerPosition, drawerType } = this.props;
const { containerWidth } = this.state;
let { translationX: dragX, velocityX, x: touchX } = nativeEvent;
if (drawerPosition !== 'left') {
// See description in _updateAnimatedEvent about why events are flipped
// for right-side drawer
dragX = -dragX;
touchX = containerWidth - touchX;
velocityX = -velocityX;
}
const gestureStartX = touchX - dragX;
let dragOffsetBasedOnStart = 0;
if (drawerType === 'front') {
dragOffsetBasedOnStart =
gestureStartX > drawerWidth ? gestureStartX - drawerWidth : 0;
}
const startOffsetX =
dragX + dragOffsetBasedOnStart + (this._drawerShown ? drawerWidth : 0);
const projOffsetX = startOffsetX + DRAG_TOSS * velocityX;
const shouldOpen = projOffsetX > drawerWidth / 2;
if (shouldOpen) {
this._animateDrawer(startOffsetX, drawerWidth, velocityX);
} else {
this._animateDrawer(startOffsetX, 0, velocityX);
}
};
_updateShowing = (showing: boolean) => {
this._drawerShown = showing;
this._accessibilityIsModalView.current &&
this._accessibilityIsModalView.current.setNativeProps({
accessibilityViewIsModal: showing,
});
this._pointerEventsView.current &&
this._pointerEventsView.current.setNativeProps({
pointerEvents: showing ? 'auto' : 'none',
});
const { drawerPosition, minSwipeDistance, edgeWidth } = this.props;
const fromLeft = drawerPosition === 'left';
// gestureOrientation is 1 if the expected gesture is from left to right and -1 otherwise
// e.g. when drawer is on the left and is closed we expect left to right gesture, thus
// orientation will be 1.
const gestureOrientation =
(fromLeft ? 1 : -1) * (this._drawerShown ? -1 : 1);
// When drawer is closed we want the hitSlop to be horizontally shorter
// than the container size by the value of SLOP. This will make it only
// activate when gesture happens not further than SLOP away from the edge
const hitSlop = fromLeft
? { left: 0, width: showing ? undefined : edgeWidth }
: { right: 0, width: showing ? undefined : edgeWidth };
this._panGestureHandler.current &&
this._panGestureHandler.current.setNativeProps({
hitSlop,
activeOffsetX: gestureOrientation * minSwipeDistance,
});
};
_animateDrawer = (fromValue: ?number, toValue: number, velocity: number) => {
this.state.dragX.setValue(0);
this.state.touchX.setValue(
this.props.drawerPosition === 'left' ? 0 : this.state.containerWidth
);
if (fromValue != null) {
let nextFramePosition = fromValue;
if (this.props.useNativeAnimations) {
// When using native driver, we predict the next position of the animation
// because it takes one frame of a roundtrip to pass RELEASE event from
// native driver to JS before we can start animating. Without it, it is more
// noticable that the frame is dropped.
if (fromValue < toValue && velocity > 0) {
nextFramePosition = Math.min(fromValue + velocity / 60.0, toValue);
} else if (fromValue > toValue && velocity < 0) {
nextFramePosition = Math.max(fromValue + velocity / 60.0, toValue);
}
}
this.state.drawerTranslation.setValue(nextFramePosition);
}
const willShow = toValue !== 0;
this._updateShowing(willShow);
this._emitStateChanged(SETTLING, willShow);
if (this.props.hideStatusBar) {
StatusBar.setHidden(willShow, this.props.statusBarAnimation || 'slide');
}
Animated.spring(this.state.drawerTranslation, {
velocity,
bounciness: 0,
toValue,
useNativeDriver: this.props.useNativeAnimations,
}).start(({ finished }) => {
if (finished) {
this._emitStateChanged(IDLE, willShow);
if (willShow) {
this.props.onDrawerOpen && this.props.onDrawerOpen();
} else {
this.props.onDrawerClose && this.props.onDrawerClose();
}
}
});
};
openDrawer = (options: DrawerMovementOptionType = {}) => {
this._animateDrawer(
undefined,
this.props.drawerWidth,
options.velocity ? options.velocity : 0
);
// We need to force the update, otherwise the overlay is not rerendered and it would not be clickable
this.forceUpdate();
};
closeDrawer = (options: DrawerMovementOptionType = {}) => {
this._animateDrawer(undefined, 0, options.velocity ? options.velocity : 0);
// We need to force the update, otherwise the overlay is not rerendered and it would be still clickable
this.forceUpdate();
};
_renderOverlay = () => {
/* Overlay styles */
invariant(this._openValue, 'should be set');
const overlayOpacity = this._openValue.interpolate({
inputRange: [0, 1],
outputRange: [0, 1],
extrapolate: 'clamp',
});
const dynamicOverlayStyles = {
opacity: overlayOpacity,
backgroundColor: this.props.overlayColor,
};
return (
<TapGestureHandler onHandlerStateChange={this._onTapHandlerStateChange}>
<Animated.View
pointerEvents={this._drawerShown ? 'auto' : 'none'}
ref={this._pointerEventsView}
style={[styles.overlay, dynamicOverlayStyles]}
/>
</TapGestureHandler>
);
};
_renderDrawer = () => {
const {
drawerBackgroundColor,
drawerWidth,
drawerPosition,
drawerType,
drawerContainerStyle,
contentContainerStyle,
} = this.props;
const fromLeft = drawerPosition === 'left';
const drawerSlide = drawerType !== 'back';
const containerSlide = drawerType !== 'front';
// we rely on row and row-reverse flex directions to position the drawer
// properly. Apparently for RTL these are flipped which requires us to use
// the opposite setting for the drawer to appear from left or right according
// to the drawerPosition prop
const reverseContentDirection = I18nManager.isRTL ? fromLeft : !fromLeft;
const dynamicDrawerStyles = {
backgroundColor: drawerBackgroundColor,
width: drawerWidth,
};
const openValue = this._openValue;
invariant(openValue, 'should be set');
let containerStyles;
if (containerSlide) {
const containerTranslateX = openValue.interpolate({
inputRange: [0, 1],
outputRange: fromLeft ? [0, drawerWidth] : [0, -drawerWidth],
extrapolate: 'clamp',
});
containerStyles = {
transform: [{ translateX: containerTranslateX }],
};
}
let drawerTranslateX = 0;
if (drawerSlide) {
const closedDrawerOffset = fromLeft ? -drawerWidth : drawerWidth;
drawerTranslateX = openValue.interpolate({
inputRange: [0, 1],
outputRange: [closedDrawerOffset, 0],
extrapolate: 'clamp',
});
}
const drawerStyles = {
transform: [{ translateX: drawerTranslateX }],
flexDirection: reverseContentDirection ? 'row-reverse' : 'row',
};
return (
<Animated.View style={styles.main} onLayout={this._handleContainerLayout}>
<Animated.View
style={[
drawerType === 'front'
? styles.containerOnBack
: styles.containerInFront,
containerStyles,
contentContainerStyle,
]}
importantForAccessibility={
this._drawerShown ? 'no-hide-descendants' : 'yes'
}>
{typeof this.props.children === 'function'
? this.props.children(this._openValue)
: this.props.children}
{this._renderOverlay()}
</Animated.View>
<Animated.View
pointerEvents="box-none"
ref={this._accessibilityIsModalView}
accessibilityViewIsModal={this._drawerShown}
style={[styles.drawerContainer, drawerStyles, drawerContainerStyle]}>
<View style={dynamicDrawerStyles}>
{this.props.renderNavigationView(this._openValue)}
</View>
</Animated.View>
</Animated.View>
);
};
_setPanGestureRef = ref => {
this._panGestureHandler.current = ref;
this.props.onGestureRef && this.props.onGestureRef(ref);
};
render() {
const {
drawerPosition,
drawerLockMode,
edgeWidth,
minSwipeDistance,
} = this.props;
const fromLeft = drawerPosition === 'left';
// gestureOrientation is 1 if the expected gesture is from left to right and -1 otherwise
// e.g. when drawer is on the left and is closed we expect left to right gesture, thus
// orientation will be 1.
const gestureOrientation =
(fromLeft ? 1 : -1) * (this._drawerShown ? -1 : 1);
// When drawer is closed we want the hitSlop to be horizontally shorter
// than the container size by the value of SLOP. This will make it only
// activate when gesture happens not further than SLOP away from the edge
const hitSlop = fromLeft
? { left: 0, width: this._drawerShown ? undefined : edgeWidth }
: { right: 0, width: this._drawerShown ? undefined : edgeWidth };
return (
<PanGestureHandler
ref={this._setPanGestureRef}
hitSlop={hitSlop}
activeOffsetX={gestureOrientation * minSwipeDistance}
failOffsetY={[-15, 15]}
onGestureEvent={this._onGestureEvent}
onHandlerStateChange={this._openingHandlerStateChange}
enabled={
drawerLockMode !== 'locked-closed' && drawerLockMode !== 'locked-open'
}>
{this._renderDrawer()}
</PanGestureHandler>
);
}
}
const styles = StyleSheet.create({
drawerContainer: {
...StyleSheet.absoluteFillObject,
zIndex: 1001,
flexDirection: 'row',
},
containerInFront: {
...StyleSheet.absoluteFillObject,
zIndex: 1002,
},
containerOnBack: {
...StyleSheet.absoluteFillObject,
},
main: {
flex: 1,
zIndex: 0,
overflow: 'hidden',
},
overlay: {
...StyleSheet.absoluteFillObject,
zIndex: 1000,
},
});

View File

@ -0,0 +1,177 @@
import PropTypes from 'prop-types';
import React from 'react';
import { Animated, Platform, processColor, StyleSheet } from 'react-native';
import createNativeWrapper from './createNativeWrapper';
import GestureHandlerButton from './GestureHandlerButton';
import State from './State';
export const RawButton = createNativeWrapper(GestureHandlerButton, {
shouldCancelWhenOutside: false,
shouldActivateOnStart: false,
});
/* Buttons */
export class BaseButton extends React.Component {
static propTypes = {
...RawButton.propTypes,
onPress: PropTypes.func,
onActiveStateChange: PropTypes.func,
};
constructor(props) {
super(props);
this._lastActive = false;
}
_handleEvent = ({ nativeEvent }) => {
const { state, oldState, pointerInside } = nativeEvent;
const active = pointerInside && state === State.ACTIVE;
if (active !== this._lastActive && this.props.onActiveStateChange) {
this.props.onActiveStateChange(active);
}
if (
oldState === State.ACTIVE &&
state !== State.CANCELLED &&
this._lastActive &&
this.props.onPress
) {
this.props.onPress(active);
}
this._lastActive = active;
};
// Normally, the parent would execute it's handler first,
// then forward the event to listeners. However, here our handler
// is virtually only forwarding events to listeners, so we reverse the order
// to keep the proper order of the callbacks (from "raw" ones to "processed").
_onHandlerStateChange = e => {
this.props.onHandlerStateChange && this.props.onHandlerStateChange(e);
this._handleEvent(e);
};
_onGestureEvent = e => {
this.props.onGestureEvent && this.props.onGestureEvent(e);
this._handleEvent(e);
};
render() {
const { rippleColor, ...rest } = this.props;
return (
<RawButton
rippleColor={processColor(rippleColor)}
{...rest}
onGestureEvent={this._onGestureEvent}
onHandlerStateChange={this._onHandlerStateChange}
/>
);
}
}
const AnimatedBaseButton = Animated.createAnimatedComponent(BaseButton);
const btnStyles = StyleSheet.create({
underlay: {
position: 'absolute',
left: 0,
right: 0,
bottom: 0,
top: 0,
},
});
export class RectButton extends React.Component {
static propTypes = BaseButton.propTypes;
static defaultProps = {
activeOpacity: 0.105,
underlayColor: 'black',
};
constructor(props) {
super(props);
this._opacity = new Animated.Value(0);
}
_onActiveStateChange = active => {
if (Platform.OS !== 'android') {
this._opacity.setValue(active ? this.props.activeOpacity : 0);
}
this.props.onActiveStateChange && this.props.onActiveStateChange(active);
};
render() {
const { children, style, ...rest } = this.props;
const resolvedStyle = StyleSheet.flatten(style ?? {});
return (
<BaseButton
{...rest}
style={resolvedStyle}
onActiveStateChange={this._onActiveStateChange}>
<Animated.View
style={[
btnStyles.underlay,
{
opacity: this._opacity,
backgroundColor: this.props.underlayColor,
borderRadius: resolvedStyle.borderRadius,
borderTopLeftRadius: resolvedStyle.borderTopLeftRadius,
borderTopRightRadius: resolvedStyle.borderTopRightRadius,
borderBottomLeftRadius: resolvedStyle.borderBottomLeftRadius,
borderBottomRightRadius: resolvedStyle.borderBottomRightRadius,
},
]}
/>
{children}
</BaseButton>
);
}
}
export class BorderlessButton extends React.Component {
static propTypes = {
...BaseButton.propTypes,
borderless: PropTypes.bool,
};
static defaultProps = {
activeOpacity: 0.3,
borderless: true,
};
constructor(props) {
super(props);
this._opacity = new Animated.Value(1);
}
_onActiveStateChange = active => {
if (Platform.OS !== 'android') {
this._opacity.setValue(active ? this.props.activeOpacity : 1);
}
this.props.onActiveStateChange && this.props.onActiveStateChange(active);
};
render() {
const { children, style, ...rest } = this.props;
return (
<AnimatedBaseButton
{...rest}
onActiveStateChange={this._onActiveStateChange}
style={[style, Platform.OS === 'ios' && { opacity: this._opacity }]}>
{children}
</AnimatedBaseButton>
);
}
}
export { default as PureNativeButton } from './GestureHandlerButton';

View File

@ -0,0 +1,58 @@
import React from 'react';
import ReactNative from 'react-native';
import createNativeWrapper from './createNativeWrapper';
const MEMOIZED = new WeakMap();
function memoizeWrap(Component, config) {
if (Component == null) {
return null;
}
let memoized = MEMOIZED.get(Component);
if (!memoized) {
memoized = createNativeWrapper(Component, config);
MEMOIZED.set(Component, memoized);
}
return memoized;
}
module.exports = {
/* RN's components */
get ScrollView() {
return memoizeWrap(ReactNative.ScrollView, {
disallowInterruption: true,
shouldCancelWhenOutside: false,
});
},
get Switch() {
return memoizeWrap(ReactNative.Switch, {
shouldCancelWhenOutside: false,
shouldActivateOnStart: true,
disallowInterruption: true,
});
},
get TextInput() {
return memoizeWrap(ReactNative.TextInput);
},
get DrawerLayoutAndroid() {
const DrawerLayoutAndroid = memoizeWrap(ReactNative.DrawerLayoutAndroid, {
disallowInterruption: true,
});
DrawerLayoutAndroid.positions = ReactNative.DrawerLayoutAndroid.positions;
return DrawerLayoutAndroid;
},
get FlatList() {
if (!MEMOIZED.FlatList) {
const ScrollView = this.ScrollView;
MEMOIZED.FlatList = React.forwardRef((props, ref) => (
<ReactNative.FlatList
ref={ref}
{...props}
renderScrollComponent={scrollProps => <ScrollView {...scrollProps} />}
/>
));
}
return MEMOIZED.FlatList;
},
};

View File

@ -0,0 +1,33 @@
import React from 'react';
import {
DrawerLayoutAndroid as RNDrawerLayoutAndroid,
FlatList as RNFlatList,
Switch as RNSwitch,
TextInput as RNTextInput,
ScrollView as RNScrollView,
} from 'react-native';
import createNativeWrapper from './createNativeWrapper';
export const ScrollView = createNativeWrapper(RNScrollView, {
disallowInterruption: true,
});
export const Switch = createNativeWrapper(RNSwitch, {
shouldCancelWhenOutside: false,
shouldActivateOnStart: true,
disallowInterruption: true,
});
export const TextInput = createNativeWrapper(RNTextInput);
export const DrawerLayoutAndroid = createNativeWrapper(RNDrawerLayoutAndroid, {
disallowInterruption: true,
});
DrawerLayoutAndroid.positions = RNDrawerLayoutAndroid.positions;
export const FlatList = React.forwardRef((props, ref) => (
<RNFlatList
ref={ref}
{...props}
renderScrollComponent={scrollProps => <ScrollView {...scrollProps} />}
/>
));

View File

@ -0,0 +1,12 @@
export { default as createNativeWrapper } from './createNativeWrapper';
export { default as Directions } from './Directions';
export { default as gestureHandlerRootHOC } from './gestureHandlerRootHOC';
export { default as GestureHandlerRootView } from './GestureHandlerRootView';
export {
default as NativeViewGestureHandler,
} from './NativeViewGestureHandler';
export { default as State } from './State';
export * from './GestureButtons';
export * from './GestureComponents';
export * from './Gestures';

View File

@ -0,0 +1,8 @@
import { requireNativeComponent } from 'react-native';
const RNGestureHandlerButton = requireNativeComponent(
'RNGestureHandlerButton',
null
);
export default RNGestureHandlerButton;

View File

@ -0,0 +1,6 @@
import React from 'react';
import { View } from 'react-native';
export default React.forwardRef((props, ref) => (
<View ref={ref} accessibilityRole="button" {...props} />
));

View File

@ -0,0 +1,45 @@
import PropTypes from 'prop-types';
// If changed, add changes to NATIVE_WRAPPER_PROPS_FILTER as well
const GestureHandlerPropTypes = {
id: PropTypes.string,
minPointers: PropTypes.number,
enabled: PropTypes.bool,
waitFor: PropTypes.oneOfType([
PropTypes.string,
PropTypes.object,
PropTypes.arrayOf(
PropTypes.oneOfType([PropTypes.string, PropTypes.object])
),
]),
simultaneousHandlers: PropTypes.oneOfType([
PropTypes.string,
PropTypes.object,
PropTypes.arrayOf(
PropTypes.oneOfType([PropTypes.string, PropTypes.object])
),
]),
shouldCancelWhenOutside: PropTypes.bool,
hitSlop: PropTypes.oneOfType([
PropTypes.number,
PropTypes.shape({
left: PropTypes.number,
top: PropTypes.number,
right: PropTypes.number,
bottom: PropTypes.number,
vertical: PropTypes.number,
horizontal: PropTypes.number,
width: PropTypes.number,
height: PropTypes.number,
}),
]),
onGestureEvent: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
onHandlerStateChange: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
onBegan: PropTypes.func,
onFailed: PropTypes.func,
onCancelled: PropTypes.func,
onActivated: PropTypes.func,
onEnded: PropTypes.func,
};
export default GestureHandlerPropTypes;

View File

@ -0,0 +1,4 @@
import React from 'react';
import { View } from 'react-native';
export default View;

View File

@ -0,0 +1,39 @@
import React from 'react';
import { View, ViewPropTypes, requireNativeComponent } from 'react-native';
const iface = {
name: 'GestureHandlerRootView',
propTypes: {
...ViewPropTypes,
},
};
const GestureHandlerRootViewNative = requireNativeComponent(
'GestureHandlerRootView',
iface
);
const GestureHandlerRootViewContext = React.createContext(false);
export default function GestureHandlerRootView({ children, ...rest }) {
return (
<GestureHandlerRootViewContext.Consumer>
{available => {
if (available) {
// If we already have a parent wrapped in the gesture handler root view,
// We don't need to wrap it again in root view
// We still wrap it in a normal view so our styling stays the same
return <View {...rest}>{children}</View>;
}
return (
<GestureHandlerRootViewContext.Provider value={true}>
<GestureHandlerRootViewNative {...rest}>
{children}
</GestureHandlerRootViewNative>
</GestureHandlerRootViewContext.Provider>
);
}}
</GestureHandlerRootViewContext.Consumer>
);
}

View File

@ -0,0 +1,4 @@
import React from 'react';
import { View } from 'react-native';
export default View;

278
node_modules/react-native-gesture-handler/Gestures.js generated vendored Normal file
View File

@ -0,0 +1,278 @@
import PropTypes from 'prop-types';
import React from 'react';
import createHandler from './createHandler';
import GestureHandlerPropTypes from './GestureHandlerPropTypes';
import PlatformConstants from './PlatformConstants';
export const TapGestureHandler = createHandler(
'TapGestureHandler',
{
...GestureHandlerPropTypes,
maxDurationMs: PropTypes.number,
maxDelayMs: PropTypes.number,
numberOfTaps: PropTypes.number,
maxDeltaX: PropTypes.number,
maxDeltaY: PropTypes.number,
maxDist: PropTypes.number,
minPointers: PropTypes.number,
},
{}
);
export const FlingGestureHandler = createHandler(
'FlingGestureHandler',
{
...GestureHandlerPropTypes,
numberOfPointers: PropTypes.number,
direction: PropTypes.number,
},
{}
);
class ForceTouchFallback extends React.Component {
componentDidMount() {
console.warn(
'ForceTouchGestureHandler is not available on this platform. Please use ForceTouchGestureHandler.forceTouchAvailable to conditionally render other components that would provide a fallback behavior specific to your usecase'
);
}
render() {
return this.props.children;
}
}
export const ForceTouchGestureHandler =
PlatformConstants && PlatformConstants.forceTouchAvailable
? createHandler(
'ForceTouchGestureHandler',
{
...GestureHandlerPropTypes,
minForce: PropTypes.number,
maxForce: PropTypes.number,
feedbackOnActivation: PropTypes.bool,
},
{}
)
: ForceTouchFallback;
ForceTouchGestureHandler.forceTouchAvailable =
(PlatformConstants && PlatformConstants.forceTouchAvailable) || false;
export const LongPressGestureHandler = createHandler(
'LongPressGestureHandler',
{
...GestureHandlerPropTypes,
minDurationMs: PropTypes.number,
maxDist: PropTypes.number,
},
{}
);
function validatePanGestureHandlerProps(props) {
if (props.minDeltaX && props.activeOffsetX) {
throw new Error(
`It's not supported use minDeltaX with activeOffsetXStart or activeOffsetXEnd`
);
}
if (props.maxDeltaX && props.failOffsetX) {
throw new Error(
`It's not supported use minDeltaX with activeOffsetXStart or activeOffsetXEnd`
);
}
if (props.minDeltaY && props.activeOffsetY) {
throw new Error(
`It's not supported use minDeltaX with activeOffsetYStart or activeOffsetYEnd`
);
}
if (props.maxDeltaY && props.failOffsetY) {
throw new Error(
`It's not supported use minDeltaX with activeOffsetYStart or activeOffsetYEnd`
);
}
if (
Array.isArray(props.activeOffsetX) &&
(props.activeOffsetX[0] > 0 || props.activeOffsetX[1] < 0)
) {
throw new Error(
`First element of activeOffsetX should be negative, a the second one should be positive`
);
}
if (
Array.isArray(props.activeOffsetY) &&
(props.activeOffsetY[0] > 0 || props.activeOffsetY[1] < 0)
) {
throw new Error(
`First element of activeOffsetY should be negative, a the second one should be positive`
);
}
if (
Array.isArray(props.failOffsetX) &&
(props.failOffsetX[0] > 0 || props.failOffsetX[1] < 0)
) {
throw new Error(
`First element of failOffsetX should be negative, a the second one should be positive`
);
}
if (
Array.isArray(props.failOffsetY) &&
(props.failOffsetY[0] > 0 || props.failOffsetY[1] < 0)
) {
throw new Error(
`First element of failOffsetY should be negative, a the second one should be positive`
);
}
}
function transformPanGestureHandlerProps(props) {
const res = { ...props };
if (props.minDeltaX !== undefined) {
delete res['minDeltaX'];
res.activeOffsetXStart = -props.minDeltaX;
res.activeOffsetXEnd = props.minDeltaX;
}
if (props.maxDeltaX !== undefined) {
delete res['maxDeltaX'];
res.failOffsetXStart = -props.maxDeltaX;
res.failOffsetXEnd = props.maxDeltaX;
}
if (props.minOffsetX !== undefined) {
delete res['minOffsetX'];
if (props.minOffsetX < 0) {
res.activeOffsetXStart = props.minOffsetX;
} else {
res.activeOffsetXEnd = props.minOffsetX;
}
}
if (props.minDeltaY !== undefined) {
delete res['minDeltaY'];
res.activeOffsetYStart = -props.minDeltaY;
res.activeOffsetYEnd = props.minDeltaY;
}
if (props.maxDeltaY !== undefined) {
delete res['maxDeltaY'];
res.failOffsetYStart = -props.maxDeltaY;
res.failOffsetYEnd = props.maxDeltaY;
}
if (props.minOffsetY !== undefined) {
delete res['minOffsetY'];
if (props.minOffsetY < 0) {
res.activeOffsetYStart = props.minOffsetY;
} else {
res.activeOffsetYEnd = props.minOffsetY;
}
}
if (props.activeOffsetX !== undefined) {
delete res['activeOffsetX'];
if (Array.isArray(props.activeOffsetX)) {
res.activeOffsetXStart = props.activeOffsetX[0];
res.activeOffsetXEnd = props.activeOffsetX[1];
} else if (props.activeOffsetX < 0) {
res.activeOffsetXStart = props.activeOffsetX;
} else {
res.activeOffsetXEnd = props.activeOffsetX;
}
}
if (props.activeOffsetY !== undefined) {
delete res['activeOffsetY'];
if (Array.isArray(props.activeOffsetY)) {
res.activeOffsetYStart = props.activeOffsetY[0];
res.activeOffsetYEnd = props.activeOffsetY[1];
} else if (props.activeOffsetY < 0) {
res.activeOffsetYStart = props.activeOffsetY;
} else {
res.activeOffsetYEnd = props.activeOffsetY;
}
}
if (props.failOffsetX !== undefined) {
delete res['failOffsetX'];
if (Array.isArray(props.failOffsetX)) {
res.failOffsetXStart = props.failOffsetX[0];
res.failOffsetXEnd = props.failOffsetX[1];
} else if (props.failOffsetX < 0) {
res.failOffsetXStart = props.failOffsetX;
} else {
res.failOffsetXEnd = props.failOffsetX;
}
}
if (props.failOffsetY !== undefined) {
delete res['failOffsetY'];
if (Array.isArray(props.failOffsetY)) {
res.failOffsetYStart = props.failOffsetY[0];
res.failOffsetYEnd = props.failOffsetY[1];
} else if (props.failOffsetY < 0) {
res.failOffsetYStart = props.failOffsetY;
} else {
res.failOffsetYEnd = props.failOffsetY;
}
}
return res;
}
function managePanProps(props) {
if (__DEV__) {
validatePanGestureHandlerProps(props);
}
return transformPanGestureHandlerProps(props);
}
export const PanGestureHandler = createHandler(
'PanGestureHandler',
{
...GestureHandlerPropTypes,
activeOffsetY: PropTypes.oneOfType([
PropTypes.number,
PropTypes.arrayOf(PropTypes.number),
]),
activeOffsetX: PropTypes.oneOfType([
PropTypes.number,
PropTypes.arrayOf(PropTypes.number),
]),
failOffsetY: PropTypes.oneOfType([
PropTypes.number,
PropTypes.arrayOf(PropTypes.number),
]),
failOffsetX: PropTypes.oneOfType([
PropTypes.number,
PropTypes.arrayOf(PropTypes.number),
]),
minDist: PropTypes.number,
minVelocity: PropTypes.number,
minVelocityX: PropTypes.number,
minVelocityY: PropTypes.number,
minPointers: PropTypes.number,
maxPointers: PropTypes.number,
avgTouches: PropTypes.bool,
},
{},
managePanProps,
{
activeOffsetYStart: true,
activeOffsetYEnd: true,
activeOffsetXStart: true,
activeOffsetXEnd: true,
failOffsetYStart: true,
failOffsetYEnd: true,
failOffsetXStart: true,
failOffsetXEnd: true,
}
);
export const PinchGestureHandler = createHandler(
'PinchGestureHandler',
GestureHandlerPropTypes,
{}
);
export const RotationGestureHandler = createHandler(
'RotationGestureHandler',
GestureHandlerPropTypes,
{}
);

21
node_modules/react-native-gesture-handler/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Krzysztof Magiera
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,14 @@
import PropTypes from 'prop-types';
import createHandler from './createHandler';
import GestureHandlerPropTypes from './GestureHandlerPropTypes';
const NativeViewGestureHandler = createHandler('NativeViewGestureHandler', {
...GestureHandlerPropTypes,
// If changed, add changes to NATIVE_WRAPPER_PROPS_FILTER as well
shouldActivateOnStart: PropTypes.bool,
disallowInterruption: PropTypes.bool,
});
export default NativeViewGestureHandler;

View File

@ -0,0 +1,3 @@
import { NativeModules } from 'react-native';
export default NativeModules.PlatformConstants;

View File

@ -0,0 +1,5 @@
export default {
get forceTouchAvailable() {
return false;
},
};

52
node_modules/react-native-gesture-handler/README.md generated vendored Normal file
View File

@ -0,0 +1,52 @@
<p align="center">
<h1 align="center">React Native Gesture Handler</h1>
<h3 align="center">Declarative API exposing platform native touch and gesture system to React Native.</h3>
</p>
React Native Gesture Handler provides native-driven gesture management APIs for building best possible touch-based experiences in React Native.
With this library gestures are no longer controlled by the JS responder system, but instead are recognized and tracked in the UI thread.
It makes touch interactions and gesture tracking not only smooth, but also dependable and deterministic.
## Installation
Check [getting started](https://docs.swmansion.com/react-native-gesture-handler/docs/#installation) section of our docs for the detailed installation instructions.
## Documentation
Check out our dedicated documentation page for info about this library, API reference and more: [https://docs.swmansion.com/react-native-gesture-handler/docs/](https://docs.swmansion.com/react-native-gesture-handler/docs/)
## Examples
If you want to play with the API but don't feel like trying it on a real app, you can run the example project. Clone the repo, go to the `Example/` folder and run:
```bash
yarn install
```
If you are running on ios, run `pod install` in the ios folder
Run `yarn start` to start the metro bundler
Run `react-native run-android` or `react-native run-ios` (depending on which platform you want to run the example app on).
You will need to have an Android or iOS device or emulator connected as well as `react-native-cli` package installed globally.
## React native Support
| version | react-native version |
| ------- | -------------------- |
| 1.4.0+ | 0.60.0+ |
| 1.1.0+ | 0.57.2+ |
| <1.1.0 | 0.50.0+ |
## License
Gesture handler library is licensed under [The MIT License](LICENSE).
## Credits
This project is supported by amazing people from [Expo.io](https://expo.io) and [Software Mansion](https://swmansion.com)
[![expo](https://avatars2.githubusercontent.com/u/12504344?v=3&s=100 'Expo.io')](https://expo.io)
[![swm](https://logo.swmansion.com/logo?color=white&variant=desktop&width=150&tag=react-native-gesture-handler-github 'Software Mansion')](https://swmansion.com)

View File

@ -0,0 +1,19 @@
require "json"
Pod::Spec.new do |s|
# NPM package specification
package = JSON.parse(File.read(File.join(File.dirname(__FILE__), "package.json")))
s.name = "RNGestureHandler"
s.version = package["version"]
s.summary = package["description"]
s.homepage = "https://github.com/software-mansion/react-native-gesture-handler"
s.license = "MIT"
s.author = { package["author"]["name"] => package["author"]["email"] }
s.platforms = { :ios => "9.0", :tvos => "9.0" }
s.source = { :git => "https://github.com/software-mansion/react-native-gesture-handler", :tag => "#{s.version}" }
s.source_files = "ios/**/*.{h,m}"
s.dependency "React"
end

View File

@ -0,0 +1,5 @@
import { NativeModules } from 'react-native';
const { RNGestureHandlerModule } = NativeModules;
export default RNGestureHandlerModule;

View File

@ -0,0 +1,49 @@
import { Direction } from './web/constants';
import FlingGestureHandler from './web/FlingGestureHandler';
import LongPressGestureHandler from './web/LongPressGestureHandler';
import NativeViewGestureHandler from './web/NativeViewGestureHandler';
import * as NodeManager from './web/NodeManager';
import PanGestureHandler from './web/PanGestureHandler';
import PinchGestureHandler from './web/PinchGestureHandler';
import RotationGestureHandler from './web/RotationGestureHandler';
import TapGestureHandler from './web/TapGestureHandler';
const Gestures = {
PanGestureHandler,
RotationGestureHandler,
PinchGestureHandler,
TapGestureHandler,
NativeViewGestureHandler,
LongPressGestureHandler,
FlingGestureHandler,
// ForceTouchGestureHandler,
};
export default {
Direction,
handleSetJSResponder(tag, blockNativeResponder) {
console.warn('handleSetJSResponder: ', tag, blockNativeResponder);
},
handleClearJSResponder() {
console.warn('handleClearJSResponder: ');
},
createGestureHandler(handlerName, handlerTag, config) {
if (!(handlerName in Gestures))
throw new Error(`react-native-gesture-handler: ${handlerName} is not supported on web.`);
const GestureClass = Gestures[handlerName];
NodeManager.createGestureHandler(handlerTag, new GestureClass());
this.updateGestureHandler(handlerTag, config);
},
attachGestureHandler(handlerTag, newView) {
NodeManager.getHandler(handlerTag).setView(newView);
},
updateGestureHandler(handlerTag, newConfig) {
NodeManager.getHandler(handlerTag).updateGestureConfig(newConfig);
},
getGestureHandlerNode(handlerTag) {
return NodeManager.getHandler(handlerTag);
},
dropGestureHandler(handlerTag) {
NodeManager.dropGestureHandler(handlerTag);
},
};

19
node_modules/react-native-gesture-handler/State.js generated vendored Normal file
View File

@ -0,0 +1,19 @@
const State = {
UNDETERMINED: 0,
FAILED: 1,
BEGAN: 2,
CANCELLED: 3,
ACTIVE: 4,
END: 5,
};
State.print = state => {
const keys = Object.keys(State);
for (let i = 0; i < keys.length; i++) {
if (state === State[keys[i]]) {
return keys[i];
}
}
};
export default State;

355
node_modules/react-native-gesture-handler/Swipeable.js generated vendored Normal file
View File

@ -0,0 +1,355 @@
// @flow
// Similarily to the DrawerLayout component this deserves to be put in a
// separate repo. Although, keeping it here for the time being will allow us
// to move faster and fix possible issues quicker
import React, { Component } from 'react';
import { Animated, StyleSheet, View, I18nManager } from 'react-native';
import { PanGestureHandler, TapGestureHandler, State } from './GestureHandler';
const DRAG_TOSS = 0.05;
export type PropType = {
children: any,
friction: number,
leftThreshold?: number,
rightThreshold?: number,
overshootLeft?: boolean,
overshootRight?: boolean,
overshootFriction: number,
onSwipeableLeftOpen?: Function,
onSwipeableRightOpen?: Function,
onSwipeableOpen?: Function,
onSwipeableClose?: Function,
onSwipeableLeftWillOpen?: Function,
onSwipeableRightWillOpen?: Function,
onSwipeableWillOpen?: Function,
onSwipeableWillClose?: Function,
renderLeftActions?: (
progressAnimatedValue: any,
dragAnimatedValue: any
) => any,
renderRightActions?: (
progressAnimatedValue: any,
dragAnimatedValue: any
) => any,
useNativeAnimations: boolean,
animationOptions?: Object,
containerStyle?: Object,
childrenContainerStyle?: Object,
};
type StateType = {
dragX: Animated.Value,
rowTranslation: Animated.Value,
rowState: number,
leftWidth: number | typeof undefined,
rightOffset: number | typeof undefined,
rowWidth: number | typeof undefined,
};
export default class Swipeable extends Component<PropType, StateType> {
static defaultProps = {
friction: 1,
overshootFriction: 1,
useNativeAnimations: true,
};
_onGestureEvent: ?Animated.Event;
_transX: ?Animated.Interpolation;
_showLeftAction: ?Animated.Interpolation | ?Animated.Value;
_leftActionTranslate: ?Animated.Interpolation;
_showRightAction: ?Animated.Interpolation | ?Animated.Value;
_rightActionTranslate: ?Animated.Interpolation;
constructor(props: PropType) {
super(props);
const dragX = new Animated.Value(0);
this.state = {
dragX,
rowTranslation: new Animated.Value(0),
rowState: 0,
leftWidth: undefined,
rightOffset: undefined,
rowWidth: undefined,
};
this._updateAnimatedEvent(props, this.state);
this._onGestureEvent = Animated.event(
[{ nativeEvent: { translationX: dragX } }],
{ useNativeDriver: props.useNativeAnimations }
);
}
UNSAFE_componentWillUpdate(props: PropType, state: StateType) {
if (
this.props.friction !== props.friction ||
this.props.overshootLeft !== props.overshootLeft ||
this.props.overshootRight !== props.overshootRight ||
this.props.overshootFriction !== props.overshootFriction ||
this.state.leftWidth !== state.leftWidth ||
this.state.rightOffset !== state.rightOffset ||
this.state.rowWidth !== state.rowWidth
) {
this._updateAnimatedEvent(props, state);
}
}
_updateAnimatedEvent = (props: PropType, state: StateType) => {
const { friction, overshootFriction, useNativeAnimations } = props;
const { dragX, rowTranslation, leftWidth = 0, rowWidth = 0 } = state;
const { rightOffset = rowWidth } = state;
const rightWidth = Math.max(0, rowWidth - rightOffset);
const {
overshootLeft = leftWidth > 0,
overshootRight = rightWidth > 0,
} = props;
const transX = Animated.add(
rowTranslation,
dragX.interpolate({
inputRange: [0, friction],
outputRange: [0, 1],
})
).interpolate({
inputRange: [
-rightWidth - (overshootRight ? 1 : overshootFriction),
-rightWidth,
leftWidth,
leftWidth + (overshootLeft ? 1 : overshootFriction),
],
outputRange: [
-rightWidth - (overshootRight || overshootFriction > 1 ? 1 : 0),
-rightWidth,
leftWidth,
leftWidth + (overshootLeft || overshootFriction > 1 ? 1 : 0),
],
});
this._transX = transX;
this._showLeftAction =
leftWidth > 0
? transX.interpolate({
inputRange: [-1, 0, leftWidth],
outputRange: [0, 0, 1],
})
: new Animated.Value(0);
this._leftActionTranslate = this._showLeftAction.interpolate({
inputRange: [0, Number.MIN_VALUE],
outputRange: [-10000, 0],
extrapolate: 'clamp',
});
this._showRightAction =
rightWidth > 0
? transX.interpolate({
inputRange: [-rightWidth, 0, 1],
outputRange: [1, 0, 0],
})
: new Animated.Value(0);
this._rightActionTranslate = this._showRightAction.interpolate({
inputRange: [0, Number.MIN_VALUE],
outputRange: [-10000, 0],
extrapolate: 'clamp',
});
};
_onTapHandlerStateChange = ({ nativeEvent }) => {
if (nativeEvent.oldState === State.ACTIVE) {
this.close();
}
};
_onHandlerStateChange = ({ nativeEvent }) => {
if (nativeEvent.oldState === State.ACTIVE) {
this._handleRelease(nativeEvent);
}
};
_handleRelease = nativeEvent => {
const { velocityX, translationX: dragX } = nativeEvent;
const { leftWidth = 0, rowWidth = 0, rowState } = this.state;
const { rightOffset = rowWidth } = this.state;
const rightWidth = rowWidth - rightOffset;
const {
friction,
leftThreshold = leftWidth / 2,
rightThreshold = rightWidth / 2,
} = this.props;
const startOffsetX = this._currentOffset() + dragX / friction;
const translationX = (dragX + DRAG_TOSS * velocityX) / friction;
let toValue = 0;
if (rowState === 0) {
if (translationX > leftThreshold) {
toValue = leftWidth;
} else if (translationX < -rightThreshold) {
toValue = -rightWidth;
}
} else if (rowState === 1) {
// swiped to left
if (translationX > -leftThreshold) {
toValue = leftWidth;
}
} else {
// swiped to right
if (translationX < rightThreshold) {
toValue = -rightWidth;
}
}
this._animateRow(startOffsetX, toValue, velocityX / friction);
};
_animateRow = (fromValue, toValue, velocityX) => {
const { dragX, rowTranslation } = this.state;
dragX.setValue(0);
rowTranslation.setValue(fromValue);
this.setState({ rowState: Math.sign(toValue) });
Animated.spring(rowTranslation, {
restSpeedThreshold: 1.7,
restDisplacementThreshold: 0.4,
velocity: velocityX,
bounciness: 0,
toValue,
useNativeDriver: this.props.useNativeAnimations,
...this.props.animationOptions,
}).start(({ finished }) => {
if (finished) {
if (toValue > 0 && this.props.onSwipeableLeftOpen) {
this.props.onSwipeableLeftOpen();
} else if (toValue < 0 && this.props.onSwipeableRightOpen) {
this.props.onSwipeableRightOpen();
}
if (toValue === 0) {
this.props.onSwipeableClose && this.props.onSwipeableClose();
} else {
this.props.onSwipeableOpen && this.props.onSwipeableOpen();
}
}
});
if (toValue > 0 && this.props.onSwipeableLeftWillOpen) {
this.props.onSwipeableLeftWillOpen();
} else if (toValue < 0 && this.props.onSwipeableRightWillOpen) {
this.props.onSwipeableRightWillOpen();
}
if (toValue === 0) {
this.props.onSwipeableWillClose && this.props.onSwipeableWillClose();
} else {
this.props.onSwipeableWillOpen && this.props.onSwipeableWillOpen();
}
};
_onRowLayout = ({ nativeEvent }) => {
this.setState({ rowWidth: nativeEvent.layout.width });
};
_currentOffset = () => {
const { leftWidth = 0, rowWidth = 0, rowState } = this.state;
const { rightOffset = rowWidth } = this.state;
const rightWidth = rowWidth - rightOffset;
if (rowState === 1) {
return leftWidth;
} else if (rowState === -1) {
return -rightWidth;
}
return 0;
};
close = () => {
this._animateRow(this._currentOffset(), 0);
};
openLeft = () => {
const { leftWidth = 0 } = this.state;
this._animateRow(this._currentOffset(), leftWidth);
};
openRight = () => {
const { rowWidth = 0 } = this.state;
const { rightOffset = rowWidth } = this.state;
const rightWidth = rowWidth - rightOffset;
this._animateRow(this._currentOffset(), -rightWidth);
};
render() {
const { rowState } = this.state;
const { children, renderLeftActions, renderRightActions } = this.props;
const left = renderLeftActions && (
<Animated.View
style={[
styles.leftActions,
{ transform: [{ translateX: this._leftActionTranslate }] },
]}>
{renderLeftActions(this._showLeftAction, this._transX)}
<View
onLayout={({ nativeEvent }) =>
this.setState({ leftWidth: nativeEvent.layout.x })
}
/>
</Animated.View>
);
const right = renderRightActions && (
<Animated.View
style={[
styles.rightActions,
{ transform: [{ translateX: this._rightActionTranslate }] },
]}>
{renderRightActions(this._showRightAction, this._transX)}
<View
onLayout={({ nativeEvent }) =>
this.setState({ rightOffset: nativeEvent.layout.x })
}
/>
</Animated.View>
);
return (
<PanGestureHandler
activeOffsetX={[-10, 10]}
{...this.props}
onGestureEvent={this._onGestureEvent}
onHandlerStateChange={this._onHandlerStateChange}>
<Animated.View
onLayout={this._onRowLayout}
style={[styles.container, this.props.containerStyle]}>
{left}
{right}
<TapGestureHandler
enabled={rowState !== 0}
onHandlerStateChange={this._onTapHandlerStateChange}>
<Animated.View
pointerEvents={rowState === 0 ? 'auto' : 'box-only'}
style={[
{
transform: [{ translateX: this._transX }],
},
this.props.childrenContainerStyle,
]}>
{children}
</Animated.View>
</TapGestureHandler>
</Animated.View>
</PanGestureHandler>
);
}
}
const styles = StyleSheet.create({
container: {
overflow: 'hidden',
},
leftActions: {
...StyleSheet.absoluteFillObject,
flexDirection: I18nManager.isRTL? 'row-reverse': 'row',
},
rightActions: {
...StyleSheet.absoluteFillObject,
flexDirection: I18nManager.isRTL ? 'row' : 'row-reverse',
},
});

View File

@ -0,0 +1,17 @@
import { View, ScrollView } from 'react-native'
export default {
ScrollView,
PanGestureHandler: View,
attachGestureHandler: () => {},
createGestureHandler: () => {},
dropGestureHandler: () => {},
updateGestureHandler: () => {},
Direction: {
RIGHT: 1,
LEFT: 2,
UP: 4,
DOWN: 8,
},
State: { BEGAN: 'BEGAN', FAILED: 'FAILED', ACTIVE: 'ACTIVE', END: 'END', UNDETERMINED: 'UNDETERMINED' },
};

View File

@ -0,0 +1,28 @@
apply plugin: 'com.android.library'
def safeExtGet(prop, fallback) {
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
}
android {
compileSdkVersion safeExtGet("compileSdkVersion", 28)
defaultConfig {
minSdkVersion safeExtGet('minSdkVersion', 16)
targetSdkVersion safeExtGet('targetSdkVersion', 28)
versionCode 1
versionName "1.0"
}
// Include "lib/" as sources, unfortunetely react-native link can't handle
// setting up alternative gradle modules. We still have "lib" defined as a
// standalone gradle module just to be used in AndroidNativeExample
sourceSets {
main.java.srcDirs += 'lib/src/main/java'
}
}
dependencies {
//noinspection GradleDynamicVersion
implementation 'com.facebook.react:react-native:+'
}

View File

@ -0,0 +1,28 @@
apply plugin: 'com.android.library'
repositories {
maven { url 'https://repo1.maven.org/maven2' }
}
android {
compileSdkVersion 23
buildToolsVersion '25.0.0'
defaultConfig {
minSdkVersion 16
targetSdkVersion 23
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.4.0'
}

View File

@ -0,0 +1,23 @@
package com.swmansion.gesturehandler;
public abstract class BaseGestureHandlerInteractionController
implements GestureHandlerInteractionController {
@Override
public boolean shouldWaitForHandlerFailure(GestureHandler handler,
GestureHandler otherHandler) {
return false;
}
@Override
public boolean shouldRequireHandlerToWaitForFailure(GestureHandler handler,
GestureHandler otherHandler) {
return false;
}
@Override
public boolean shouldRecognizeSimultaneously(GestureHandler handler,
GestureHandler otherHandler) {
return false;
}
}

View File

@ -0,0 +1,110 @@
package com.swmansion.gesturehandler;
import android.os.Handler;
import android.view.MotionEvent;
public class FlingGestureHandler extends GestureHandler<FlingGestureHandler> {
private static final long DEFAULT_MAX_DURATION_MS = 800;
private static final long DEFAULT_MIN_ACCEPTABLE_DELTA = 160;
private static final int DEFAULT_DIRECTION = DIRECTION_RIGHT;
private static final int DEFAULT_NUMBER_OF_TOUCHES_REQUIRED = 1;
private long mMaxDurationMs = DEFAULT_MAX_DURATION_MS;
private long mMinAcceptableDelta = DEFAULT_MIN_ACCEPTABLE_DELTA;
private int mDirection = DEFAULT_DIRECTION;
private int mNumberOfPointersRequired = DEFAULT_NUMBER_OF_TOUCHES_REQUIRED;
private float mStartX, mStartY;
private Handler mHandler;
private int mMaxNumberOfPointersSimultaneously;
private final Runnable mFailDelayed = new Runnable() {
@Override
public void run() {
fail();
}
};
public void setNumberOfPointersRequired(int numberOfPointersRequired) {
mNumberOfPointersRequired = numberOfPointersRequired;
}
public void setDirection(int direction) {
mDirection = direction;
}
private void startFling(MotionEvent event) {
mStartX = event.getRawX();
mStartY = event.getRawY();
begin();
mMaxNumberOfPointersSimultaneously = 1;
if (mHandler == null) {
mHandler = new Handler();
} else {
mHandler.removeCallbacksAndMessages(null);
}
mHandler.postDelayed(mFailDelayed, mMaxDurationMs);
}
private boolean tryEndFling(MotionEvent event) {
if (mMaxNumberOfPointersSimultaneously == mNumberOfPointersRequired &&
(((mDirection & DIRECTION_RIGHT) != 0 &&
event.getRawX() - mStartX > mMinAcceptableDelta) ||
((mDirection & DIRECTION_LEFT) !=0 &&
mStartX - event.getRawX() > mMinAcceptableDelta) ||
((mDirection & DIRECTION_UP) !=0 &&
mStartY - event.getRawY() > mMinAcceptableDelta) ||
((mDirection & DIRECTION_DOWN) !=0 &&
event.getRawY() - mStartY > mMinAcceptableDelta))) {
mHandler.removeCallbacksAndMessages(null);
activate();
end();
return true;
} else {
return false;
}
}
private void endFling(MotionEvent event) {
if (!tryEndFling(event)) {
fail();
}
}
@Override
protected void onHandle(MotionEvent event) {
int state = getState();
if (state == STATE_UNDETERMINED) {
startFling(event);
}
if (state == STATE_BEGAN) {
tryEndFling(event);
if (event.getPointerCount() > mMaxNumberOfPointersSimultaneously) {
mMaxNumberOfPointersSimultaneously = event.getPointerCount();
}
int action = event.getActionMasked();
if (action == MotionEvent.ACTION_UP) {
endFling(event);
}
}
}
@Override
protected void onCancel() {
if (mHandler != null) {
mHandler.removeCallbacksAndMessages(null);
}
}
@Override
protected void onReset() {
if (mHandler != null) {
mHandler.removeCallbacksAndMessages(null);
}
}
}

View File

@ -0,0 +1,531 @@
package com.swmansion.gesturehandler;
import android.view.MotionEvent;
import android.view.View;
import com.facebook.react.bridge.UiThreadUtil;
import java.util.Arrays;
public class GestureHandler<T extends GestureHandler> {
public static final int STATE_UNDETERMINED = 0;
public static final int STATE_FAILED = 1;
public static final int STATE_BEGAN = 2;
public static final int STATE_CANCELLED = 3;
public static final int STATE_ACTIVE = 4;
public static final int STATE_END = 5;
public static final float HIT_SLOP_NONE = Float.NaN;
private static final int HIT_SLOP_LEFT_IDX = 0;
private static final int HIT_SLOP_TOP_IDX = 1;
private static final int HIT_SLOP_RIGHT_IDX = 2;
private static final int HIT_SLOP_BOTTOM_IDX = 3;
private static final int HIT_SLOP_WIDTH_IDX = 4;
private static final int HIT_SLOP_HEIGHT_IDX = 5;
public static final int DIRECTION_RIGHT = 1;
public static final int DIRECTION_LEFT = 2;
public static final int DIRECTION_UP = 4;
public static final int DIRECTION_DOWN = 8;
private static int MAX_POINTERS_COUNT = 12;
private static MotionEvent.PointerProperties[] sPointerProps;
private static MotionEvent.PointerCoords[] sPointerCoords;
private static void initPointerProps(int size) {
if (sPointerProps == null) {
sPointerProps = new MotionEvent.PointerProperties[MAX_POINTERS_COUNT];
sPointerCoords = new MotionEvent.PointerCoords[MAX_POINTERS_COUNT];
}
for (; size > 0 && sPointerProps[size - 1] == null; size--) {
sPointerProps[size - 1] = new MotionEvent.PointerProperties();
sPointerCoords[size - 1] = new MotionEvent.PointerCoords();
}
}
private final int[] mTrackedPointerIDs = new int[MAX_POINTERS_COUNT];
private int mTrackedPointersCount = 0;
private int mTag;
private View mView;
private int mState = STATE_UNDETERMINED;
private float mX, mY;
private boolean mWithinBounds;
private boolean mEnabled = true;
private float mHitSlop[];
private static short sNextEventCoalescingKey = 0;
private short mEventCoalescingKey;
private float mLastX, mLastY;
private float mLastEventOffsetX, mLastEventOffsetY;
private boolean mShouldCancelWhenOutside;
private int mNumberOfPointers = 0;
private GestureHandlerOrchestrator mOrchestrator;
private OnTouchEventListener<T> mListener;
private GestureHandlerInteractionController mInteractionController;
/*package*/ int mActivationIndex; // set and accessed only by the orchestrator
/*package*/ boolean mIsActive; // set and accessed only by the orchestrator
/*package*/ boolean mIsAwaiting; // set and accessed only by the orchestrator
private static boolean hitSlopSet(float value) {
return !Float.isNaN(value);
}
/*package*/ void dispatchStateChange(int newState, int prevState) {
if (mListener != null) {
mListener.onStateChange((T) this, newState, prevState);
}
}
/*package*/ void dispatchTouchEvent(MotionEvent event) {
if (mListener != null) {
mListener.onTouchEvent((T) this, event);
}
}
public boolean hasCommonPointers(GestureHandler other) {
for (int i = 0; i < mTrackedPointerIDs.length; i++) {
if (mTrackedPointerIDs[i] != -1 && other.mTrackedPointerIDs[i] != -1) {
return true;
}
}
return false;
}
public T setShouldCancelWhenOutside(boolean shouldCancelWhenOutside) {
mShouldCancelWhenOutside = shouldCancelWhenOutside;
return (T) this;
}
public T setEnabled(boolean enabled) {
if (mView != null) {
// If view is set then handler is in "active" state. In that case we want to "cancel" handler
// when it changes enabled state so that it gets cleared from the orchestrator
UiThreadUtil.runOnUiThread(new Runnable() {
@Override
public void run() {
cancel();
}
});
}
mEnabled = enabled;
return (T) this;
}
public boolean isEnabled() {
return mEnabled;
}
public T setHitSlop(float leftPad, float topPad, float rightPad, float bottomPad, float width, float height) {
if (mHitSlop == null) {
mHitSlop = new float[6];
}
mHitSlop[HIT_SLOP_LEFT_IDX] = leftPad;
mHitSlop[HIT_SLOP_TOP_IDX] = topPad;
mHitSlop[HIT_SLOP_RIGHT_IDX] = rightPad;
mHitSlop[HIT_SLOP_BOTTOM_IDX] = bottomPad;
mHitSlop[HIT_SLOP_WIDTH_IDX] = width;
mHitSlop[HIT_SLOP_HEIGHT_IDX] = height;
if (hitSlopSet(width) && hitSlopSet(leftPad) && hitSlopSet(rightPad)) {
throw new IllegalArgumentException("Cannot have all of left, right and width defined");
}
if (hitSlopSet(width) && !hitSlopSet(leftPad) && !hitSlopSet(rightPad)) {
throw new IllegalArgumentException("When width is set one of left or right pads need to be defined");
}
if (hitSlopSet(height) && hitSlopSet(bottomPad) && hitSlopSet(topPad)) {
throw new IllegalArgumentException("Cannot have all of top, bottom and height defined");
}
if (hitSlopSet(height) && !hitSlopSet(bottomPad) && !hitSlopSet(topPad)) {
throw new IllegalArgumentException("When height is set one of top or bottom pads need to be defined");
}
return (T) this;
}
public T setHitSlop(float padding) {
return setHitSlop(padding, padding, padding, padding, HIT_SLOP_NONE, HIT_SLOP_NONE);
}
public T setInteractionController(GestureHandlerInteractionController controller) {
mInteractionController = controller;
return (T) this;
}
public void setTag(int tag) {
mTag = tag;
}
public int getTag() {
return mTag;
}
public View getView() {
return mView;
}
public float getX() {
return mX;
}
public float getY() {
return mY;
}
public int getNumberOfPointers() {
return mNumberOfPointers;
}
public boolean isWithinBounds() {
return mWithinBounds;
}
public short getEventCoalescingKey() {
return mEventCoalescingKey;
}
public final void prepare(View view, GestureHandlerOrchestrator orchestrator) {
if (mView != null || mOrchestrator != null) {
throw new IllegalStateException("Already prepared or hasn't been reset");
}
Arrays.fill(mTrackedPointerIDs, -1);
mTrackedPointersCount = 0;
mState = STATE_UNDETERMINED;
mView = view;
mOrchestrator = orchestrator;
}
private int findNextLocalPointerId() {
int localPointerId = 0;
for (; localPointerId < mTrackedPointersCount; localPointerId++) {
int i = 0;
for (; i < mTrackedPointerIDs.length; i++) {
if (mTrackedPointerIDs[i] == localPointerId) {
break;
}
}
if (i == mTrackedPointerIDs.length) {
return localPointerId;
}
}
return localPointerId;
}
public void startTrackingPointer(int pointerId) {
if (mTrackedPointerIDs[pointerId] == -1) {
mTrackedPointerIDs[pointerId] = findNextLocalPointerId();
mTrackedPointersCount++;
}
}
public void stopTrackingPointer(int pointerId) {
if (mTrackedPointerIDs[pointerId] != -1) {
mTrackedPointerIDs[pointerId] = -1;
mTrackedPointersCount--;
}
}
private boolean needAdapt(MotionEvent event) {
if (event.getPointerCount() != mTrackedPointersCount) {
return true;
}
for (int i = 0; i < mTrackedPointerIDs.length; i++) {
if (mTrackedPointerIDs[i] != -1 && mTrackedPointerIDs[i] != i) {
return true;
}
}
return false;
}
private MotionEvent adaptEvent(MotionEvent event) {
if (!needAdapt(event)) {
return event;
}
int action = event.getActionMasked();
int actionIndex = -1;
if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_POINTER_DOWN) {
actionIndex = event.getActionIndex();
int actionPointer = event.getPointerId(actionIndex);
if (mTrackedPointerIDs[actionPointer] != -1) {
action = mTrackedPointersCount == 1 ? MotionEvent.ACTION_DOWN : MotionEvent.ACTION_POINTER_DOWN;
} else {
action = MotionEvent.ACTION_MOVE;
}
} else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP) {
actionIndex = event.getActionIndex();
int actionPointer = event.getPointerId(actionIndex);
if (mTrackedPointerIDs[actionPointer] != -1) {
action = mTrackedPointersCount == 1 ? MotionEvent.ACTION_UP : MotionEvent.ACTION_POINTER_UP;
} else {
action = MotionEvent.ACTION_MOVE;
}
}
initPointerProps(mTrackedPointersCount);
int count = 0;
float oldX = event.getX();
float oldY = event.getY();
event.setLocation(event.getRawX(), event.getRawY());
for (int index = 0, size = event.getPointerCount(); index < size; index++) {
int origPointerId = event.getPointerId(index);
if (mTrackedPointerIDs[origPointerId] != -1) {
event.getPointerProperties(index, sPointerProps[count]);
sPointerProps[count].id = mTrackedPointerIDs[origPointerId];
event.getPointerCoords(index, sPointerCoords[count]);
if (index == actionIndex) {
action = action | (count << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
}
count++;
}
}
MotionEvent result = MotionEvent.obtain(
event.getDownTime(),
event.getEventTime(),
action,
count,
sPointerProps, /* props are copied and hence it is safe to use static array here */
sPointerCoords, /* same applies to coords */
event.getMetaState(),
event.getButtonState(),
event.getXPrecision(),
event.getYPrecision(),
event.getDeviceId(),
event.getEdgeFlags(),
event.getSource(),
event.getFlags());
event.setLocation(oldX, oldY);
result.setLocation(oldX, oldY);
return result;
}
public final void handle(MotionEvent origEvent) {
if (!mEnabled || mState == STATE_CANCELLED || mState == STATE_FAILED
|| mState == STATE_END || mTrackedPointersCount < 1) {
return;
}
MotionEvent event = adaptEvent(origEvent);
mX = event.getX();
mY = event.getY();
mNumberOfPointers = event.getPointerCount();
mWithinBounds = isWithinBounds(mView, mX, mY);
if (mShouldCancelWhenOutside && !mWithinBounds) {
if (mState == STATE_ACTIVE) {
cancel();
} else if (mState == STATE_BEGAN) {
fail();
}
return;
}
mLastX = GestureUtils.getLastPointerX(event, true);
mLastY = GestureUtils.getLastPointerY(event, true);
mLastEventOffsetX = event.getRawX() - event.getX();
mLastEventOffsetY = event.getRawY() - event.getY();
onHandle(event);
if (event != origEvent) {
event.recycle();
}
}
private void moveToState(int newState) {
UiThreadUtil.assertOnUiThread();
if (mState == newState) {
return;
}
int oldState = mState;
mState = newState;
if (mState == STATE_ACTIVE) {
// Generate a unique coalescing-key each time the gesture-handler becomes active. All events will have
// the same coalescing-key allowing EventDispatcher to coalesce RNGestureHandlerEvents when events are
// generated faster than they can be treated by JS thread
mEventCoalescingKey = sNextEventCoalescingKey++;
}
mOrchestrator.onHandlerStateChange(this, newState, oldState);
onStateChange(newState, oldState);
}
public boolean wantEvents() {
return mEnabled && mState != STATE_FAILED && mState != STATE_CANCELLED
&& mState != STATE_END && mTrackedPointersCount > 0;
}
public int getState() {
return mState;
}
public boolean shouldRequireToWaitForFailure(GestureHandler handler) {
if (handler != this && mInteractionController != null) {
return mInteractionController.shouldRequireHandlerToWaitForFailure(this, handler);
}
return false;
}
public boolean shouldWaitForHandlerFailure(GestureHandler handler) {
if (handler != this && mInteractionController != null) {
return mInteractionController.shouldWaitForHandlerFailure(this, handler);
}
return false;
}
public boolean shouldRecognizeSimultaneously(GestureHandler handler) {
if (handler == this) {
return true;
}
if (mInteractionController != null) {
return mInteractionController.shouldRecognizeSimultaneously(this, handler);
}
return false;
}
public boolean shouldBeCancelledBy(GestureHandler handler) {
if (handler == this) {
return false;
}
if (mInteractionController != null) {
return mInteractionController.shouldHandlerBeCancelledBy(this, handler);
}
return false;
}
public boolean isWithinBounds(View view, float posX, float posY) {
float left = 0;
float top = 0;
float right = view.getWidth();
float bottom = view.getHeight();
if (mHitSlop != null) {
float padLeft = mHitSlop[HIT_SLOP_LEFT_IDX];
float padTop = mHitSlop[HIT_SLOP_TOP_IDX];
float padRight = mHitSlop[HIT_SLOP_RIGHT_IDX];
float padBottom = mHitSlop[HIT_SLOP_BOTTOM_IDX];
if (hitSlopSet(padLeft)) {
left -= padLeft;
}
if (hitSlopSet(padTop)) {
top -= padBottom;
}
if (hitSlopSet(padRight)) {
right += padRight;
}
if (hitSlopSet(padBottom)) {
bottom += padBottom;
}
float width = mHitSlop[HIT_SLOP_WIDTH_IDX];
float height= mHitSlop[HIT_SLOP_HEIGHT_IDX];
if (hitSlopSet(width)) {
if (!hitSlopSet(padLeft)) {
left = right - width;
} else if (!hitSlopSet(padRight)) {
right = left + width;
}
}
if (hitSlopSet(height)) {
if (!hitSlopSet(top)) {
top = bottom - height;
} else if (!hitSlopSet(bottom)) {
bottom = top + height;
}
}
}
return posX >= left && posX <= right && posY >= top && posY <= bottom;
}
public final void cancel() {
if (mState == STATE_ACTIVE || mState == STATE_UNDETERMINED || mState == STATE_BEGAN) {
onCancel();
moveToState(STATE_CANCELLED);
}
}
public final void fail() {
if (mState == STATE_ACTIVE || mState == STATE_UNDETERMINED || mState == STATE_BEGAN) {
moveToState(STATE_FAILED);
}
}
public final void activate() {
if (mState == STATE_UNDETERMINED || mState == STATE_BEGAN) {
moveToState(STATE_ACTIVE);
}
}
public final void begin() {
if (mState == STATE_UNDETERMINED) {
moveToState(STATE_BEGAN);
}
}
public final void end() {
if (mState == STATE_BEGAN || mState == STATE_ACTIVE) {
moveToState(STATE_END);
}
}
protected void onHandle(MotionEvent event) {
moveToState(STATE_FAILED);
}
protected void onStateChange(int newState, int previousState) {
}
protected void onReset() {
}
protected void onCancel() {
}
public final void reset() {
mView = null;
mOrchestrator = null;
Arrays.fill(mTrackedPointerIDs, -1);
mTrackedPointersCount = 0;
onReset();
}
public static String stateToString(int state) {
switch (state) {
case STATE_UNDETERMINED: return "UNDETERMINED";
case STATE_ACTIVE: return "ACTIVE";
case STATE_FAILED: return "FAILED";
case STATE_BEGAN: return "BEGIN";
case STATE_CANCELLED: return "CANCELLED";
case STATE_END: return "END";
}
return null;
}
public GestureHandler setOnTouchEventListener(OnTouchEventListener<T> listener) {
mListener = listener;
return this;
}
@Override
public String toString() {
String viewString = mView == null ? null : mView.getClass().getSimpleName();
return this.getClass().getSimpleName() + "@[" + mTag + "]:" + viewString;
}
public float getLastAbsolutePositionX() {
return mLastX;
}
public float getLastAbsolutePositionY() {
return mLastY;
}
public float getLastRelativePositionX() {
return mLastX - mLastEventOffsetX;
}
public float getLastRelativePositionY() {
return mLastY - mLastEventOffsetY;
}
}

View File

@ -0,0 +1,8 @@
package com.swmansion.gesturehandler;
public interface GestureHandlerInteractionController {
boolean shouldWaitForHandlerFailure(GestureHandler handler, GestureHandler otherHandler);
boolean shouldRequireHandlerToWaitForFailure(GestureHandler handler, GestureHandler otherHandler);
boolean shouldRecognizeSimultaneously(GestureHandler handler, GestureHandler otherHandler);
boolean shouldHandlerBeCancelledBy(GestureHandler handler, GestureHandler otherHandler);
}

View File

@ -0,0 +1,543 @@
package com.swmansion.gesturehandler;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import androidx.annotation.Nullable;
public class GestureHandlerOrchestrator {
// The limit doesn't necessarily need to exists, it was just simpler to implement it that way
// it is also more allocation-wise efficient to have a fixed limit
private static final int SIMULTANEOUS_GESTURE_HANDLER_LIMIT = 20;
// Be default fully transparent views can receive touch
private static final float DEFAULT_MIN_ALPHA_FOR_TRAVERSAL = 0f;
private static final PointF sTempPoint = new PointF();
private static final float[] sMatrixTransformCoords = new float[2];
private static final Matrix sInverseMatrix = new Matrix();
private static final float[] sTempCoords = new float[2];
private static final Comparator<GestureHandler> sHandlersComparator =
new Comparator<GestureHandler>() {
@Override
public int compare(GestureHandler a, GestureHandler b) {
if (a.mIsActive && b.mIsActive || a.mIsAwaiting && b.mIsAwaiting) {
// both A and B are either active or awaiting activation, in which case we prefer one that
// has activated (or turned into "awaiting" state) earlier
return Integer.signum(b.mActivationIndex - a.mActivationIndex);
} else if (a.mIsActive) {
return -1; // only A is active
} else if (b.mIsActive) {
return 1; // only B is active
} else if (a.mIsAwaiting) {
return -1; // only A is awaiting, B is inactive
} else if (b.mIsAwaiting) {
return 1; // only B is awaiting, A is inactive
}
return 0; // both A and B are inactive, stable order matters
}
};
private final ViewGroup mWrapperView;
private final GestureHandlerRegistry mHandlerRegistry;
private final ViewConfigurationHelper mViewConfigHelper;
private final GestureHandler[] mGestureHandlers
= new GestureHandler[SIMULTANEOUS_GESTURE_HANDLER_LIMIT];
private final GestureHandler[] mAwaitingHandlers
= new GestureHandler[SIMULTANEOUS_GESTURE_HANDLER_LIMIT];
private final GestureHandler[] mPreparedHandlers
= new GestureHandler[SIMULTANEOUS_GESTURE_HANDLER_LIMIT];
private final GestureHandler[] mHandlersToCancel
= new GestureHandler[SIMULTANEOUS_GESTURE_HANDLER_LIMIT];
private int mGestureHandlersCount = 0;
private int mAwaitingHandlersCount = 0;
private boolean mIsHandlingTouch = false;
private int mHandlingChangeSemaphore = 0;
private boolean mFinishedHandlersCleanupScheduled = false;
private int mActivationIndex = 0;
private float mMinAlphaForTraversal = DEFAULT_MIN_ALPHA_FOR_TRAVERSAL;
public GestureHandlerOrchestrator(
ViewGroup wrapperView,
GestureHandlerRegistry registry,
ViewConfigurationHelper viewConfigurationHelper) {
mWrapperView = wrapperView;
mHandlerRegistry = registry;
mViewConfigHelper = viewConfigurationHelper;
}
/**
* Minimum alpha (value from 0 to 1) that should be set to a view so that it can be treated as a
* gesture target. E.g. if set to 0.1 then views that less than 10% opaque will be ignored when
* traversing view hierarchy and looking for gesture handlers.
*/
public void setMinimumAlphaForTraversal(float alpha) {
mMinAlphaForTraversal = alpha;
}
/**
* Should be called from the view wrapper
*/
public boolean onTouchEvent(MotionEvent event) {
mIsHandlingTouch = true;
int action = event.getActionMasked();
if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_POINTER_DOWN) {
extractGestureHandlers(event);
} else if (action == MotionEvent.ACTION_CANCEL) {
cancelAll();
}
deliverEventToGestureHandlers(event);
mIsHandlingTouch = false;
if (mFinishedHandlersCleanupScheduled && mHandlingChangeSemaphore == 0) {
cleanupFinishedHandlers();
}
return true;
}
private void scheduleFinishedHandlersCleanup() {
if (mIsHandlingTouch || mHandlingChangeSemaphore != 0) {
mFinishedHandlersCleanupScheduled = true;
} else {
cleanupFinishedHandlers();
}
}
private void cleanupFinishedHandlers() {
boolean shouldCleanEmptyCells = false;
for (int i = mGestureHandlersCount - 1; i >= 0; i--) {
GestureHandler handler = mGestureHandlers[i];
if (isFinished(handler.getState()) && !handler.mIsAwaiting) {
mGestureHandlers[i] = null;
shouldCleanEmptyCells = true;
handler.reset();
handler.mIsActive = false;
handler.mIsAwaiting = false;
handler.mActivationIndex = Integer.MAX_VALUE;
}
}
if (shouldCleanEmptyCells) {
int out = 0;
for (int i = 0; i < mGestureHandlersCount; i++) {
if (mGestureHandlers[i] != null) {
mGestureHandlers[out++] = mGestureHandlers[i];
}
}
mGestureHandlersCount = out;
}
mFinishedHandlersCleanupScheduled = false;
}
private boolean hasOtherHandlerToWaitFor(GestureHandler handler) {
for (int i = 0; i < mGestureHandlersCount; i++) {
GestureHandler otherHandler = mGestureHandlers[i];
if (!isFinished(otherHandler.getState())
&& shouldHandlerWaitForOther(handler, otherHandler)) {
return true;
}
}
return false;
}
private void tryActivate(GestureHandler handler) {
// see if there is anyone else who we need to wait for
if (hasOtherHandlerToWaitFor(handler)) {
addAwaitingHandler(handler);
} else {
// we can activate handler right away
makeActive(handler);
handler.mIsAwaiting = false;
}
}
private void cleanupAwaitingHandlers() {
int out = 0;
for (int i = 0; i < mAwaitingHandlersCount; i++) {
if (mAwaitingHandlers[i].mIsAwaiting) {
mAwaitingHandlers[out++] = mAwaitingHandlers[i];
}
}
mAwaitingHandlersCount = out;
}
/*package*/ void onHandlerStateChange(GestureHandler handler, int newState, int prevState) {
mHandlingChangeSemaphore += 1;
if (isFinished(newState)) {
// if there were handlers awaiting completion of this handler, we can trigger active state
for (int i = 0; i < mAwaitingHandlersCount; i++) {
GestureHandler otherHandler = mAwaitingHandlers[i];
if (shouldHandlerWaitForOther(otherHandler, handler)) {
if (newState == GestureHandler.STATE_END) {
// gesture has ended, we need to kill the awaiting handler
otherHandler.cancel();
otherHandler.mIsAwaiting = false;
} else {
// gesture has failed recognition, we may try activating
tryActivate(otherHandler);
}
}
}
cleanupAwaitingHandlers();
}
if (newState == GestureHandler.STATE_ACTIVE) {
tryActivate(handler);
} else if (prevState == GestureHandler.STATE_ACTIVE || prevState == GestureHandler.STATE_END) {
if (handler.mIsActive) {
handler.dispatchStateChange(newState, prevState);
}
} else {
handler.dispatchStateChange(newState, prevState);
}
mHandlingChangeSemaphore -= 1;
scheduleFinishedHandlersCleanup();
}
private void makeActive(GestureHandler handler) {
int currentState = handler.getState();
handler.mIsAwaiting = false;
handler.mIsActive = true;
handler.mActivationIndex = mActivationIndex++;
int toCancelCount = 0;
// Cancel all handlers that are required to be cancel upon current handler's activation
for (int i = 0; i < mGestureHandlersCount; i++) {
GestureHandler otherHandler = mGestureHandlers[i];
if (shouldHandlerBeCancelledBy(otherHandler, handler)) {
mHandlersToCancel[toCancelCount++] = otherHandler;
}
}
for (int i = toCancelCount - 1; i >= 0; i--) {
mHandlersToCancel[i].cancel();
}
// Clear all awaiting handlers waiting for the current handler to fail
for (int i = mAwaitingHandlersCount - 1; i >= 0; i--) {
GestureHandler otherHandler = mAwaitingHandlers[i];
if (shouldHandlerBeCancelledBy(otherHandler, handler)) {
otherHandler.cancel();
otherHandler.mIsAwaiting = false;
}
}
cleanupAwaitingHandlers();
// Dispatch state change event if handler is no longer in the active state we should also
// trigger END state change and UNDETERMINED state change if necessary
handler.dispatchStateChange(GestureHandler.STATE_ACTIVE, GestureHandler.STATE_BEGAN);
if (currentState != GestureHandler.STATE_ACTIVE) {
handler.dispatchStateChange(GestureHandler.STATE_END, GestureHandler.STATE_ACTIVE);
if (currentState != GestureHandler.STATE_END) {
handler.dispatchStateChange(GestureHandler.STATE_UNDETERMINED, GestureHandler.STATE_END);
}
}
}
public void deliverEventToGestureHandlers(MotionEvent event) {
// Copy handlers to "prepared handlers" array, because the list of active handlers can change
// as a result of state updates
int handlersCount = mGestureHandlersCount;
System.arraycopy(mGestureHandlers, 0, mPreparedHandlers, 0, handlersCount);
// We want to deliver events to active handlers first in order of their activation (handlers
// that activated first will first get event delivered). Otherwise we deliver events in the
// order in which handlers has been added ("most direct" children goes first). Therefore we rely
// on Arrays.sort providing a stable sort (as children are registered in order in which they
// should be tested)
Arrays.sort(mPreparedHandlers, 0, handlersCount, sHandlersComparator);
for (int i = 0; i < handlersCount; i++) {
deliverEventToGestureHandler(mPreparedHandlers[i], event);
}
}
private void cancelAll() {
for (int i = mAwaitingHandlersCount - 1; i >= 0; i--) {
mAwaitingHandlers[i].cancel();
}
// Copy handlers to "prepared handlers" array, because the list of active handlers can change
// as a result of state updates
int handlersCount = mGestureHandlersCount;
for (int i = 0; i < handlersCount; i++) {
mPreparedHandlers[i] = mGestureHandlers[i];
}
for (int i = handlersCount - 1; i >= 0; i--) {
mPreparedHandlers[i].cancel();
}
}
private void deliverEventToGestureHandler(GestureHandler handler, MotionEvent event) {
if (!isViewAttachedUnderWrapper(handler.getView())) {
handler.cancel();
return;
}
if (!handler.wantEvents()) {
return;
}
int action = event.getActionMasked();
if (handler.mIsAwaiting && action == MotionEvent.ACTION_MOVE) {
return;
}
float[] coords = sTempCoords;
extractCoordsForView(handler.getView(), event, coords);
float oldX = event.getX();
float oldY = event.getY();
// TODO: we may conside scaling events if necessary using MotionEvent.transform
// for now the events are only offset to the top left corner of the view but if
// view or any ot the parents is scaled the other pointers position will not reflect
// their actual place in the view. On the other hand not scaling seems like a better
// approach when we want to use pointer coordinates to calculate velocity or distance
// for pinch so I don't know yet if we should transform or not...
event.setLocation(coords[0], coords[1]);
handler.handle(event);
if (handler.mIsActive) {
handler.dispatchTouchEvent(event);
}
event.setLocation(oldX, oldY);
// if event was of type UP or POINTER_UP we request handler to stop tracking now that
// the event has been dispatched
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP) {
int pointerId = event.getPointerId(event.getActionIndex());
handler.stopTrackingPointer(pointerId);
}
}
/**
* isViewAttachedUnderWrapper checks whether all of parents for view related to handler
* view are attached. Since there might be an issue rarely observed when view
* has been detached and handler's state hasn't been change to canceled, failed or
* ended yet. Probably it's a result of some race condition and stopping delivering
* for this handler and changing its state to failed of end appear to be good enough solution.
*/
private boolean isViewAttachedUnderWrapper(@Nullable View view) {
if (view == null) {
return false;
}
if (view == mWrapperView) {
return true;
}
@Nullable ViewParent parent = view.getParent();
while (parent != null && parent != mWrapperView) {
parent = parent.getParent();
}
return parent == mWrapperView;
}
private void extractCoordsForView(View view, MotionEvent event, float[] outputCoords) {
if (view == mWrapperView) {
outputCoords[0] = event.getX();
outputCoords[1] = event.getY();
return;
}
if (view == null || !(view.getParent() instanceof ViewGroup)) {
throw new IllegalArgumentException("Parent is null? View is no longer in the tree");
}
ViewGroup parent = (ViewGroup) view.getParent();
extractCoordsForView(parent, event, outputCoords);
PointF childPoint = sTempPoint;
transformTouchPointToViewCoords(outputCoords[0], outputCoords[1], parent, view, childPoint);
outputCoords[0] = childPoint.x;
outputCoords[1] = childPoint.y;
}
private void addAwaitingHandler(GestureHandler handler) {
for (int i = 0; i < mAwaitingHandlersCount; i++) {
if (mAwaitingHandlers[i] == handler) {
return;
}
}
if (mAwaitingHandlersCount >= mAwaitingHandlers.length) {
throw new IllegalStateException("Too many recognizers");
}
mAwaitingHandlers[mAwaitingHandlersCount++] = handler;
handler.mIsAwaiting = true;
handler.mActivationIndex = mActivationIndex++;
}
private void recordHandlerIfNotPresent(GestureHandler handler, View view) {
for (int i = 0; i < mGestureHandlersCount; i++) {
if (mGestureHandlers[i] == handler) {
return;
}
}
if (mGestureHandlersCount >= mGestureHandlers.length) {
throw new IllegalStateException("Too many recognizers");
}
mGestureHandlers[mGestureHandlersCount++] = handler;
handler.mIsActive = false;
handler.mIsAwaiting = false;
handler.mActivationIndex = Integer.MAX_VALUE;
handler.prepare(view, this);
}
private boolean recordViewHandlersForPointer(View view, float[] coords, int pointerId) {
ArrayList<GestureHandler> handlers = mHandlerRegistry.getHandlersForView(view);
boolean found = false;
if (handlers != null) {
for (int i = 0, size = handlers.size(); i < size; i++) {
GestureHandler handler = handlers.get(i);
if (handler.isEnabled() && handler.isWithinBounds(view, coords[0], coords[1])) {
recordHandlerIfNotPresent(handler, view);
handler.startTrackingPointer(pointerId);
found = true;
}
}
}
return found;
}
private void extractGestureHandlers(MotionEvent event) {
int actionIndex = event.getActionIndex();
int pointerId = event.getPointerId(actionIndex);
sTempCoords[0] = event.getX(actionIndex);
sTempCoords[1] = event.getY(actionIndex);
traverseWithPointerEvents(mWrapperView, sTempCoords, pointerId);
extractGestureHandlers(mWrapperView, sTempCoords, pointerId);
}
private boolean extractGestureHandlers(ViewGroup viewGroup, float[] coords, int pointerId) {
int childrenCount = viewGroup.getChildCount();
for (int i = childrenCount - 1; i >= 0; i--) {
View child = mViewConfigHelper.getChildInDrawingOrderAtIndex(viewGroup, i);
if (canReceiveEvents(child)) {
PointF childPoint = sTempPoint;
transformTouchPointToViewCoords(coords[0], coords[1], viewGroup, child, childPoint);
float restoreX = coords[0];
float restoreY = coords[1];
coords[0] = childPoint.x;
coords[1] = childPoint.y;
boolean found = false;
if (!isClipping(child) || isTransformedTouchPointInView(coords[0], coords[1], child)) {
// we only consider the view if touch is inside the view bounds or if the view's children
// can render outside of the view bounds (overflow visible)
found = traverseWithPointerEvents(child, coords, pointerId);
}
coords[0] = restoreX;
coords[1] = restoreY;
if (found) {
return true;
}
}
}
return false;
}
private static boolean shouldHandlerlessViewBecomeTouchTarget(View view, float coords[]) {
// The following code is to match the iOS behavior where transparent parts of the views can
// pass touch events through them allowing sibling nodes to handle them.
// TODO: this is not an ideal solution as we only consider ViewGroups that has no background set
// TODO: ideally we should determine the pixel color under the given coordinates and return
// false if the color is transparent
boolean isLeafOrTransparent = !(view instanceof ViewGroup) || view.getBackground() != null;
return isLeafOrTransparent && isTransformedTouchPointInView(coords[0], coords[1], view);
}
private boolean traverseWithPointerEvents(View view, float coords[], int pointerId) {
PointerEventsConfig pointerEvents = mViewConfigHelper.getPointerEventsConfigForView(view);
if (pointerEvents == PointerEventsConfig.NONE) {
// This view and its children can't be the target
return false;
} else if (pointerEvents == PointerEventsConfig.BOX_ONLY) {
// This view is the target, its children don't matter
return recordViewHandlersForPointer(view, coords, pointerId)
|| shouldHandlerlessViewBecomeTouchTarget(view, coords);
} else if (pointerEvents == PointerEventsConfig.BOX_NONE) {
// This view can't be the target, but its children might
if (view instanceof ViewGroup) {
return extractGestureHandlers((ViewGroup) view, coords, pointerId);
}
return false;
} else if (pointerEvents == PointerEventsConfig.AUTO) {
// Either this view or one of its children is the target
boolean found = false;
if (view instanceof ViewGroup) {
found = extractGestureHandlers((ViewGroup) view, coords, pointerId);
}
return recordViewHandlersForPointer(view, coords, pointerId)
|| found || shouldHandlerlessViewBecomeTouchTarget(view, coords);
} else {
throw new IllegalArgumentException(
"Unknown pointer event type: " + pointerEvents.toString());
}
}
private boolean canReceiveEvents(View view) {
return view.getVisibility() == View.VISIBLE && view.getAlpha() >= mMinAlphaForTraversal;
}
private static void transformTouchPointToViewCoords(
float x,
float y,
ViewGroup parent,
View child,
PointF outLocalPoint) {
float localX = x + parent.getScrollX() - child.getLeft();
float localY = y + parent.getScrollY() - child.getTop();
Matrix matrix = child.getMatrix();
if (!matrix.isIdentity()) {
float[] localXY = sMatrixTransformCoords;
localXY[0] = localX;
localXY[1] = localY;
Matrix inverseMatrix = sInverseMatrix;
matrix.invert(inverseMatrix);
inverseMatrix.mapPoints(localXY);
localX = localXY[0];
localY = localXY[1];
}
outLocalPoint.set(localX, localY);
}
private boolean isClipping(View view) {
// if view is not a view group it is clipping, otherwise we check for `getClipChildren` flag to
// be turned on and also confirm with the ViewConfigHelper implementation
return !(view instanceof ViewGroup) || mViewConfigHelper.isViewClippingChildren((ViewGroup) view);
}
private static boolean isTransformedTouchPointInView(float x, float y, View child) {
return x >= 0 && x <= child.getWidth() && y >= 0 && y < child.getHeight();
}
private static boolean shouldHandlerWaitForOther(GestureHandler handler, GestureHandler other) {
return handler != other && (handler.shouldWaitForHandlerFailure(other)
|| other.shouldRequireToWaitForFailure(handler));
}
private static boolean canRunSimultaneously(GestureHandler a, GestureHandler b) {
return a == b || a.shouldRecognizeSimultaneously(b) || b.shouldRecognizeSimultaneously(a);
}
private static boolean shouldHandlerBeCancelledBy(GestureHandler handler, GestureHandler other) {
if (!handler.hasCommonPointers(other)) {
// if two handlers share no common pointer one can never trigger cancel for the other
return false;
}
if (canRunSimultaneously(handler, other)) {
// if handlers are allowed to run simultaneously, when first activates second can still remain
// in began state
return false;
}
if (handler != other &&
(handler.mIsAwaiting || handler.getState() == GestureHandler.STATE_ACTIVE)) {
// in every other case as long as the handler is about to be activated or already in active
// state, we delegate the decision to the implementation of GestureHandler#shouldBeCancelledBy
return handler.shouldBeCancelledBy(other);
}
return true;
}
private static boolean isFinished(int state) {
return state == GestureHandler.STATE_CANCELLED || state == GestureHandler.STATE_FAILED
|| state == GestureHandler.STATE_END;
}
}

View File

@ -0,0 +1,10 @@
package com.swmansion.gesturehandler;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
public interface GestureHandlerRegistry {
ArrayList<GestureHandler> getHandlersForView(View view);
}

View File

@ -0,0 +1,29 @@
package com.swmansion.gesturehandler;
import android.view.View;
import java.util.ArrayList;
import java.util.WeakHashMap;
public class GestureHandlerRegistryImpl implements GestureHandlerRegistry {
private WeakHashMap<View, ArrayList<GestureHandler>> mHandlers = new WeakHashMap<>();
public <T extends GestureHandler> T registerHandlerForView(View view, T handler) {
ArrayList<GestureHandler> listToAdd = mHandlers.get(view);
if (listToAdd == null) {
listToAdd = new ArrayList<>(1);
listToAdd.add(handler);
mHandlers.put(view, listToAdd);
} else {
listToAdd.add(handler);
}
return handler;
}
@Override
public ArrayList<GestureHandler> getHandlersForView(View view) {
return mHandlers.get(view);
}
}

View File

@ -0,0 +1,53 @@
package com.swmansion.gesturehandler;
import android.view.MotionEvent;
public class GestureUtils {
public static float getLastPointerX(MotionEvent event, boolean averageTouches) {
float offset = event.getRawX() - event.getX();
int excludeIndex = event.getActionMasked() == MotionEvent.ACTION_POINTER_UP ?
event.getActionIndex() : -1;
if (averageTouches) {
float sum = 0f;
int count = 0;
for (int i = 0, size = event.getPointerCount(); i < size; i++) {
if (i != excludeIndex) {
sum += event.getX(i) + offset;
count++;
}
}
return sum / count;
} else {
int lastPointerIdx = event.getPointerCount() - 1;
if (lastPointerIdx == excludeIndex) {
lastPointerIdx--;
}
return event.getX(lastPointerIdx) + offset;
}
}
public static float getLastPointerY(MotionEvent event, boolean averageTouches) {
float offset = event.getRawY() - event.getY();
int excludeIndex = event.getActionMasked() == MotionEvent.ACTION_POINTER_UP ?
event.getActionIndex() : -1;
if (averageTouches) {
float sum = 0f;
int count = 0;
for (int i = 0, size = event.getPointerCount(); i < size; i++) {
if (i != excludeIndex) {
sum += event.getY(i) + offset;
count++;
}
}
return sum / count;
} else {
int lastPointerIdx = event.getPointerCount() - 1;
if (lastPointerIdx == excludeIndex) {
lastPointerIdx -= 1;
}
return event.getY(lastPointerIdx) + offset;
}
}
}

View File

@ -0,0 +1,77 @@
package com.swmansion.gesturehandler;
import android.content.Context;
import android.os.Handler;
import android.view.MotionEvent;
public class LongPressGestureHandler extends GestureHandler<LongPressGestureHandler> {
private static final long DEFAULT_MIN_DURATION_MS = 500; // 1 sec
private static float DEFAULT_MAX_DIST_DP = 10; // 20dp
private long mMinDurationMs = DEFAULT_MIN_DURATION_MS;
private float mMaxDistSq;
private float mStartX, mStartY;
private Handler mHandler;
public LongPressGestureHandler(Context context) {
setShouldCancelWhenOutside(true);
mMaxDistSq = DEFAULT_MAX_DIST_DP * context.getResources().getDisplayMetrics().density;
}
public void setMinDurationMs(long minDurationMs) {
mMinDurationMs = minDurationMs;
}
public LongPressGestureHandler setMaxDist(float maxDist) {
mMaxDistSq = maxDist * maxDist;
return this;
}
@Override
protected void onHandle(MotionEvent event) {
if (getState() == STATE_UNDETERMINED) {
begin();
mStartX = event.getRawX();
mStartY = event.getRawY();
mHandler = new Handler();
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
activate();
}
}, mMinDurationMs);
}
if (event.getActionMasked() == MotionEvent.ACTION_UP) {
if (mHandler != null) {
mHandler.removeCallbacksAndMessages(null);
mHandler = null;
}
if (getState() == STATE_ACTIVE) {
end();
} else {
fail();
}
} else {
// calculate distance from start
float deltaX = event.getRawX() - mStartX;
float deltaY = event.getRawY() - mStartY;
float distSq = deltaX * deltaX + deltaY * deltaY;
if (distSq > mMaxDistSq) {
if (getState() == STATE_ACTIVE) {
cancel();
} else {
fail();
}
}
}
}
@Override
protected void onStateChange(int newState, int previousState) {
if (mHandler != null) {
mHandler.removeCallbacksAndMessages(null);
mHandler = null;
}
}
}

View File

@ -0,0 +1,110 @@
package com.swmansion.gesturehandler;
import android.os.SystemClock;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
public class NativeViewGestureHandler extends GestureHandler<NativeViewGestureHandler> {
private boolean mShouldActivateOnStart;
private boolean mDisallowInterruption;
public NativeViewGestureHandler() {
setShouldCancelWhenOutside(true);
}
public NativeViewGestureHandler setShouldActivateOnStart(boolean shouldActivateOnStart) {
mShouldActivateOnStart = shouldActivateOnStart;
return this;
}
/**
* Set this to {@code true} when wrapping native components that are supposed to be an exclusive
* target for a touch stream. Like for example switch or slider component which when activated
* aren't supposed to be cancelled by scrollview or other container that may also handle touches.
*/
public NativeViewGestureHandler setDisallowInterruption(boolean disallowInterruption) {
mDisallowInterruption = disallowInterruption;
return this;
}
@Override
public boolean shouldRequireToWaitForFailure(GestureHandler handler) {
return super.shouldRequireToWaitForFailure(handler);
}
@Override
public boolean shouldRecognizeSimultaneously(GestureHandler handler) {
if (handler instanceof NativeViewGestureHandler) {
// Special case when the peer handler is also an instance of NativeViewGestureHandler:
// For the `disallowInterruption` to work correctly we need to check the property when
// accessed as a peer, because simultaneous recognizers can be set on either side of the
// connection.
NativeViewGestureHandler nativeWrapper = (NativeViewGestureHandler) handler;
if (nativeWrapper.getState() == STATE_ACTIVE && nativeWrapper.mDisallowInterruption) {
// other handler is active and it disallows interruption, we don't want to get into its way
return false;
}
}
boolean canBeInterrupted = !mDisallowInterruption;
int state = getState();
int otherState = handler.getState();
if (state == STATE_ACTIVE && otherState == STATE_ACTIVE && canBeInterrupted) {
// if both handlers are active and the current handler can be interruped it we return `false`
// as it means the other handler has turned active and returning `true` would prevent it from
// interrupting the current handler
return false;
}
// otherwise we can only return `true` if already in an active state
return state == STATE_ACTIVE && canBeInterrupted;
}
@Override
public boolean shouldBeCancelledBy(GestureHandler handler) {
return !mDisallowInterruption;
}
@Override
protected void onHandle(MotionEvent event) {
View view = getView();
int state = getState();
if (event.getActionMasked() == MotionEvent.ACTION_UP) {
view.onTouchEvent(event);
if ((state == STATE_UNDETERMINED || state == STATE_BEGAN) && view.isPressed()) {
activate();
}
end();
} else if (state == STATE_UNDETERMINED || state == STATE_BEGAN) {
if (mShouldActivateOnStart) {
tryIntercept(view, event);
view.onTouchEvent(event);
activate();
} else if (tryIntercept(view, event)) {
view.onTouchEvent(event);
activate();
} else if (state != STATE_BEGAN) {
begin();
}
} else if (state == STATE_ACTIVE) {
view.onTouchEvent(event);
}
}
private static boolean tryIntercept(View view, MotionEvent event) {
if (view instanceof ViewGroup && ((ViewGroup) view).onInterceptTouchEvent(event)) {
return true;
}
return false;
}
@Override
protected void onCancel() {
long time = SystemClock.uptimeMillis();
MotionEvent event = MotionEvent.obtain(time, time, MotionEvent.ACTION_CANCEL, 0, 0, 0);
event.setAction(MotionEvent.ACTION_CANCEL);
getView().onTouchEvent(event);
}
}

View File

@ -0,0 +1,8 @@
package com.swmansion.gesturehandler;
import android.view.MotionEvent;
public interface OnTouchEventListener<T extends GestureHandler> {
void onTouchEvent(T handler, MotionEvent event);
void onStateChange(T handler, int newState, int oldState);
}

View File

@ -0,0 +1,312 @@
package com.swmansion.gesturehandler;
import android.content.Context;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
public class PanGestureHandler extends GestureHandler<PanGestureHandler> {
private static float MIN_VALUE_IGNORE = Float.MAX_VALUE;
private static float MAX_VALUE_IGNORE = Float.MIN_VALUE;
private static int DEFAULT_MIN_POINTERS = 1;
private static int DEFAULT_MAX_POINTERS = 10;
private float mMinDistSq = MAX_VALUE_IGNORE;
private float mActiveOffsetXStart = MIN_VALUE_IGNORE;
private float mActiveOffsetXEnd = MAX_VALUE_IGNORE;
private float mFailOffsetXStart = MAX_VALUE_IGNORE;
private float mFailOffsetXEnd = MIN_VALUE_IGNORE;
private float mActiveOffsetYStart = MIN_VALUE_IGNORE;
private float mActiveOffsetYEnd = MAX_VALUE_IGNORE;
private float mFailOffsetYStart = MAX_VALUE_IGNORE;
private float mFailOffsetYEnd = MIN_VALUE_IGNORE;
private float mMinVelocityX = MIN_VALUE_IGNORE;
private float mMinVelocityY = MIN_VALUE_IGNORE;
private float mMinVelocitySq = MIN_VALUE_IGNORE;
private int mMinPointers = DEFAULT_MIN_POINTERS;
private int mMaxPointers = DEFAULT_MAX_POINTERS;
private float mStartX, mStartY;
private float mOffsetX, mOffsetY;
private float mLastX, mLastY;
private float mLastVelocityX, mLastVelocityY;
private VelocityTracker mVelocityTracker;
private boolean mAverageTouches;
/**
* On Android when there are multiple pointers on the screen pan gestures most often just consider
* the last placed pointer. The behaviour on iOS is quite different where the x and y component
* of the pan pointer is calculated as an average out of all the pointers placed on the screen.
*
* This behaviour can be customized on android by setting averageTouches property of the handler
* object. This could be useful in particular for the usecases when we attach other handlers that
* recognizes multi-finger gestures such as rotation. In that case when we only rely on the last
* placed finger it is easier for the gesture handler to trigger when we do a rotation gesture
* because each finger when treated separately will travel some distance, whereas the average
* position of all the fingers will remain still while doing a rotation gesture.
*/
public PanGestureHandler(Context context) {
ViewConfiguration vc = ViewConfiguration.get(context);
int touchSlop = vc.getScaledTouchSlop();
mMinDistSq = touchSlop * touchSlop;
}
public PanGestureHandler setActiveOffsetXStart(float activeOffsetXStart) {
mActiveOffsetXStart = activeOffsetXStart;
return this;
}
public PanGestureHandler setActiveOffsetXEnd(float activeOffsetXEnd) {
mActiveOffsetXEnd = activeOffsetXEnd;
return this;
}
public PanGestureHandler setFailOffsetXStart(float failOffsetXStart) {
mFailOffsetXStart = failOffsetXStart;
return this;
}
public PanGestureHandler setFailOffsetXEnd(float failOffsetXEnd) {
mFailOffsetXEnd = failOffsetXEnd;
return this;
}
public PanGestureHandler setActiveOffsetYStart(float activeOffsetYStart) {
mActiveOffsetYStart = activeOffsetYStart;
return this;
}
public PanGestureHandler setActiveOffsetYEnd(float activeOffsetYEnd) {
mActiveOffsetYEnd = activeOffsetYEnd;
return this;
}
public PanGestureHandler setFailOffsetYStart(float failOffsetYStart) {
mFailOffsetYStart = failOffsetYStart;
return this;
}
public PanGestureHandler setFailOffsetYEnd(float failOffsetYEnd) {
mFailOffsetYEnd = failOffsetYEnd;
return this;
}
public PanGestureHandler setMinDist(float minDist) {
mMinDistSq = minDist * minDist;
return this;
}
public PanGestureHandler setMinPointers(int minPointers) {
mMinPointers = minPointers;
return this;
}
public PanGestureHandler setMaxPointers(int maxPointers) {
mMaxPointers = maxPointers;
return this;
}
public PanGestureHandler setAverageTouches(boolean averageTouches) {
mAverageTouches = averageTouches;
return this;
}
/**
* @param minVelocity in pixels per second
*/
public PanGestureHandler setMinVelocity(float minVelocity) {
mMinVelocitySq = minVelocity * minVelocity;
return this;
}
public PanGestureHandler setMinVelocityX(float minVelocityX) {
mMinVelocityX = minVelocityX;
return this;
}
public PanGestureHandler setMinVelocityY(float minVelocityY) {
mMinVelocityY = minVelocityY;
return this;
}
private boolean shouldActivate() {
float dx = mLastX - mStartX + mOffsetX;
if (mActiveOffsetXStart != MIN_VALUE_IGNORE && dx < mActiveOffsetXStart) {
return true;
}
if (mActiveOffsetXEnd != MAX_VALUE_IGNORE && dx > mActiveOffsetXEnd) {
return true;
}
float dy = mLastY - mStartY + mOffsetY;
if (mActiveOffsetYStart != MIN_VALUE_IGNORE && dy < mActiveOffsetYStart) {
return true;
}
if (mActiveOffsetYEnd != MAX_VALUE_IGNORE && dy > mActiveOffsetYEnd) {
return true;
}
float distSq = dx * dx + dy * dy;
if (mMinDistSq != MIN_VALUE_IGNORE && distSq >= mMinDistSq) {
return true;
}
float vx = mLastVelocityX;
if (mMinVelocityX != MIN_VALUE_IGNORE &&
((mMinVelocityX < 0 && vx <= mMinVelocityX) || (mMinVelocityX >= 0 && vx >= mMinVelocityX))) {
return true;
}
float vy = mLastVelocityY;
if (mMinVelocityY != MIN_VALUE_IGNORE &&
((mMinVelocityY < 0 && vx <= mMinVelocityY) || (mMinVelocityY >= 0 && vx >= mMinVelocityY))) {
return true;
}
float velocitySq = vx * vx + vy * vy;
if (mMinVelocitySq != MIN_VALUE_IGNORE && velocitySq >= mMinVelocitySq) {
return true;
}
return false;
}
private boolean shouldFail() {
float dx = mLastX - mStartX + mOffsetX;
if (mFailOffsetXStart != MAX_VALUE_IGNORE && dx < mFailOffsetXStart) {
return true;
}
if (mFailOffsetXEnd != MIN_VALUE_IGNORE && dx > mFailOffsetXEnd) {
return true;
}
float dy = mLastY - mStartY + mOffsetY;
if (mFailOffsetYStart != MAX_VALUE_IGNORE && dy < mFailOffsetYStart) {
return true;
}
if (mFailOffsetYEnd != MIN_VALUE_IGNORE && dy > mFailOffsetYEnd) {
return true;
}
return false;
}
@Override
protected void onHandle(MotionEvent event) {
int state = getState();
int action = event.getActionMasked();
if (action == MotionEvent.ACTION_POINTER_UP || action == MotionEvent.ACTION_POINTER_DOWN) {
// update offset if new pointer gets added or removed
mOffsetX += mLastX - mStartX;
mOffsetY += mLastY - mStartY;
// reset starting point
mLastX = GestureUtils.getLastPointerX(event, mAverageTouches);
mLastY = GestureUtils.getLastPointerY(event, mAverageTouches);
mStartX = mLastX;
mStartY = mLastY;
} else {
mLastX = GestureUtils.getLastPointerX(event, mAverageTouches);
mLastY = GestureUtils.getLastPointerY(event, mAverageTouches);
}
if (state == STATE_UNDETERMINED && event.getPointerCount() >= mMinPointers) {
mStartX = mLastX;
mStartY = mLastY;
mOffsetX = 0;
mOffsetY = 0;
mVelocityTracker = VelocityTracker.obtain();
addVelocityMovement(mVelocityTracker, event);
begin();
} else if (mVelocityTracker != null) {
addVelocityMovement(mVelocityTracker, event);
mVelocityTracker.computeCurrentVelocity(1000);
mLastVelocityX = mVelocityTracker.getXVelocity();
mLastVelocityY = mVelocityTracker.getYVelocity();
}
if (action == MotionEvent.ACTION_UP) {
if (state == STATE_ACTIVE || state == STATE_BEGAN) {
end();
} else {
fail();
}
} else if (action == MotionEvent.ACTION_POINTER_DOWN && event.getPointerCount() > mMaxPointers) {
// When new finger is placed down (POINTER_DOWN) we check if MAX_POINTERS is not exceeded
if (state == STATE_ACTIVE) {
cancel();
} else {
fail();
}
} else if (action == MotionEvent.ACTION_POINTER_UP && state == STATE_ACTIVE
&& event.getPointerCount() < mMinPointers) {
// When finger is lifted up (POINTER_UP) and the number of pointers falls below MIN_POINTERS
// threshold, we only want to take an action when the handler has already activated. Otherwise
// we can still expect more fingers to be placed on screen and fulfill MIN_POINTERS criteria.
fail();
} else if (state == STATE_BEGAN) {
if (shouldFail()) {
fail();
} else if (shouldActivate()) {
// reset starting point
mStartX = mLastX;
mStartY = mLastY;
activate();
}
}
}
@Override
protected void onReset() {
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
public float getTranslationX() {
return mLastX - mStartX + mOffsetX;
}
public float getTranslationY() {
return mLastY - mStartY + mOffsetY;
}
public float getVelocityX() {
return mLastVelocityX;
}
public float getVelocityY() {
return mLastVelocityY;
}
/**
* This method adds movement to {@class VelocityTracker} first resetting offset of the event so
* that the velocity is calculated based on the absolute position of touch pointers. This is
* because if the underlying view moves along with the finger using relative x/y coords yields
* incorrect results.
*/
private static void addVelocityMovement(VelocityTracker tracker, MotionEvent event) {
float offsetX = event.getRawX() - event.getX();
float offsetY = event.getRawY() - event.getY();
event.offsetLocation(offsetX, offsetY);
tracker.addMovement(event);
event.offsetLocation(-offsetX, -offsetY);
}
}

View File

@ -0,0 +1,109 @@
package com.swmansion.gesturehandler;
import android.content.Context;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.ViewConfiguration;
public class PinchGestureHandler extends GestureHandler<PinchGestureHandler> {
private ScaleGestureDetector mScaleGestureDetector;
private double mLastScaleFactor;
private double mLastVelocity;
private float mStartingSpan;
private float mSpanSlop;
private ScaleGestureDetector.OnScaleGestureListener mGestureListener =
new ScaleGestureDetector.OnScaleGestureListener() {
@Override
public boolean onScale(ScaleGestureDetector detector) {
double prevScaleFactor = mLastScaleFactor;
mLastScaleFactor *= detector.getScaleFactor();
long delta = detector.getTimeDelta();
if (delta > 0) {
mLastVelocity = (mLastScaleFactor - prevScaleFactor) / delta;
}
if (Math.abs(mStartingSpan - detector.getCurrentSpan()) >= mSpanSlop
&& getState() == STATE_BEGAN) {
activate();
}
return true;
}
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
mStartingSpan = detector.getCurrentSpan();
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
// ScaleGestureDetector thinks that when fingers are 27mm away that's a sufficiently good
// reason to trigger this method giving us no other choice but to ignore it completely.
}
};
public PinchGestureHandler() {
setShouldCancelWhenOutside(false);
}
@Override
protected void onHandle(MotionEvent event) {
if (getState() == STATE_UNDETERMINED) {
Context context = getView().getContext();
mLastVelocity = 0f;
mLastScaleFactor = 1f;
mScaleGestureDetector = new ScaleGestureDetector(context, mGestureListener);
ViewConfiguration configuration = ViewConfiguration.get(context);
mSpanSlop = configuration.getScaledTouchSlop();
begin();
}
if (mScaleGestureDetector != null) {
mScaleGestureDetector.onTouchEvent(event);
}
int activePointers = event.getPointerCount();
if (event.getActionMasked() == MotionEvent.ACTION_POINTER_UP) {
activePointers -= 1;
}
if (getState() == STATE_ACTIVE && activePointers < 2) {
end();
} else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
fail();
}
}
@Override
protected void onReset() {
mScaleGestureDetector = null;
mLastVelocity = 0f;
mLastScaleFactor = 1f;
}
public double getScale() {
return mLastScaleFactor;
}
public double getVelocity() {
return mLastVelocity;
}
public float getFocalPointX() {
if (mScaleGestureDetector == null) {
return Float.NaN;
}
return mScaleGestureDetector.getFocusX();
}
public float getFocalPointY() {
if (mScaleGestureDetector == null) {
return Float.NaN;
}
return mScaleGestureDetector.getFocusY();
}
}

View File

@ -0,0 +1,25 @@
package com.swmansion.gesturehandler;
public enum PointerEventsConfig {
/**
* Neither the container nor its children receive events.
*/
NONE,
/**
* Container doesn't get events but all of its children do.
*/
BOX_NONE,
/**
* Container gets events but none of its children do.
*/
BOX_ONLY,
/**
* Container and all of its children receive touch events (like pointerEvents is unspecified).
*/
AUTO,
;
}

View File

@ -0,0 +1,169 @@
package com.swmansion.gesturehandler;
import android.view.MotionEvent;
public class RotationGestureDetector {
public interface OnRotationGestureListener {
boolean onRotation(RotationGestureDetector detector);
boolean onRotationBegin(RotationGestureDetector detector);
void onRotationEnd(RotationGestureDetector detector);
}
private long mCurrTime;
private long mPrevTime;
private double mPrevAngle;
private double mAngleDiff;
private float mAnchorX;
private float mAnchorY;
private boolean mInProgress;
private int mPointerIds[] = new int[2];
private OnRotationGestureListener mListener;
public RotationGestureDetector(OnRotationGestureListener listener) {
mListener = listener;
}
private void updateCurrent(MotionEvent event) {
mPrevTime = mCurrTime;
mCurrTime = event.getEventTime();
int firstPointerIndex = event.findPointerIndex(mPointerIds[0]);
int secondPointerIndex = event.findPointerIndex(mPointerIds[1]);
float firstPtX = event.getX(firstPointerIndex);
float firstPtY = event.getY(firstPointerIndex);
float secondPtX = event.getX(secondPointerIndex);
float secondPtY = event.getY(secondPointerIndex);
float vectorX = secondPtX - firstPtX;
float vectorY = secondPtY - firstPtY;
mAnchorX = (firstPtX + secondPtX) * 0.5f;
mAnchorY = (firstPtY + secondPtY) * 0.5f;
// Angle diff should be positive when rotating in clockwise direction
double angle = -Math.atan2(vectorY, vectorX);
if (Double.isNaN(mPrevAngle)) {
mAngleDiff = 0.;
} else {
mAngleDiff = mPrevAngle - angle;
}
mPrevAngle = angle;
if (mAngleDiff > Math.PI) {
mAngleDiff -= Math.PI;
} else if (mAngleDiff < -Math.PI) {
mAngleDiff += Math.PI;
}
if (mAngleDiff > Math.PI / 2.) {
mAngleDiff -= Math.PI;
} else if (mAngleDiff < -Math.PI / 2.) {
mAngleDiff += Math.PI;
}
}
private void finish() {
if (mInProgress) {
mInProgress = false;
if (mListener != null) {
mListener.onRotationEnd(this);
}
}
}
public boolean onTouchEvent(MotionEvent event) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
mInProgress = false;
mPointerIds[0] = event.getPointerId(event.getActionIndex());
mPointerIds[1] = MotionEvent.INVALID_POINTER_ID;
break;
case MotionEvent.ACTION_POINTER_DOWN:
if (!mInProgress) {
mPointerIds[1] = event.getPointerId(event.getActionIndex());
mInProgress = true;
mPrevTime = event.getEventTime();
mPrevAngle = Double.NaN;
updateCurrent(event);
if (mListener != null) {
mListener.onRotationBegin(this);
}
}
break;
case MotionEvent.ACTION_MOVE:
if (mInProgress) {
updateCurrent(event);
if (mListener != null) {
mListener.onRotation(this);
}
}
break;
case MotionEvent.ACTION_POINTER_UP:
if (mInProgress) {
int pointerId = event.getPointerId(event.getActionIndex());
if (pointerId == mPointerIds[0] || pointerId == mPointerIds[1]) {
// One of the key pointer has been lifted up, we have to end the gesture
finish();
}
}
break;
case MotionEvent.ACTION_UP:
finish();
break;
}
return true;
}
/**
* Returns rotation in radians since the previous rotation event.
*
* @return current rotation step in radians.
*/
public double getRotation() {
return mAngleDiff;
}
/**
* Return the time difference in milliseconds between the previous
* accepted rotation event and the current rotation event.
*
* @return Time difference since the last rotation event in milliseconds.
*/
public long getTimeDelta() {
return mCurrTime - mPrevTime;
}
/**
* Returns X coordinate of the rotation anchor point relative to the view that the provided motion
* event coordinates (usually relative to the view event was sent to).
*
* @return X coordinate of the rotation anchor point
*/
public float getAnchorX() {
return mAnchorX;
}
/**
* Returns Y coordinate of the rotation anchor point relative to the view that the provided motion
* event coordinates (usually relative to the view event was sent to).
*
* @return Y coordinate of the rotation anchor point
*/
public float getAnchorY() {
return mAnchorY;
}
}

View File

@ -0,0 +1,96 @@
package com.swmansion.gesturehandler;
import android.view.MotionEvent;
public class RotationGestureHandler extends GestureHandler<RotationGestureHandler> {
private static final double ROTATION_RECOGNITION_THRESHOLD = Math.PI / 36.; // 5 deg in radians
private RotationGestureDetector mRotationGestureDetector;
private double mLastRotation;
private double mLastVelocity;
private RotationGestureDetector.OnRotationGestureListener mGestureListener = new RotationGestureDetector.OnRotationGestureListener() {
@Override
public boolean onRotation(RotationGestureDetector detector) {
double prevRotation = mLastRotation;
mLastRotation += detector.getRotation();
long delta = detector.getTimeDelta();
if (delta > 0) {
mLastVelocity = (mLastRotation - prevRotation) / delta;
}
if (Math.abs(mLastRotation) >= ROTATION_RECOGNITION_THRESHOLD && getState() == STATE_BEGAN) {
activate();
}
return true;
}
@Override
public boolean onRotationBegin(RotationGestureDetector detector) {
return true;
}
@Override
public void onRotationEnd(RotationGestureDetector detector) {
end();
}
};
public RotationGestureHandler() {
setShouldCancelWhenOutside(false);
}
@Override
protected void onHandle(MotionEvent event) {
int state = getState();
if (state == STATE_UNDETERMINED) {
mLastVelocity = 0f;
mLastRotation = 0f;
mRotationGestureDetector = new RotationGestureDetector(mGestureListener);
begin();
}
if (mRotationGestureDetector != null) {
mRotationGestureDetector.onTouchEvent(event);
}
if (event.getActionMasked() == MotionEvent.ACTION_UP) {
if (state == STATE_ACTIVE) {
end();
} else {
fail();
}
}
}
@Override
protected void onReset() {
mRotationGestureDetector = null;
mLastVelocity = 0f;
mLastRotation = 0f;
}
public double getRotation() {
return mLastRotation;
}
public double getVelocity() {
return mLastVelocity;
}
public float getAnchorX() {
if (mRotationGestureDetector == null) {
return Float.NaN;
}
return mRotationGestureDetector.getAnchorX();
}
public float getAnchorY() {
if (mRotationGestureDetector == null) {
return Float.NaN;
}
return mRotationGestureDetector.getAnchorY();
}
}

View File

@ -0,0 +1,172 @@
package com.swmansion.gesturehandler;
import android.os.Handler;
import android.view.MotionEvent;
public class TapGestureHandler extends GestureHandler<TapGestureHandler> {
private static float MAX_VALUE_IGNORE = Float.MIN_VALUE;
private static final long DEFAULT_MAX_DURATION_MS = 500;
private static final long DEFAULT_MAX_DELAY_MS = 500;
private static final int DEFAULT_NUMBER_OF_TAPS = 1;
private static final int DEFAULT_MIN_NUMBER_OF_POINTERS = 1;
private float mMaxDeltaX = MAX_VALUE_IGNORE;
private float mMaxDeltaY = MAX_VALUE_IGNORE;
private float mMaxDistSq = MAX_VALUE_IGNORE;
private long mMaxDurationMs = DEFAULT_MAX_DURATION_MS;
private long mMaxDelayMs = DEFAULT_MAX_DELAY_MS;
private int mNumberOfTaps = DEFAULT_NUMBER_OF_TAPS;
private int mMinNumberOfPointers = DEFAULT_MIN_NUMBER_OF_POINTERS;
private int mNumberOfPointers = 1;
private float mStartX, mStartY;
private float mOffsetX, mOffsetY;
private float mLastX, mLastY;
private Handler mHandler;
private int mTapsSoFar;
private final Runnable mFailDelayed = new Runnable() {
@Override
public void run() {
fail();
}
};
public TapGestureHandler setNumberOfTaps(int numberOfTaps) {
mNumberOfTaps = numberOfTaps;
return this;
}
public TapGestureHandler setMaxDelayMs(long maxDelayMs) {
mMaxDelayMs = maxDelayMs;
return this;
}
public TapGestureHandler setMaxDurationMs(long maxDurationMs) {
mMaxDurationMs = maxDurationMs;
return this;
}
public TapGestureHandler setMaxDx(float deltaX) {
mMaxDeltaX = deltaX;
return this;
}
public TapGestureHandler setMaxDy(float deltaY) {
mMaxDeltaY = deltaY;
return this;
}
public TapGestureHandler setMaxDist(float maxDist) {
mMaxDistSq = maxDist * maxDist;
return this;
}
public TapGestureHandler setMinNumberOfPointers(int minNumberOfPointers) {
mMinNumberOfPointers = minNumberOfPointers;
return this;
}
public TapGestureHandler() {
setShouldCancelWhenOutside(true);
}
private void startTap() {
if (mHandler == null) {
mHandler = new Handler();
} else {
mHandler.removeCallbacksAndMessages(null);
}
mHandler.postDelayed(mFailDelayed, mMaxDurationMs);
}
private void endTap() {
if (mHandler == null) {
mHandler = new Handler();
} else {
mHandler.removeCallbacksAndMessages(null);
}
if (++mTapsSoFar == mNumberOfTaps && mNumberOfPointers >= mMinNumberOfPointers) {
activate();
end();
} else {
mHandler.postDelayed(mFailDelayed, mMaxDelayMs);
}
}
private boolean shouldFail() {
float dx = mLastX - mStartX + mOffsetX;
if (mMaxDeltaX != MAX_VALUE_IGNORE && Math.abs(dx) > mMaxDeltaX) {
return true;
}
float dy = mLastY - mStartY + mOffsetY;
if (mMaxDeltaY != MAX_VALUE_IGNORE && Math.abs(dy) > mMaxDeltaY) {
return true;
}
float dist = dy * dy + dx * dx;
return mMaxDistSq != MAX_VALUE_IGNORE && dist > mMaxDistSq;
}
@Override
protected void onHandle(MotionEvent event) {
int state = getState();
int action = event.getActionMasked();
if (state == STATE_UNDETERMINED) {
mOffsetX = 0;
mOffsetY = 0;
mStartX = event.getRawX();
mStartY = event.getRawY();
}
if (action == MotionEvent.ACTION_POINTER_UP || action == MotionEvent.ACTION_POINTER_DOWN) {
mOffsetX += mLastX - mStartX;
mOffsetY += mLastY - mStartY;
mLastX = GestureUtils.getLastPointerX(event, true);
mLastY = GestureUtils.getLastPointerY(event, true);
mStartX = mLastX;
mStartY = mLastY;
} else {
mLastX = GestureUtils.getLastPointerX(event, true);
mLastY = GestureUtils.getLastPointerY(event, true);
}
if (mNumberOfPointers < event.getPointerCount()) {
mNumberOfPointers = event.getPointerCount();
}
if (shouldFail()) {
fail();
} else if (state == STATE_UNDETERMINED) {
if (action == MotionEvent.ACTION_DOWN) {
begin();
}
startTap();
} else if (state == STATE_BEGAN) {
if (action == MotionEvent.ACTION_UP) {
endTap();
} else if (action == MotionEvent.ACTION_DOWN) {
startTap();
}
}
}
@Override
protected void onCancel() {
if (mHandler != null) {
mHandler.removeCallbacksAndMessages(null);
}
}
@Override
protected void onReset() {
mTapsSoFar = 0;
mNumberOfPointers = 0;
if (mHandler != null) {
mHandler.removeCallbacksAndMessages(null);
}
}
}

View File

@ -0,0 +1,10 @@
package com.swmansion.gesturehandler;
import android.view.View;
import android.view.ViewGroup;
public interface ViewConfigurationHelper {
PointerEventsConfig getPointerEventsConfigForView(View view);
View getChildInDrawingOrderAtIndex(ViewGroup parent, int index);
boolean isViewClippingChildren(ViewGroup view);
}

View File

@ -0,0 +1,3 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.swmansion.gesturehandler.react">
</manifest>

View File

@ -0,0 +1,21 @@
package com.facebook.react.views.modal;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.view.ViewParent;
/**
* For handling gestures inside RNGH we need to have access to some methods of
* `ReactModalHostView.DialogRootViewGroup`. This class is not available outside
* package so this file exports important features.
*/
public class RNGHModalUtils {
public static void dialogRootViewGroupOnChildStartedNativeGesture(ViewGroup modal, MotionEvent androidEvent) {
((ReactModalHostView.DialogRootViewGroup) modal).onChildStartedNativeGesture(androidEvent);
}
public static boolean isDialogRootViewGroup(ViewParent modal) {
return modal instanceof ReactModalHostView.DialogRootViewGroup;
}
}

View File

@ -0,0 +1,261 @@
package com.swmansion.gesturehandler.react;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.PaintDrawable;
import android.graphics.drawable.RippleDrawable;
import android.os.Build;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.ViewGroup;
import com.facebook.react.bridge.SoftAssertions;
import com.facebook.react.uimanager.PixelUtil;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.ViewProps;
import com.facebook.react.uimanager.annotations.ReactProp;
public class RNGestureHandlerButtonViewManager extends
ViewGroupManager<RNGestureHandlerButtonViewManager.ButtonViewGroup> {
static class ButtonViewGroup extends ViewGroup {
static TypedValue sResolveOutValue = new TypedValue();
static ButtonViewGroup sResponder;
int mBackgroundColor = Color.TRANSPARENT;
// Using object because of handling null representing no value set.
Integer mRippleColor;
Integer mRippleRadius;
boolean mUseForeground = false;
boolean mUseBorderless = false;
float mBorderRadius = 0;
boolean mNeedBackgroundUpdate = false;
public static final String SELECTABLE_ITEM_BACKGROUND = "selectableItemBackground";
public static final String SELECTABLE_ITEM_BACKGROUND_BORDERLESS = "selectableItemBackgroundBorderless";
public ButtonViewGroup(Context context) {
super(context);
setClickable(true);
setFocusable(true);
mNeedBackgroundUpdate = true;
}
@Override
public void setBackgroundColor(int color) {
mBackgroundColor = color;
mNeedBackgroundUpdate = true;
}
public void setRippleColor(Integer color) {
mRippleColor = color;
mNeedBackgroundUpdate = true;
}
public void setRippleRadius(Integer radius) {
mRippleRadius = radius;
mNeedBackgroundUpdate = true;
}
public void setBorderRadius(float borderRadius) {
mBorderRadius = borderRadius * getResources().getDisplayMetrics().density;
mNeedBackgroundUpdate = true;
}
private Drawable applyRippleEffectWhenNeeded(Drawable selectable) {
if (mRippleColor != null
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
&& selectable instanceof RippleDrawable) {
int[][] states = new int[][]{ new int[]{ android.R.attr.state_enabled } };
int[] colors = new int[]{ mRippleColor };
ColorStateList colorStateList = new ColorStateList(states, colors);
((RippleDrawable) selectable).setColor(colorStateList);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& mRippleRadius != null
&& selectable instanceof RippleDrawable) {
RippleDrawable rippleDrawable = (RippleDrawable) selectable;
rippleDrawable.setRadius((int) PixelUtil.toPixelFromDIP(mRippleRadius));
}
return selectable;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (super.onInterceptTouchEvent(ev)) {
return true;
}
// We call `onTouchEvent` to and wait until button changes state to `pressed`, if it's pressed
// we return true so that the gesture handler can activate
onTouchEvent(ev);
return isPressed();
}
private void updateBackground() {
if (!mNeedBackgroundUpdate) {
return;
}
mNeedBackgroundUpdate = false;
if (mBackgroundColor == Color.TRANSPARENT) {
// reset background
setBackground(null);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// reset foreground
setForeground(null);
}
if (mUseForeground && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
setForeground(applyRippleEffectWhenNeeded(createSelectableDrawable()));
if (mBackgroundColor != Color.TRANSPARENT) {
setBackgroundColor(mBackgroundColor);
}
} else if (mBackgroundColor == Color.TRANSPARENT && mRippleColor == null) {
setBackground(createSelectableDrawable());
} else {
PaintDrawable colorDrawable = new PaintDrawable(mBackgroundColor);
Drawable selectable = createSelectableDrawable();
if (mBorderRadius != 0) {
// Radius-connected lines below ought to be considered
// as a temporary solution. It do not allow to set
// different radius on each corner. However, I suppose it's fairly
// fine for button-related use cases.
// Therefore it might be used as long as:
// 1. ReactViewManager is not a generic class with a possibility to handle another ViewGroup
// 2. There's no way to force native behavior of ReactViewGroup's superclass's onTouchEvent
colorDrawable.setCornerRadius(mBorderRadius);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
&& selectable instanceof RippleDrawable) {
PaintDrawable mask = new PaintDrawable(Color.WHITE);
mask.setCornerRadius(mBorderRadius);
((RippleDrawable) selectable).setDrawableByLayerId(android.R.id.mask, mask);
}
}
applyRippleEffectWhenNeeded(selectable);
LayerDrawable layerDrawable = new LayerDrawable(
new Drawable[] { colorDrawable, selectable});
setBackground(layerDrawable);
}
}
public void setUseDrawableOnForeground(boolean useForeground) {
mUseForeground = useForeground;
mNeedBackgroundUpdate = true;
}
public void setUseBorderlessDrawable(boolean useBorderless) {
mUseBorderless = useBorderless;
}
private Drawable createSelectableDrawable() {
final int version = Build.VERSION.SDK_INT;
String identifier = mUseBorderless && version >= 21 ? SELECTABLE_ITEM_BACKGROUND_BORDERLESS
: SELECTABLE_ITEM_BACKGROUND;
int attrID = getAttrId(getContext(), identifier);
getContext().getTheme().resolveAttribute(attrID, sResolveOutValue, true);
if (version >= 21) {
return getResources().getDrawable(sResolveOutValue.resourceId, getContext().getTheme());
} else {
return getResources().getDrawable(sResolveOutValue.resourceId);
}
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private static int getAttrId(Context context, String attr) {
SoftAssertions.assertNotNull(attr);
if (SELECTABLE_ITEM_BACKGROUND.equals(attr)) {
return android.R.attr.selectableItemBackground;
} else if (SELECTABLE_ITEM_BACKGROUND_BORDERLESS.equals(attr)) {
return android.R.attr.selectableItemBackgroundBorderless;
} else {
return context.getResources().getIdentifier(attr, "attr", "android");
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// No-op
}
@Override
public void drawableHotspotChanged(float x, float y) {
if (sResponder == null || sResponder == this) {
super.drawableHotspotChanged(x, y);
}
}
@Override
public void setPressed(boolean pressed) {
if (pressed && sResponder == null) {
// first button to be pressed grabs button responder
sResponder = this;
}
if (!pressed || sResponder == this) {
// we set pressed state only for current responder
super.setPressed(pressed);
}
if (!pressed && sResponder == this) {
// if the responder is no longer pressed we release button responder
sResponder = null;
}
}
@Override
public void dispatchDrawableHotspotChanged(float x, float y) {
// by default viewgroup would pass hotspot change events
}
}
@Override
public String getName() {
return "RNGestureHandlerButton";
}
@Override
public ButtonViewGroup createViewInstance(ThemedReactContext context) {
return new ButtonViewGroup(context);
}
@TargetApi(Build.VERSION_CODES.M)
@ReactProp(name = "foreground")
public void setForeground(ButtonViewGroup view, boolean useDrawableOnForeground) {
view.setUseDrawableOnForeground(useDrawableOnForeground);
}
@ReactProp(name = "borderless")
public void setBorderless(ButtonViewGroup view, boolean useBorderlessDrawable) {
view.setUseBorderlessDrawable(useBorderlessDrawable);
}
@ReactProp(name = "enabled")
public void setEnabled(ButtonViewGroup view, boolean enabled) {
view.setEnabled(enabled);
}
@ReactProp(name = ViewProps.BORDER_RADIUS)
public void setBorderRadius(ButtonViewGroup view, float borderRadius) {
view.setBorderRadius(borderRadius);
}
@ReactProp(name = "rippleColor")
public void setRippleColor(ButtonViewGroup view, Integer rippleColor) {
view.setRippleColor(rippleColor);
}
@ReactProp(name = "rippleRadius")
public void setRippleRadius(ButtonViewGroup view, Integer rippleRadius) {
view.setRippleRadius(rippleRadius);
}
@Override
protected void onAfterUpdateTransaction(ButtonViewGroup view) {
view.updateBackground();
}
}

View File

@ -0,0 +1,72 @@
package com.swmansion.gesturehandler.react;
import android.content.Context;
import android.os.Bundle;
import android.util.AttributeSet;
import android.view.MotionEvent;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactRootView;
import androidx.annotation.Nullable;
public class RNGestureHandlerEnabledRootView extends ReactRootView {
private @Nullable ReactInstanceManager mReactInstanceManager;
private @Nullable RNGestureHandlerRootHelper mGestureRootHelper;
public RNGestureHandlerEnabledRootView(Context context) {
super(context);
}
public RNGestureHandlerEnabledRootView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (mGestureRootHelper != null) {
mGestureRootHelper.requestDisallowInterceptTouchEvent(disallowIntercept);
}
super.requestDisallowInterceptTouchEvent(disallowIntercept);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mGestureRootHelper != null && mGestureRootHelper.dispatchTouchEvent(ev)) {
return true;
}
return super.dispatchTouchEvent(ev);
}
/**
* This method is used to enable root view to start processing touch events through the gesture
* handler library logic. Unless this method is called (which happens as a result of instantiating
* new gesture handler from JS) the root view component will just proxy all touch related methods
* to its superclass. Thus in the "disabled" state all touch related events will fallback to
* default RN behavior.
*/
public void initialize() {
if (mGestureRootHelper != null) {
throw new IllegalStateException("GestureHandler already initialized for root view " + this);
}
mGestureRootHelper = new RNGestureHandlerRootHelper(
mReactInstanceManager.getCurrentReactContext(), this);
}
public void tearDown() {
if (mGestureRootHelper != null) {
mGestureRootHelper.tearDown();
mGestureRootHelper = null;
}
}
@Override
public void startReactApplication(
ReactInstanceManager reactInstanceManager,
String moduleName,
@Nullable Bundle initialProperties) {
super.startReactApplication(reactInstanceManager, moduleName, initialProperties);
mReactInstanceManager = reactInstanceManager;
}
}

View File

@ -0,0 +1,77 @@
package com.swmansion.gesturehandler.react;
import androidx.core.util.Pools;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
import com.facebook.react.uimanager.events.RCTEventEmitter;
import com.swmansion.gesturehandler.GestureHandler;
import androidx.annotation.Nullable;
public class RNGestureHandlerEvent extends Event<RNGestureHandlerEvent> {
public static final String EVENT_NAME = "onGestureHandlerEvent";
private static final int TOUCH_EVENTS_POOL_SIZE = 7; // magic
private static final Pools.SynchronizedPool<RNGestureHandlerEvent> EVENTS_POOL =
new Pools.SynchronizedPool<>(TOUCH_EVENTS_POOL_SIZE);
public static RNGestureHandlerEvent obtain(
GestureHandler handler,
@Nullable RNGestureHandlerEventDataExtractor dataExtractor) {
RNGestureHandlerEvent event = EVENTS_POOL.acquire();
if (event == null) {
event = new RNGestureHandlerEvent();
}
event.init(handler, dataExtractor);
return event;
}
private WritableMap mExtraData;
private short mCoalescingKey;
private RNGestureHandlerEvent() {
}
private void init(
GestureHandler handler,
@Nullable RNGestureHandlerEventDataExtractor dataExtractor) {
super.init(handler.getView().getId());
mExtraData = Arguments.createMap();
if (dataExtractor != null) {
dataExtractor.extractEventData(handler, mExtraData);
}
mExtraData.putInt("handlerTag", handler.getTag());
mExtraData.putInt("state", handler.getState());
mCoalescingKey = handler.getEventCoalescingKey();
}
@Override
public void onDispose() {
mExtraData = null;
EVENTS_POOL.release(this);
}
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public boolean canCoalesce() {
return true;
}
@Override
public short getCoalescingKey() {
return mCoalescingKey;
}
@Override
public void dispatch(RCTEventEmitter rctEventEmitter) {
rctEventEmitter.receiveEvent(getViewTag(), EVENT_NAME, mExtraData);
}
}

View File

@ -0,0 +1,8 @@
package com.swmansion.gesturehandler.react;
import com.facebook.react.bridge.WritableMap;
import com.swmansion.gesturehandler.GestureHandler;
public interface RNGestureHandlerEventDataExtractor<T extends GestureHandler> {
void extractEventData(T handler, WritableMap eventData);
}

View File

@ -0,0 +1,86 @@
package com.swmansion.gesturehandler.react;
import android.util.SparseArray;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.swmansion.gesturehandler.GestureHandler;
import com.swmansion.gesturehandler.GestureHandlerInteractionController;
public class RNGestureHandlerInteractionManager implements GestureHandlerInteractionController {
private static final String KEY_WAIT_FOR = "waitFor";
private static final String KEY_SIMULTANEOUS_HANDLERS = "simultaneousHandlers";
private SparseArray<int[]> mWaitForRelations = new SparseArray<>();
private SparseArray<int[]> mSimultaneousRelations = new SparseArray<>();
public void dropRelationsForHandlerWithTag(int handlerTag) {
mWaitForRelations.remove(handlerTag);
mSimultaneousRelations.remove(handlerTag);
}
private int[] convertHandlerTagsArray(ReadableMap config, String key) {
ReadableArray array = config.getArray(key);
int[] result = new int[array.size()];
for (int i = 0; i < result.length; i++) {
result[i] = array.getInt(i);
}
return result;
}
public void configureInteractions(GestureHandler handler, ReadableMap config) {
handler.setInteractionController(this);
if (config.hasKey(KEY_WAIT_FOR)) {
int[] tags = convertHandlerTagsArray(config, KEY_WAIT_FOR);
mWaitForRelations.put(handler.getTag(), tags);
}
if (config.hasKey(KEY_SIMULTANEOUS_HANDLERS)) {
int[] tags = convertHandlerTagsArray(config, KEY_SIMULTANEOUS_HANDLERS);
mSimultaneousRelations.put(handler.getTag(), tags);
}
}
@Override
public boolean shouldWaitForHandlerFailure(GestureHandler handler, GestureHandler otherHandler) {
int[] waitForTags = mWaitForRelations.get(handler.getTag());
if (waitForTags != null) {
for (int i = 0; i < waitForTags.length; i++) {
if (waitForTags[i] == otherHandler.getTag()) {
return true;
}
}
}
return false;
}
@Override
public boolean shouldRequireHandlerToWaitForFailure(GestureHandler handler,
GestureHandler otherHandler) {
return false;
}
@Override
public boolean shouldHandlerBeCancelledBy(GestureHandler handler, GestureHandler otherHandler) {
return false;
}
@Override
public boolean shouldRecognizeSimultaneously(GestureHandler handler,
GestureHandler otherHandler) {
int[] simultHandlerTags = mSimultaneousRelations.get(handler.getTag());
if (simultHandlerTags != null) {
for (int i = 0; i < simultHandlerTags.length; i++) {
if (simultHandlerTags[i] == otherHandler.getTag()) {
return true;
}
}
}
return false;
}
public void reset() {
mWaitForRelations.clear();
mSimultaneousRelations.clear();
}
}

View File

@ -0,0 +1,731 @@
package com.swmansion.gesturehandler.react;
import android.content.Context;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import com.facebook.react.ReactRootView;
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableType;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.NativeViewHierarchyManager;
import com.facebook.react.uimanager.PixelUtil;
import com.facebook.react.uimanager.UIBlock;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.events.EventDispatcher;
import com.swmansion.gesturehandler.FlingGestureHandler;
import com.swmansion.gesturehandler.GestureHandler;
import com.swmansion.gesturehandler.LongPressGestureHandler;
import com.swmansion.gesturehandler.NativeViewGestureHandler;
import com.swmansion.gesturehandler.OnTouchEventListener;
import com.swmansion.gesturehandler.PanGestureHandler;
import com.swmansion.gesturehandler.PinchGestureHandler;
import com.swmansion.gesturehandler.RotationGestureHandler;
import com.swmansion.gesturehandler.TapGestureHandler;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import androidx.annotation.Nullable;
import static com.swmansion.gesturehandler.GestureHandler.HIT_SLOP_NONE;
@ReactModule(name=RNGestureHandlerModule.MODULE_NAME)
public class RNGestureHandlerModule extends ReactContextBaseJavaModule {
public static final String MODULE_NAME = "RNGestureHandlerModule";
private static final String KEY_SHOULD_CANCEL_WHEN_OUTSIDE = "shouldCancelWhenOutside";
private static final String KEY_ENABLED = "enabled";
private static final String KEY_HIT_SLOP = "hitSlop";
private static final String KEY_HIT_SLOP_LEFT = "left";
private static final String KEY_HIT_SLOP_TOP = "top";
private static final String KEY_HIT_SLOP_RIGHT = "right";
private static final String KEY_HIT_SLOP_BOTTOM = "bottom";
private static final String KEY_HIT_SLOP_VERTICAL = "vertical";
private static final String KEY_HIT_SLOP_HORIZONTAL = "horizontal";
private static final String KEY_HIT_SLOP_WIDTH = "width";
private static final String KEY_HIT_SLOP_HEIGHT = "height";
private static final String KEY_NATIVE_VIEW_SHOULD_ACTIVATE_ON_START = "shouldActivateOnStart";
private static final String KEY_NATIVE_VIEW_DISALLOW_INTERRUPTION = "disallowInterruption";
private static final String KEY_TAP_NUMBER_OF_TAPS = "numberOfTaps";
private static final String KEY_TAP_MAX_DURATION_MS = "maxDurationMs";
private static final String KEY_TAP_MAX_DELAY_MS = "maxDelayMs";
private static final String KEY_TAP_MAX_DELTA_X = "maxDeltaX";
private static final String KEY_TAP_MAX_DELTA_Y = "maxDeltaY";
private static final String KEY_TAP_MAX_DIST = "maxDist";
private static final String KEY_TAP_MIN_POINTERS = "minPointers";
private static final String KEY_LONG_PRESS_MIN_DURATION_MS = "minDurationMs";
private static final String KEY_LONG_PRESS_MAX_DIST = "maxDist";
private static final String KEY_PAN_ACTIVE_OFFSET_X_START = "activeOffsetXStart";
private static final String KEY_PAN_ACTIVE_OFFSET_X_END = "activeOffsetXEnd";
private static final String KEY_PAN_FAIL_OFFSET_RANGE_X_START = "failOffsetXStart";
private static final String KEY_PAN_FAIL_OFFSET_RANGE_X_END = "failOffsetXEnd";
private static final String KEY_PAN_ACTIVE_OFFSET_Y_START = "activeOffsetYStart";
private static final String KEY_PAN_ACTIVE_OFFSET_Y_END = "activeOffsetYEnd";
private static final String KEY_PAN_FAIL_OFFSET_RANGE_Y_START = "failOffsetYStart";
private static final String KEY_PAN_FAIL_OFFSET_RANGE_Y_END = "failOffsetYEnd";
private static final String KEY_PAN_MIN_DIST = "minDist";
private static final String KEY_PAN_MIN_VELOCITY = "minVelocity";
private static final String KEY_PAN_MIN_VELOCITY_X = "minVelocityX";
private static final String KEY_PAN_MIN_VELOCITY_Y = "minVelocityY";
private static final String KEY_PAN_MIN_POINTERS = "minPointers";
private static final String KEY_PAN_MAX_POINTERS = "maxPointers";
private static final String KEY_PAN_AVG_TOUCHES = "avgTouches";
private static final String KEY_NUMBER_OF_POINTERS = "numberOfPointers";
private static final String KEY_DIRECTION= "direction";
private abstract static class HandlerFactory<T extends GestureHandler>
implements RNGestureHandlerEventDataExtractor<T> {
public abstract Class<T> getType();
public abstract String getName();
public abstract T create(Context context);
public void configure(T handler, ReadableMap config) {
if (config.hasKey(KEY_SHOULD_CANCEL_WHEN_OUTSIDE)) {
handler.setShouldCancelWhenOutside(config.getBoolean(KEY_SHOULD_CANCEL_WHEN_OUTSIDE));
}
if (config.hasKey(KEY_ENABLED)) {
handler.setEnabled(config.getBoolean(KEY_ENABLED));
}
if (config.hasKey(KEY_HIT_SLOP)) {
handleHitSlopProperty(handler, config);
}
}
@Override
public void extractEventData(T handler, WritableMap eventData) {
eventData.putDouble("numberOfPointers", handler.getNumberOfPointers());
}
}
private static class NativeViewGestureHandlerFactory extends
HandlerFactory<NativeViewGestureHandler> {
@Override
public Class<NativeViewGestureHandler> getType() {
return NativeViewGestureHandler.class;
}
@Override
public String getName() {
return "NativeViewGestureHandler";
}
@Override
public NativeViewGestureHandler create(Context context) {
return new NativeViewGestureHandler();
}
@Override
public void configure(NativeViewGestureHandler handler, ReadableMap config) {
super.configure(handler, config);
if (config.hasKey(KEY_NATIVE_VIEW_SHOULD_ACTIVATE_ON_START)) {
handler.setShouldActivateOnStart(
config.getBoolean(KEY_NATIVE_VIEW_SHOULD_ACTIVATE_ON_START));
}
if (config.hasKey(KEY_NATIVE_VIEW_DISALLOW_INTERRUPTION)) {
handler.setDisallowInterruption(config.getBoolean(KEY_NATIVE_VIEW_DISALLOW_INTERRUPTION));
}
}
@Override
public void extractEventData(NativeViewGestureHandler handler, WritableMap eventData) {
super.extractEventData(handler, eventData);
eventData.putBoolean("pointerInside", handler.isWithinBounds());
}
}
private static class TapGestureHandlerFactory extends HandlerFactory<TapGestureHandler> {
@Override
public Class<TapGestureHandler> getType() {
return TapGestureHandler.class;
}
@Override
public String getName() {
return "TapGestureHandler";
}
@Override
public TapGestureHandler create(Context context) {
return new TapGestureHandler();
}
@Override
public void configure(TapGestureHandler handler, ReadableMap config) {
super.configure(handler, config);
if (config.hasKey(KEY_TAP_NUMBER_OF_TAPS)) {
handler.setNumberOfTaps(config.getInt(KEY_TAP_NUMBER_OF_TAPS));
}
if (config.hasKey(KEY_TAP_MAX_DURATION_MS)) {
handler.setMaxDurationMs(config.getInt(KEY_TAP_MAX_DURATION_MS));
}
if (config.hasKey(KEY_TAP_MAX_DELAY_MS)) {
handler.setMaxDelayMs(config.getInt(KEY_TAP_MAX_DELAY_MS));
}
if (config.hasKey(KEY_TAP_MAX_DELTA_X)) {
handler.setMaxDx(PixelUtil.toPixelFromDIP(config.getDouble(KEY_TAP_MAX_DELTA_X)));
}
if (config.hasKey(KEY_TAP_MAX_DELTA_Y)) {
handler.setMaxDy(PixelUtil.toPixelFromDIP(config.getDouble(KEY_TAP_MAX_DELTA_Y)));
}
if (config.hasKey(KEY_TAP_MAX_DIST)) {
handler.setMaxDist(PixelUtil.toPixelFromDIP(config.getDouble(KEY_TAP_MAX_DIST)));
}
if (config.hasKey(KEY_TAP_MIN_POINTERS)) {
handler.setMinNumberOfPointers(config.getInt(KEY_TAP_MIN_POINTERS));
}
}
@Override
public void extractEventData(TapGestureHandler handler, WritableMap eventData) {
super.extractEventData(handler, eventData);
eventData.putDouble("x", PixelUtil.toDIPFromPixel(handler.getLastRelativePositionX()));
eventData.putDouble("y", PixelUtil.toDIPFromPixel(handler.getLastRelativePositionY()));
eventData.putDouble("absoluteX", PixelUtil.toDIPFromPixel(handler.getLastAbsolutePositionX()));
eventData.putDouble("absoluteY", PixelUtil.toDIPFromPixel(handler.getLastAbsolutePositionY()));
}
}
private static class LongPressGestureHandlerFactory extends
HandlerFactory<LongPressGestureHandler> {
@Override
public Class<LongPressGestureHandler> getType() {
return LongPressGestureHandler.class;
}
@Override
public String getName() {
return "LongPressGestureHandler";
}
@Override
public LongPressGestureHandler create(Context context) {
return new LongPressGestureHandler(context);
}
@Override
public void configure(LongPressGestureHandler handler, ReadableMap config) {
super.configure(handler, config);
if (config.hasKey(KEY_LONG_PRESS_MIN_DURATION_MS)) {
handler.setMinDurationMs(config.getInt(KEY_LONG_PRESS_MIN_DURATION_MS));
}
if (config.hasKey(KEY_LONG_PRESS_MAX_DIST)) {
handler.setMaxDist(PixelUtil.toPixelFromDIP(config.getDouble(KEY_LONG_PRESS_MAX_DIST)));
}
}
@Override
public void extractEventData(LongPressGestureHandler handler, WritableMap eventData) {
super.extractEventData(handler, eventData);
eventData.putDouble("x", PixelUtil.toDIPFromPixel(handler.getLastRelativePositionX()));
eventData.putDouble("y", PixelUtil.toDIPFromPixel(handler.getLastRelativePositionY()));
eventData.putDouble("absoluteX", PixelUtil.toDIPFromPixel(handler.getLastAbsolutePositionX()));
eventData.putDouble("absoluteY", PixelUtil.toDIPFromPixel(handler.getLastAbsolutePositionY()));
}
}
private static class PanGestureHandlerFactory extends HandlerFactory<PanGestureHandler> {
@Override
public Class<PanGestureHandler> getType() {
return PanGestureHandler.class;
}
@Override
public String getName() {
return "PanGestureHandler";
}
@Override
public PanGestureHandler create(Context context) {
return new PanGestureHandler(context);
}
@Override
public void configure(PanGestureHandler handler, ReadableMap config) {
super.configure(handler, config);
boolean hasCustomActivationCriteria = false;
if(config.hasKey(KEY_PAN_ACTIVE_OFFSET_X_START)) {
handler.setActiveOffsetXStart(PixelUtil.toPixelFromDIP(config.getDouble(KEY_PAN_ACTIVE_OFFSET_X_START)));
hasCustomActivationCriteria = true;
}
if(config.hasKey(KEY_PAN_ACTIVE_OFFSET_X_END)) {
handler.setActiveOffsetXEnd(PixelUtil.toPixelFromDIP(config.getDouble(KEY_PAN_ACTIVE_OFFSET_X_END)));
hasCustomActivationCriteria = true;
}
if(config.hasKey(KEY_PAN_FAIL_OFFSET_RANGE_X_START)) {
handler.setFailOffsetXStart(PixelUtil.toPixelFromDIP(config.getDouble(KEY_PAN_FAIL_OFFSET_RANGE_X_START)));
hasCustomActivationCriteria = true;
}
if(config.hasKey(KEY_PAN_FAIL_OFFSET_RANGE_X_END)) {
handler.setFailOffsetXEnd(PixelUtil.toPixelFromDIP(config.getDouble(KEY_PAN_FAIL_OFFSET_RANGE_X_END)));
hasCustomActivationCriteria = true;
}
if(config.hasKey(KEY_PAN_ACTIVE_OFFSET_Y_START)) {
handler.setActiveOffsetYStart(PixelUtil.toPixelFromDIP(config.getDouble(KEY_PAN_ACTIVE_OFFSET_Y_START)));
hasCustomActivationCriteria = true;
}
if(config.hasKey(KEY_PAN_ACTIVE_OFFSET_Y_END)) {
handler.setActiveOffsetYEnd(PixelUtil.toPixelFromDIP(config.getDouble(KEY_PAN_ACTIVE_OFFSET_Y_END)));
hasCustomActivationCriteria = true;
}
if(config.hasKey(KEY_PAN_FAIL_OFFSET_RANGE_Y_START)) {
handler.setFailOffsetYStart(PixelUtil.toPixelFromDIP(config.getDouble(KEY_PAN_FAIL_OFFSET_RANGE_Y_START)));
hasCustomActivationCriteria = true;
}
if(config.hasKey(KEY_PAN_FAIL_OFFSET_RANGE_Y_END)) {
handler.setFailOffsetYEnd(PixelUtil.toPixelFromDIP(config.getDouble(KEY_PAN_FAIL_OFFSET_RANGE_Y_END)));
hasCustomActivationCriteria = true;
}
if (config.hasKey(KEY_PAN_MIN_VELOCITY)) {
// This value is actually in DPs/ms, but we can use the same function as for converting
// from DPs to pixels as the unit we're converting is in the numerator
handler.setMinVelocity(PixelUtil.toPixelFromDIP(config.getDouble(KEY_PAN_MIN_VELOCITY)));
hasCustomActivationCriteria = true;
}
if (config.hasKey(KEY_PAN_MIN_VELOCITY_X)) {
handler.setMinVelocityX(PixelUtil.toPixelFromDIP(config.getDouble(KEY_PAN_MIN_VELOCITY_X)));
hasCustomActivationCriteria = true;
}
if (config.hasKey(KEY_PAN_MIN_VELOCITY_Y)) {
handler.setMinVelocityY(PixelUtil.toPixelFromDIP(config.getDouble(KEY_PAN_MIN_VELOCITY_Y)));
hasCustomActivationCriteria = true;
}
// PanGestureHandler sets minDist by default, if there are custom criteria specified we want
// to reset that setting and use provided criteria instead.
if (config.hasKey(KEY_PAN_MIN_DIST)) {
handler.setMinDist(PixelUtil.toPixelFromDIP(config.getDouble(KEY_PAN_MIN_DIST)));
} else if (hasCustomActivationCriteria) {
handler.setMinDist(Float.MAX_VALUE);
}
if (config.hasKey(KEY_PAN_MIN_POINTERS)) {
handler.setMinPointers(config.getInt(KEY_PAN_MIN_POINTERS));
}
if (config.hasKey(KEY_PAN_MAX_POINTERS)) {
handler.setMaxPointers(config.getInt(KEY_PAN_MAX_POINTERS));
}
if (config.hasKey(KEY_PAN_AVG_TOUCHES)) {
handler.setAverageTouches(config.getBoolean(KEY_PAN_AVG_TOUCHES));
}
}
@Override
public void extractEventData(PanGestureHandler handler, WritableMap eventData) {
super.extractEventData(handler, eventData);
eventData.putDouble("x", PixelUtil.toDIPFromPixel(handler.getLastRelativePositionX()));
eventData.putDouble("y", PixelUtil.toDIPFromPixel(handler.getLastRelativePositionY()));
eventData.putDouble("absoluteX", PixelUtil.toDIPFromPixel(handler.getLastAbsolutePositionX()));
eventData.putDouble("absoluteY", PixelUtil.toDIPFromPixel(handler.getLastAbsolutePositionY()));
eventData.putDouble("translationX", PixelUtil.toDIPFromPixel(handler.getTranslationX()));
eventData.putDouble("translationY", PixelUtil.toDIPFromPixel(handler.getTranslationY()));
eventData.putDouble("velocityX", PixelUtil.toDIPFromPixel(handler.getVelocityX()));
eventData.putDouble("velocityY", PixelUtil.toDIPFromPixel(handler.getVelocityY()));
}
}
private static class PinchGestureHandlerFactory extends HandlerFactory<PinchGestureHandler> {
@Override
public Class<PinchGestureHandler> getType() {
return PinchGestureHandler.class;
}
@Override
public String getName() {
return "PinchGestureHandler";
}
@Override
public PinchGestureHandler create(Context context) {
return new PinchGestureHandler();
}
@Override
public void extractEventData(PinchGestureHandler handler, WritableMap eventData) {
super.extractEventData(handler, eventData);
eventData.putDouble("scale", handler.getScale());
eventData.putDouble("focalX", PixelUtil.toDIPFromPixel(handler.getFocalPointX()));
eventData.putDouble("focalY", PixelUtil.toDIPFromPixel(handler.getFocalPointY()));
eventData.putDouble("velocity", handler.getVelocity());
}
}
private static class FlingGestureHandlerFactory extends HandlerFactory<FlingGestureHandler> {
@Override
public Class<FlingGestureHandler> getType() {
return FlingGestureHandler.class;
}
@Override
public String getName() {
return "FlingGestureHandler";
}
@Override
public FlingGestureHandler create(Context context) {
return new FlingGestureHandler();
}
@Override
public void configure(FlingGestureHandler handler, ReadableMap config) {
super.configure(handler, config);
if (config.hasKey(KEY_NUMBER_OF_POINTERS)) {
handler.setNumberOfPointersRequired(config.getInt(KEY_NUMBER_OF_POINTERS));
}
if (config.hasKey(KEY_DIRECTION)) {
handler.setDirection(config.getInt(KEY_DIRECTION));
}
}
@Override
public void extractEventData(FlingGestureHandler handler, WritableMap eventData) {
super.extractEventData(handler, eventData);
eventData.putDouble("x", PixelUtil.toDIPFromPixel(handler.getLastRelativePositionX()));
eventData.putDouble("y", PixelUtil.toDIPFromPixel(handler.getLastRelativePositionY()));
eventData.putDouble("absoluteX", PixelUtil.toDIPFromPixel(handler.getLastAbsolutePositionX()));
eventData.putDouble("absoluteY", PixelUtil.toDIPFromPixel(handler.getLastAbsolutePositionY()));
}
}
private static class RotationGestureHandlerFactory extends HandlerFactory<RotationGestureHandler> {
@Override
public Class<RotationGestureHandler> getType() {
return RotationGestureHandler.class;
}
@Override
public String getName() {
return "RotationGestureHandler";
}
@Override
public RotationGestureHandler create(Context context) {
return new RotationGestureHandler();
}
@Override
public void extractEventData(RotationGestureHandler handler, WritableMap eventData) {
super.extractEventData(handler, eventData);
eventData.putDouble("rotation", handler.getRotation());
eventData.putDouble("anchorX", PixelUtil.toDIPFromPixel(handler.getAnchorX()));
eventData.putDouble("anchorY", PixelUtil.toDIPFromPixel(handler.getAnchorY()));
eventData.putDouble("velocity", handler.getVelocity());
}
}
private OnTouchEventListener mEventListener = new OnTouchEventListener() {
@Override
public void onTouchEvent(GestureHandler handler, MotionEvent event) {
RNGestureHandlerModule.this.onTouchEvent(handler, event);
}
@Override
public void onStateChange(GestureHandler handler, int newState, int oldState) {
RNGestureHandlerModule.this.onStateChange(handler, newState, oldState);
}
};
private HandlerFactory[] mHandlerFactories = new HandlerFactory[] {
new NativeViewGestureHandlerFactory(),
new TapGestureHandlerFactory(),
new LongPressGestureHandlerFactory(),
new PanGestureHandlerFactory(),
new PinchGestureHandlerFactory(),
new RotationGestureHandlerFactory(),
new FlingGestureHandlerFactory()
};
private final RNGestureHandlerRegistry mRegistry = new RNGestureHandlerRegistry();
private RNGestureHandlerInteractionManager mInteractionManager =
new RNGestureHandlerInteractionManager();
private List<RNGestureHandlerRootHelper> mRoots = new ArrayList<>();
private List<Integer> mEnqueuedRootViewInit = new ArrayList<>();
public RNGestureHandlerModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
public String getName() {
return MODULE_NAME;
}
@ReactMethod
public void createGestureHandler(
String handlerName,
int handlerTag,
ReadableMap config) {
for (int i = 0; i < mHandlerFactories.length; i++) {
HandlerFactory handlerFactory = mHandlerFactories[i];
if (handlerFactory.getName().equals(handlerName)) {
GestureHandler handler = handlerFactory.create(getReactApplicationContext());
handler.setTag(handlerTag);
handler.setOnTouchEventListener(mEventListener);
mRegistry.registerHandler(handler);
mInteractionManager.configureInteractions(handler, config);
handlerFactory.configure(handler, config);
return;
}
}
throw new JSApplicationIllegalArgumentException("Invalid handler name " + handlerName);
}
@ReactMethod
public void attachGestureHandler(int handlerTag, int viewTag) {
tryInitializeHandlerForReactRootView(viewTag);
if (!mRegistry.attachHandlerToView(handlerTag, viewTag)) {
throw new JSApplicationIllegalArgumentException(
"Handler with tag " + handlerTag + " does not exists");
}
}
@ReactMethod
public void updateGestureHandler(
int handlerTag,
ReadableMap config) {
GestureHandler handler = mRegistry.getHandler(handlerTag);
if (handler != null) {
HandlerFactory factory = findFactoryForHandler(handler);
if (factory != null) {
mInteractionManager.dropRelationsForHandlerWithTag(handlerTag);
mInteractionManager.configureInteractions(handler, config);
factory.configure(handler, config);
}
}
}
@ReactMethod
public void dropGestureHandler(int handlerTag) {
mInteractionManager.dropRelationsForHandlerWithTag(handlerTag);
mRegistry.dropHandler(handlerTag);
}
@ReactMethod
public void handleSetJSResponder(int viewTag, boolean blockNativeResponder) {
if (mRegistry != null) {
RNGestureHandlerRootHelper rootView = findRootHelperForViewAncestor(viewTag);
if (rootView != null) {
rootView.handleSetJSResponder(viewTag, blockNativeResponder);
}
}
}
@ReactMethod
public void handleClearJSResponder() {
}
@Override
public @Nullable Map getConstants() {
return MapBuilder.of("State", MapBuilder.of(
"UNDETERMINED", GestureHandler.STATE_UNDETERMINED,
"BEGAN", GestureHandler.STATE_BEGAN,
"ACTIVE", GestureHandler.STATE_ACTIVE,
"CANCELLED", GestureHandler.STATE_CANCELLED,
"FAILED", GestureHandler.STATE_FAILED,
"END", GestureHandler.STATE_END
), "Direction", MapBuilder.of(
"RIGHT", GestureHandler.DIRECTION_RIGHT,
"LEFT", GestureHandler.DIRECTION_LEFT,
"UP", GestureHandler.DIRECTION_UP,
"DOWN", GestureHandler.DIRECTION_DOWN
));
}
public RNGestureHandlerRegistry getRegistry() {
return mRegistry;
}
@Override
public void onCatalystInstanceDestroy() {
mRegistry.dropAllHandlers();
mInteractionManager.reset();
synchronized (mRoots) {
while (!mRoots.isEmpty()) {
int sizeBefore = mRoots.size();
RNGestureHandlerRootHelper root = mRoots.get(0);
ViewGroup reactRootView = root.getRootView();
if (reactRootView instanceof RNGestureHandlerEnabledRootView) {
((RNGestureHandlerEnabledRootView) reactRootView).tearDown();
} else {
root.tearDown();
}
if (mRoots.size() >= sizeBefore) {
throw new IllegalStateException("Expected root helper to get unregistered while tearing down");
}
}
}
super.onCatalystInstanceDestroy();
}
private void tryInitializeHandlerForReactRootView(int ancestorViewTag) {
UIManagerModule uiManager = getReactApplicationContext().getNativeModule(UIManagerModule.class);
final int rootViewTag = uiManager.resolveRootTagFromReactTag(ancestorViewTag);
if (rootViewTag < 1) {
throw new JSApplicationIllegalArgumentException("Could find root view for a given ancestor with tag "
+ ancestorViewTag);
}
synchronized (mRoots) {
for (int i = 0; i < mRoots.size(); i++) {
RNGestureHandlerRootHelper root = mRoots.get(i);
ViewGroup rootView = root.getRootView();
if (rootView instanceof ReactRootView && ((ReactRootView) rootView).getRootViewTag() == rootViewTag) {
// we have found root helper registered for a given react root, we don't need to
// initialize a new one then
return;
}
}
}
synchronized (mEnqueuedRootViewInit) {
if (mEnqueuedRootViewInit.contains(rootViewTag)) {
// root view initialization already enqueued -> we skip
return;
}
mEnqueuedRootViewInit.add(rootViewTag);
}
// root helper for a given root tag has not been found, we may wat to check if the root view is
// an instance of RNGestureHandlerEnabledRootView and then initialize gesture handler with it
uiManager.addUIBlock(new UIBlock() {
@Override
public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) {
View view = nativeViewHierarchyManager.resolveView(rootViewTag);
if (view instanceof RNGestureHandlerEnabledRootView) {
((RNGestureHandlerEnabledRootView) view).initialize();
} else {
// Seems like the root view is something else than RNGestureHandlerEnabledRootView, this
// is fine though as long as gestureHandlerRootHOC is used in JS
// FIXME: check and warn about gestureHandlerRootHOC
}
synchronized (mEnqueuedRootViewInit) {
mEnqueuedRootViewInit.remove(new Integer(rootViewTag));
}
}
});
}
public void registerRootHelper(RNGestureHandlerRootHelper root) {
synchronized (mRoots) {
if (mRoots.contains(root)) {
throw new IllegalStateException("Root helper" + root + " already registered");
}
mRoots.add(root);
}
}
public void unregisterRootHelper(RNGestureHandlerRootHelper root) {
synchronized (mRoots) {
mRoots.remove(root);
}
}
private @Nullable RNGestureHandlerRootHelper findRootHelperForViewAncestor(int viewTag) {
UIManagerModule uiManager = getReactApplicationContext().getNativeModule(UIManagerModule.class);
int rootViewTag = uiManager.resolveRootTagFromReactTag(viewTag);
if (rootViewTag < 1) {
return null;
}
synchronized (mRoots) {
for (int i = 0; i < mRoots.size(); i++) {
RNGestureHandlerRootHelper root = mRoots.get(i);
ViewGroup rootView = root.getRootView();
if (rootView instanceof ReactRootView && ((ReactRootView) rootView).getRootViewTag() == rootViewTag) {
return root;
}
}
}
return null;
}
private @Nullable HandlerFactory findFactoryForHandler(GestureHandler handler) {
for (int i = 0; i < mHandlerFactories.length; i++) {
HandlerFactory factory = mHandlerFactories[i];
if (factory.getType().equals(handler.getClass())) {
return factory;
}
}
return null;
}
private void onTouchEvent(GestureHandler handler, MotionEvent motionEvent) {
if (handler.getTag() < 0) {
// root containers use negative tags, we don't need to dispatch events for them to the JS
return;
}
if (handler.getState() == GestureHandler.STATE_ACTIVE) {
HandlerFactory handlerFactory = findFactoryForHandler(handler);
EventDispatcher eventDispatcher = getReactApplicationContext()
.getNativeModule(UIManagerModule.class)
.getEventDispatcher();
RNGestureHandlerEvent event = RNGestureHandlerEvent.obtain(handler, handlerFactory);
eventDispatcher.dispatchEvent(event);
}
}
private void onStateChange(GestureHandler handler, int newState, int oldState) {
if (handler.getTag() < 0) {
// root containers use negative tags, we don't need to dispatch events for them to the JS
return;
}
HandlerFactory handlerFactory = findFactoryForHandler(handler);
EventDispatcher eventDispatcher = getReactApplicationContext()
.getNativeModule(UIManagerModule.class)
.getEventDispatcher();
RNGestureHandlerStateChangeEvent event = RNGestureHandlerStateChangeEvent.obtain(
handler,
newState,
oldState,
handlerFactory);
eventDispatcher.dispatchEvent(event);
}
private static void handleHitSlopProperty(GestureHandler handler, ReadableMap config) {
if (config.getType(KEY_HIT_SLOP) == ReadableType.Number) {
float hitSlop = PixelUtil.toPixelFromDIP(config.getDouble(KEY_HIT_SLOP));
handler.setHitSlop(hitSlop, hitSlop, hitSlop, hitSlop, HIT_SLOP_NONE, HIT_SLOP_NONE);
} else {
ReadableMap hitSlop = config.getMap(KEY_HIT_SLOP);
float left = HIT_SLOP_NONE, top = HIT_SLOP_NONE, right = HIT_SLOP_NONE, bottom = HIT_SLOP_NONE;
float width = HIT_SLOP_NONE, height = HIT_SLOP_NONE;
if (hitSlop.hasKey(KEY_HIT_SLOP_HORIZONTAL)) {
float horizontalPad = PixelUtil.toPixelFromDIP(hitSlop.getDouble(KEY_HIT_SLOP_HORIZONTAL));
left = right = horizontalPad;
}
if (hitSlop.hasKey(KEY_HIT_SLOP_VERTICAL)) {
float verticalPad = PixelUtil.toPixelFromDIP(hitSlop.getDouble(KEY_HIT_SLOP_VERTICAL));
top = bottom = verticalPad;
}
if (hitSlop.hasKey(KEY_HIT_SLOP_LEFT)) {
left = PixelUtil.toPixelFromDIP(hitSlop.getDouble(KEY_HIT_SLOP_LEFT));
}
if (hitSlop.hasKey(KEY_HIT_SLOP_TOP)) {
top = PixelUtil.toPixelFromDIP(hitSlop.getDouble(KEY_HIT_SLOP_TOP));
}
if (hitSlop.hasKey(KEY_HIT_SLOP_RIGHT)) {
right = PixelUtil.toPixelFromDIP(hitSlop.getDouble(KEY_HIT_SLOP_RIGHT));
}
if (hitSlop.hasKey(KEY_HIT_SLOP_BOTTOM)) {
bottom = PixelUtil.toPixelFromDIP(hitSlop.getDouble(KEY_HIT_SLOP_BOTTOM));
}
if (hitSlop.hasKey(KEY_HIT_SLOP_WIDTH)) {
width = PixelUtil.toPixelFromDIP(hitSlop.getDouble(KEY_HIT_SLOP_WIDTH));
}
if (hitSlop.hasKey(KEY_HIT_SLOP_HEIGHT)) {
height = PixelUtil.toPixelFromDIP(hitSlop.getDouble(KEY_HIT_SLOP_HEIGHT));
}
handler.setHitSlop(left, top, right, bottom, width, height);
}
}
}

View File

@ -0,0 +1,31 @@
package com.swmansion.gesturehandler.react;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.ViewManager;
import com.facebook.react.views.view.ReactViewManager;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import androidx.annotation.Nullable;
public class RNGestureHandlerPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Arrays.<NativeModule>asList(new RNGestureHandlerModule(reactContext));
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Arrays.<ViewManager>asList(
new RNGestureHandlerRootViewManager(),
new RNGestureHandlerButtonViewManager());
}
}

View File

@ -0,0 +1,101 @@
package com.swmansion.gesturehandler.react;
import android.util.SparseArray;
import android.view.View;
import com.facebook.react.bridge.UiThreadUtil;
import com.swmansion.gesturehandler.GestureHandler;
import com.swmansion.gesturehandler.GestureHandlerRegistry;
import java.util.ArrayList;
import androidx.annotation.Nullable;
public class RNGestureHandlerRegistry implements GestureHandlerRegistry {
private final SparseArray<GestureHandler> mHandlers = new SparseArray<>();
private final SparseArray<Integer> mAttachedTo = new SparseArray<>();
private final SparseArray<ArrayList<GestureHandler>> mHandlersForView = new SparseArray<>();
public synchronized void registerHandler(GestureHandler handler) {
mHandlers.put(handler.getTag(), handler);
}
public synchronized @Nullable GestureHandler getHandler(int handlerTag) {
return mHandlers.get(handlerTag);
}
public synchronized boolean attachHandlerToView(int handlerTag, int viewTag) {
GestureHandler handler = mHandlers.get(handlerTag);
if (handler != null) {
detachHandler(handler);
registerHandlerForViewWithTag(viewTag, handler);
return true;
} else {
return false;
}
}
private synchronized void registerHandlerForViewWithTag(int viewTag, GestureHandler handler) {
if (mAttachedTo.get(handler.getTag()) != null) {
throw new IllegalStateException("Handler " + handler + " already attached");
}
mAttachedTo.put(handler.getTag(), viewTag);
ArrayList<GestureHandler> listToAdd = mHandlersForView.get(viewTag);
if (listToAdd == null) {
listToAdd = new ArrayList<>(1);
listToAdd.add(handler);
mHandlersForView.put(viewTag, listToAdd);
} else {
listToAdd.add(handler);
}
}
private synchronized void detachHandler(final GestureHandler handler) {
Integer attachedToView = mAttachedTo.get(handler.getTag());
if (attachedToView != null) {
mAttachedTo.remove(handler.getTag());
ArrayList<GestureHandler> attachedHandlers = mHandlersForView.get(attachedToView);
if (attachedHandlers != null) {
attachedHandlers.remove(handler);
if (attachedHandlers.size() == 0) {
mHandlersForView.remove(attachedToView);
}
}
}
if (handler.getView() != null) {
// Handler is in "prepared" state which means it is registered in the orchestrator and can
// receive touch events. This means that before we remove it from the registry we need to
// "cancel" it so that orchestrator does no longer keep a reference to it.
UiThreadUtil.runOnUiThread(new Runnable() {
@Override
public void run() {
handler.cancel();
}
});
}
}
public synchronized void dropHandler(int handlerTag) {
GestureHandler handler = mHandlers.get(handlerTag);
if (handler != null) {
detachHandler(handler);
mHandlers.remove(handlerTag);
}
}
public synchronized void dropAllHandlers() {
mHandlers.clear();
mAttachedTo.clear();
mHandlersForView.clear();
}
public synchronized ArrayList<GestureHandler> getHandlersForViewWithTag(int viewTag) {
return mHandlersForView.get(viewTag);
}
@Override
public synchronized ArrayList<GestureHandler> getHandlersForView(View view) {
return getHandlersForViewWithTag(view.getId());
}
}

View File

@ -0,0 +1,151 @@
package com.swmansion.gesturehandler.react;
import android.os.SystemClock;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.view.ViewParent;
import com.facebook.react.ReactRootView;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.views.modal.RNGHModalUtils;
import com.swmansion.gesturehandler.GestureHandler;
import com.swmansion.gesturehandler.GestureHandlerOrchestrator;
public class RNGestureHandlerRootHelper {
private static final float MIN_ALPHA_FOR_TOUCH = 0.1f;
private final ReactContext mContext;
private final GestureHandlerOrchestrator mOrchestrator;
private final GestureHandler mJSGestureHandler;
private final ViewGroup mRootView;
private boolean mShouldIntercept = false;
private boolean mPassingTouch = false;
private static ViewGroup findRootViewTag(ViewGroup viewGroup) {
UiThreadUtil.assertOnUiThread();
ViewParent parent = viewGroup;
while (parent != null && !(parent instanceof ReactRootView || RNGHModalUtils.isDialogRootViewGroup(parent))) {
parent = parent.getParent();
}
if (parent == null) {
throw new IllegalStateException("View " + viewGroup + " has not been mounted under" +
" ReactRootView");
}
return (ViewGroup) parent;
}
public RNGestureHandlerRootHelper(ReactContext context, ViewGroup wrappedView) {
UiThreadUtil.assertOnUiThread();
int wrappedViewTag = wrappedView.getId();
if (wrappedViewTag < 1) {
throw new IllegalStateException("Expect view tag to be set for " + wrappedView);
}
RNGestureHandlerModule module = context.getNativeModule(RNGestureHandlerModule.class);
RNGestureHandlerRegistry registry = module.getRegistry();
mRootView = findRootViewTag(wrappedView);
Log.i(
ReactConstants.TAG,
"[GESTURE HANDLER] Initialize gesture handler for root view " + mRootView);
mContext = context;
mOrchestrator = new GestureHandlerOrchestrator(
wrappedView, registry, new RNViewConfigurationHelper());
mOrchestrator.setMinimumAlphaForTraversal(MIN_ALPHA_FOR_TOUCH);
mJSGestureHandler = new RootViewGestureHandler();
mJSGestureHandler.setTag(-wrappedViewTag);
registry.registerHandler(mJSGestureHandler);
registry.attachHandlerToView(mJSGestureHandler.getTag(), wrappedViewTag);
module.registerRootHelper(this);
}
public void tearDown() {
Log.i(
ReactConstants.TAG,
"[GESTURE HANDLER] Tearing down gesture handler registered for root view " + mRootView);
RNGestureHandlerModule module = mContext.getNativeModule(RNGestureHandlerModule.class);
module.getRegistry().dropHandler(mJSGestureHandler.getTag());
module.unregisterRootHelper(this);
}
public ViewGroup getRootView() {
return mRootView;
}
private class RootViewGestureHandler extends GestureHandler {
@Override
protected void onHandle(MotionEvent event) {
int currentState = getState();
if (currentState == STATE_UNDETERMINED) {
begin();
mShouldIntercept = false;
}
if (event.getActionMasked() == MotionEvent.ACTION_UP) {
end();
}
}
@Override
protected void onCancel() {
mShouldIntercept = true;
long time = SystemClock.uptimeMillis();
MotionEvent event = MotionEvent.obtain(time, time, MotionEvent.ACTION_CANCEL, 0, 0, 0);
event.setAction(MotionEvent.ACTION_CANCEL);
if (mRootView instanceof ReactRootView) {
((ReactRootView) mRootView).onChildStartedNativeGesture(event);
} else {
RNGHModalUtils.dialogRootViewGroupOnChildStartedNativeGesture(mRootView, event);
}
}
}
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
// If this method gets called it means that some native view is attempting to grab lock for
// touch event delivery. In that case we cancel all gesture recognizers
if (mOrchestrator != null && !mPassingTouch) {
// if we are in the process of delivering touch events via GH orchestrator, we don't want to
// treat it as a native gesture capturing the lock
tryCancelAllHandlers();
}
}
public boolean dispatchTouchEvent(MotionEvent ev) {
// We mark `mPassingTouch` before we get into `mOrchestrator.onTouchEvent` so that we can tell
// if `requestDisallow` has been called as a result of a normal gesture handling process or
// as a result of one of the gesture handlers activating
mPassingTouch = true;
mOrchestrator.onTouchEvent(ev);
mPassingTouch = false;
return mShouldIntercept;
}
private void tryCancelAllHandlers() {
// In order to cancel handlers we activate handler that is hooked to the root view
if (mJSGestureHandler != null && mJSGestureHandler.getState() == GestureHandler.STATE_BEGAN) {
// Try activate main JS handler
mJSGestureHandler.activate();
mJSGestureHandler.end();
}
}
/*package*/ void handleSetJSResponder(final int viewTag, final boolean blockNativeResponder) {
if (blockNativeResponder) {
UiThreadUtil.runOnUiThread(new Runnable() {
@Override
public void run() {
tryCancelAllHandlers();
}
});
}
}
}

View File

@ -0,0 +1,7 @@
package com.swmansion.gesturehandler.react;
import androidx.annotation.Nullable;
public interface RNGestureHandlerRootInterface {
@Nullable RNGestureHandlerRootHelper getRootHelper();
}

View File

@ -0,0 +1,76 @@
package com.swmansion.gesturehandler.react;
import android.util.Log;
import android.content.Context;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.view.ViewParent;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.views.view.ReactViewGroup;
import androidx.annotation.Nullable;
public class RNGestureHandlerRootView extends ReactViewGroup {
private static boolean hasGestureHandlerEnabledRootView(ViewGroup viewGroup) {
UiThreadUtil.assertOnUiThread();
ViewParent parent = viewGroup.getParent();
while (parent != null) {
if (parent instanceof RNGestureHandlerEnabledRootView || parent instanceof RNGestureHandlerRootView) {
return true;
}
parent = parent.getParent();
}
return false;
}
private boolean mEnabled;
private @Nullable RNGestureHandlerRootHelper mRootHelper;
public RNGestureHandlerRootView(Context context) {
super(context);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mEnabled = !hasGestureHandlerEnabledRootView(this);
if (!mEnabled) {
Log.i(
ReactConstants.TAG,
"[GESTURE HANDLER] Gesture handler is already enabled for a parent view");
}
if (mEnabled && mRootHelper == null) {
mRootHelper = new RNGestureHandlerRootHelper((ReactContext) getContext(), this);
}
}
public void tearDown() {
if (mRootHelper != null) {
mRootHelper.tearDown();
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mEnabled && Assertions.assertNotNull(mRootHelper).dispatchTouchEvent(ev)) {
return true;
}
return super.dispatchTouchEvent(ev);
}
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (mEnabled) {
Assertions.assertNotNull(mRootHelper).requestDisallowInterceptTouchEvent(disallowIntercept);
}
super.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}

View File

@ -0,0 +1,49 @@
package com.swmansion.gesturehandler.react;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import java.util.Map;
import androidx.annotation.Nullable;
/**
* React native's view manager used for creating instances of {@link }RNGestureHandlerRootView}. It
* is being used by projects using react-native-navigation where for each screen new root view need
* to be provided.
*/
@ReactModule(name = RNGestureHandlerRootViewManager.REACT_CLASS)
public class RNGestureHandlerRootViewManager extends ViewGroupManager<RNGestureHandlerRootView> {
public static final String REACT_CLASS = "GestureHandlerRootView";
@Override
public String getName() {
return REACT_CLASS;
}
@Override
protected RNGestureHandlerRootView createViewInstance(ThemedReactContext reactContext) {
return new RNGestureHandlerRootView(reactContext);
}
@Override
public void onDropViewInstance(RNGestureHandlerRootView view) {
view.tearDown();
}
/**
* The following event configuration is necessary even if you are not using
* GestureHandlerRootView component directly.
*/
@Override
public @Nullable Map getExportedCustomDirectEventTypeConstants() {
return MapBuilder.of(
RNGestureHandlerEvent.EVENT_NAME,
MapBuilder.of("registrationName", RNGestureHandlerEvent.EVENT_NAME),
RNGestureHandlerStateChangeEvent.EVENT_NAME,
MapBuilder.of("registrationName", RNGestureHandlerStateChangeEvent.EVENT_NAME));
}
}

View File

@ -0,0 +1,82 @@
package com.swmansion.gesturehandler.react;
import androidx.core.util.Pools;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
import com.facebook.react.uimanager.events.RCTEventEmitter;
import com.swmansion.gesturehandler.GestureHandler;
import androidx.annotation.Nullable;
public class RNGestureHandlerStateChangeEvent extends Event<RNGestureHandlerStateChangeEvent>{
public static final String EVENT_NAME = "onGestureHandlerStateChange";
private static final int TOUCH_EVENTS_POOL_SIZE = 7; // magic
private static final Pools.SynchronizedPool<RNGestureHandlerStateChangeEvent> EVENTS_POOL =
new Pools.SynchronizedPool<>(TOUCH_EVENTS_POOL_SIZE);
public static RNGestureHandlerStateChangeEvent obtain(
GestureHandler handler,
int newState,
int oldState,
@Nullable RNGestureHandlerEventDataExtractor dataExtractor) {
RNGestureHandlerStateChangeEvent event = EVENTS_POOL.acquire();
if (event == null) {
event = new RNGestureHandlerStateChangeEvent();
}
event.init(handler, newState, oldState, dataExtractor);
return event;
}
private WritableMap mExtraData;
private RNGestureHandlerStateChangeEvent() {
}
private void init(
GestureHandler handler,
int newState,
int oldState,
@Nullable RNGestureHandlerEventDataExtractor dataExtractor) {
super.init(handler.getView().getId());
mExtraData = Arguments.createMap();
if (dataExtractor != null) {
dataExtractor.extractEventData(handler, mExtraData);
}
mExtraData.putInt("handlerTag", handler.getTag());
mExtraData.putInt("state", newState);
mExtraData.putInt("oldState", oldState);
}
@Override
public void onDispose() {
mExtraData = null;
EVENTS_POOL.release(this);
}
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public boolean canCoalesce() {
// TODO: coalescing
return false;
}
@Override
public short getCoalescingKey() {
// TODO: coalescing
return 0;
}
@Override
public void dispatch(RCTEventEmitter rctEventEmitter) {
rctEventEmitter.receiveEvent(getViewTag(), EVENT_NAME, mExtraData);
}
}

View File

@ -0,0 +1,61 @@
package com.swmansion.gesturehandler.react;
import android.os.Build;
import android.view.View;
import android.view.ViewGroup;
import com.facebook.react.uimanager.PointerEvents;
import com.facebook.react.uimanager.ReactPointerEventsView;
import com.facebook.react.views.view.ReactViewGroup;
import com.swmansion.gesturehandler.PointerEventsConfig;
import com.swmansion.gesturehandler.ViewConfigurationHelper;
public class RNViewConfigurationHelper implements ViewConfigurationHelper {
@Override
public PointerEventsConfig getPointerEventsConfigForView(View view) {
PointerEvents pointerEvents;
pointerEvents = view instanceof ReactPointerEventsView ?
((ReactPointerEventsView) view).getPointerEvents() :
PointerEvents.AUTO;
// Views that are disabled should never be the target of pointer events. However, their children
// can be because some views (SwipeRefreshLayout) use enabled but still have children that can
// be valid targets.
if (!view.isEnabled()) {
if (pointerEvents == PointerEvents.AUTO) {
return PointerEventsConfig.BOX_NONE;
} else if (pointerEvents == PointerEvents.BOX_ONLY) {
return PointerEventsConfig.NONE;
}
}
switch (pointerEvents) {
case BOX_ONLY: return PointerEventsConfig.BOX_ONLY;
case BOX_NONE: return PointerEventsConfig.BOX_NONE;
case NONE: return PointerEventsConfig.NONE;
}
return PointerEventsConfig.AUTO;
}
@Override
public View getChildInDrawingOrderAtIndex(ViewGroup parent, int index) {
if (parent instanceof ReactViewGroup) {
return parent.getChildAt(((ReactViewGroup) parent).getZIndexMappedChildIndex(index));
}
return parent.getChildAt(index);
}
@Override
public boolean isViewClippingChildren(ViewGroup view) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 && !view.getClipChildren()) {
if (view instanceof ReactViewGroup) {
String overflow = ((ReactViewGroup) view).getOverflow();
return "hidden".equals(overflow);
}
return false;
}
return true;
}
}

View File

@ -0,0 +1,352 @@
import React from 'react';
import {
findNodeHandle as findNodeHandleRN,
NativeModules,
Touchable,
Platform,
} from 'react-native';
import deepEqual from 'fbjs/lib/areEqual';
import RNGestureHandlerModule from './RNGestureHandlerModule';
import State from './State';
function findNodeHandle(node) {
if (Platform.OS === 'web') return node;
return findNodeHandleRN(node);
}
const { UIManager = {} } = NativeModules;
const customGHEventsConfig = {
onGestureHandlerEvent: { registrationName: 'onGestureHandlerEvent' },
onGestureHandlerStateChange: {
registrationName: 'onGestureHandlerStateChange',
},
};
// Add gesture specific events to genericDirectEventTypes object exported from UIManager
// native module.
// Once new event types are registered with react it is possible to dispatch these
// events to all kind of native views.
UIManager.genericDirectEventTypes = {
...UIManager.genericDirectEventTypes,
...customGHEventsConfig,
};
// In newer versions of RN the `genericDirectEventTypes` is located in the object
// returned by UIManager.getConstants(), we need to add it there as well to make
// it compatible with RN 61+
if (UIManager.getConstants) {
UIManager.getConstants().genericDirectEventTypes = {
...UIManager.getConstants().genericDirectEventTypes,
...customGHEventsConfig,
};
}
// Wrap JS responder calls and notify gesture handler manager
const {
setJSResponder: oldSetJSResponder = () => {},
clearJSResponder: oldClearJSResponder = () => {},
} = UIManager;
UIManager.setJSResponder = (tag, blockNativeResponder) => {
RNGestureHandlerModule.handleSetJSResponder(tag, blockNativeResponder);
oldSetJSResponder(tag, blockNativeResponder);
};
UIManager.clearJSResponder = () => {
RNGestureHandlerModule.handleClearJSResponder();
oldClearJSResponder();
};
let handlerTag = 1;
const handlerIDToTag = {};
function isConfigParam(param, name) {
// param !== Object(param) returns false if `param` is a function
// or an object and returns true if `param` is null
return (
param !== undefined &&
(param !== Object(param) || !('__isNative' in param)) &&
name !== 'onHandlerStateChange' &&
name !== 'onGestureEvent'
);
}
function filterConfig(props, validProps, defaults = {}) {
const res = { ...defaults };
Object.keys(validProps).forEach(key => {
const value = props[key];
if (isConfigParam(value, key)) {
let value = props[key];
if (key === 'simultaneousHandlers' || key === 'waitFor') {
value = transformIntoHandlerTags(props[key]);
} else if (key === 'hitSlop') {
if (typeof value !== 'object') {
value = { top: value, left: value, bottom: value, right: value };
}
}
res[key] = value;
}
});
return res;
}
function transformIntoHandlerTags(handlerIDs) {
if (!Array.isArray(handlerIDs)) {
handlerIDs = [handlerIDs];
}
if (Platform.OS === 'web') {
return handlerIDs.map(({ current }) => current).filter(handle => handle);
}
// converts handler string IDs into their numeric tags
return handlerIDs
.map(
handlerID =>
handlerIDToTag[handlerID] ||
(handlerID.current && handlerID.current._handlerTag) ||
-1
)
.filter(handlerTag => handlerTag > 0);
}
function hasUnresolvedRefs(props) {
const extract = refs => {
if (!Array.isArray(refs)) {
return refs && refs.current === null;
}
return refs.some(r => r && r.current === null);
};
return extract(props['simultaneousHandlers']) || extract(props['waitFor']);
}
const stateToPropMappings = {
[State.BEGAN]: 'onBegan',
[State.FAILED]: 'onFailed',
[State.CANCELLED]: 'onCancelled',
[State.ACTIVE]: 'onActivated',
[State.END]: 'onEnded',
};
export default function createHandler(
handlerName,
propTypes = {},
config = {},
transformProps,
customNativeProps = {}
) {
class Handler extends React.Component {
static displayName = handlerName;
static propTypes = propTypes;
constructor(props) {
super(props);
this._handlerTag = handlerTag++;
this._config = {};
if (props.id) {
if (handlerIDToTag[props.id] !== undefined) {
throw new Error(`Handler with ID "${props.id}" already registered`);
}
handlerIDToTag[props.id] = this._handlerTag;
}
}
_onGestureHandlerEvent = event => {
if (event.nativeEvent.handlerTag === this._handlerTag) {
this.props.onGestureEvent && this.props.onGestureEvent(event);
} else {
this.props.onGestureHandlerEvent &&
this.props.onGestureHandlerEvent(event);
}
};
_onGestureHandlerStateChange = event => {
if (event.nativeEvent.handlerTag === this._handlerTag) {
this.props.onHandlerStateChange &&
this.props.onHandlerStateChange(event);
const stateEventName = stateToPropMappings[event.nativeEvent.state];
if (typeof this.props[stateEventName] === 'function') {
this.props[stateEventName](event);
}
} else {
this.props.onGestureHandlerStateChange &&
this.props.onGestureHandlerStateChange(event);
}
};
_refHandler = node => {
this._viewNode = node;
const child = React.Children.only(this.props.children);
const { ref } = child;
if (ref !== null) {
if (typeof ref === 'function') {
ref(node);
} else {
ref.current = node;
}
}
};
_createGestureHandler = newConfig => {
this._config = newConfig;
RNGestureHandlerModule.createGestureHandler(
handlerName,
this._handlerTag,
newConfig
);
};
_attachGestureHandler = newViewTag => {
this._viewTag = newViewTag;
RNGestureHandlerModule.attachGestureHandler(this._handlerTag, newViewTag);
};
_updateGestureHandler = newConfig => {
this._config = newConfig;
RNGestureHandlerModule.updateGestureHandler(this._handlerTag, newConfig);
};
componentWillUnmount() {
RNGestureHandlerModule.dropGestureHandler(this._handlerTag);
if (this._updateEnqueued) {
clearImmediate(this._updateEnqueued);
}
if (this.props.id) {
delete handlerIDToTag[this.props.id];
}
}
componentDidMount() {
if (hasUnresolvedRefs(this.props)) {
// If there are unresolved refs (e.g. ".current" has not yet been set)
// passed as `simultaneousHandlers` or `waitFor`, we enqueue a call to
// _update method that will try to update native handler props using
// setImmediate. This makes it so _update function gets called after all
// react components are mounted and we expect the missing ref object to
// be resolved by then.
this._updateEnqueued = setImmediate(() => {
this._updateEnqueued = null;
this._update();
});
}
this._createGestureHandler(
filterConfig(
transformProps ? transformProps(this.props) : this.props,
{ ...this.constructor.propTypes, ...customNativeProps },
config
)
);
this._attachGestureHandler(findNodeHandle(this._viewNode));
}
componentDidUpdate() {
const viewTag = findNodeHandle(this._viewNode);
if (this._viewTag !== viewTag) {
this._attachGestureHandler(viewTag);
}
this._update();
}
_update() {
const newConfig = filterConfig(
transformProps ? transformProps(this.props) : this.props,
{ ...this.constructor.propTypes, ...customNativeProps },
config
);
if (!deepEqual(this._config, newConfig)) {
this._updateGestureHandler(newConfig);
}
}
setNativeProps(updates) {
const mergedProps = { ...this.props, ...updates };
const newConfig = filterConfig(
transformProps ? transformProps(mergedProps) : mergedProps,
{ ...this.constructor.propTypes, ...customNativeProps },
config
);
this._updateGestureHandler(newConfig);
}
render() {
let gestureEventHandler = this._onGestureHandlerEvent;
const { onGestureEvent, onGestureHandlerEvent } = this.props;
if (onGestureEvent && typeof onGestureEvent !== 'function') {
// If it's not a method it should be an native Animated.event
// object. We set it directly as the handler for the view
// In this case nested handlers are not going to be supported
if (onGestureHandlerEvent) {
throw new Error(
'Nesting touch handlers with native animated driver is not supported yet'
);
}
gestureEventHandler = this.props.onGestureEvent;
} else {
if (
onGestureHandlerEvent &&
typeof onGestureHandlerEvent !== 'function'
) {
throw new Error(
'Nesting touch handlers with native animated driver is not supported yet'
);
}
}
let gestureStateEventHandler = this._onGestureHandlerStateChange;
const { onHandlerStateChange, onGestureHandlerStateChange } = this.props;
if (onHandlerStateChange && typeof onHandlerStateChange !== 'function') {
// If it's not a method it should be an native Animated.event
// object. We set it directly as the handler for the view
// In this case nested handlers are not going to be supported
if (onGestureHandlerStateChange) {
throw new Error(
'Nesting touch handlers with native animated driver is not supported yet'
);
}
gestureStateEventHandler = this.props.onHandlerStateChange;
} else {
if (
onGestureHandlerStateChange &&
typeof onGestureHandlerStateChange !== 'function'
) {
throw new Error(
'Nesting touch handlers with native animated driver is not supported yet'
);
}
}
const child = React.Children.only(this.props.children);
let grandChildren = child.props.children;
if (
Touchable.TOUCH_TARGET_DEBUG &&
child.type &&
(child.type === 'RNGestureHandlerButton' ||
child.type.name === 'View' ||
child.type.displayName === 'View')
) {
grandChildren = React.Children.toArray(grandChildren);
grandChildren.push(
Touchable.renderDebugView({
color: 'mediumspringgreen',
hitSlop: child.props.hitSlop,
})
);
}
return React.cloneElement(
child,
{
ref: this._refHandler,
collapsable: false,
onGestureHandlerEvent: gestureEventHandler,
onGestureHandlerStateChange: gestureStateEventHandler,
},
grandChildren
);
}
}
return Handler;
}

View File

@ -0,0 +1,58 @@
import React from 'react';
import NativeViewGestureHandler from './NativeViewGestureHandler';
/*
* This array should consist of:
* - All keys in propTypes from NativeGestureHandler
* (and all keys in GestureHandlerPropTypes)
* - 'onGestureHandlerEvent'
* - 'onGestureHandlerStateChange'
*/
const NATIVE_WRAPPER_PROPS_FILTER = [
'id',
'minPointers',
'enabled',
'waitFor',
'simultaneousHandlers',
'shouldCancelWhenOutside',
'hitSlop',
'onGestureEvent',
'onHandlerStateChange',
'onBegan',
'onFailed',
'onCancelled',
'onActivated',
'onEnded',
'shouldActivateOnStart',
'disallowInterruption',
'onGestureHandlerEvent',
'onGestureHandlerStateChange',
];
export default function createNativeWrapper(Component, config = {}) {
const ComponentWrapper = React.forwardRef((props, ref) => {
// filter out props that should be passed to gesture handler wrapper
const gestureHandlerProps = Object.keys(props).reduce(
(res, key) => {
if (NATIVE_WRAPPER_PROPS_FILTER.indexOf(key) !== -1) {
res[key] = props[key];
}
return res;
},
{ ...config } // watch out not to modify config
);
return (
<NativeViewGestureHandler {...gestureHandlerProps}>
<Component {...props} ref={ref} />
</NativeViewGestureHandler>
);
});
ComponentWrapper.propTypes = {
...Component.propTypes,
};
ComponentWrapper.displayName = Component.displayName || 'ComponentWrapper';
return ComponentWrapper;
}

View File

@ -0,0 +1,28 @@
import React from 'react';
import { StyleSheet } from 'react-native';
import hoistNonReactStatics from 'hoist-non-react-statics';
import GestureHandlerRootView from './GestureHandlerRootView';
export default function gestureHandlerRootHOC(
Component,
containerStyles = undefined
) {
function Wrapper(props) {
return (
<GestureHandlerRootView style={[styles.container, containerStyles]}>
<Component {...props} />
</GestureHandlerRootView>
);
}
Wrapper.displayName = `gestureHandlerRootHOC(${Component.displayName ||
Component.name})`;
hoistNonReactStatics(Wrapper, Component);
return Wrapper;
}
const styles = StyleSheet.create({
container: { flex: 1 },
});

4
node_modules/react-native-gesture-handler/index.js generated vendored Normal file
View File

@ -0,0 +1,4 @@
export { default as Swipeable } from './Swipeable';
export { default as DrawerLayout } from './DrawerLayout';
export * from './GestureHandler';
export * from './touchables';

View File

@ -0,0 +1,4 @@
#import "RNGestureHandler.h"
@interface RNFlingGestureHandler : RNGestureHandler
@end

View File

@ -0,0 +1,33 @@
#import "RNFlingHandler.h"
@implementation RNFlingGestureHandler
- (instancetype)initWithTag:(NSNumber *)tag
{
if ((self = [super initWithTag:tag])) {
_recognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(handleGesture:)];
}
return self;
}
- (void)configure:(NSDictionary *)config
{
[super configure:config];
UISwipeGestureRecognizer *recognizer = (UISwipeGestureRecognizer *)_recognizer;
id prop = config[@"direction"];
if (prop != nil) {
recognizer.direction = [RCTConvert NSInteger:prop];
}
#if !TARGET_OS_TV
prop = config[@"numberOfPointers"];
if (prop != nil) {
recognizer.numberOfTouchesRequired = [RCTConvert NSInteger:prop];
}
#endif
}
@end

View File

@ -0,0 +1,4 @@
#import "RNGestureHandler.h"
@interface RNForceTouchHandler : RNGestureHandler
@end

View File

@ -0,0 +1,148 @@
#import "RNForceTouchHandler.h"
#import <UIKit/UIGestureRecognizerSubclass.h>
#import <React/RCTConvert.h>
@interface RNForceTouchGestureRecognizer : UIGestureRecognizer
@property (nonatomic) CGFloat maxForce;
@property (nonatomic) CGFloat minForce;
@property (nonatomic) CGFloat force;
@property (nonatomic) BOOL feedbackOnActivation;
- (id)initWithGestureHandler:(RNGestureHandler*)gestureHandler;
@end
@implementation RNForceTouchGestureRecognizer {
__weak RNGestureHandler *_gestureHandler;
UITouch *_firstTouch;
}
- (id)initWithGestureHandler:(RNGestureHandler*)gestureHandler
{
if ((self = [super initWithTarget:gestureHandler action:@selector(handleGesture:)])) {
_gestureHandler = gestureHandler;
_force = 0;
_minForce = 0.2;
_maxForce = NAN;
_feedbackOnActivation = NO;
}
return self;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
if (_firstTouch) {
// ignore rest of fingers
return;
}
[super touchesBegan:touches withEvent:event];
_firstTouch = [touches anyObject];
[self handleForceWithTouches:touches];
self.state = UIGestureRecognizerStatePossible;
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
if (![touches containsObject:_firstTouch]) {
// Considered only the very first touch
return;
}
[super touchesMoved:touches withEvent:event];
[self handleForceWithTouches:touches];
if ([self shouldFail]) {
self.state = UIGestureRecognizerStateFailed;
return;
}
if (self.state == UIGestureRecognizerStatePossible && [self shouldActivate]) {
[self performFeedbackIfRequired];
self.state = UIGestureRecognizerStateBegan;
}
}
- (BOOL)shouldActivate {
return (_force >= _minForce);
}
- (BOOL)shouldFail {
return TEST_MAX_IF_NOT_NAN(_force, _maxForce);
}
- (void)performFeedbackIfRequired
{
#if !TARGET_OS_TV
if (_feedbackOnActivation) {
if (@available(iOS 10.0, *)) {
[[[UIImpactFeedbackGenerator alloc] initWithStyle:(UIImpactFeedbackStyleMedium)] impactOccurred];
}
}
#endif
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
if (![touches containsObject:_firstTouch]) {
// Considered only the very first touch
return;
}
[super touchesEnded:touches withEvent:event];
if (self.state == UIGestureRecognizerStateBegan || self.state == UIGestureRecognizerStateChanged) {
self.state = UIGestureRecognizerStateEnded;
} else {
self.state = UIGestureRecognizerStateFailed;
}
}
- (void)handleForceWithTouches:(NSSet<UITouch *> *)touches {
_force = _firstTouch.force / _firstTouch.maximumPossibleForce;
}
- (void)reset {
[super reset];
_force = 0;
_firstTouch = NULL;
}
@end
@implementation RNForceTouchHandler
- (instancetype)initWithTag:(NSNumber *)tag
{
if ((self = [super initWithTag:tag])) {
_recognizer = [[RNForceTouchGestureRecognizer alloc] initWithGestureHandler:self];
}
return self;
}
- (void)configure:(NSDictionary *)config
{
[super configure:config];
RNForceTouchGestureRecognizer *recognizer = (RNForceTouchGestureRecognizer *)_recognizer;
APPLY_FLOAT_PROP(maxForce);
APPLY_FLOAT_PROP(minForce);
id prop = config[@"feedbackOnActivation"];
if (prop != nil) {
recognizer.feedbackOnActivation = [RCTConvert BOOL:prop];
}
}
- (RNGestureHandlerEventExtraData *)eventExtraData:(RNForceTouchGestureRecognizer *)recognizer
{
return [RNGestureHandlerEventExtraData
forForce: recognizer.force
forPosition:[recognizer locationInView:recognizer.view]
withAbsolutePosition:[recognizer locationInView:recognizer.view.window]
withNumberOfTouches:recognizer.numberOfTouches];
}
@end

View File

@ -0,0 +1,12 @@
//
// RNLongPressHandler.h
// RNGestureHandler
//
// Created by Krzysztof Magiera on 12/10/2017.
// Copyright © 2017 Software Mansion. All rights reserved.
//
#import "RNGestureHandler.h"
@interface RNLongPressGestureHandler : RNGestureHandler
@end

View File

@ -0,0 +1,83 @@
//
// RNLongPressHandler.m
// RNGestureHandler
//
// Created by Krzysztof Magiera on 12/10/2017.
// Copyright © 2017 Software Mansion. All rights reserved.
//
#import "RNLongPressHandler.h"
#import <UIKit/UIGestureRecognizerSubclass.h>
#import <React/RCTConvert.h>
@interface RNBetterLongPressGestureRecognizer : UILongPressGestureRecognizer
- (id)initWithGestureHandler:(RNGestureHandler*)gestureHandler;
@end
@implementation RNBetterLongPressGestureRecognizer {
__weak RNGestureHandler *_gestureHandler;
}
- (id)initWithGestureHandler:(RNGestureHandler*)gestureHandler
{
if ((self = [super initWithTarget:gestureHandler action:@selector(handleGesture:)])) {
_gestureHandler = gestureHandler;
}
return self;
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesMoved:touches withEvent:event];
if (_gestureHandler.shouldCancelWhenOutside && ![_gestureHandler containsPointInView]) {
self.enabled = NO;
self.enabled = YES;
}
}
@end
@implementation RNLongPressGestureHandler
- (instancetype)initWithTag:(NSNumber *)tag
{
if ((self = [super initWithTag:tag])) {
_recognizer = [[RNBetterLongPressGestureRecognizer alloc] initWithGestureHandler:self];
}
return self;
}
- (void)configure:(NSDictionary *)config
{
[super configure:config];
UILongPressGestureRecognizer *recognizer = (UILongPressGestureRecognizer *)_recognizer;
id prop = config[@"minDurationMs"];
if (prop != nil) {
recognizer.minimumPressDuration = [RCTConvert CGFloat:prop] / 1000.0;
}
prop = config[@"maxDist"];
if (prop != nil) {
recognizer.allowableMovement = [RCTConvert CGFloat:prop];
}
}
- (RNGestureHandlerState)state
{
// For long press recognizer we treat "Began" state as "active"
// as it changes its state to "Began" as soon as the the minimum
// hold duration timeout is reached, whereas state "Changed" is
// only set after "Began" phase if there is some movement.
if (_recognizer.state == UIGestureRecognizerStateBegan) {
return RNGestureHandlerStateActive;
}
return [super state];
}
@end

View File

@ -0,0 +1,15 @@
//
// RNNativeViewHandler.h
// RNGestureHandler
//
// Created by Krzysztof Magiera on 12/10/2017.
// Copyright © 2017 Software Mansion. All rights reserved.
//
#import "RNGestureHandler.h"
@interface RNDummyGestureRecognizer : UIGestureRecognizer
@end
@interface RNNativeViewGestureHandler : RNGestureHandler
@end

View File

@ -0,0 +1,149 @@
//
// RNNativeViewHandler.m
// RNGestureHandler
//
// Created by Krzysztof Magiera on 12/10/2017.
// Copyright © 2017 Software Mansion. All rights reserved.
//
#import "RNNativeViewHandler.h"
#import <UIKit/UIGestureRecognizerSubclass.h>
#import <React/RCTConvert.h>
#import <React/RCTScrollView.h>
#import <React/UIView+React.h>
#pragma mark RNDummyGestureRecognizer
@implementation RNDummyGestureRecognizer
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
self.state = UIGestureRecognizerStateFailed;
[self reset];
}
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
self.state = UIGestureRecognizerStateCancelled;
[self reset];
}
@end
#pragma mark RNNativeViewgestureHandler
@implementation RNNativeViewGestureHandler {
BOOL _shouldActivateOnStart;
BOOL _disallowInterruption;
}
- (instancetype)initWithTag:(NSNumber *)tag
{
if ((self = [super initWithTag:tag])) {
_recognizer = [[RNDummyGestureRecognizer alloc] init];
}
return self;
}
- (void)configure:(NSDictionary *)config
{
[super configure:config];
_shouldActivateOnStart = [RCTConvert BOOL:config[@"shouldActivateOnStart"]];
_disallowInterruption = [RCTConvert BOOL:config[@"disallowInterruption"]];
}
- (void)bindToView:(UIView *)view
{
// For UIControl based views (UIButton, UISwitch) we provide special handling that would allow
// for properties like `disallowInterruption` to work.
if ([view isKindOfClass:[UIControl class]]) {
UIControl *control = (UIControl *)view;
[control addTarget:self action:@selector(handleTouchDown:forEvent:) forControlEvents:UIControlEventTouchDown];
[control addTarget:self action:@selector(handleTouchUpOutside:forEvent:) forControlEvents:UIControlEventTouchUpOutside];
[control addTarget:self action:@selector(handleTouchUpInside:forEvent:) forControlEvents:UIControlEventTouchUpInside];
[control addTarget:self action:@selector(handleDragExit:forEvent:) forControlEvents:UIControlEventTouchDragExit];
[control addTarget:self action:@selector(handleDragEnter:forEvent:) forControlEvents:UIControlEventTouchDragEnter];
[control addTarget:self action:@selector(handleTouchCancel:forEvent:) forControlEvents:UIControlEventTouchCancel];
} else {
[super bindToView:view];
}
// We can restore default scrollview behaviour to delay touches to scrollview's children
// because gesture handler system can handle cancellation of scroll recognizer when JS responder
// is set
if ([view isKindOfClass:[RCTScrollView class]]) {
// This part of the code is coupled with RN implementation of ScrollView native wrapper and
// we expect for RCTScrollView component to contain a subclass of UIScrollview as the only
// subview
UIScrollView *scrollView = [view.subviews objectAtIndex:0];
scrollView.delaysContentTouches = YES;
}
}
- (void)handleTouchDown:(UIView *)sender forEvent:(UIEvent *)event
{
[self reset];
if (_disallowInterruption) {
// When `disallowInterruption` is set we cancel all gesture handlers when this UIControl
// gets DOWN event
for (UITouch *touch in [event allTouches]) {
for (UIGestureRecognizer *recogn in [touch gestureRecognizers]) {
recogn.enabled = NO;
recogn.enabled = YES;
}
}
}
[self sendEventsInState:RNGestureHandlerStateActive
forViewWithTag:sender.reactTag
withExtraData:[RNGestureHandlerEventExtraData forPointerInside:YES]];
}
- (void)handleTouchUpOutside:(UIView *)sender forEvent:(UIEvent *)event
{
[self sendEventsInState:RNGestureHandlerStateEnd
forViewWithTag:sender.reactTag
withExtraData:[RNGestureHandlerEventExtraData forPointerInside:NO]];
}
- (void)handleTouchUpInside:(UIView *)sender forEvent:(UIEvent *)event
{
[self sendEventsInState:RNGestureHandlerStateEnd
forViewWithTag:sender.reactTag
withExtraData:[RNGestureHandlerEventExtraData forPointerInside:YES]];
}
- (void)handleDragExit:(UIView *)sender forEvent:(UIEvent *)event
{
// Pointer is moved outside of the view bounds, we cancel button when `shouldCancelWhenOutside` is set
if (self.shouldCancelWhenOutside) {
UIControl *control = (UIControl *)sender;
[control cancelTrackingWithEvent:event];
[self sendEventsInState:RNGestureHandlerStateEnd
forViewWithTag:sender.reactTag
withExtraData:[RNGestureHandlerEventExtraData forPointerInside:NO]];
} else {
[self sendEventsInState:RNGestureHandlerStateActive
forViewWithTag:sender.reactTag
withExtraData:[RNGestureHandlerEventExtraData forPointerInside:NO]];
}
}
- (void)handleDragEnter:(UIView *)sender forEvent:(UIEvent *)event
{
[self sendEventsInState:RNGestureHandlerStateActive
forViewWithTag:sender.reactTag
withExtraData:[RNGestureHandlerEventExtraData forPointerInside:YES]];
}
- (void)handleTouchCancel:(UIView *)sender forEvent:(UIEvent *)event
{
[self sendEventsInState:RNGestureHandlerStateCancelled
forViewWithTag:sender.reactTag
withExtraData:[RNGestureHandlerEventExtraData forPointerInside:NO]];
}
@end

View File

@ -0,0 +1,12 @@
//
// RNPanHandler.h
// RNGestureHandler
//
// Created by Krzysztof Magiera on 12/10/2017.
// Copyright © 2017 Software Mansion. All rights reserved.
//
#import "RNGestureHandler.h"
@interface RNPanGestureHandler : RNGestureHandler
@end

View File

@ -0,0 +1,239 @@
//
// RNPanHandler.m
// RNGestureHandler
//
// Created by Krzysztof Magiera on 12/10/2017.
// Copyright © 2017 Software Mansion. All rights reserved.
//
#import "RNPanHandler.h"
#import <UIKit/UIGestureRecognizerSubclass.h>
@interface RNBetterPanGestureRecognizer : UIPanGestureRecognizer
@property (nonatomic) CGFloat minDistSq;
@property (nonatomic) CGFloat minVelocityX;
@property (nonatomic) CGFloat minVelocityY;
@property (nonatomic) CGFloat minVelocitySq;
@property (nonatomic) CGFloat activeOffsetXStart;
@property (nonatomic) CGFloat activeOffsetXEnd;
@property (nonatomic) CGFloat failOffsetXStart;
@property (nonatomic) CGFloat failOffsetXEnd;
@property (nonatomic) CGFloat activeOffsetYStart;
@property (nonatomic) CGFloat activeOffsetYEnd;
@property (nonatomic) CGFloat failOffsetYStart;
@property (nonatomic) CGFloat failOffsetYEnd;
- (id)initWithGestureHandler:(RNGestureHandler*)gestureHandler;
@end
@implementation RNBetterPanGestureRecognizer {
__weak RNGestureHandler *_gestureHandler;
NSUInteger _realMinimumNumberOfTouches;
BOOL _hasCustomActivationCriteria;
}
- (id)initWithGestureHandler:(RNGestureHandler*)gestureHandler
{
if ((self = [super initWithTarget:gestureHandler action:@selector(handleGesture:)])) {
_gestureHandler = gestureHandler;
_minDistSq = NAN;
_minVelocityX = NAN;
_minVelocityY = NAN;
_minVelocitySq = NAN;
_activeOffsetXStart = NAN;
_activeOffsetXEnd = NAN;
_failOffsetXStart = NAN;
_failOffsetXEnd = NAN;
_activeOffsetYStart = NAN;
_activeOffsetYEnd = NAN;
_failOffsetYStart = NAN;
_failOffsetYEnd = NAN;
_hasCustomActivationCriteria = NO;
#if !TARGET_OS_TV
_realMinimumNumberOfTouches = self.minimumNumberOfTouches;
#endif
}
return self;
}
- (void)setMinimumNumberOfTouches:(NSUInteger)minimumNumberOfTouches
{
_realMinimumNumberOfTouches = minimumNumberOfTouches;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
#if !TARGET_OS_TV
if (_hasCustomActivationCriteria) {
// We use "minimumNumberOfTouches" property to prevent pan handler from recognizing
// the gesture too early before we are sure that all criteria (e.g. minimum distance
// etc. are met)
super.minimumNumberOfTouches = 20;
} else {
super.minimumNumberOfTouches = _realMinimumNumberOfTouches;
}
#endif
[super touchesBegan:touches withEvent:event];
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesMoved:touches withEvent:event];
if (self.state == UIGestureRecognizerStatePossible && [self shouldFailUnderCustomCriteria]) {
self.state = UIGestureRecognizerStateFailed;
return;
}
if ((self.state == UIGestureRecognizerStatePossible || self.state == UIGestureRecognizerStateChanged)) {
if (_gestureHandler.shouldCancelWhenOutside && ![_gestureHandler containsPointInView]) {
// If the previous recognizer state is UIGestureRecognizerStateChanged
// then UIGestureRecognizer's sate machine will only transition to
// UIGestureRecognizerStateCancelled even if you set the state to
// UIGestureRecognizerStateFailed here. Making the behavior explicit.
self.state = (self.state == UIGestureRecognizerStatePossible)
? UIGestureRecognizerStateFailed
: UIGestureRecognizerStateCancelled;
[self reset];
return;
}
}
if (_hasCustomActivationCriteria && self.state == UIGestureRecognizerStatePossible && [self shouldActivateUnderCustomCriteria]) {
#if !TARGET_OS_TV
super.minimumNumberOfTouches = _realMinimumNumberOfTouches;
if ([self numberOfTouches] >= _realMinimumNumberOfTouches) {
self.state = UIGestureRecognizerStateBegan;
[self setTranslation:CGPointMake(0, 0) inView:self.view];
}
#endif
}
}
- (void)reset
{
self.enabled = YES;
[super reset];
}
- (void)updateHasCustomActivationCriteria
{
_hasCustomActivationCriteria = !isnan(_minDistSq)
|| !isnan(_minVelocityX) || !isnan(_minVelocityY) || !isnan(_minVelocitySq)
|| !isnan(_activeOffsetXStart) || !isnan(_activeOffsetXEnd)
|| !isnan(_activeOffsetYStart) || !isnan(_activeOffsetYEnd);
}
- (BOOL)shouldFailUnderCustomCriteria
{
CGPoint trans = [self translationInView:self.view];
if (!isnan(_failOffsetXStart) && trans.x < _failOffsetXStart) {
return YES;
}
if (!isnan(_failOffsetXEnd) && trans.x > _failOffsetXEnd) {
return YES;
}
if (!isnan(_failOffsetYStart) && trans.y < _failOffsetYStart) {
return YES;
}
if (!isnan(_failOffsetYEnd) && trans.y > _failOffsetYEnd) {
return YES;
}
return NO;
}
- (BOOL)shouldActivateUnderCustomCriteria
{
CGPoint trans = [self translationInView:self.view];
if (!isnan(_activeOffsetXStart) && trans.x < _activeOffsetXStart) {
return YES;
}
if (!isnan(_activeOffsetXEnd) && trans.x > _activeOffsetXEnd) {
return YES;
}
if (!isnan(_activeOffsetYStart) && trans.y < _activeOffsetYStart) {
return YES;
}
if (!isnan(_activeOffsetYEnd) && trans.y > _activeOffsetYEnd) {
return YES;
}
if (TEST_MIN_IF_NOT_NAN(VEC_LEN_SQ(trans), _minDistSq)) {
return YES;
}
CGPoint velocity = [self velocityInView:self.view];
if (TEST_MIN_IF_NOT_NAN(velocity.x, _minVelocityX)) {
return YES;
}
if (TEST_MIN_IF_NOT_NAN(velocity.y, _minVelocityY)) {
return YES;
}
if (TEST_MIN_IF_NOT_NAN(VEC_LEN_SQ(velocity), _minVelocitySq)) {
return YES;
}
return NO;
}
@end
@implementation RNPanGestureHandler
- (instancetype)initWithTag:(NSNumber *)tag
{
if ((self = [super initWithTag:tag])) {
_recognizer = [[RNBetterPanGestureRecognizer alloc] initWithGestureHandler:self];
}
return self;
}
- (void)configure:(NSDictionary *)config
{
[super configure:config];
RNBetterPanGestureRecognizer *recognizer = (RNBetterPanGestureRecognizer *)_recognizer;
APPLY_FLOAT_PROP(minVelocityX);
APPLY_FLOAT_PROP(minVelocityY);
APPLY_FLOAT_PROP(activeOffsetXStart);
APPLY_FLOAT_PROP(activeOffsetXEnd);
APPLY_FLOAT_PROP(failOffsetXStart);
APPLY_FLOAT_PROP(failOffsetXEnd);
APPLY_FLOAT_PROP(activeOffsetYStart);
APPLY_FLOAT_PROP(activeOffsetYEnd);
APPLY_FLOAT_PROP(failOffsetYStart);
APPLY_FLOAT_PROP(failOffsetYEnd);
#if !TARGET_OS_TV
APPLY_NAMED_INT_PROP(minimumNumberOfTouches, @"minPointers");
APPLY_NAMED_INT_PROP(maximumNumberOfTouches, @"maxPointers");
#endif
id prop = config[@"minDist"];
if (prop != nil) {
CGFloat dist = [RCTConvert CGFloat:prop];
recognizer.minDistSq = dist * dist;
}
prop = config[@"minVelocity"];
if (prop != nil) {
CGFloat velocity = [RCTConvert CGFloat:prop];
recognizer.minVelocitySq = velocity * velocity;
}
[recognizer updateHasCustomActivationCriteria];
}
- (RNGestureHandlerEventExtraData *)eventExtraData:(UIPanGestureRecognizer *)recognizer
{
return [RNGestureHandlerEventExtraData
forPan:[recognizer locationInView:recognizer.view]
withAbsolutePosition:[recognizer locationInView:recognizer.view.window]
withTranslation:[recognizer translationInView:recognizer.view]
withVelocity:[recognizer velocityInView:recognizer.view.window]
withNumberOfTouches:recognizer.numberOfTouches];
}
@end

View File

@ -0,0 +1,12 @@
//
// RNPinchHandler.h
// RNGestureHandler
//
// Created by Krzysztof Magiera on 12/10/2017.
// Copyright © 2017 Software Mansion. All rights reserved.
//
#import "RNGestureHandler.h"
@interface RNPinchGestureHandler : RNGestureHandler
@end

View File

@ -0,0 +1,35 @@
//
// RNPinchHandler.m
// RNGestureHandler
//
// Created by Krzysztof Magiera on 12/10/2017.
// Copyright © 2017 Software Mansion. All rights reserved.
//
#import "RNPinchHandler.h"
@implementation RNPinchGestureHandler
- (instancetype)initWithTag:(NSNumber *)tag
{
if ((self = [super initWithTag:tag])) {
#if !TARGET_OS_TV
_recognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handleGesture:)];
#endif
}
return self;
}
#if !TARGET_OS_TV
- (RNGestureHandlerEventExtraData *)eventExtraData:(UIPinchGestureRecognizer *)recognizer
{
return [RNGestureHandlerEventExtraData
forPinch:recognizer.scale
withFocalPoint:[recognizer locationInView:recognizer.view]
withVelocity:recognizer.velocity
withNumberOfTouches:recognizer.numberOfTouches];
}
#endif
@end

View File

@ -0,0 +1,12 @@
//
// RNRotationHandler.h
// RNGestureHandler
//
// Created by Krzysztof Magiera on 12/10/2017.
// Copyright © 2017 Software Mansion. All rights reserved.
//
#import "RNGestureHandler.h"
@interface RNRotationGestureHandler : RNGestureHandler
@end

View File

@ -0,0 +1,35 @@
//
// RNRotationHandler.m
// RNGestureHandler
//
// Created by Krzysztof Magiera on 12/10/2017.
// Copyright © 2017 Software Mansion. All rights reserved.
//
#import "RNRotationHandler.h"
@implementation RNRotationGestureHandler
- (instancetype)initWithTag:(NSNumber *)tag
{
if ((self = [super initWithTag:tag])) {
#if !TARGET_OS_TV
_recognizer = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(handleGesture:)];
#endif
}
return self;
}
#if !TARGET_OS_TV
- (RNGestureHandlerEventExtraData *)eventExtraData:(UIRotationGestureRecognizer *)recognizer
{
return [RNGestureHandlerEventExtraData
forRotation:recognizer.rotation
withAnchorPoint:[recognizer locationInView:recognizer.view]
withVelocity:recognizer.velocity
withNumberOfTouches:recognizer.numberOfTouches];
}
#endif
@end

View File

@ -0,0 +1,12 @@
//
// RNTapHandler.h
// RNGestureHandler
//
// Created by Krzysztof Magiera on 12/10/2017.
// Copyright © 2017 Software Mansion. All rights reserved.
//
#import "RNGestureHandler.h"
@interface RNTapGestureHandler : RNGestureHandler
@end

View File

@ -0,0 +1,218 @@
//
// RNTapHandler.m
// RNGestureHandler
//
// Created by Krzysztof Magiera on 12/10/2017.
// Copyright © 2017 Software Mansion. All rights reserved.
//
#import "RNTapHandler.h"
#import <UIKit/UIGestureRecognizerSubclass.h>
#import <React/RCTConvert.h>
// RNBetterTapGestureRecognizer extends UIGestureRecognizer instead of UITapGestureRecognizer
// because the latter does not allow for parameters like maxDelay, maxDuration, minPointers,
// maxDelta to be configured. Using our custom implementation of tap recognizer we are able
// to support these.
@interface RNBetterTapGestureRecognizer : UIGestureRecognizer
@property (nonatomic) NSUInteger numberOfTaps;
@property (nonatomic) NSTimeInterval maxDelay;
@property (nonatomic) NSTimeInterval maxDuration;
@property (nonatomic) CGFloat maxDistSq;
@property (nonatomic) CGFloat maxDeltaX;
@property (nonatomic) CGFloat maxDeltaY;
@property (nonatomic) NSInteger minPointers;
- (id)initWithGestureHandler:(RNGestureHandler*)gestureHandler;
@end
@implementation RNBetterTapGestureRecognizer {
__weak RNGestureHandler *_gestureHandler;
NSUInteger _tapsSoFar;
CGPoint _initPosition;
NSInteger _maxNumberOfTouches;
}
- (id)initWithGestureHandler:(RNGestureHandler*)gestureHandler
{
if ((self = [super initWithTarget:gestureHandler action:@selector(handleGesture:)])) {
_gestureHandler = gestureHandler;
_tapsSoFar = 0;
_numberOfTaps = 1;
_minPointers = 1;
_maxDelay = 0.2;
_maxDuration = NAN;
_maxDeltaX = NAN;
_maxDeltaY = NAN;
_maxDistSq = NAN;
}
return self;
}
- (void)triggerAction
{
[_gestureHandler handleGesture:self];
}
- (void)cancel
{
self.enabled = NO;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
if (_tapsSoFar == 0) {
_initPosition = [self locationInView:self.view];
}
_tapsSoFar++;
if (_tapsSoFar) {
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(cancel) object:nil];
}
NSInteger numberOfTouches = [touches count];
if (numberOfTouches > _maxNumberOfTouches) {
_maxNumberOfTouches = numberOfTouches;
}
if (!isnan(_maxDuration)) {
[self performSelector:@selector(cancel) withObject:nil afterDelay:_maxDuration];
}
self.state = UIGestureRecognizerStatePossible;
[self triggerAction];
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesMoved:touches withEvent:event];
NSInteger numberOfTouches = [touches count];
if (numberOfTouches > _maxNumberOfTouches) {
_maxNumberOfTouches = numberOfTouches;
}
if (self.state != UIGestureRecognizerStatePossible) {
return;
}
if ([self shouldFailUnderCustomCriteria]) {
self.state = UIGestureRecognizerStateFailed;
[self triggerAction];
[self reset];
return;
}
self.state = UIGestureRecognizerStatePossible;
[self triggerAction];
}
- (CGPoint)translationInView {
CGPoint currentPosition = [self locationInView:self.view];
return CGPointMake(currentPosition.x - _initPosition.x, currentPosition.y - _initPosition.y);
}
- (BOOL)shouldFailUnderCustomCriteria
{
if (_gestureHandler.shouldCancelWhenOutside) {
if (![_gestureHandler containsPointInView]) {
return YES;
}
}
CGPoint trans = [self translationInView];
if (TEST_MAX_IF_NOT_NAN(fabs(trans.x), _maxDeltaX)) {
return YES;
}
if (TEST_MAX_IF_NOT_NAN(fabs(trans.y), _maxDeltaY)) {
return YES;
}
if (TEST_MAX_IF_NOT_NAN(fabs(trans.y * trans.y + trans.x + trans.x), _maxDistSq)) {
return YES;
}
return NO;
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
if (_numberOfTaps == _tapsSoFar && _maxNumberOfTouches >= _minPointers) {
self.state = UIGestureRecognizerStateEnded;
[self reset];
} else {
[self performSelector:@selector(cancel) withObject:nil afterDelay:_maxDelay];
}
}
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesCancelled:touches withEvent:event];
self.state = UIGestureRecognizerStateCancelled;
[self reset];
}
- (void)reset
{
if (self.state == UIGestureRecognizerStateFailed) {
[self triggerAction];
}
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(cancel) object:nil];
_tapsSoFar = 0;
_maxNumberOfTouches = 0;
self.enabled = YES;
[super reset];
}
@end
@implementation RNTapGestureHandler {
RNGestureHandlerEventExtraData * _lastData;
}
- (instancetype)initWithTag:(NSNumber *)tag
{
if ((self = [super initWithTag:tag])) {
_recognizer = [[RNBetterTapGestureRecognizer alloc] initWithGestureHandler:self];
}
return self;
}
- (void)configure:(NSDictionary *)config
{
[super configure:config];
RNBetterTapGestureRecognizer *recognizer = (RNBetterTapGestureRecognizer *)_recognizer;
APPLY_INT_PROP(numberOfTaps);
APPLY_INT_PROP(minPointers);
APPLY_FLOAT_PROP(maxDeltaX);
APPLY_FLOAT_PROP(maxDeltaY);
id prop = config[@"maxDelayMs"];
if (prop != nil) {
recognizer.maxDelay = [RCTConvert CGFloat:prop] / 1000.0;
}
prop = config[@"maxDurationMs"];
if (prop != nil) {
recognizer.maxDuration = [RCTConvert CGFloat:prop] / 1000.0;
}
prop = config[@"maxDist"];
if (prop != nil) {
CGFloat dist = [RCTConvert CGFloat:prop];
recognizer.maxDistSq = dist * dist;
}
}
- (RNGestureHandlerEventExtraData *)eventExtraData:(UIGestureRecognizer *)recognizer{
if (recognizer.state == UIGestureRecognizerStateEnded) {
return _lastData;
}
_lastData = [super eventExtraData:recognizer];
return _lastData;
}
@end

View File

@ -0,0 +1,73 @@
#import "RNGestureHandlerState.h"
#import "RNGestureHandlerDirection.h"
#import "RNGestureHandlerEvents.h"
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <React/RCTConvert.h>
#define VEC_LEN_SQ(pt) (pt.x * pt.x + pt.y * pt.y)
#define TEST_MIN_IF_NOT_NAN(value, limit) \
(!isnan(limit) && ((limit < 0 && value <= limit) || (limit >= 0 && value >= limit)))
#define TEST_MAX_IF_NOT_NAN(value, max) \
(!isnan(max) && ((max < 0 && value < max) || (max >= 0 && value > max)))
#define APPLY_PROP(recognizer, config, type, prop, propName) do { \
id value = config[propName]; \
if (value != nil) recognizer.prop = [RCTConvert type:value]; \
} while(0)
#define APPLY_FLOAT_PROP(prop) do { APPLY_PROP(recognizer, config, CGFloat, prop, @#prop); } while(0)
#define APPLY_INT_PROP(prop) do { APPLY_PROP(recognizer, config, NSInteger, prop, @#prop); } while(0)
#define APPLY_NAMED_INT_PROP(prop, propName) do { APPLY_PROP(recognizer, config, NSInteger, prop, propName); } while(0)
@protocol RNGestureHandlerEventEmitter
- (void)sendTouchEvent:(nonnull RNGestureHandlerEvent *)event;
- (void)sendStateChangeEvent:(nonnull RNGestureHandlerStateChange *)event;
@end
@protocol RNRootViewGestureRecognizerDelegate <UIGestureRecognizerDelegate>
- (void)gestureRecognizer:(nullable UIGestureRecognizer *)gestureRecognizer
didActivateInRootView:(nullable UIView *)rootView;
@end
@interface RNGestureHandler : NSObject <UIGestureRecognizerDelegate> {
@protected UIGestureRecognizer *_recognizer;
@protected RNGestureHandlerState _lastState;
}
+ (nullable RNGestureHandler *)findGestureHandlerByRecognizer:(nonnull UIGestureRecognizer *)recognizer;
- (nonnull instancetype)initWithTag:(nonnull NSNumber *)tag;
@property (nonatomic, readonly, nonnull) NSNumber *tag;
@property (nonatomic, weak, nullable) id<RNGestureHandlerEventEmitter> emitter;
@property (nonatomic, readonly, nullable) UIGestureRecognizer *recognizer;
@property (nonatomic) BOOL enabled;
@property(nonatomic) BOOL shouldCancelWhenOutside;
- (void)bindToView:(nonnull UIView *)view;
- (void)unbindFromView;
- (void)configure:(nullable NSDictionary *)config NS_REQUIRES_SUPER;
- (void)handleGesture:(nonnull id)recognizer;
- (BOOL)containsPointInView;
- (RNGestureHandlerState)state;
- (nullable RNGestureHandlerEventExtraData *)eventExtraData:(nonnull id)recognizer;
- (void)reset;
- (void)sendEventsInState:(RNGestureHandlerState)state
forViewWithTag:(nonnull NSNumber *)reactTag
withExtraData:(RNGestureHandlerEventExtraData *)extraData;
@end

View File

@ -0,0 +1,344 @@
#import "RNGestureHandler.h"
#import "Handlers/RNNativeViewHandler.h"
#import <UIKit/UIGestureRecognizerSubclass.h>
#import <React/UIView+React.h>
@interface UIGestureRecognizer (GestureHandler)
@property (nonatomic, readonly) RNGestureHandler *gestureHandler;
@end
@implementation UIGestureRecognizer (GestureHandler)
- (RNGestureHandler *)gestureHandler
{
id delegate = self.delegate;
if ([delegate isKindOfClass:[RNGestureHandler class]]) {
return (RNGestureHandler *)delegate;
}
return nil;
}
@end
typedef struct RNGHHitSlop {
CGFloat top, left, bottom, right, width, height;
} RNGHHitSlop;
static RNGHHitSlop RNGHHitSlopEmpty = { NAN, NAN, NAN, NAN, NAN, NAN };
#define RNGH_HIT_SLOP_GET(key) (prop[key] == nil ? NAN : [prop[key] doubleValue])
#define RNGH_HIT_SLOP_IS_SET(hitSlop) (!isnan(hitSlop.left) || !isnan(hitSlop.right) || \
!isnan(hitSlop.top) || !isnan(hitSlop.bottom))
#define RNGH_HIT_SLOP_INSET(key) (isnan(hitSlop.key) ? 0. : hitSlop.key)
CGRect RNGHHitSlopInsetRect(CGRect rect, RNGHHitSlop hitSlop) {
rect.origin.x -= RNGH_HIT_SLOP_INSET(left);
rect.origin.y -= RNGH_HIT_SLOP_INSET(top);
if (!isnan(hitSlop.width)) {
if (!isnan(hitSlop.right)) {
rect.origin.x = rect.size.width - hitSlop.width + RNGH_HIT_SLOP_INSET(right);
}
rect.size.width = hitSlop.width;
} else {
rect.size.width += (RNGH_HIT_SLOP_INSET(left) + RNGH_HIT_SLOP_INSET(right));
}
if (!isnan(hitSlop.height)) {
if (!isnan(hitSlop.bottom)) {
rect.origin.y = rect.size.height - hitSlop.height + RNGH_HIT_SLOP_INSET(bottom);
}
rect.size.height = hitSlop.height;
} else {
rect.size.height += (RNGH_HIT_SLOP_INSET(top) + RNGH_HIT_SLOP_INSET(bottom));
}
return rect;
}
static NSHashTable<RNGestureHandler *> *allGestureHandlers;
@implementation RNGestureHandler {
NSArray<NSNumber *> *_handlersToWaitFor;
NSArray<NSNumber *> *_simultaneousHandlers;
RNGHHitSlop _hitSlop;
uint16_t _eventCoalescingKey;
}
- (instancetype)initWithTag:(NSNumber *)tag
{
if ((self = [super init])) {
_tag = tag;
_lastState = RNGestureHandlerStateUndetermined;
_hitSlop = RNGHHitSlopEmpty;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
allGestureHandlers = [NSHashTable weakObjectsHashTable];
});
[allGestureHandlers addObject:self];
}
return self;
}
- (void)configure:(NSDictionary *)config
{
_handlersToWaitFor = [RCTConvert NSNumberArray:config[@"waitFor"]];
_simultaneousHandlers = [RCTConvert NSNumberArray:config[@"simultaneousHandlers"]];
id prop = config[@"enabled"];
if (prop != nil) {
self.enabled = [RCTConvert BOOL:prop];
} else {
self.enabled = YES;
}
prop = config[@"shouldCancelWhenOutside"];
if (prop != nil) {
_shouldCancelWhenOutside = [RCTConvert BOOL:prop];
} else {
_shouldCancelWhenOutside = NO;
}
prop = config[@"hitSlop"];
if ([prop isKindOfClass:[NSNumber class]]) {
_hitSlop.left = _hitSlop.right = _hitSlop.top = _hitSlop.bottom = [prop doubleValue];
} else if (prop != nil) {
_hitSlop.left = _hitSlop.right = RNGH_HIT_SLOP_GET(@"horizontal");
_hitSlop.top = _hitSlop.bottom = RNGH_HIT_SLOP_GET(@"vertical");
_hitSlop.left = RNGH_HIT_SLOP_GET(@"left");
_hitSlop.right = RNGH_HIT_SLOP_GET(@"right");
_hitSlop.top = RNGH_HIT_SLOP_GET(@"top");
_hitSlop.bottom = RNGH_HIT_SLOP_GET(@"bottom");
_hitSlop.width = RNGH_HIT_SLOP_GET(@"width");
_hitSlop.height = RNGH_HIT_SLOP_GET(@"height");
if (isnan(_hitSlop.left) && isnan(_hitSlop.right) && !isnan(_hitSlop.width)) {
RCTLogError(@"When width is set one of left or right pads need to be defined");
}
if (!isnan(_hitSlop.width) && !isnan(_hitSlop.left) && !isnan(_hitSlop.right)) {
RCTLogError(@"Cannot have all of left, right and width defined");
}
if (isnan(_hitSlop.top) && isnan(_hitSlop.bottom) && !isnan(_hitSlop.height)) {
RCTLogError(@"When height is set one of top or bottom pads need to be defined");
}
if (!isnan(_hitSlop.height) && !isnan(_hitSlop.top) && !isnan(_hitSlop.bottom)) {
RCTLogError(@"Cannot have all of top, bottom and height defined");
}
}
}
- (void)setEnabled:(BOOL)enabled
{
_enabled = enabled;
self.recognizer.enabled = enabled;
}
- (void)bindToView:(UIView *)view
{
view.userInteractionEnabled = YES;
self.recognizer.delegate = self;
[view addGestureRecognizer:self.recognizer];
}
- (void)unbindFromView
{
[self.recognizer.view removeGestureRecognizer:self.recognizer];
self.recognizer.delegate = nil;
}
- (RNGestureHandlerEventExtraData *)eventExtraData:(UIGestureRecognizer *)recognizer
{
return [RNGestureHandlerEventExtraData
forPosition:[recognizer locationInView:recognizer.view]
withAbsolutePosition:[recognizer locationInView:recognizer.view.window]
withNumberOfTouches:recognizer.numberOfTouches];
}
- (void)handleGesture:(UIGestureRecognizer *)recognizer
{
RNGestureHandlerEventExtraData *eventData = [self eventExtraData:recognizer];
[self sendEventsInState:self.state forViewWithTag:recognizer.view.reactTag withExtraData:eventData];
}
- (void)sendEventsInState:(RNGestureHandlerState)state
forViewWithTag:(nonnull NSNumber *)reactTag
withExtraData:(RNGestureHandlerEventExtraData *)extraData
{
if (state != _lastState) {
if (state == RNGestureHandlerStateActive) {
// Generate a unique coalescing-key each time the gesture-handler becomes active. All events will have
// the same coalescing-key allowing RCTEventDispatcher to coalesce RNGestureHandlerEvents when events are
// generated faster than they can be treated by JS thread
static uint16_t nextEventCoalescingKey = 0;
self->_eventCoalescingKey = nextEventCoalescingKey++;
} else if (state == RNGestureHandlerStateEnd && _lastState != RNGestureHandlerStateActive) {
[self.emitter sendStateChangeEvent:[[RNGestureHandlerStateChange alloc] initWithReactTag:reactTag
handlerTag:_tag
state:RNGestureHandlerStateActive
prevState:_lastState
extraData:extraData]];
_lastState = RNGestureHandlerStateActive;
}
id stateEvent = [[RNGestureHandlerStateChange alloc] initWithReactTag:reactTag
handlerTag:_tag
state:state
prevState:_lastState
extraData:extraData];
[self.emitter sendStateChangeEvent:stateEvent];
_lastState = state;
}
if (state == RNGestureHandlerStateActive) {
id touchEvent = [[RNGestureHandlerEvent alloc] initWithReactTag:reactTag
handlerTag:_tag
state:state
extraData:extraData
coalescingKey:self->_eventCoalescingKey];
[self.emitter sendTouchEvent:touchEvent];
}
}
- (RNGestureHandlerState)state
{
switch (_recognizer.state) {
case UIGestureRecognizerStateBegan:
case UIGestureRecognizerStatePossible:
return RNGestureHandlerStateBegan;
case UIGestureRecognizerStateEnded:
return RNGestureHandlerStateEnd;
case UIGestureRecognizerStateFailed:
return RNGestureHandlerStateFailed;
case UIGestureRecognizerStateCancelled:
return RNGestureHandlerStateCancelled;
case UIGestureRecognizerStateChanged:
return RNGestureHandlerStateActive;
}
return RNGestureHandlerStateUndetermined;
}
#pragma mark UIGestureRecognizerDelegate
+ (RNGestureHandler *)findGestureHandlerByRecognizer:(UIGestureRecognizer *)recognizer
{
RNGestureHandler *handler = recognizer.gestureHandler;
if (handler != nil) {
return handler;
}
// We may try to extract "DummyGestureHandler" in case when "otherGestureRecognizer" belongs to
// a native view being wrapped with "NativeViewGestureHandler"
UIView *reactView = recognizer.view;
while (reactView != nil && reactView.reactTag == nil) {
reactView = reactView.superview;
}
for (UIGestureRecognizer *recognizer in reactView.gestureRecognizers) {
if ([recognizer isKindOfClass:[RNDummyGestureRecognizer class]]) {
return recognizer.gestureHandler;
}
}
return nil;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
RNGestureHandler *handler = [RNGestureHandler findGestureHandlerByRecognizer:otherGestureRecognizer];
if ([handler isKindOfClass:[RNNativeViewGestureHandler class]]) {
for (NSNumber *handlerTag in handler->_handlersToWaitFor) {
if ([_tag isEqual:handlerTag]) {
return YES;
}
}
}
return NO;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
if ([_handlersToWaitFor count]) {
RNGestureHandler *handler = [RNGestureHandler findGestureHandlerByRecognizer:otherGestureRecognizer];
if (handler != nil) {
for (NSNumber *handlerTag in _handlersToWaitFor) {
if ([handler.tag isEqual:handlerTag]) {
return YES;
}
}
}
}
return NO;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
if (_recognizer.state == UIGestureRecognizerStateBegan && _recognizer.state == UIGestureRecognizerStatePossible) {
return YES;
}
if ([_simultaneousHandlers count]) {
RNGestureHandler *handler = [RNGestureHandler findGestureHandlerByRecognizer:otherGestureRecognizer];
if (handler != nil) {
for (NSNumber *handlerTag in _simultaneousHandlers) {
if ([handler.tag isEqual:handlerTag]) {
return YES;
}
}
}
}
return NO;
}
- (void)reset
{
_lastState = RNGestureHandlerStateUndetermined;
}
- (BOOL)containsPointInView
{
CGPoint pt = [_recognizer locationInView:_recognizer.view];
CGRect hitFrame = RNGHHitSlopInsetRect(_recognizer.view.bounds, _hitSlop);
return CGRectContainsPoint(hitFrame, pt);
}
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
if ([_handlersToWaitFor count]) {
for (RNGestureHandler *handler in [allGestureHandlers allObjects]) {
if (handler != nil
&& (handler.state == RNGestureHandlerStateActive || handler->_recognizer.state == UIGestureRecognizerStateBegan)) {
for (NSNumber *handlerTag in _handlersToWaitFor) {
if ([handler.tag isEqual:handlerTag]) {
return NO;
}
}
}
}
}
[self reset];
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
// If hitSlop is set we use it to determine if a given gesture recognizer should start processing
// touch stream. This only works for negative values of hitSlop as this method won't be triggered
// unless touch startes in the bounds of the attached view. To acheve similar effect with positive
// values of hitSlop one should set hitSlop for the underlying view. This limitation is due to the
// fact that hitTest method is only available at the level of UIView
if (RNGH_HIT_SLOP_IS_SET(_hitSlop)) {
CGPoint location = [touch locationInView:gestureRecognizer.view];
CGRect hitFrame = RNGHHitSlopInsetRect(gestureRecognizer.view.bounds, _hitSlop);
return CGRectContainsPoint(hitFrame, location);
}
return YES;
}
@end

View File

@ -0,0 +1,690 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
446E7FDE1ED57CA6009282E7 /* RNGestureHandlerModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 446E7FDD1ED57CA6009282E7 /* RNGestureHandlerModule.m */; };
446E7FE61ED6E177009282E7 /* RNGestureHandlerModule.h in Headers */ = {isa = PBXBuildFile; fileRef = 446E7FDC1ED57CA6009282E7 /* RNGestureHandlerModule.h */; };
446E7FE71ED6E177009282E7 /* RNGestureHandlerState.h in Headers */ = {isa = PBXBuildFile; fileRef = 44384A781ECDE0DB006BAD02 /* RNGestureHandlerState.h */; };
446E7FF61ED89A31009282E7 /* RNGestureHandlerEvents.h in Headers */ = {isa = PBXBuildFile; fileRef = 446E7FF51ED89A31009282E7 /* RNGestureHandlerEvents.h */; };
446E7FF81ED89A4B009282E7 /* RNGestureHandlerEvents.m in Sources */ = {isa = PBXBuildFile; fileRef = 446E7FF71ED89A4B009282E7 /* RNGestureHandlerEvents.m */; };
448802DD1F6803DF00018214 /* RNGestureHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 448802DB1F6803DF00018214 /* RNGestureHandler.h */; };
448802DE1F6803DF00018214 /* RNGestureHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 448802DC1F6803DF00018214 /* RNGestureHandler.m */; };
44AEC7111F8F9B6C0086889F /* RNRootViewGestureRecognizer.h in Headers */ = {isa = PBXBuildFile; fileRef = 44AEC7101F8F9B6C0086889F /* RNRootViewGestureRecognizer.h */; };
44AEC7131F8F9B770086889F /* RNRootViewGestureRecognizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 44AEC7121F8F9B770086889F /* RNRootViewGestureRecognizer.m */; };
44AEC7151F8F9BEF0086889F /* RNGestureHandlerRegistry.h in Headers */ = {isa = PBXBuildFile; fileRef = 44AEC7141F8F9BEF0086889F /* RNGestureHandlerRegistry.h */; };
44AEC7171F8F9C090086889F /* RNGestureHandlerRegistry.m in Sources */ = {isa = PBXBuildFile; fileRef = 44AEC7161F8F9C090086889F /* RNGestureHandlerRegistry.m */; };
44AEC71F1F8FA0700086889F /* RNGestureHandlerButton.h in Headers */ = {isa = PBXBuildFile; fileRef = 44AEC71D1F8FA0700086889F /* RNGestureHandlerButton.h */; };
44AEC7201F8FA0700086889F /* RNGestureHandlerButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 44AEC71E1F8FA0700086889F /* RNGestureHandlerButton.m */; };
44AEC72F1F8FA1270086889F /* RNLongPressHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 44AEC7231F8FA1270086889F /* RNLongPressHandler.h */; };
44AEC7301F8FA1270086889F /* RNLongPressHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 44AEC7241F8FA1270086889F /* RNLongPressHandler.m */; };
44AEC7311F8FA1270086889F /* RNNativeViewHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 44AEC7251F8FA1270086889F /* RNNativeViewHandler.h */; };
44AEC7321F8FA1270086889F /* RNNativeViewHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 44AEC7261F8FA1270086889F /* RNNativeViewHandler.m */; };
44AEC7331F8FA1270086889F /* RNPanHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 44AEC7271F8FA1270086889F /* RNPanHandler.h */; };
44AEC7341F8FA1270086889F /* RNPanHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 44AEC7281F8FA1270086889F /* RNPanHandler.m */; };
44AEC7351F8FA1270086889F /* RNPinchHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 44AEC7291F8FA1270086889F /* RNPinchHandler.h */; };
44AEC7361F8FA1270086889F /* RNPinchHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 44AEC72A1F8FA1270086889F /* RNPinchHandler.m */; };
44AEC7371F8FA1270086889F /* RNRotationHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 44AEC72B1F8FA1270086889F /* RNRotationHandler.h */; };
44AEC7381F8FA1270086889F /* RNRotationHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 44AEC72C1F8FA1270086889F /* RNRotationHandler.m */; };
44AEC7391F8FA1270086889F /* RNTapHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 44AEC72D1F8FA1270086889F /* RNTapHandler.h */; };
44AEC73A1F8FA1270086889F /* RNTapHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 44AEC72E1F8FA1270086889F /* RNTapHandler.m */; };
44BE34481F1E1AAA008679D1 /* RNGestureHandlerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 44BE34471F1E1AAA008679D1 /* RNGestureHandlerManager.m */; };
44BE344A1F1E1ABA008679D1 /* RNGestureHandlerManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 44BE34491F1E1ABA008679D1 /* RNGestureHandlerManager.h */; };
660F46742080D8F700B7B50D /* RNGestureHandlerDirection.h in Headers */ = {isa = PBXBuildFile; fileRef = 660F46732080D8F600B7B50D /* RNGestureHandlerDirection.h */; };
668E083C21BDD70900EDDF40 /* RNForceTouchHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 668E083A21BDD70900EDDF40 /* RNForceTouchHandler.h */; };
668E083D21BDD70900EDDF40 /* RNForceTouchHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 668E083B21BDD70900EDDF40 /* RNForceTouchHandler.m */; };
66A291D5207D032400809C27 /* RNFlingHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 66A291D3207D032400809C27 /* RNFlingHandler.m */; };
66A291D6207D032400809C27 /* RNFlingHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 66A291D4207D032400809C27 /* RNFlingHandler.h */; };
B5C32A0E220C603B000FFB8D /* RNGestureHandlerButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 44AEC71E1F8FA0700086889F /* RNGestureHandlerButton.m */; };
B5C32A0F220C603B000FFB8D /* RNForceTouchHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 668E083B21BDD70900EDDF40 /* RNForceTouchHandler.m */; };
B5C32A10220C603B000FFB8D /* RNGestureHandlerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 44BE34471F1E1AAA008679D1 /* RNGestureHandlerManager.m */; };
B5C32A11220C603B000FFB8D /* RNLongPressHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 44AEC7241F8FA1270086889F /* RNLongPressHandler.m */; };
B5C32A12220C603B000FFB8D /* RNGestureHandlerEvents.m in Sources */ = {isa = PBXBuildFile; fileRef = 446E7FF71ED89A4B009282E7 /* RNGestureHandlerEvents.m */; };
B5C32A13220C603B000FFB8D /* RNPanHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 44AEC7281F8FA1270086889F /* RNPanHandler.m */; };
B5C32A14220C603B000FFB8D /* RNGestureHandlerRegistry.m in Sources */ = {isa = PBXBuildFile; fileRef = 44AEC7161F8F9C090086889F /* RNGestureHandlerRegistry.m */; };
B5C32A15220C603B000FFB8D /* RNGestureHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 448802DC1F6803DF00018214 /* RNGestureHandler.m */; };
B5C32A16220C603B000FFB8D /* RNTapHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 44AEC72E1F8FA1270086889F /* RNTapHandler.m */; };
B5C32A17220C603B000FFB8D /* RNGestureHandlerModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 446E7FDD1ED57CA6009282E7 /* RNGestureHandlerModule.m */; };
B5C32A18220C603B000FFB8D /* RNRotationHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 44AEC72C1F8FA1270086889F /* RNRotationHandler.m */; };
B5C32A19220C603B000FFB8D /* RNNativeViewHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 44AEC7261F8FA1270086889F /* RNNativeViewHandler.m */; };
B5C32A1A220C603B000FFB8D /* RNRootViewGestureRecognizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 44AEC7121F8F9B770086889F /* RNRootViewGestureRecognizer.m */; };
B5C32A1B220C603B000FFB8D /* RNFlingHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 66A291D3207D032400809C27 /* RNFlingHandler.m */; };
B5C32A1C220C603B000FFB8D /* RNPinchHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 44AEC72A1F8FA1270086889F /* RNPinchHandler.m */; };
B5C32A1E220C603B000FFB8D /* RNForceTouchHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 668E083A21BDD70900EDDF40 /* RNForceTouchHandler.h */; };
B5C32A1F220C603B000FFB8D /* RNFlingHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 66A291D4207D032400809C27 /* RNFlingHandler.h */; };
B5C32A20220C603B000FFB8D /* RNLongPressHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 44AEC7231F8FA1270086889F /* RNLongPressHandler.h */; };
B5C32A21220C603B000FFB8D /* RNGestureHandlerModule.h in Headers */ = {isa = PBXBuildFile; fileRef = 446E7FDC1ED57CA6009282E7 /* RNGestureHandlerModule.h */; };
B5C32A22220C603B000FFB8D /* RNPinchHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 44AEC7291F8FA1270086889F /* RNPinchHandler.h */; };
B5C32A23220C603B000FFB8D /* RNGestureHandlerRegistry.h in Headers */ = {isa = PBXBuildFile; fileRef = 44AEC7141F8F9BEF0086889F /* RNGestureHandlerRegistry.h */; };
B5C32A24220C603B000FFB8D /* RNGestureHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 448802DB1F6803DF00018214 /* RNGestureHandler.h */; };
B5C32A25220C603B000FFB8D /* RNRotationHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 44AEC72B1F8FA1270086889F /* RNRotationHandler.h */; };
B5C32A26220C603B000FFB8D /* RNPanHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 44AEC7271F8FA1270086889F /* RNPanHandler.h */; };
B5C32A27220C603B000FFB8D /* RNGestureHandlerManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 44BE34491F1E1ABA008679D1 /* RNGestureHandlerManager.h */; };
B5C32A28220C603B000FFB8D /* RNGestureHandlerEvents.h in Headers */ = {isa = PBXBuildFile; fileRef = 446E7FF51ED89A31009282E7 /* RNGestureHandlerEvents.h */; };
B5C32A29220C603B000FFB8D /* RNTapHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 44AEC72D1F8FA1270086889F /* RNTapHandler.h */; };
B5C32A2A220C603B000FFB8D /* RNRootViewGestureRecognizer.h in Headers */ = {isa = PBXBuildFile; fileRef = 44AEC7101F8F9B6C0086889F /* RNRootViewGestureRecognizer.h */; };
B5C32A2B220C603B000FFB8D /* RNNativeViewHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 44AEC7251F8FA1270086889F /* RNNativeViewHandler.h */; };
B5C32A2C220C603B000FFB8D /* RNGestureHandlerState.h in Headers */ = {isa = PBXBuildFile; fileRef = 44384A781ECDE0DB006BAD02 /* RNGestureHandlerState.h */; };
B5C32A2D220C603B000FFB8D /* RNGestureHandlerButton.h in Headers */ = {isa = PBXBuildFile; fileRef = 44AEC71D1F8FA0700086889F /* RNGestureHandlerButton.h */; };
B5C32A2E220C603B000FFB8D /* RNGestureHandlerDirection.h in Headers */ = {isa = PBXBuildFile; fileRef = 660F46732080D8F600B7B50D /* RNGestureHandlerDirection.h */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
58B511D91A9E6C8500147676 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "include/$(PRODUCT_NAME)";
dstSubfolderSpec = 16;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
B5C32A30220C603B000FFB8D /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "include/$(PRODUCT_NAME)";
dstSubfolderSpec = 16;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
134814201AA4EA6300B7C361 /* libRNGestureHandler.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNGestureHandler.a; sourceTree = BUILT_PRODUCTS_DIR; };
44384A781ECDE0DB006BAD02 /* RNGestureHandlerState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNGestureHandlerState.h; sourceTree = "<group>"; };
446E7FDC1ED57CA6009282E7 /* RNGestureHandlerModule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNGestureHandlerModule.h; sourceTree = "<group>"; };
446E7FDD1ED57CA6009282E7 /* RNGestureHandlerModule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNGestureHandlerModule.m; sourceTree = "<group>"; };
446E7FF51ED89A31009282E7 /* RNGestureHandlerEvents.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNGestureHandlerEvents.h; sourceTree = "<group>"; };
446E7FF71ED89A4B009282E7 /* RNGestureHandlerEvents.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNGestureHandlerEvents.m; sourceTree = "<group>"; };
448802DB1F6803DF00018214 /* RNGestureHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNGestureHandler.h; sourceTree = "<group>"; };
448802DC1F6803DF00018214 /* RNGestureHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNGestureHandler.m; sourceTree = "<group>"; };
44AEC7101F8F9B6C0086889F /* RNRootViewGestureRecognizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNRootViewGestureRecognizer.h; sourceTree = "<group>"; };
44AEC7121F8F9B770086889F /* RNRootViewGestureRecognizer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNRootViewGestureRecognizer.m; sourceTree = "<group>"; };
44AEC7141F8F9BEF0086889F /* RNGestureHandlerRegistry.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNGestureHandlerRegistry.h; sourceTree = "<group>"; };
44AEC7161F8F9C090086889F /* RNGestureHandlerRegistry.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNGestureHandlerRegistry.m; sourceTree = "<group>"; };
44AEC71D1F8FA0700086889F /* RNGestureHandlerButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNGestureHandlerButton.h; sourceTree = "<group>"; };
44AEC71E1F8FA0700086889F /* RNGestureHandlerButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNGestureHandlerButton.m; sourceTree = "<group>"; };
44AEC7231F8FA1270086889F /* RNLongPressHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNLongPressHandler.h; path = Handlers/RNLongPressHandler.h; sourceTree = "<group>"; };
44AEC7241F8FA1270086889F /* RNLongPressHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNLongPressHandler.m; path = Handlers/RNLongPressHandler.m; sourceTree = "<group>"; };
44AEC7251F8FA1270086889F /* RNNativeViewHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNNativeViewHandler.h; path = Handlers/RNNativeViewHandler.h; sourceTree = "<group>"; };
44AEC7261F8FA1270086889F /* RNNativeViewHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNNativeViewHandler.m; path = Handlers/RNNativeViewHandler.m; sourceTree = "<group>"; };
44AEC7271F8FA1270086889F /* RNPanHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNPanHandler.h; path = Handlers/RNPanHandler.h; sourceTree = "<group>"; };
44AEC7281F8FA1270086889F /* RNPanHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNPanHandler.m; path = Handlers/RNPanHandler.m; sourceTree = "<group>"; };
44AEC7291F8FA1270086889F /* RNPinchHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNPinchHandler.h; path = Handlers/RNPinchHandler.h; sourceTree = "<group>"; };
44AEC72A1F8FA1270086889F /* RNPinchHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNPinchHandler.m; path = Handlers/RNPinchHandler.m; sourceTree = "<group>"; };
44AEC72B1F8FA1270086889F /* RNRotationHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNRotationHandler.h; path = Handlers/RNRotationHandler.h; sourceTree = "<group>"; };
44AEC72C1F8FA1270086889F /* RNRotationHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNRotationHandler.m; path = Handlers/RNRotationHandler.m; sourceTree = "<group>"; };
44AEC72D1F8FA1270086889F /* RNTapHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNTapHandler.h; path = Handlers/RNTapHandler.h; sourceTree = "<group>"; };
44AEC72E1F8FA1270086889F /* RNTapHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNTapHandler.m; path = Handlers/RNTapHandler.m; sourceTree = "<group>"; };
44BE34471F1E1AAA008679D1 /* RNGestureHandlerManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNGestureHandlerManager.m; sourceTree = "<group>"; };
44BE34491F1E1ABA008679D1 /* RNGestureHandlerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNGestureHandlerManager.h; sourceTree = "<group>"; };
660F46732080D8F600B7B50D /* RNGestureHandlerDirection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNGestureHandlerDirection.h; sourceTree = "<group>"; };
668E083A21BDD70900EDDF40 /* RNForceTouchHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNForceTouchHandler.h; path = Handlers/RNForceTouchHandler.h; sourceTree = "<group>"; };
668E083B21BDD70900EDDF40 /* RNForceTouchHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNForceTouchHandler.m; path = Handlers/RNForceTouchHandler.m; sourceTree = "<group>"; };
66A291D3207D032400809C27 /* RNFlingHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNFlingHandler.m; path = Handlers/RNFlingHandler.m; sourceTree = "<group>"; };
66A291D4207D032400809C27 /* RNFlingHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNFlingHandler.h; path = Handlers/RNFlingHandler.h; sourceTree = "<group>"; };
B5C32A36220C603B000FFB8D /* libRNGestureHandler-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libRNGestureHandler-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
58B511D81A9E6C8500147676 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
B5C32A2F220C603B000FFB8D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
134814211AA4EA7D00B7C361 /* Products */ = {
isa = PBXGroup;
children = (
134814201AA4EA6300B7C361 /* libRNGestureHandler.a */,
);
name = Products;
sourceTree = "<group>";
};
44AEC7221F8FA1150086889F /* Handlers */ = {
isa = PBXGroup;
children = (
668E083A21BDD70900EDDF40 /* RNForceTouchHandler.h */,
668E083B21BDD70900EDDF40 /* RNForceTouchHandler.m */,
66A291D4207D032400809C27 /* RNFlingHandler.h */,
66A291D3207D032400809C27 /* RNFlingHandler.m */,
44AEC7231F8FA1270086889F /* RNLongPressHandler.h */,
44AEC7241F8FA1270086889F /* RNLongPressHandler.m */,
44AEC7251F8FA1270086889F /* RNNativeViewHandler.h */,
44AEC7261F8FA1270086889F /* RNNativeViewHandler.m */,
44AEC7271F8FA1270086889F /* RNPanHandler.h */,
44AEC7281F8FA1270086889F /* RNPanHandler.m */,
44AEC7291F8FA1270086889F /* RNPinchHandler.h */,
44AEC72A1F8FA1270086889F /* RNPinchHandler.m */,
44AEC72B1F8FA1270086889F /* RNRotationHandler.h */,
44AEC72C1F8FA1270086889F /* RNRotationHandler.m */,
44AEC72D1F8FA1270086889F /* RNTapHandler.h */,
44AEC72E1F8FA1270086889F /* RNTapHandler.m */,
);
name = Handlers;
sourceTree = "<group>";
};
58B511D21A9E6C8500147676 = {
isa = PBXGroup;
children = (
660F46732080D8F600B7B50D /* RNGestureHandlerDirection.h */,
44AEC7221F8FA1150086889F /* Handlers */,
44AEC7161F8F9C090086889F /* RNGestureHandlerRegistry.m */,
44AEC7141F8F9BEF0086889F /* RNGestureHandlerRegistry.h */,
44AEC7121F8F9B770086889F /* RNRootViewGestureRecognizer.m */,
44AEC7101F8F9B6C0086889F /* RNRootViewGestureRecognizer.h */,
448802DB1F6803DF00018214 /* RNGestureHandler.h */,
448802DC1F6803DF00018214 /* RNGestureHandler.m */,
44BE34491F1E1ABA008679D1 /* RNGestureHandlerManager.h */,
44BE34471F1E1AAA008679D1 /* RNGestureHandlerManager.m */,
446E7FF71ED89A4B009282E7 /* RNGestureHandlerEvents.m */,
446E7FF51ED89A31009282E7 /* RNGestureHandlerEvents.h */,
446E7FDC1ED57CA6009282E7 /* RNGestureHandlerModule.h */,
446E7FDD1ED57CA6009282E7 /* RNGestureHandlerModule.m */,
44384A781ECDE0DB006BAD02 /* RNGestureHandlerState.h */,
44AEC71D1F8FA0700086889F /* RNGestureHandlerButton.h */,
44AEC71E1F8FA0700086889F /* RNGestureHandlerButton.m */,
134814211AA4EA7D00B7C361 /* Products */,
B5C32A36220C603B000FFB8D /* libRNGestureHandler-tvOS.a */,
);
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
446E7FE51ED6DBD8009282E7 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
668E083C21BDD70900EDDF40 /* RNForceTouchHandler.h in Headers */,
66A291D6207D032400809C27 /* RNFlingHandler.h in Headers */,
44AEC72F1F8FA1270086889F /* RNLongPressHandler.h in Headers */,
446E7FE61ED6E177009282E7 /* RNGestureHandlerModule.h in Headers */,
44AEC7351F8FA1270086889F /* RNPinchHandler.h in Headers */,
44AEC7151F8F9BEF0086889F /* RNGestureHandlerRegistry.h in Headers */,
448802DD1F6803DF00018214 /* RNGestureHandler.h in Headers */,
44AEC7371F8FA1270086889F /* RNRotationHandler.h in Headers */,
44AEC7331F8FA1270086889F /* RNPanHandler.h in Headers */,
44BE344A1F1E1ABA008679D1 /* RNGestureHandlerManager.h in Headers */,
446E7FF61ED89A31009282E7 /* RNGestureHandlerEvents.h in Headers */,
44AEC7391F8FA1270086889F /* RNTapHandler.h in Headers */,
44AEC7111F8F9B6C0086889F /* RNRootViewGestureRecognizer.h in Headers */,
44AEC7311F8FA1270086889F /* RNNativeViewHandler.h in Headers */,
446E7FE71ED6E177009282E7 /* RNGestureHandlerState.h in Headers */,
44AEC71F1F8FA0700086889F /* RNGestureHandlerButton.h in Headers */,
660F46742080D8F700B7B50D /* RNGestureHandlerDirection.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
B5C32A1D220C603B000FFB8D /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
B5C32A1E220C603B000FFB8D /* RNForceTouchHandler.h in Headers */,
B5C32A1F220C603B000FFB8D /* RNFlingHandler.h in Headers */,
B5C32A20220C603B000FFB8D /* RNLongPressHandler.h in Headers */,
B5C32A21220C603B000FFB8D /* RNGestureHandlerModule.h in Headers */,
B5C32A22220C603B000FFB8D /* RNPinchHandler.h in Headers */,
B5C32A23220C603B000FFB8D /* RNGestureHandlerRegistry.h in Headers */,
B5C32A24220C603B000FFB8D /* RNGestureHandler.h in Headers */,
B5C32A25220C603B000FFB8D /* RNRotationHandler.h in Headers */,
B5C32A26220C603B000FFB8D /* RNPanHandler.h in Headers */,
B5C32A27220C603B000FFB8D /* RNGestureHandlerManager.h in Headers */,
B5C32A28220C603B000FFB8D /* RNGestureHandlerEvents.h in Headers */,
B5C32A29220C603B000FFB8D /* RNTapHandler.h in Headers */,
B5C32A2A220C603B000FFB8D /* RNRootViewGestureRecognizer.h in Headers */,
B5C32A2B220C603B000FFB8D /* RNNativeViewHandler.h in Headers */,
B5C32A2C220C603B000FFB8D /* RNGestureHandlerState.h in Headers */,
B5C32A2D220C603B000FFB8D /* RNGestureHandlerButton.h in Headers */,
B5C32A2E220C603B000FFB8D /* RNGestureHandlerDirection.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
58B511DA1A9E6C8500147676 /* RNGestureHandler */ = {
isa = PBXNativeTarget;
buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNGestureHandler" */;
buildPhases = (
58B511D71A9E6C8500147676 /* Sources */,
446E7FE51ED6DBD8009282E7 /* Headers */,
58B511D81A9E6C8500147676 /* Frameworks */,
58B511D91A9E6C8500147676 /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = RNGestureHandler;
productName = RCTDataManager;
productReference = 134814201AA4EA6300B7C361 /* libRNGestureHandler.a */;
productType = "com.apple.product-type.library.static";
};
B5C32A0C220C603B000FFB8D /* RNGestureHandler-tvOS */ = {
isa = PBXNativeTarget;
buildConfigurationList = B5C32A31220C603B000FFB8D /* Build configuration list for PBXNativeTarget "RNGestureHandler-tvOS" */;
buildPhases = (
B5C32A0D220C603B000FFB8D /* Sources */,
B5C32A1D220C603B000FFB8D /* Headers */,
B5C32A2F220C603B000FFB8D /* Frameworks */,
B5C32A30220C603B000FFB8D /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = "RNGestureHandler-tvOS";
productName = RCTDataManager;
productReference = B5C32A36220C603B000FFB8D /* libRNGestureHandler-tvOS.a */;
productType = "com.apple.product-type.library.static";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
58B511D31A9E6C8500147676 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 610;
ORGANIZATIONNAME = "Software Mansion";
TargetAttributes = {
58B511DA1A9E6C8500147676 = {
CreatedOnToolsVersion = 6.1.1;
};
};
};
buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RNGestureHandler" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
);
mainGroup = 58B511D21A9E6C8500147676;
productRefGroup = 58B511D21A9E6C8500147676;
projectDirPath = "";
projectRoot = "";
targets = (
58B511DA1A9E6C8500147676 /* RNGestureHandler */,
B5C32A0C220C603B000FFB8D /* RNGestureHandler-tvOS */,
);
};
/* End PBXProject section */
/* Begin PBXSourcesBuildPhase section */
58B511D71A9E6C8500147676 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
44AEC7201F8FA0700086889F /* RNGestureHandlerButton.m in Sources */,
668E083D21BDD70900EDDF40 /* RNForceTouchHandler.m in Sources */,
44BE34481F1E1AAA008679D1 /* RNGestureHandlerManager.m in Sources */,
44AEC7301F8FA1270086889F /* RNLongPressHandler.m in Sources */,
446E7FF81ED89A4B009282E7 /* RNGestureHandlerEvents.m in Sources */,
44AEC7341F8FA1270086889F /* RNPanHandler.m in Sources */,
44AEC7171F8F9C090086889F /* RNGestureHandlerRegistry.m in Sources */,
448802DE1F6803DF00018214 /* RNGestureHandler.m in Sources */,
44AEC73A1F8FA1270086889F /* RNTapHandler.m in Sources */,
446E7FDE1ED57CA6009282E7 /* RNGestureHandlerModule.m in Sources */,
44AEC7381F8FA1270086889F /* RNRotationHandler.m in Sources */,
44AEC7321F8FA1270086889F /* RNNativeViewHandler.m in Sources */,
44AEC7131F8F9B770086889F /* RNRootViewGestureRecognizer.m in Sources */,
66A291D5207D032400809C27 /* RNFlingHandler.m in Sources */,
44AEC7361F8FA1270086889F /* RNPinchHandler.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
B5C32A0D220C603B000FFB8D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B5C32A0E220C603B000FFB8D /* RNGestureHandlerButton.m in Sources */,
B5C32A0F220C603B000FFB8D /* RNForceTouchHandler.m in Sources */,
B5C32A10220C603B000FFB8D /* RNGestureHandlerManager.m in Sources */,
B5C32A11220C603B000FFB8D /* RNLongPressHandler.m in Sources */,
B5C32A12220C603B000FFB8D /* RNGestureHandlerEvents.m in Sources */,
B5C32A13220C603B000FFB8D /* RNPanHandler.m in Sources */,
B5C32A14220C603B000FFB8D /* RNGestureHandlerRegistry.m in Sources */,
B5C32A15220C603B000FFB8D /* RNGestureHandler.m in Sources */,
B5C32A16220C603B000FFB8D /* RNTapHandler.m in Sources */,
B5C32A17220C603B000FFB8D /* RNGestureHandlerModule.m in Sources */,
B5C32A18220C603B000FFB8D /* RNRotationHandler.m in Sources */,
B5C32A19220C603B000FFB8D /* RNNativeViewHandler.m in Sources */,
B5C32A1A220C603B000FFB8D /* RNRootViewGestureRecognizer.m in Sources */,
B5C32A1B220C603B000FFB8D /* RNFlingHandler.m in Sources */,
B5C32A1C220C603B000FFB8D /* RNPinchHandler.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
3C75380331224952A9D19739 /* Testflight */ = {
isa = XCBuildConfiguration;
buildSettings = {
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../../React/**",
"$(SRCROOT)/../../react-native/React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = RNGestureHandler;
SKIP_INSTALL = YES;
};
name = Testflight;
};
58B511ED1A9E6C8500147676 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
};
name = Debug;
};
58B511EE1A9E6C8500147676 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
58B511F01A9E6C8500147676 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../../React/**",
"$(SRCROOT)/../../react-native/React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = RNGestureHandler;
SKIP_INSTALL = YES;
};
name = Debug;
};
58B511F11A9E6C8500147676 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../../React/**",
"$(SRCROOT)/../../react-native/React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = RNGestureHandler;
SKIP_INSTALL = YES;
};
name = Release;
};
64C7ABFB934A41BFB09378ED /* Testflight */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
};
name = Testflight;
};
6E1E231AAE4C4EB5B94B5418 /* Testflight */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
};
name = Testflight;
};
8F4E4CFC1C3048678D76E403 /* Testflight */ = {
isa = XCBuildConfiguration;
buildSettings = {
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../../React/**",
"$(SRCROOT)/../../react-native/React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = RNGestureHandler;
SKIP_INSTALL = YES;
};
name = Testflight;
};
B5C32A32220C603B000FFB8D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../../React/**",
"$(SRCROOT)/../../react-native/React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = appletvos;
SKIP_INSTALL = YES;
};
name = Debug;
};
B5C32A33220C603B000FFB8D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../../React/**",
"$(SRCROOT)/../../react-native/React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = appletvos;
SKIP_INSTALL = YES;
};
name = Release;
};
B5C32A34220C603B000FFB8D /* Testflight */ = {
isa = XCBuildConfiguration;
buildSettings = {
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../../React/**",
"$(SRCROOT)/../../react-native/React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = appletvos;
SKIP_INSTALL = YES;
};
name = Testflight;
};
B5C32A35220C603B000FFB8D /* Testflight */ = {
isa = XCBuildConfiguration;
buildSettings = {
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../../React/**",
"$(SRCROOT)/../../react-native/React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = RNGestureHandler;
SKIP_INSTALL = YES;
};
name = Testflight;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RNGestureHandler" */ = {
isa = XCConfigurationList;
buildConfigurations = (
58B511ED1A9E6C8500147676 /* Debug */,
58B511EE1A9E6C8500147676 /* Release */,
6E1E231AAE4C4EB5B94B5418 /* Testflight */,
64C7ABFB934A41BFB09378ED /* Testflight */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNGestureHandler" */ = {
isa = XCConfigurationList;
buildConfigurations = (
58B511F01A9E6C8500147676 /* Debug */,
58B511F11A9E6C8500147676 /* Release */,
3C75380331224952A9D19739 /* Testflight */,
8F4E4CFC1C3048678D76E403 /* Testflight */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
B5C32A31220C603B000FFB8D /* Build configuration list for PBXNativeTarget "RNGestureHandler-tvOS" */ = {
isa = XCConfigurationList;
buildConfigurations = (
B5C32A32220C603B000FFB8D /* Debug */,
B5C32A33220C603B000FFB8D /* Release */,
B5C32A34220C603B000FFB8D /* Testflight */,
B5C32A35220C603B000FFB8D /* Testflight */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 58B511D31A9E6C8500147676 /* Project object */;
}

View File

@ -0,0 +1,18 @@
//
// RNGestureHandlerButton.h
// RNGestureHandler
//
// Created by Krzysztof Magiera on 12/10/2017.
// Copyright © 2017 Software Mansion. All rights reserved.
//
#import "RNGestureHandler.h"
@interface RNGestureHandlerButton : UIControl
/**
* Insets used when hit testing inside this view.
*/
@property (nonatomic, assign) UIEdgeInsets hitTestEdgeInsets;
@end

View File

@ -0,0 +1,70 @@
//
// RNGestureHandlerButton.m
// RNGestureHandler
//
// Created by Krzysztof Magiera on 12/10/2017.
// Copyright © 2017 Software Mansion. All rights reserved.
//
#import "RNGestureHandlerButton.h"
#import <UIKit/UIKit.h>
/**
* Gesture Handler Button components overrides standard mechanism used by RN
* to determine touch target, which normally would reurn the UIView that is placed
* as the deepest element in the view hierarchy.
* It's done this way as it allows for the actual target determination to run in JS
* where we can travers up the view ierarchy to find first element that want to became
* JS responder.
*
* Since we want to use native button (or actually a `UIControl`) we need to determine
* the target in native. This makes it impossible for JS responder based components to
* function as a subviews of the button component. Here we override `hitTest:withEvent:`
* method and we only determine the target to be either a subclass of `UIControl` or a
* view that has gesture recognizers registered.
*
* This "default" behaviour of target determinator should be sufficient in most of the
* cases as in fact it is not that common UI pattern to have many nested buttons (usually
* there are just two levels e.g. when you have clickable table cells with additional
* buttons). In cases when the default behaviour is insufficient it is recommended to use
* `TapGestureHandler` instead of a button which gives much better flexibility as far as
* controlling the touch flow.
*/
@implementation RNGestureHandlerButton
- (instancetype)init
{
self = [super init];
if (self) {
_hitTestEdgeInsets = UIEdgeInsetsZero;
#if !TARGET_OS_TV
[self setExclusiveTouch:YES];
#endif
}
return self;
}
- (BOOL)shouldHandleTouch:(UIView *)view
{
return [view isKindOfClass:[UIControl class]] || [view.gestureRecognizers count] > 0;
}
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
if (UIEdgeInsetsEqualToEdgeInsets(self.hitTestEdgeInsets, UIEdgeInsetsZero)) {
return [super pointInside:point withEvent:event];
}
CGRect hitFrame = UIEdgeInsetsInsetRect(self.bounds, self.hitTestEdgeInsets);
return CGRectContainsPoint(hitFrame, point);
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView *inner = [super hitTest:point withEvent:event];
while (inner && ![self shouldHandleTouch:inner]) inner = inner.superview;
return inner;
}
@end

View File

@ -0,0 +1,8 @@
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSInteger, RNGestureHandlerDirection) {
RNGestureHandlerDirectionRight = 1,
RNGestureHandlerDirectionLeft = 2,
RNGestureHandlerDirectionUp = 4,
RNGestureHandlerDirectionDown = 8,
};

View File

@ -0,0 +1,56 @@
#import <React/RCTEventDispatcher.h>
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "RNGestureHandlerState.h"
@interface RNGestureHandlerEventExtraData : NSObject
@property (readonly) NSDictionary *data;
- (instancetype)initWithData:(NSDictionary *)data;
+ (RNGestureHandlerEventExtraData *)forPosition:(CGPoint)position
withAbsolutePosition:(CGPoint)absolutePosition
withNumberOfTouches:(NSUInteger)numberOfTouches;
+ (RNGestureHandlerEventExtraData *)forPan:(CGPoint)position
withAbsolutePosition:(CGPoint)absolutePosition
withTranslation:(CGPoint)translation
withVelocity:(CGPoint)velocity
withNumberOfTouches:(NSUInteger)numberOfTouches;
+ (RNGestureHandlerEventExtraData *)forForce:(CGFloat)force
forPosition:(CGPoint)position
withAbsolutePosition:(CGPoint)absolutePosition
withNumberOfTouches:(NSUInteger)numberOfTouches;
+ (RNGestureHandlerEventExtraData *)forPinch:(CGFloat)scale
withFocalPoint:(CGPoint)focalPoint
withVelocity:(CGFloat)velocity
withNumberOfTouches:(NSUInteger)numberOfTouches;
+ (RNGestureHandlerEventExtraData *)forRotation:(CGFloat)rotation
withAnchorPoint:(CGPoint)anchorPoint
withVelocity:(CGFloat)velocity
withNumberOfTouches:(NSUInteger)numberOfTouches;
+ (RNGestureHandlerEventExtraData *)forPointerInside:(BOOL)pointerInside;
@end
@interface RNGestureHandlerEvent : NSObject <RCTEvent>
- (instancetype)initWithReactTag:(NSNumber *)reactTag
handlerTag:(NSNumber *)handlerTag
state:(RNGestureHandlerState)state
extraData:(RNGestureHandlerEventExtraData*)extraData
coalescingKey:(uint16_t)coalescingKey NS_DESIGNATED_INITIALIZER;
@end
@interface RNGestureHandlerStateChange : NSObject <RCTEvent>
- (instancetype)initWithReactTag:(NSNumber *)reactTag
handlerTag:(NSNumber *)handlerTag
state:(RNGestureHandlerState)state
prevState:(RNGestureHandlerState)prevState
extraData:(RNGestureHandlerEventExtraData*)extraData NS_DESIGNATED_INITIALIZER;
@end

View File

@ -0,0 +1,221 @@
#import "RNGestureHandlerEvents.h"
#define SAFE_VELOCITY(velocity) @(isnan(velocity) ? 0 : velocity)
@implementation RNGestureHandlerEventExtraData
- (instancetype)initWithData:(NSDictionary *)data;
{
if ((self = [super init])) {
_data = data;
}
return self;
}
+ (RNGestureHandlerEventExtraData *)forPosition:(CGPoint)position
withAbsolutePosition:(CGPoint)absolutePosition
withNumberOfTouches:(NSUInteger)numberOfTouches
{
return [[RNGestureHandlerEventExtraData alloc]
initWithData:@{
@"x": @(position.x),
@"y": @(position.y),
@"absoluteX": @(absolutePosition.x),
@"absoluteY": @(absolutePosition.y),
@"numberOfPointers": @(numberOfTouches)}];
}
+ (RNGestureHandlerEventExtraData *)forPan:(CGPoint)position
withAbsolutePosition:(CGPoint)absolutePosition
withTranslation:(CGPoint)translation
withVelocity:(CGPoint)velocity
withNumberOfTouches:(NSUInteger)numberOfTouches
{
return [[RNGestureHandlerEventExtraData alloc]
initWithData:@{
@"x": @(position.x),
@"y": @(position.y),
@"absoluteX": @(absolutePosition.x),
@"absoluteY": @(absolutePosition.y),
@"translationX": @(translation.x),
@"translationY": @(translation.y),
@"velocityX": SAFE_VELOCITY(velocity.x),
@"velocityY": SAFE_VELOCITY(velocity.y),
@"numberOfPointers": @(numberOfTouches)}];
}
+ (RNGestureHandlerEventExtraData *)forForce:(CGFloat)force
forPosition:(CGPoint)position
withAbsolutePosition:(CGPoint)absolutePosition
withNumberOfTouches:(NSUInteger)numberOfTouches
{
return [[RNGestureHandlerEventExtraData alloc]
initWithData:@{
@"x": @(position.x),
@"y": @(position.y),
@"absoluteX": @(absolutePosition.x),
@"absoluteY": @(absolutePosition.y),
@"force": @(force),
@"numberOfPointers": @(numberOfTouches)}];
}
+ (RNGestureHandlerEventExtraData *)forPinch:(CGFloat)scale
withFocalPoint:(CGPoint)focalPoint
withVelocity:(CGFloat)velocity
withNumberOfTouches:(NSUInteger)numberOfTouches
{
return [[RNGestureHandlerEventExtraData alloc]
initWithData:@{
@"scale": @(scale),
@"focalX": @(focalPoint.x),
@"focalY": @(focalPoint.y),
@"velocity": SAFE_VELOCITY(velocity),
@"numberOfPointers": @(numberOfTouches)}];
}
+ (RNGestureHandlerEventExtraData *)forRotation:(CGFloat)rotation
withAnchorPoint:(CGPoint)anchorPoint
withVelocity:(CGFloat)velocity
withNumberOfTouches:(NSUInteger)numberOfTouches
{
return [[RNGestureHandlerEventExtraData alloc]
initWithData:@{@"rotation": @(rotation),
@"anchorX": @(anchorPoint.x),
@"anchorY": @(anchorPoint.y),
@"velocity": SAFE_VELOCITY(velocity),
@"numberOfPointers": @(numberOfTouches)}];
}
+ (RNGestureHandlerEventExtraData *)forPointerInside:(BOOL)pointerInside
{
return [[RNGestureHandlerEventExtraData alloc]
initWithData:@{@"pointerInside": @(pointerInside)}];
}
@end
@implementation RNGestureHandlerEvent
{
NSNumber *_handlerTag;
RNGestureHandlerState _state;
RNGestureHandlerEventExtraData *_extraData;
}
@synthesize viewTag = _viewTag;
@synthesize coalescingKey = _coalescingKey;
- (instancetype)initWithReactTag:(NSNumber *)reactTag
handlerTag:(NSNumber *)handlerTag
state:(RNGestureHandlerState)state
extraData:(RNGestureHandlerEventExtraData *)extraData
coalescingKey:(uint16_t)coalescingKey
{
if ((self = [super init])) {
_viewTag = reactTag;
_handlerTag = handlerTag;
_state = state;
_extraData = extraData;
_coalescingKey = coalescingKey;
}
return self;
}
RCT_NOT_IMPLEMENTED(- (instancetype)init)
- (NSString *)eventName
{
return @"onGestureHandlerEvent";
}
- (BOOL)canCoalesce
{
return YES;
}
- (id<RCTEvent>)coalesceWithEvent:(id<RCTEvent>)newEvent;
{
return newEvent;
}
+ (NSString *)moduleDotMethod
{
return @"RCTEventEmitter.receiveEvent";
}
- (NSArray *)arguments
{
NSMutableDictionary *body = [NSMutableDictionary dictionaryWithDictionary:_extraData.data];
[body setObject:_viewTag forKey:@"target"];
[body setObject:_handlerTag forKey:@"handlerTag"];
[body setObject:@(_state) forKey:@"state"];
return @[self.viewTag, @"onGestureHandlerEvent", body];
}
@end
@implementation RNGestureHandlerStateChange
{
NSNumber *_handlerTag;
RNGestureHandlerState _state;
RNGestureHandlerState _prevState;
RNGestureHandlerEventExtraData *_extraData;
}
@synthesize viewTag = _viewTag;
@synthesize coalescingKey = _coalescingKey;
- (instancetype)initWithReactTag:(NSNumber *)reactTag
handlerTag:(NSNumber *)handlerTag
state:(RNGestureHandlerState)state
prevState:(RNGestureHandlerState)prevState
extraData:(RNGestureHandlerEventExtraData *)extraData
{
static uint16_t coalescingKey = 0;
if ((self = [super init])) {
_viewTag = reactTag;
_handlerTag = handlerTag;
_state = state;
_prevState = prevState;
_extraData = extraData;
_coalescingKey = coalescingKey++;
}
return self;
}
RCT_NOT_IMPLEMENTED(- (instancetype)init)
- (NSString *)eventName
{
return @"onGestureHandlerStateChange";
}
- (BOOL)canCoalesce
{
// TODO: event coalescing
return NO;
}
- (id<RCTEvent>)coalesceWithEvent:(id<RCTEvent>)newEvent;
{
return newEvent;
}
+ (NSString *)moduleDotMethod
{
return @"RCTEventEmitter.receiveEvent";
}
- (NSArray *)arguments
{
NSMutableDictionary *body = [NSMutableDictionary dictionaryWithDictionary:_extraData.data];
[body setObject:_viewTag forKey:@"target"];
[body setObject:_handlerTag forKey:@"handlerTag"];
[body setObject:@(_state) forKey:@"state"];
[body setObject:@(_prevState) forKey:@"oldState"];
return @[self.viewTag, @"onGestureHandlerStateChange", body];
}
@end

View File

@ -0,0 +1,29 @@
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
@class RCTUIManager;
@class RCTEventDispatcher;
@interface RNGestureHandlerManager : NSObject
- (nonnull instancetype)initWithUIManager:(nonnull RCTUIManager *)uiManager
eventDispatcher:(nonnull RCTEventDispatcher *)eventDispatcher;
- (void)createGestureHandler:(nonnull NSString *)handlerName
tag:(nonnull NSNumber *)handlerTag
config:(nonnull NSDictionary *)config;
- (void)attachGestureHandler:(nonnull NSNumber *)handlerTag
toViewWithTag:(nonnull NSNumber *)viewTag;
- (void)updateGestureHandler:(nonnull NSNumber *)handlerTag config:(nonnull NSDictionary *)config;
- (void)dropGestureHandler:(nonnull NSNumber *)handlerTag;
- (void)handleSetJSResponder:(nonnull NSNumber *)viewTag
blockNativeResponder:(nonnull NSNumber *)blockNativeResponder;
- (void)handleClearJSResponder;
@end

View File

@ -0,0 +1,188 @@
#import "RNGestureHandlerManager.h"
#import <React/RCTLog.h>
#import <React/RCTViewManager.h>
#import <React/RCTComponent.h>
#import <React/RCTRootView.h>
#import <React/RCTTouchHandler.h>
#import <React/RCTRootContentView.h>
#import <React/RCTUIManager.h>
#import <React/RCTEventDispatcher.h>
#import "RNGestureHandlerState.h"
#import "RNGestureHandler.h"
#import "RNGestureHandlerRegistry.h"
#import "RNRootViewGestureRecognizer.h"
#import "Handlers/RNPanHandler.h"
#import "Handlers/RNTapHandler.h"
#import "Handlers/RNFlingHandler.h"
#import "Handlers/RNLongPressHandler.h"
#import "Handlers/RNNativeViewHandler.h"
#import "Handlers/RNPinchHandler.h"
#import "Handlers/RNRotationHandler.h"
#import "Handlers/RNForceTouchHandler.h"
// We use the method below instead of RCTLog because we log out messages after the bridge gets
// turned down in some cases. Which normally with RCTLog would cause a crash in DEBUG mode
#define RCTLifecycleLog(...) RCTDefaultLogFunction(RCTLogLevelInfo, RCTLogSourceNative, @(__FILE__), @(__LINE__), [NSString stringWithFormat:__VA_ARGS__])
@interface RNGestureHandlerManager () <RNGestureHandlerEventEmitter, RNRootViewGestureRecognizerDelegate>
@end
@implementation RNGestureHandlerManager
{
RNGestureHandlerRegistry *_registry;
RCTUIManager *_uiManager;
NSMutableSet<UIView*> *_rootViews;
RCTEventDispatcher *_eventDispatcher;
}
- (instancetype)initWithUIManager:(RCTUIManager *)uiManager
eventDispatcher:(RCTEventDispatcher *)eventDispatcher
{
if ((self = [super init])) {
_uiManager = uiManager;
_eventDispatcher = eventDispatcher;
_registry = [RNGestureHandlerRegistry new];
_rootViews = [NSMutableSet new];
}
return self;
}
- (void)createGestureHandler:(NSString *)handlerName
tag:(NSNumber *)handlerTag
config:(NSDictionary *)config
{
static NSDictionary *map;
static dispatch_once_t mapToken;
dispatch_once(&mapToken, ^{
map = @{
@"PanGestureHandler" : [RNPanGestureHandler class],
@"TapGestureHandler" : [RNTapGestureHandler class],
@"FlingGestureHandler" : [RNFlingGestureHandler class],
@"LongPressGestureHandler": [RNLongPressGestureHandler class],
@"NativeViewGestureHandler": [RNNativeViewGestureHandler class],
@"PinchGestureHandler": [RNPinchGestureHandler class],
@"RotationGestureHandler": [RNRotationGestureHandler class],
@"ForceTouchGestureHandler": [RNForceTouchHandler class],
};
});
Class nodeClass = map[handlerName];
if (!nodeClass) {
RCTLogError(@"Gesture handler type %@ is not supported", handlerName);
return;
}
RNGestureHandler *gestureHandler = [[nodeClass alloc] initWithTag:handlerTag];
[gestureHandler configure:config];
[_registry registerGestureHandler:gestureHandler];
__weak id<RNGestureHandlerEventEmitter> emitter = self;
gestureHandler.emitter = emitter;
}
- (void)attachGestureHandler:(nonnull NSNumber *)handlerTag
toViewWithTag:(nonnull NSNumber *)viewTag
{
UIView *view = [_uiManager viewForReactTag:viewTag];
[_registry attachHandlerWithTag:handlerTag toView:view];
// register root view if not already there
[self registerRootViewIfNeeded:view];
}
- (void)updateGestureHandler:(NSNumber *)handlerTag config:(NSDictionary *)config
{
RNGestureHandler *handler = [_registry handlerWithTag:handlerTag];
[handler configure:config];
}
- (void)dropGestureHandler:(NSNumber *)handlerTag
{
[_registry dropHandlerWithTag:handlerTag];
}
- (void)handleSetJSResponder:(NSNumber *)viewTag blockNativeResponder:(NSNumber *)blockNativeResponder
{
if ([blockNativeResponder boolValue]) {
for (RCTRootView *rootView in _rootViews) {
for (UIGestureRecognizer *recognizer in rootView.gestureRecognizers) {
if ([recognizer isKindOfClass:[RNRootViewGestureRecognizer class]]) {
[(RNRootViewGestureRecognizer *)recognizer blockOtherRecognizers];
}
}
}
}
}
- (void)handleClearJSResponder
{
// ignore...
}
#pragma mark Root Views Management
- (void)registerRootViewIfNeeded:(UIView*)childView
{
UIView *parent = childView;
while (parent != nil && ![parent isKindOfClass:[RCTRootView class]]) parent = parent.superview;
RCTRootView *rootView = (RCTRootView *)parent;
UIView *rootContentView = rootView.contentView;
if (rootContentView != nil && ![_rootViews containsObject:rootContentView]) {
RCTLifecycleLog(@"[GESTURE HANDLER] Initialize gesture handler for root view %@", rootContentView);
[_rootViews addObject:rootContentView];
RNRootViewGestureRecognizer *recognizer = [RNRootViewGestureRecognizer new];
recognizer.delegate = self;
rootContentView.userInteractionEnabled = YES;
[rootContentView addGestureRecognizer:recognizer];
}
}
- (void)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
didActivateInRootView:(UIView *)rootContentView
{
// Cancel touches in RN's root view in order to cancel all in-js recognizers
// As scroll events are special-cased in RN responder implementation and sending them would
// trigger JS responder change, we don't cancel touches if the handler that got activated is
// a scroll recognizer. This way root view will keep sending touchMove and touchEnd events
// and therefore allow JS responder to properly release the responder at the end of the touch
// stream.
// NOTE: this is not a proper fix and solving this problem requires upstream fixes to RN. In
// particular if we have one PanHandler and ScrollView that can work simultaniously then when
// the Pan handler activates it would still tigger cancel events.
// Once the upstream fix lands the line below along with this comment can be removed
if ([gestureRecognizer.view isKindOfClass:[UIScrollView class]]) return;
UIView *parent = rootContentView.superview;
if ([parent isKindOfClass:[RCTRootView class]]) {
[((RCTRootContentView*)rootContentView).touchHandler cancel];
}
}
- (void)dealloc
{
if ([_rootViews count] > 0) {
RCTLifecycleLog(@"[GESTURE HANDLER] Tearing down gesture handler registered for views %@", _rootViews);
}
}
#pragma mark Events
- (void)sendTouchEvent:(RNGestureHandlerEvent *)event
{
[_eventDispatcher sendEvent:event];
}
- (void)sendStateChangeEvent:(RNGestureHandlerStateChange *)event
{
[_eventDispatcher sendEvent:event];
}
@end

View File

@ -0,0 +1,8 @@
#import <React/RCTEventEmitter.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTUIManager.h>
@interface RNGestureHandlerModule : RCTEventEmitter <RCTBridgeModule>
@end

View File

@ -0,0 +1,200 @@
#import "RNGestureHandlerModule.h"
#import <React/RCTLog.h>
#import <React/RCTViewManager.h>
#import <React/RCTComponent.h>
#import <React/RCTUIManager.h>
#import <React/RCTUIManagerUtils.h>
#import <React/RCTUIManagerObserverCoordinator.h>
#import "RNGestureHandlerState.h"
#import "RNGestureHandlerDirection.h"
#import "RNGestureHandler.h"
#import "RNGestureHandlerManager.h"
#import "RNGestureHandlerButton.h"
@interface RNGestureHandlerModule () <RCTUIManagerObserver>
@end
@interface RNGestureHandlerButtonManager : RCTViewManager
@end
@implementation RNGestureHandlerButtonManager
RCT_EXPORT_MODULE(RNGestureHandlerButton)
RCT_EXPORT_VIEW_PROPERTY(enabled, BOOL)
#if !TARGET_OS_TV
RCT_CUSTOM_VIEW_PROPERTY(exclusive, BOOL, RNGestureHandlerButton)
{
[view setExclusiveTouch: json == nil ? YES : [RCTConvert BOOL: json]];
}
#endif
RCT_CUSTOM_VIEW_PROPERTY(hitSlop, UIEdgeInsets, RNGestureHandlerButton)
{
if (json) {
UIEdgeInsets hitSlopInsets = [RCTConvert UIEdgeInsets:json];
view.hitTestEdgeInsets = UIEdgeInsetsMake(-hitSlopInsets.top, -hitSlopInsets.left, -hitSlopInsets.bottom, -hitSlopInsets.right);
} else {
view.hitTestEdgeInsets = defaultView.hitTestEdgeInsets;
}
}
- (UIView *)view
{
return [RNGestureHandlerButton new];
}
@end
typedef void (^GestureHandlerOperation)(RNGestureHandlerManager *manager);
@implementation RNGestureHandlerModule
{
RNGestureHandlerManager *_manager;
// Oparations called after views have been updated.
NSMutableArray<GestureHandlerOperation> *_operations;
}
RCT_EXPORT_MODULE()
+ (BOOL)requiresMainQueueSetup
{
return YES;
}
- (void)invalidate
{
_manager = nil;
[self.bridge.uiManager.observerCoordinator removeObserver:self];
}
- (dispatch_queue_t)methodQueue
{
// This module needs to be on the same queue as the UIManager to avoid
// having to lock `_operations` and `_preOperations` since `uiManagerWillFlushUIBlocks`
// will be called from that queue.
// This is required as this module rely on having all the view nodes created before
// gesture handlers can be associated with them
return RCTGetUIManagerQueue();
}
- (void)setBridge:(RCTBridge *)bridge
{
[super setBridge:bridge];
_manager = [[RNGestureHandlerManager alloc]
initWithUIManager:bridge.uiManager
eventDispatcher:bridge.eventDispatcher];
_operations = [NSMutableArray new];
[bridge.uiManager.observerCoordinator addObserver:self];
}
RCT_EXPORT_METHOD(createGestureHandler:(nonnull NSString *)handlerName tag:(nonnull NSNumber *)handlerTag config:(NSDictionary *)config)
{
[self addOperationBlock:^(RNGestureHandlerManager *manager) {
[manager createGestureHandler:handlerName tag:handlerTag config:config];
}];
}
RCT_EXPORT_METHOD(attachGestureHandler:(nonnull NSNumber *)handlerTag toViewWithTag:(nonnull NSNumber *)viewTag)
{
[self addOperationBlock:^(RNGestureHandlerManager *manager) {
[manager attachGestureHandler:handlerTag toViewWithTag:viewTag];
}];
}
RCT_EXPORT_METHOD(updateGestureHandler:(nonnull NSNumber *)handlerTag config:(NSDictionary *)config)
{
[self addOperationBlock:^(RNGestureHandlerManager *manager) {
[manager updateGestureHandler:handlerTag config:config];
}];
}
RCT_EXPORT_METHOD(dropGestureHandler:(nonnull NSNumber *)handlerTag)
{
[self addOperationBlock:^(RNGestureHandlerManager *manager) {
[manager dropGestureHandler:handlerTag];
}];
}
RCT_EXPORT_METHOD(handleSetJSResponder:(nonnull NSNumber *)viewTag blockNativeResponder:(nonnull NSNumber *)blockNativeResponder)
{
[self addOperationBlock:^(RNGestureHandlerManager *manager) {
[manager handleSetJSResponder:viewTag blockNativeResponder:blockNativeResponder];
}];
}
RCT_EXPORT_METHOD(handleClearJSResponder)
{
[self addOperationBlock:^(RNGestureHandlerManager *manager) {
[manager handleClearJSResponder];
}];
}
#pragma mark -- Batch handling
- (void)addOperationBlock:(GestureHandlerOperation)operation
{
[_operations addObject:operation];
}
#pragma mark - RCTUIManagerObserver
- (void)uiManagerWillFlushUIBlocks:(RCTUIManager *)uiManager
{
[self uiManagerWillPerformMounting:uiManager];
}
- (void)uiManagerWillPerformMounting:(RCTUIManager *)uiManager
{
if (_operations.count == 0) {
return;
}
NSArray<GestureHandlerOperation> *operations = _operations;
_operations = [NSMutableArray new];
[uiManager addUIBlock:^(__unused RCTUIManager *manager, __unused NSDictionary<NSNumber *, UIView *> *viewRegistry) {
for (GestureHandlerOperation operation in operations) {
operation(self->_manager);
}
}];
}
#pragma mark Events
- (NSArray<NSString *> *)supportedEvents
{
return @[@"onGestureHandlerEvent", @"onGestureHandlerStateChange"];
}
#pragma mark Module Constants
- (NSDictionary *)constantsToExport
{
return @{ @"State": @{
@"UNDETERMINED": @(RNGestureHandlerStateUndetermined),
@"BEGAN": @(RNGestureHandlerStateBegan),
@"ACTIVE": @(RNGestureHandlerStateActive),
@"CANCELLED": @(RNGestureHandlerStateCancelled),
@"FAILED": @(RNGestureHandlerStateFailed),
@"END": @(RNGestureHandlerStateEnd)
},
@"Direction": @{
@"RIGHT": @(RNGestureHandlerDirectionRight),
@"LEFT": @(RNGestureHandlerDirectionLeft),
@"UP": @(RNGestureHandlerDirectionUp),
@"DOWN": @(RNGestureHandlerDirectionDown)
}
};
}
@end

View File

@ -0,0 +1,18 @@
//
// RNGestureHandlerRegistry.h
// RNGestureHandler
//
// Created by Krzysztof Magiera on 12/10/2017.
// Copyright © 2017 Software Mansion. All rights reserved.
//
#import "RNGestureHandler.h"
@interface RNGestureHandlerRegistry : NSObject
- (nullable RNGestureHandler *)handlerWithTag:(nonnull NSNumber *)handlerTag;
- (void)registerGestureHandler:(nonnull RNGestureHandler *)gestureHandler;
- (void)attachHandlerWithTag:(nonnull NSNumber *)handlerTag toView:(nonnull UIView *)view;
- (void)dropHandlerWithTag:(nonnull NSNumber *)handlerTag;
@end

View File

@ -0,0 +1,50 @@
//
// RNGestureHandlerRegistry.m
// RNGestureHandler
//
// Created by Krzysztof Magiera on 12/10/2017.
// Copyright © 2017 Software Mansion. All rights reserved.
//
#import "RNGestureHandlerRegistry.h"
#import <React/RCTAssert.h>
@implementation RNGestureHandlerRegistry {
NSMutableDictionary<NSNumber *, RNGestureHandler *> *_handlers;
}
- (instancetype)init
{
if ((self = [super init])) {
_handlers = [NSMutableDictionary new];
}
return self;
}
- (RNGestureHandler *)handlerWithTag:(NSNumber *)handlerTag
{
return _handlers[handlerTag];
}
- (void)registerGestureHandler:(RNGestureHandler *)gestureHandler
{
_handlers[gestureHandler.tag] = gestureHandler;
}
- (void)attachHandlerWithTag:(NSNumber *)handlerTag toView:(UIView *)view
{
RNGestureHandler *handler = _handlers[handlerTag];
RCTAssert(handler != nil, @"Handler for tag %@ does not exists", handlerTag);
[handler unbindFromView];
[handler bindToView:view];
}
- (void)dropHandlerWithTag:(NSNumber *)handlerTag
{
RNGestureHandler *handler = _handlers[handlerTag];
[handler unbindFromView];
[_handlers removeObjectForKey:handlerTag];
}
@end

View File

@ -0,0 +1,10 @@
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSInteger, RNGestureHandlerState) {
RNGestureHandlerStateUndetermined = 0,
RNGestureHandlerStateFailed,
RNGestureHandlerStateBegan,
RNGestureHandlerStateCancelled,
RNGestureHandlerStateActive,
RNGestureHandlerStateEnd,
};

View File

@ -0,0 +1,17 @@
//
// RNRootViewGestureRecognizer.h
// RNGestureHandler
//
// Created by Krzysztof Magiera on 12/10/2017.
// Copyright © 2017 Software Mansion. All rights reserved.
//
#import "RNGestureHandler.h"
@interface RNRootViewGestureRecognizer : UIGestureRecognizer
@property (nullable, nonatomic, weak) id<RNRootViewGestureRecognizerDelegate> delegate;
- (void)blockOtherRecognizers;
@end

View File

@ -0,0 +1,93 @@
//
// RNRootViewGestureRecognizer.m
// RNGestureHandler
//
// Created by Krzysztof Magiera on 12/10/2017.
// Copyright © 2017 Software Mansion. All rights reserved.
//
#import "RNRootViewGestureRecognizer.h"
#import <UIKit/UIGestureRecognizerSubclass.h>
#import <React/RCTTouchHandler.h>
@implementation RNRootViewGestureRecognizer
{
BOOL _active;
}
@dynamic delegate;
- (instancetype)init
{
if (self = [super init]) {
self.delaysTouchesEnded = NO;
self.delaysTouchesBegan = NO;
}
return self;
}
- (BOOL)shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
// This method is used to implement "enabled" feature for gesture handlers. We enforce gesture
// recognizers that are connected with "disabled" handlers to wait for the root gesture
// recognizer to fail and this way we block them from acting.
RNGestureHandler *otherHandler = [RNGestureHandler
findGestureHandlerByRecognizer:otherGestureRecognizer];
if (otherHandler != nil && otherHandler.enabled == NO) {
return YES;
}
return NO;
}
- (BOOL)canPreventGestureRecognizer:(UIGestureRecognizer *)preventedGestureRecognizer
{
return ![preventedGestureRecognizer isKindOfClass:[RCTTouchHandler class]];
}
- (BOOL)canBePreventedByGestureRecognizer:(UIGestureRecognizer *)preventingGestureRecognizer
{
// When this method is called it means that one of handlers has activated, in this case we want
// to send an info to JS so that it cancells all JS responders
[self.delegate gestureRecognizer:preventingGestureRecognizer didActivateInRootView:self.view];
return [super canBePreventedByGestureRecognizer:preventingGestureRecognizer];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
_active = YES;
self.state = UIGestureRecognizerStatePossible;
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
self.state = UIGestureRecognizerStatePossible;
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
if (self.state == UIGestureRecognizerStateBegan || self.state == UIGestureRecognizerStateChanged) {
self.state = UIGestureRecognizerStateEnded;
} else {
self.state = UIGestureRecognizerStateFailed;
}
[self reset];
_active = NO;
}
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
self.state = UIGestureRecognizerStateCancelled;
[self reset];
_active = NO;
}
- (void)blockOtherRecognizers
{
if (_active) {
self.state = UIGestureRecognizerStateBegan;
}
}
@end

View File

@ -0,0 +1 @@
jest.mock('./RNGestureHandlerModule');

110
node_modules/react-native-gesture-handler/package.json generated vendored Normal file
View File

@ -0,0 +1,110 @@
{
"name": "react-native-gesture-handler",
"version": "1.8.0",
"description": "Experimental implementation of a new declarative API for gesture handling in react-native",
"scripts": {
"start": "node node_modules/react-native/local-cli/cli.js start",
"test": "jest",
"precommit": "lint-staged",
"web": "expo start:web",
"release": "npm login && release-it"
},
"main": "index.js",
"types": "react-native-gesture-handler.d.ts",
"files": [
"android/build.gradle",
"android/src/main/AndroidManifest.xml",
"android/src/main/java/",
"android/lib/build.gradle",
"android/lib/src/main/java/",
"ios/",
"web/",
"__mocks__/",
"touchables/**/*.js",
"createHandler.js",
"Directions.js",
"DrawerLayout.js",
"GestureHandler.js",
"Gestures.js",
"GestureButtons.js",
"GestureComponents.js",
"GestureComponents.web.js",
"createNativeWrapper.js",
"GestureHandlerPropTypes.js",
"NativeViewGestureHandler.js",
"GestureHandlerButton.js",
"GestureHandlerRootView.js",
"GestureHandlerRootView.android.js",
"GestureHandlerRootView.android.expo.js",
"GestureHandlerButton.web.js",
"gestureHandlerRootHOC.android.js",
"gestureHandlerRootHOC.ios.js",
"gestureHandlerRootHOC.js",
"index.js",
"PlatformConstants.js",
"PlatformConstants.web.js",
"react-native-gesture-handler.d.ts",
"README.md",
"RNGestureHandlerModule.js",
"RNGestureHandlerModule.web.js",
"jestSetup.js",
"RNGestureHandler.podspec",
"State.js",
"Swipeable.js"
],
"repository": {
"type": "git",
"url": "git+https://github.com/software-mansion/react-native-gesture-handler.git"
},
"author": {
"email": "krzys.magiera@gmail.com",
"name": "Krzysztof Magiera"
},
"license": "MIT",
"readmeFilename": "README.md",
"bugs": {
"url": "https://github.com/software-mansion/react-native-gesture-handler/issues"
},
"homepage": "https://github.com/software-mansion/react-native-gesture-handler#readme",
"dependencies": {
"@egjs/hammerjs": "^2.0.17",
"hoist-non-react-statics": "^3.3.0",
"invariant": "^2.2.4",
"prop-types": "^15.7.2"
},
"jest": {
"preset": "jest-react-native"
},
"devDependencies": {
"@expo/webpack-config": "^0.10.4",
"@types/react": "^16.8.6",
"@types/react-native": "^0.60.0",
"babel-jest": "16.0.0",
"babel-preset-expo": "^7.1.0",
"expo": "^35.0.1",
"flow-bin": "^0.98.0",
"husky": "^0.14.3",
"jest": "^24.7.1",
"jest-react-native": "16.0.0",
"lint-staged": "^10.2.11",
"prettier": "^1.13.7",
"react": "^16.8.6",
"react-dom": "^16.12.0",
"react-native": "^0.60.0",
"react-native-web": "^0.11.7",
"react-test-renderer": "16.8.6",
"release-it": "^13.6.5"
},
"lint-staged": {
"*.js": [
"prettier --write"
]
},
"release-it": {
"hooks": {
"before:git:bump": [
"# check if version corresponds to changes in native files \n git diff --name-only ${latestVersion} HEAD | egrep \"(android/.*)|(ios/.*)\" -q && egrep '\\.0$' -q <<< ${version}"
]
}
}
}

View File

@ -0,0 +1,585 @@
// Project: https://github.com/software-mansion/react-native-gesture-handler
// TypeScript Version: 2.6.2
declare module 'react-native-gesture-handler' {
import * as React from 'react';
import {
Animated,
FlatListProperties,
ScrollViewProperties,
SwitchProperties,
TextInputProperties,
DrawerLayoutAndroidProperties,
TouchableHighlightProperties,
TouchableOpacityProperties,
TouchableNativeFeedbackProperties,
TouchableWithoutFeedbackProperties,
Insets,
ViewStyle,
StyleProp,
ViewProps,
} from 'react-native';
/* GESTURE HANDLER STATE */
export enum Directions {
RIGHT = 1,
LEFT = 2,
UP = 4,
DOWN = 8,
}
export enum State {
UNDETERMINED = 0,
FAILED,
BEGAN,
CANCELLED,
ACTIVE,
END,
}
/* STATE CHANGE EVENTS */
export interface GestureHandlerGestureEventNativeEvent {
handlerTag: number;
numberOfPointers: number;
state: State;
}
export interface GestureHandlerStateChangeNativeEvent {
handlerTag: number;
numberOfPointers: number;
state: State;
oldState: State;
}
export interface GestureHandlerStateChangeEvent {
nativeEvent: GestureHandlerStateChangeNativeEvent;
}
export interface GestureHandlerGestureEvent {
nativeEvent: GestureHandlerGestureEventNativeEvent;
}
interface NativeViewGestureHandlerEventExtra {
pointerInside: boolean;
}
export interface NativeViewGestureHandlerStateChangeEvent
extends GestureHandlerStateChangeEvent {
nativeEvent: GestureHandlerStateChangeNativeEvent &
NativeViewGestureHandlerEventExtra;
}
export interface NativeViewGestureHandlerGestureEvent
extends GestureHandlerGestureEvent {
nativeEvent: GestureHandlerGestureEventNativeEvent &
NativeViewGestureHandlerEventExtra;
}
interface TapGestureHandlerEventExtra {
x: number;
y: number;
absoluteX: number;
absoluteY: number;
}
interface ForceTouchGestureHandlerEventExtra {
x: number;
y: number;
absoluteX: number;
absoluteY: number;
force: number;
}
export interface TapGestureHandlerStateChangeEvent
extends GestureHandlerStateChangeEvent {
nativeEvent: GestureHandlerStateChangeNativeEvent &
TapGestureHandlerEventExtra;
}
export interface TapGestureHandlerGestureEvent
extends GestureHandlerGestureEvent {
nativeEvent: GestureHandlerGestureEventNativeEvent &
TapGestureHandlerEventExtra;
}
export interface ForceTouchGestureHandlerGestureEvent
extends GestureHandlerGestureEvent {
nativeEvent: GestureHandlerGestureEventNativeEvent &
ForceTouchGestureHandlerEventExtra;
}
export interface LongPressGestureHandlerStateChangeEvent
extends GestureHandlerStateChangeEvent {
nativeEvent: GestureHandlerStateChangeNativeEvent &
LongPressGestureHandlerEventExtra;
}
export interface ForceTouchGestureHandlerStateChangeEvent
extends GestureHandlerStateChangeEvent {
nativeEvent: GestureHandlerStateChangeNativeEvent &
ForceTouchGestureHandlerEventExtra;
}
interface LongPressGestureHandlerEventExtra {
x: number;
y: number;
absoluteX: number;
absoluteY: number;
}
export interface LongPressGestureHandlerGestureEvent
extends GestureHandlerGestureEvent {
nativeEvent: GestureHandlerGestureEventNativeEvent &
LongPressGestureHandlerEventExtra;
}
interface PanGestureHandlerEventExtra {
x: number;
y: number;
absoluteX: number;
absoluteY: number;
translationX: number;
translationY: number;
velocityX: number;
velocityY: number;
}
export interface PanGestureHandlerStateChangeEvent
extends GestureHandlerStateChangeEvent {
nativeEvent: GestureHandlerStateChangeNativeEvent &
PanGestureHandlerEventExtra;
}
export interface PanGestureHandlerGestureEvent
extends GestureHandlerGestureEvent {
nativeEvent: GestureHandlerGestureEventNativeEvent &
PanGestureHandlerEventExtra;
}
interface PinchGestureHandlerEventExtra {
scale: number;
focalX: number;
focalY: number;
velocity: number;
}
export interface PinchGestureHandlerStateChangeEvent
extends GestureHandlerStateChangeEvent {
nativeEvent: GestureHandlerStateChangeNativeEvent &
PinchGestureHandlerEventExtra;
}
export interface PinchGestureHandlerGestureEvent
extends GestureHandlerGestureEvent {
nativeEvent: GestureHandlerGestureEventNativeEvent &
PinchGestureHandlerEventExtra;
}
interface RotationGestureHandlerEventExtra {
rotation: number;
anchorX: number;
anchorY: number;
velocity: number;
}
export interface RotationGestureHandlerStateChangeEvent
extends GestureHandlerStateChangeEvent {
nativeEvent: GestureHandlerStateChangeNativeEvent &
RotationGestureHandlerEventExtra;
}
export interface RotationGestureHandlerGestureEvent
extends GestureHandlerGestureEvent {
nativeEvent: GestureHandlerGestureEventNativeEvent &
RotationGestureHandlerEventExtra;
}
export interface FlingGestureHandlerStateChangeEvent
extends GestureHandlerStateChangeEvent {
nativeEvent: GestureHandlerStateChangeNativeEvent &
FlingGestureHandlerEventExtra;
}
export interface FlingGestureHandlerGestureEvent
extends GestureHandlerGestureEvent {
nativeEvent: GestureHandlerGestureEventNativeEvent;
}
interface FlingGestureHandlerEventExtra {
x: number;
y: number;
absoluteX: number;
absoluteY: number;
}
/* GESTURE HANDLERS PROPERTIES */
export interface GestureHandlerProperties {
id?: string;
enabled?: boolean;
waitFor?: React.Ref<any> | React.Ref<any>[];
simultaneousHandlers?: React.Ref<any> | React.Ref<any>[];
shouldCancelWhenOutside?: boolean;
hitSlop?:
| number
| {
left?: number;
right?: number;
top?: number;
bottom?: number;
vertical?: number;
horizontal?: number;
}
| {
width: number;
left: number;
}
| {
width: number;
right: number;
}
| {
height: number;
top: number;
}
| {
height: number;
bottom: number;
};
}
export interface NativeViewGestureHandlerProperties
extends GestureHandlerProperties {
shouldActivateOnStart?: boolean;
disallowInterruption?: boolean;
onGestureEvent?: (event: NativeViewGestureHandlerGestureEvent) => void;
onHandlerStateChange?: (
event: NativeViewGestureHandlerStateChangeEvent
) => void;
}
export interface TapGestureHandlerProperties extends GestureHandlerProperties {
minPointers?: number;
maxDurationMs?: number;
maxDelayMs?: number;
numberOfTaps?: number;
maxDeltaX?: number;
maxDeltaY?: number;
maxDist?: number;
onGestureEvent?: (event: TapGestureHandlerGestureEvent) => void;
onHandlerStateChange?: (event: TapGestureHandlerStateChangeEvent) => void;
}
export interface ForceTouchGestureHandlerProperties extends GestureHandlerProperties {
minForce?: number,
maxForce?: number,
feedbackOnActivation?: boolean,
onGestureEvent?: (event: ForceTouchGestureHandlerGestureEvent) => void;
onHandlerStateChange?: (event: ForceTouchGestureHandlerStateChangeEvent) => void;
}
export interface LongPressGestureHandlerProperties
extends GestureHandlerProperties {
minDurationMs?: number;
maxDist?: number;
onGestureEvent?: (event: LongPressGestureHandlerGestureEvent) => void;
onHandlerStateChange?: (event: LongPressGestureHandlerStateChangeEvent) => void;
}
export interface PanGestureHandlerProperties extends GestureHandlerProperties {
/** @deprecated use activeOffsetX*/
minDeltaX?: number;
/** @deprecated use activeOffsetY*/
minDeltaY?: number;
/** @deprecated use failOffsetX*/
maxDeltaX?: number;
/** @deprecated use failOffsetY*/
maxDeltaY?: number;
/** @deprecated use activeOffsetX*/
minOffsetX?: number;
/** @deprecated use failOffsetY*/
minOffsetY?: number;
activeOffsetY?: number | number[];
activeOffsetX?: number | number[];
failOffsetY?: number | number[];
failOffsetX?: number | number[];
minDist?: number;
minVelocity?: number;
minVelocityX?: number;
minVelocityY?: number;
minPointers?: number;
maxPointers?: number;
avgTouches?: boolean;
onGestureEvent?: (event: PanGestureHandlerGestureEvent) => void;
onHandlerStateChange?: (event: PanGestureHandlerStateChangeEvent) => void;
}
export interface PinchGestureHandlerProperties
extends GestureHandlerProperties {
onGestureEvent?: (event: PinchGestureHandlerGestureEvent) => void;
onHandlerStateChange?: (event: PinchGestureHandlerStateChangeEvent) => void;
}
export interface RotationGestureHandlerProperties
extends GestureHandlerProperties {
onGestureEvent?: (event: RotationGestureHandlerGestureEvent) => void;
onHandlerStateChange?: (
event: RotationGestureHandlerStateChangeEvent
) => void;
}
export interface FlingGestureHandlerProperties
extends GestureHandlerProperties {
direction?: number;
numberOfPointers?: number;
onGestureEvent?: (event: FlingGestureHandlerGestureEvent) => void;
onHandlerStateChange?: (event: FlingGestureHandlerStateChangeEvent) => void;
}
/* GESTURE HANDLERS CLASSES */
export class NativeViewGestureHandler extends React.Component<
NativeViewGestureHandlerProperties
> {}
export class TapGestureHandler extends React.Component<
TapGestureHandlerProperties
> {}
export class LongPressGestureHandler extends React.Component<
LongPressGestureHandlerProperties
> {}
export class PanGestureHandler extends React.Component<
PanGestureHandlerProperties
> {}
export class PinchGestureHandler extends React.Component<
PinchGestureHandlerProperties
> {}
export class RotationGestureHandler extends React.Component<
RotationGestureHandlerProperties
> {}
export class FlingGestureHandler extends React.Component<
FlingGestureHandlerProperties
> {}
export class ForceTouchGestureHandler extends React.Component<
ForceTouchGestureHandlerProperties
> {}
/* BUTTONS PROPERTIES */
export interface RawButtonProperties
extends NativeViewGestureHandlerProperties {
exclusive?: boolean;
testID?: string;
accessibilityLabel?: string;
}
export interface BaseButtonProperties extends RawButtonProperties {
onPress?: (pointerInside: boolean) => void;
onActiveStateChange?: (active: boolean) => void;
style?: StyleProp<ViewStyle>;
rippleColor?: string;
}
export interface RectButtonProperties extends BaseButtonProperties {
underlayColor?: string;
activeOpacity?: number;
}
export interface BorderlessButtonProperties extends BaseButtonProperties {
borderless?: boolean;
activeOpacity?: number;
}
/* BUTTONS CLASSES */
export class RawButton extends React.Component<RawButtonProperties> {}
export class BaseButton extends React.Component<BaseButtonProperties> {}
export class RectButton extends React.Component<RectButtonProperties> {}
export class BorderlessButton extends React.Component<
BorderlessButtonProperties
> {}
export interface ContainedTouchableProperties {
containerStyle?: StyleProp<ViewStyle>
}
export class TouchableHighlight extends React.Component<
TouchableHighlightProperties | ContainedTouchableProperties
> {}
export class TouchableNativeFeedback extends React.Component<
TouchableNativeFeedbackProperties | ContainedTouchableProperties
> {}
export class TouchableOpacity extends React.Component<
TouchableOpacityProperties | ContainedTouchableProperties
> {}
export class TouchableWithoutFeedback extends React.Component<
TouchableWithoutFeedbackProperties | ContainedTouchableProperties
> {}
/* GESTURE HANDLER WRAPPED CLASSES */
export class ScrollView extends React.Component<
NativeViewGestureHandlerProperties & ScrollViewProperties
> {
scrollTo(y?: number | { x?: number; y?: number; animated?: boolean }, x?: number, animated?: boolean): void;
scrollToEnd(options?: { animated: boolean }): void;
}
export class Switch extends React.Component<
NativeViewGestureHandlerProperties & SwitchProperties
> {}
export class TextInput extends React.Component<
NativeViewGestureHandlerProperties & TextInputProperties
> {}
export class DrawerLayoutAndroid extends React.Component<
NativeViewGestureHandlerProperties & DrawerLayoutAndroidProperties
> {}
/* OTHER */
export class FlatList<ItemT> extends React.Component<
NativeViewGestureHandlerProperties & FlatListProperties<ItemT>
> {
scrollToEnd: (params?: { animated?: boolean }) => void;
scrollToIndex: (params: { animated?: boolean; index: number; viewOffset?: number; viewPosition?: number }) => void;
scrollToItem: (params: { animated?: boolean; item: ItemT; viewPosition?: number }) => void;
scrollToOffset: (params: { animated?: boolean; offset: number }) => void;
}
export const GestureHandlerRootView: React.ComponentType<ViewProps>;
export function gestureHandlerRootHOC<P = {}>(
Component: React.ComponentType<P>,
containerStyles?: StyleProp<ViewStyle>
): React.ComponentType<P>;
export function createNativeWrapper<P = {}>(
Component: React.ComponentType<P>,
config: NativeViewGestureHandlerProperties
): React.ComponentType<P>;
}
declare module 'react-native-gesture-handler/Swipeable' {
import { Animated, StyleProp, ViewStyle } from 'react-native';
import { PanGestureHandlerProperties } from 'react-native-gesture-handler'
type SwipeableExcludes = Exclude<keyof PanGestureHandlerProperties, 'onGestureEvent' | 'onHandlerStateChange'>
interface SwipeableProperties extends Pick<PanGestureHandlerProperties, SwipeableExcludes> {
friction?: number;
leftThreshold?: number;
rightThreshold?: number;
overshootLeft?: boolean;
overshootRight?: boolean;
overshootFriction?: number,
onSwipeableLeftOpen?: () => void;
onSwipeableRightOpen?: () => void;
onSwipeableOpen?: () => void;
onSwipeableClose?: () => void;
onSwipeableLeftWillOpen?: () => void;
onSwipeableRightWillOpen?: () => void;
onSwipeableWillOpen?: () => void;
onSwipeableWillClose?: () => void;
/**
*
* This map describes the values to use as inputRange for extra interpolation:
* AnimatedValue: [startValue, endValue]
*
* progressAnimatedValue: [0, 1]
* dragAnimatedValue: [0, +]
*
* To support `rtl` flexbox layouts use `flexDirection` styling.
* */
renderLeftActions?: (
progressAnimatedValue: Animated.AnimatedInterpolation,
dragAnimatedValue: Animated.AnimatedInterpolation
) => React.ReactNode;
/**
*
* This map describes the values to use as inputRange for extra interpolation:
* AnimatedValue: [startValue, endValue]
*
* progressAnimatedValue: [0, 1]
* dragAnimatedValue: [0, -]
*
* To support `rtl` flexbox layouts use `flexDirection` styling.
* */
renderRightActions?: (
progressAnimatedValue: Animated.AnimatedInterpolation,
dragAnimatedValue: Animated.AnimatedInterpolation
) => React.ReactNode;
useNativeAnimations?: boolean;
containerStyle?: StyleProp<ViewStyle>;
childrenContainerStyle?: StyleProp<ViewStyle>;
}
export default class Swipeable extends React.Component<SwipeableProperties> {
close: () => void;
openLeft: () => void;
openRight: () => void;
}
}
declare module 'react-native-gesture-handler/DrawerLayout' {
import { Animated, StatusBarAnimation, StyleProp, ViewStyle } from 'react-native';
export type DrawerPosition = 'left' | 'right';
export type DrawerState = 'Idle' | 'Dragging' | 'Settling';
export type DrawerType = 'front' | 'back' | 'slide';
export type DrawerLockMode = 'unlocked' | 'locked-closed' | 'locked-open';
export type DrawerKeyboardDismissMode = 'none' | 'on-drag';
export interface DrawerLayoutProperties {
renderNavigationView: (
progressAnimatedValue: Animated.Value
) => React.ReactNode;
drawerPosition?: DrawerPosition;
drawerWidth?: number;
drawerBackgroundColor?: string;
drawerLockMode?: DrawerLockMode;
keyboardDismissMode?: DrawerKeyboardDismissMode;
onDrawerClose?: () => void;
onDrawerOpen?: () => void;
onDrawerStateChanged?: (
newState: DrawerState,
drawerWillShow: boolean
) => void;
useNativeAnimations?: boolean;
drawerType?: DrawerType;
edgeWidth?: number;
minSwipeDistance?: number;
hideStatusBar?: boolean;
statusBarAnimation?: StatusBarAnimation;
overlayColor?: string;
contentContainerStyle?: StyleProp<ViewStyle>;
}
interface DrawerMovementOptionType {
velocity?: number;
}
export default class DrawerLayout extends React.Component<DrawerLayoutProperties> {
openDrawer: (options?: DrawerMovementOptionType) => void;
closeDrawer: (options?: DrawerMovementOptionType) => void;
}
}

Some files were not shown because too many files have changed in this diff Show More