question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Ugly Android performance with PanResponder

See original GitHub issue

Try to zoom — urgh!

import React from 'react'
import {
  Dimensions,
  PanResponder,
  Animated,
} from 'react-native'
import { Svg, Rect, G } from 'react-native-svg'

const { width, height } = Dimensions.get('window')
const GAnimated = Animated.createAnimatedComponent(G)

// utils beginning
function calcDistance(x1, y1, x2, y2) {
  const dx = x1 - x2
  const dy = y1 - y2
  return Math.sqrt(dx * dx + dy * dy)
}
const range = (min, max) =>
  Math.floor(Math.random() * (
    max - min + 1
  )) + min
const genPoly = (count) => {
  return Array.from({ length: count }).map((_, i) => {
    const x = range(20, 320)
    const y = range(170, 470)
    const radius = range(10, 30)

    return <Rect
      onPress={() => alert(i)}
      x={x}
      y={y}
      width={radius}
      height={radius}
      fill={`rgba(${range(0, 255)}, ${range(0, 255)}, ${range(0, 255)}, 0.2)`}
      strokeWidth={1}
      stroke='black'
      key={i}
      style={{
        zIndex: 100,
      }}
    />
  })
}
const polygons = genPoly(100)
// utils ending

function ZoomProblem() {
  const zoom = new Animated.Value(1)
  const position = new Animated.ValueXY()
  let pinchDistanceInit = 0
  let zoomInit = zoom.__getValue()
  const panResponder = PanResponder.create({
    onStartShouldSetPanResponder: (evt, gestureState) => false,
    onStartShouldSetPanResponderCapture: (evt, gestureState) => false,
    onMoveShouldSetPanResponder: (evt, gestureState) => true,
    onMoveShouldSetPanResponderCapture: (evt, gestureState) => false,
    onPanResponderTerminationRequest: (evt, gestureState) => true,
    onPanResponderTerminate: (evt, gestureState) => {},
    onPanResponderGrant: (evt) => {
      if (evt.nativeEvent.touches.length === 2) {
        const [
          { locationX: x1, locationY: y1 },
          { locationX: x2, locationY: y2 },
        ] = evt.nativeEvent.touches

        pinchDistanceInit = calcDistance(x1, y1, x2, y2)
        zoomInit = zoom.__getValue()
      }

      position.setOffset(position.__getValue()) // prevents offset from resetting
    },
    onPanResponderMove: (evt, gesture) => {
      switch (gesture.numberActiveTouches) {
        case 1: {
          position.setValue({ x: gesture.dx, y: gesture.dy })
          break
        }
        case 2: {
          const [
            { locationX: x1, locationY: y1 },
            { locationX: x2, locationY: y2 },
          ] = evt.nativeEvent.touches
          const distance = calcDistance(x1, y1, x2, y2)

          Animated.timing(
            zoom,
            {
              toValue: zoomInit * (distance / pinchDistanceInit),
              duration: 0,
              useNativeDriver: true,
            },
          ).start()
          break
        }
      }
    },
  })
  const { top, left } = position.getLayout()

  return (
    <Animated.View
      {...panResponder.panHandlers}
      style={{
        transform: [
          { translateX: left },
          { translateY: top },
        ],
      }}
    >
      <Svg
        width={width}
        height={height}
      >
        <GAnimated
          style={{
            transform: [{ scale: zoom }],
          }}
        >
          {polygons}
        </GAnimated>
      </Svg>
    </Animated.View>
  )
}

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Comments:11

github_iconTop GitHub Comments

1reaction
msandcommented, Aug 5, 2019

Slightly more advanced viewer example, adapted to svg and slightly stripped down version from: https://github.com/kmagiera/react-native-gesture-handler/issues/546#issuecomment-481341466 https://github.com/Ashoat/squadcal/blob/master/native/media/multimedia-modal.react.js

import React, { memo } from 'react';
import { StyleSheet, Dimensions, View, Platform, Text, TouchableOpacity } from 'react-native';
import { Svg, Rect, G } from 'react-native-svg';
import Animated, { Easing } from 'react-native-reanimated';
import {
  State,
  PanGestureHandler,
  PinchGestureHandler,
  TapGestureHandler,
} from 'react-native-gesture-handler';

const android = Platform.OS === 'android';
const { width, height } = Dimensions.get('window');
const AnimatedG = Animated.createAnimatedComponent(G);

const {
  Value,
  Clock,
  event,
  Extrapolate,
  set,
  call,
  cond,
  not,
  and,
  or,
  eq,
  neq,
  greaterThan,
  add,
  sub,
  multiply,
  divide,
  pow,
  max,
  min,
  round,
  abs,
  interpolate,
  startClock,
  stopClock,
  clockRunning,
  timing,
  decay,
  diffClamp
} = Animated;

function clamp(value: Value, minValue: Value, maxValue: Value): Value {
  return cond(
    greaterThan(value, maxValue),
    maxValue,
    cond(
      greaterThan(minValue, value),
      minValue,
      value,
    ),
  );
}

function scaleDelta(value: Value, gestureActive: Value) {
  const diffThisFrame = new Value(1);
  const prevValue = new Value(1);
  return cond(
    gestureActive,
    [
      set(diffThisFrame, divide(value, prevValue)),
      set(prevValue, value),
      diffThisFrame,
    ],
    set(prevValue, 1),
  );
}

function panDelta(value: Value, gestureActive: Value) {
  const diffThisFrame = new Value(0);
  const prevValue = new Value(0);
  return cond(
    gestureActive,
    [
      set(diffThisFrame, sub(value, prevValue)),
      set(prevValue, value),
      diffThisFrame,
    ],
    set(prevValue, 0),
  );
}

function gestureJustEnded(tapState: Value) {
  const prevValue = new Value(-1);
  return cond(
    eq(prevValue, tapState),
    0,
    [
      set(prevValue, tapState),
      eq(tapState, State.END),
    ],
  );
}

function runTiming(
  clock: Clock,
  initialValue: Value | number,
  finalValue: Value | number,
  startStopClock: bool = true,
): Value {
  const state = {
    finished: new Value(0),
    position: new Value(0),
    frameTime: new Value(0),
    time: new Value(0),
  };
  const config = {
    toValue: new Value(0),
    duration: 250,
    easing: Easing.out(Easing.ease),
  };
  return [
    cond(
      not(clockRunning(clock)),
      [
        set(state.finished, 0),
        set(state.frameTime, 0),
        set(state.time, 0),
        set(state.position, initialValue),
        set(config.toValue, finalValue),
        startStopClock && startClock(clock),
      ],
    ),
    timing(clock, state, config),
    cond(
      state.finished,
      startStopClock && stopClock(clock),
    ),
    state.position,
  ];
}

function runDecay(
  clock: Clock,
  velocity: Value,
  initialPosition: Value,
  startStopClock: bool = true,
): Value {
  const state = {
    finished: new Value(0),
    velocity: new Value(0),
    position: new Value(0),
    time: new Value(0),
  };
  const config = { deceleration: 0.99 };
  return [
    cond(
      not(clockRunning(clock)),
      [
        set(state.finished, 0),
        set(state.velocity, velocity),
        set(state.position, initialPosition),
        set(state.time, 0),
        startStopClock && startClock(clock),
      ],
    ),
    decay(clock, state, config),
    set(velocity, state.velocity),
    cond(
      state.finished,
      startStopClock && stopClock(clock),
    ),
    state.position,
  ];
}

class MultimediaModal extends React.PureComponent {

  centerX = new Value(0);
  centerY = new Value(0);
  screenWidth = new Value(0);
  screenHeight = new Value(0);
  imageWidth = new Value(0);
  imageHeight = new Value(0);

  pinchHandler = React.createRef();
  panHandler = React.createRef();
  tapHandler = React.createRef();
  handlerRefs = [ this.pinchHandler, this.panHandler, this.tapHandler ];
  priorityHandlerRefs = [ this.pinchHandler, this.panHandler ];

  pinchEvent;
  panEvent;
  tapEvent;

  scale: Value;
  x: Value;
  y: Value;
  opacity: Value;
  imageContainerOpacity: Value;

  constructor(props: Props) {
    super(props);
    this.updateDimensions();

    const { screenWidth, screenHeight, imageWidth, imageHeight } = this;
    const left = sub(this.centerX, divide(imageWidth, 2));
    const top = sub(this.centerY, divide(imageHeight, 2));

    const initialCoordinates = { x: 0, y: 0, width, height };
    const initialScale = divide(
      initialCoordinates.width,
      imageWidth,
    );
    const initialTranslateX = sub(
      initialCoordinates.x + initialCoordinates.width / 2,
      add(left, divide(imageWidth, 2)),
    );
    const initialTranslateY = sub(
      initialCoordinates.y + initialCoordinates.height / 2,
      add(top, divide(imageHeight, 2)),
    );

    const position = new Value(1);
    const progress = interpolate(
      position,
      {
        inputRange: [ 0, 1 ],
        outputRange: [ 0, 1 ],
        extrapolate: Extrapolate.CLAMP,
      },
    );

    // The inputs we receive from PanGestureHandler
    const panState = new Value(-1);
    const panTranslationX = new Value(0);
    const panTranslationY = new Value(0);
    const panVelocityX = new Value(0);
    const panVelocityY = new Value(0);
    this.panEvent = event([{
      nativeEvent: {
        state: panState,
        translationX: panTranslationX,
        translationY: panTranslationY,
        velocityX: panVelocityX,
        velocityY: panVelocityY,
      },
    }]);
    const panActive = eq(panState, State.ACTIVE);

    // The inputs we receive from PinchGestureHandler
    const pinchState = new Value(-1);
    const pinchScale = new Value(1);
    const pinchFocalX = new Value(0);
    const pinchFocalY = new Value(0);
    this.pinchEvent = event([{
      nativeEvent: {
        state: pinchState,
        scale: pinchScale,
        focalX: pinchFocalX,
        focalY: pinchFocalY,
      },
    }]);
    const pinchActive = eq(pinchState, State.ACTIVE);

    // The inputs we receive from TapGestureHandler
    const tapState = new Value(-1);
    const tapX = new Value(0);
    const tapY = new Value(0);
    this.tapEvent = event([{
      nativeEvent: {
        state: tapState,
        x: tapX,
        y: tapY,
      },
    }]);

    // The all-important outputs
    const curScale = new Value(1);
    const curX = new Value(0);
    const curY = new Value(0);
    const curOpacity = new Value(1);

    // The centered variables help us know if we need to be recentered
    const recenteredScale = max(curScale, 1);
    const horizontalPanSpace = this.horizontalPanSpace(recenteredScale);
    const verticalPanSpace = this.verticalPanSpace(recenteredScale);

    const resetXClock = new Clock();
    const resetYClock = new Clock();
    const zoomClock = new Clock();

    const dismissingFromPan = new Value(0);

    const roundedCurScale = divide(round(multiply(curScale, 1000)), 1000);
    const gestureActive = or(pinchActive, panActive);
    const activeInteraction = or(
      gestureActive,
      clockRunning(zoomClock),
      dismissingFromPan,
    );

    const updates = [
      this.pinchUpdate(
        pinchActive,
        pinchScale,
        pinchFocalX,
        pinchFocalY,
        curScale,
        curX,
        curY,
      ),
      this.panUpdate(
        panActive,
        panTranslationX,
        panTranslationY,
        curX,
        curY,
      ),
      this.doubleTapUpdate(
        tapState,
        tapX,
        tapY,
        roundedCurScale,
        zoomClock,
        gestureActive,
        curScale,
        curX,
        curY,
      ),
      this.opacityUpdate(
        panState,
        pinchActive,
        panVelocityX,
        panVelocityY,
        curX,
        curY,
        roundedCurScale,
        curOpacity,
        dismissingFromPan,
      ),
      this.recenter(
        resetXClock,
        resetYClock,
        activeInteraction,
        recenteredScale,
        horizontalPanSpace,
        verticalPanSpace,
        curScale,
        curX,
        curY,
      ),
      this.flingUpdate(
        resetXClock,
        resetYClock,
        activeInteraction,
        panVelocityX,
        panVelocityY,
        horizontalPanSpace,
        verticalPanSpace,
        curX,
        curY,
      ),
    ];
    const updatedScale = [ updates, curScale ];
    const updatedCurX = [ updates, curX ];
    const updatedCurY = [ updates, curY ];
    const updatedOpacity = [ updates, curOpacity ];

    const reverseProgress = sub(1, progress);
    this.scale = add(
      multiply(reverseProgress, initialScale),
      multiply(progress, updatedScale),
    );
    this.x = add(
      multiply(reverseProgress, initialTranslateX),
      multiply(progress, updatedCurX),
    );
    this.y = add(
      multiply(reverseProgress, initialTranslateY),
      multiply(progress, updatedCurY),
    );
    this.opacity = multiply(progress, updatedOpacity);
    this.imageContainerOpacity = interpolate(
      progress,
      {
        inputRange: [ 0, 0.1 ],
        outputRange: [ 0, 1 ],
        extrapolate: Extrapolate.CLAMP,
      },
    );
  }

  // How much space do we have to pan the image horizontally?
  horizontalPanSpace(scale: Value) {
    const apparentWidth = multiply(this.imageWidth, scale);
    const horizPop = divide(
      sub(apparentWidth, this.screenWidth),
      2,
    );
    return max(horizPop, 0);
  }

  // How much space do we have to pan the image vertically?
  verticalPanSpace(scale: Value) {
    const apparentHeight = multiply(this.imageHeight, scale);
    const vertPop = divide(
      sub(apparentHeight, this.screenHeight),
      2,
    );
    return max(vertPop, 0);
  }

  pinchUpdate(
    // Inputs
    pinchActive: Value,
    pinchScale: Value,
    pinchFocalX: Value,
    pinchFocalY: Value,
    // Outputs
    curScale: Value,
    curX: Value,
    curY: Value,
  ): Value {
    const deltaScale = scaleDelta(pinchScale, pinchActive);
    const deltaPinchX = multiply(
      sub(1, deltaScale),
      sub(
        pinchFocalX,
        curX,
        this.centerX,
      ),
    );
    const deltaPinchY = multiply(
      sub(1, deltaScale),
      sub(
        pinchFocalY,
        curY,
        this.centerY,
      ),
    );

    return cond(
      [ deltaScale, pinchActive ],
      [
        set(curX, add(curX, deltaPinchX)),
        set(curY, add(curY, deltaPinchY)),
        set(curScale, multiply(curScale, deltaScale)),
      ],
    );
  }

  panUpdate(
    // Inputs
    panActive: Value,
    panTranslationX: Value,
    panTranslationY: Value,
    // Outputs
    curX: Value,
    curY: Value,
  ): Value {
    const deltaX = panDelta(panTranslationX, panActive);
    const deltaY = panDelta(panTranslationY, panActive);
    return cond(
      [ deltaX, deltaY, panActive ],
      [
        set(curX, add(curX, deltaX)),
        set(curY, add(curY, deltaY)),
      ],
    );
  }

  doubleTapUpdate(
    // Inputs
    tapState: Value,
    tapX: Value,
    tapY: Value,
    roundedCurScale: Value,
    zoomClock: Clock,
    gestureActive: Value,
    // Outputs
    curScale: Value,
    curX: Value,
    curY: Value,
  ): Value {
    const zoomClockRunning = clockRunning(zoomClock);
    const zoomActive = and(not(gestureActive), zoomClockRunning);
    const targetScale = cond(greaterThan(roundedCurScale, 1), 1, 3);

    const tapXDiff = sub(tapX, this.centerX, curX);
    const tapYDiff = sub(tapY, this.centerY, curY);
    const tapXPercent = divide(tapXDiff, this.imageWidth, curScale);
    const tapYPercent = divide(tapYDiff, this.imageHeight, curScale);

    const horizPanSpace = this.horizontalPanSpace(targetScale);
    const vertPanSpace = this.verticalPanSpace(targetScale);
    const horizPanPercent = divide(horizPanSpace, this.imageWidth, targetScale);
    const vertPanPercent = divide(vertPanSpace, this.imageHeight, targetScale);

    const tapXPercentClamped = clamp(
      tapXPercent,
      multiply(-1, horizPanPercent),
      horizPanPercent,
    );
    const tapYPercentClamped = clamp(
      tapYPercent,
      multiply(-1, vertPanPercent),
      vertPanPercent,
    );
    const targetX = multiply(tapXPercentClamped, this.imageWidth, targetScale);
    const targetY = multiply(tapYPercentClamped, this.imageHeight, targetScale);

    const targetRelativeScale = divide(targetScale, curScale);
    const targetRelativeX = multiply(-1, add(targetX, curX));
    const targetRelativeY = multiply(-1, add(targetY, curY));

    const zoomScale = runTiming(zoomClock, 1, targetRelativeScale);
    const zoomX = runTiming(zoomClock, 0, targetRelativeX, false);
    const zoomY = runTiming(zoomClock, 0, targetRelativeY, false);

    const deltaScale = scaleDelta(zoomScale, zoomActive);
    const deltaX = panDelta(zoomX, zoomActive);
    const deltaY = panDelta(zoomY, zoomActive);

    const tapJustEnded = gestureJustEnded(tapState);

    return cond(
      [ tapJustEnded, deltaX, deltaY, deltaScale, gestureActive ],
      stopClock(zoomClock),
      cond(
        or(zoomClockRunning, tapJustEnded),
        [
          zoomX,
          zoomY,
          zoomScale,
          set(curX, add(curX, deltaX)),
          set(curY, add(curY, deltaY)),
          set(curScale, multiply(curScale, deltaScale)),
        ],
      ),
    );
  }

  opacityUpdate(
    // Inputs
    panState: Value,
    pinchActive: Value,
    panVelocityX: Value,
    panVelocityY: Value,
    curX: Value,
    curY: Value,
    roundedCurScale: Value,
    // Outputs
    curOpacity: Value,
    dismissingFromPan: Value,
  ): Value {
    const progressiveOpacity = max(
      min(
        sub(1, abs(divide(curX, this.screenWidth))),
        sub(1, abs(divide(curY, this.screenHeight))),
      ),
      0,
    );
    const panJustEnded = gestureJustEnded(panState);

    const resetClock = new Clock();

    const velocity = pow(add(pow(panVelocityX, 2), pow(panVelocityY, 2)), 0.5);
    const shouldGoBack = and(
      panJustEnded,
      or(
        greaterThan(velocity, 50),
        greaterThan(0.7, progressiveOpacity),
      ),
    );

    const decayClock = new Clock();
    const decay = [
      set(curX, runDecay(decayClock, panVelocityX, curX, false)),
      set(curY, runDecay(decayClock, panVelocityY, curY)),
    ];

    return cond(
      [ panJustEnded, dismissingFromPan ],
      decay,
      cond(
        or(pinchActive, greaterThan(roundedCurScale, 1)),
        set(curOpacity, runTiming(resetClock, curOpacity, 1)),
        [
          stopClock(resetClock),
          set(curOpacity, progressiveOpacity),
          set(dismissingFromPan, shouldGoBack),
          cond(
            shouldGoBack,
            [
              decay,
              call([], this.close),
            ],
          ),
        ],
      ),
    );
  }

  recenter(
    // Inputs
    resetXClock: Clock,
    resetYClock: Clock,
    activeInteraction: Value,
    recenteredScale: Value,
    horizontalPanSpace: Value,
    verticalPanSpace: Value,
    // Outputs
    curScale: Value,
    curX: Value,
    curY: Value,
  ): Value {
    const resetScaleClock = new Clock();

    const recenteredX = clamp(
      curX,
      multiply(-1, horizontalPanSpace),
      horizontalPanSpace,
    );
    const recenteredY = clamp(
      curY,
      multiply(-1, verticalPanSpace),
      verticalPanSpace,
    );

    return cond(
      activeInteraction,
      [
        stopClock(resetScaleClock),
        stopClock(resetXClock),
        stopClock(resetYClock),
      ],
      [
        cond(
          or(
            clockRunning(resetScaleClock),
            neq(recenteredScale, curScale),
          ),
          set(curScale, runTiming(resetScaleClock, curScale, recenteredScale)),
        ),
        cond(
          or(
            clockRunning(resetXClock),
            neq(recenteredX, curX),
          ),
          set(curX, runTiming(resetXClock, curX, recenteredX)),
        ),
        cond(
          or(
            clockRunning(resetYClock),
            neq(recenteredY, curY),
          ),
          set(curY, runTiming(resetYClock, curY, recenteredY)),
        ),
      ],
    );
  }

  flingUpdate(
    // Inputs
    resetXClock: Clock,
    resetYClock: Clock,
    activeInteraction: Value,
    panVelocityX: Value,
    panVelocityY: Value,
    horizontalPanSpace: Value,
    verticalPanSpace: Value,
    // Outputs
    curX: Value,
    curY: Value,
  ): Value {
    const flingXClock = new Clock();
    const flingYClock = new Clock();

    const decayX = runDecay(flingXClock, panVelocityX, curX);
    const recenteredX = clamp(
      decayX,
      multiply(-1, horizontalPanSpace),
      horizontalPanSpace,
    );
    const decayY = runDecay(flingYClock, panVelocityY, curY);
    const recenteredY = clamp(
      decayY,
      multiply(-1, verticalPanSpace),
      verticalPanSpace,
    );

    return cond(
      activeInteraction,
      [
        stopClock(flingXClock),
        stopClock(flingYClock),
      ],
      [
        set(curX, recenteredX),
        set(curY, recenteredY),
        cond(
          or(
            clockRunning(resetXClock),
            neq(decayX, recenteredX),
          ),
          stopClock(flingXClock),
        ),
        cond(
          or(
            clockRunning(resetYClock),
            neq(decayY, recenteredY),
          ),
          stopClock(flingYClock),
        ),
      ],
    );
  }

  updateDimensions() {
    this.screenWidth.setValue(width);
    this.screenHeight.setValue(height);

    this.centerX.setValue(width / 2);
    this.centerY.setValue(height / 2);

    this.imageWidth.setValue(width);
    this.imageHeight.setValue(height);
  }

  componentDidUpdate(prevProps: Props) {
    if (
      this.props.screenDimensions !== prevProps.screenDimensions ||
      this.props.contentVerticalOffset !== prevProps.contentVerticalOffset
    ) {
      this.updateDimensions();
    }
  }

  get screenDimensions(): Dimensions {
    return { width, height }
  }

  get imageDimensions(): Dimensions {
    return { width, height }
  }

  get imageContainerStyle() {
    const { height, width } = this.imageDimensions;
    const { height: screenHeight, width: screenWidth } = this.screenDimensions;
    const top = (screenHeight - height) / 2;
    const left = (screenWidth - width) / 2;
    return {
      height,
      width,
      marginTop: top,
      marginLeft: left,
      opacity: this.imageContainerOpacity,
    };
  }

  static isActive(props) {
    return true;
  }

  get contentContainerStyle() {
    const fullScreenHeight = this.screenDimensions.height;
    const top = 0;
    const bottom = fullScreenHeight;

    // margin will clip, but padding won't
    const verticalStyle = MultimediaModal.isActive(this.props)
      ? { paddingTop: top, paddingBottom: bottom }
      : { marginTop: top, marginBottom: bottom };
    return [ styles.contentContainer, verticalStyle ];
  }

  render() {
    const statusBar = null;
    const backdropStyle = { opacity: this.opacity };
    const closeButtonStyle = {
      opacity: this.opacity,
      top: 4,
    };
    const view = (
      <Animated.View style={styles.container}>
        {statusBar}
        <Animated.View style={[ styles.backdrop, backdropStyle ]} />
        <View style={this.contentContainerStyle}>
          <Animated.View style={this.imageContainerStyle}>
            <Svg
              width={width}
              height={height}
              viewBox={`${origin} ${origin * ratio} ${width} ${width}`}
            >
              <AnimatedG
                style={{
                  transform: [
                    { translateX: this.x },
                    { translateY: this.y },
                    { translateX: oX },
                    { scale: this.scale },
                    { translateX: -oX },
                  ],
                }}
              >
                <G transform={`translate(${origin},${origin})`}>
                  <Rect
                    x={0}
                    y={0}
                    width={width}
                    height={width}
                    fill={fill()}
                  />
                  <Polygons />
                </G>
              </AnimatedG>
            </Svg>
          </Animated.View>
        </View>
        <Animated.View style={[
          styles.closeButtonContainer,
          closeButtonStyle,
        ]}>
          <TouchableOpacity onPress={this.close}>
            <Text style={styles.closeButton}>
              ×
            </Text>
          </TouchableOpacity>
        </Animated.View>
      </Animated.View>
    );
    return (
      <PinchGestureHandler
        onGestureEvent={this.pinchEvent}
        onHandlerStateChange={this.pinchEvent}
        simultaneousHandlers={this.handlerRefs}
        ref={this.pinchHandler}
      >
        <Animated.View style={styles.container}>
          <PanGestureHandler
            onGestureEvent={this.panEvent}
            onHandlerStateChange={this.panEvent}
            simultaneousHandlers={this.handlerRefs}
            ref={this.panHandler}
            avgTouches
          >
            <Animated.View style={styles.container}>
              <TapGestureHandler
                onHandlerStateChange={this.tapEvent}
                simultaneousHandlers={this.handlerRefs}
                ref={this.tapHandler}
                waitFor={this.priorityHandlerRefs}
                numberOfTaps={2}
              >
                {view}
              </TapGestureHandler>
            </Animated.View>
          </PanGestureHandler>
        </Animated.View>
      </PinchGestureHandler>
    );
  }

  close = () => {
  }

}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  backdrop: {
    position: "absolute",
    top: 0,
    bottom: 0,
    left: 0,
    right: 0,
    backgroundColor: "white",
  },
  contentContainer: {
    flex: 1,
    overflow: "hidden",
  },
  closeButtonContainer: {
    position: "absolute",
    right: 4,
  },
  closeButton: {
    paddingTop: 2,
    paddingBottom: 2,
    paddingLeft: 8,
    paddingRight: 8,
    fontSize: 36,
    color: "white",
    textShadowColor: "#000",
    textShadowOffset: { width: 0, height: 1 },
    textShadowRadius: 1,
  },
});

export default MultimediaModal

const xy = 0.9 * width;
const side = 0.1 * width;
const origin = -width / 2;
const ratio = width / height;
const oX = android ? origin : 0;

const pos = () => Math.random() * xy;
const byte = () => Math.floor(Math.random() * 256);
const fill = () => `rgba(${byte()}, ${byte()}, ${byte()}, 0.2)`;

const polygons = Array.from({ length: 200 }).map((_, i) => (
  <Rect
    key={i}
    x={pos()}
    y={pos()}
    width={side}
    height={side}
    fill={fill()}
    stroke="black"
  />
));

const Polygons = memo(() => polygons, () => true);

0reactions
msandcommented, Aug 5, 2019

Hooks instead of components:

import React, { useRef, memo } from 'react';
import { StyleSheet, Dimensions, View, Platform } from 'react-native';
import { Svg, Rect, G } from 'react-native-svg';
import Animated from 'react-native-reanimated';
import {
  State,
  PanGestureHandler,
  PinchGestureHandler,
  RotationGestureHandler,
} from 'react-native-gesture-handler';

const android = Platform.OS === 'android';
const { width, height } = Dimensions.get('window');
const AnimatedG = Animated.createAnimatedComponent(G);
const { set, cond, block, eq, add, Value, event, concat, multiply } = Animated;

const max = 0.9 * width;
const side = 0.1 * width;
const origin = -width / 2;
const ratio = width / height;
const oX = android ? origin : 0;

const pos = () => Math.random() * max;
const byte = () => Math.floor(Math.random() * 256);
const fill = () => `rgba(${byte()}, ${byte()}, ${byte()}, 0.2)`;

const polygons = Array.from({ length: 200 }).map((_, i) => (
  <Rect
    key={i}
    x={pos()}
    y={pos()}
    width={side}
    height={side}
    fill={fill()}
    stroke="black"
  />
));

const Polygons = memo(() => polygons, () => true);

export default () => {
  const X = new Value(0);
  const Y = new Value(0);
  const R = new Value(0);
  const Z = new Value(1);
  const offsetX = new Value(0);
  const offsetY = new Value(0);
  const offsetR = new Value(0);
  const offsetZ = new Value(1);

  const handlePan = event([
    {
      nativeEvent: ({ translationX: x, translationY: y, state }) =>
        block([
          set(X, add(x, offsetX)),
          set(Y, add(y, offsetY)),
          cond(eq(state, State.END), [
            set(offsetX, add(offsetX, x)),
            set(offsetY, add(offsetY, y)),
          ]),
        ]),
    },
  ]);
  const handlePinch = event([
    {
      nativeEvent: ({ scale: z, state }) =>
        block([
          cond(eq(state, State.ACTIVE), set(Z, multiply(z, offsetZ))),
          cond(eq(state, State.END), [set(offsetZ, multiply(offsetZ, z))]),
        ]),
    },
  ]);
  const handleRotation = event([
    {
      nativeEvent: ({ rotation: r, state }) =>
        block([
          set(R, add(r, offsetR)),
          cond(eq(state, State.END), [set(offsetR, add(offsetR, r))]),
        ]),
    },
  ]);

  const panRef = useRef(null);
  const pinchRef = useRef(null);
  const rotationRef = useRef(null);

  return (
    <View style={styles.container}>
      <PanGestureHandler
        ref={panRef}
        avgTouches
        onGestureEvent={handlePan}
        onHandlerStateChange={handlePan}
        simultaneousHandlers={[rotationRef, pinchRef]}
      >
        <Animated.View>
          <PinchGestureHandler
            ref={pinchRef}
            onGestureEvent={handlePinch}
            onHandlerStateChange={handlePinch}
            simultaneousHandlers={[rotationRef, panRef]}
          >
            <Animated.View>
              <RotationGestureHandler
                ref={rotationRef}
                onGestureEvent={handleRotation}
                onHandlerStateChange={handleRotation}
                simultaneousHandlers={[pinchRef, panRef]}
              >
                <Animated.View>
                  <Svg
                    width={width}
                    height={height}
                    viewBox={`${origin} ${origin * ratio} ${width} ${width}`}
                  >
                    <AnimatedG
                      style={{
                        transform: [
                          { translateX: X },
                          { translateY: Y },
                          { translateX: oX },
                          { rotate: concat(R, 'rad') },
                          { scale: Z },
                          { translateX: -oX },
                        ],
                      }}
                    >
                      <G transform={`translate(${origin},${origin})`}>
                        <Rect
                          x={0}
                          y={0}
                          width={width}
                          height={width}
                          fill={fill()}
                        />
                        <Polygons />
                      </G>
                    </AnimatedG>
                  </Svg>
                </Animated.View>
              </RotationGestureHandler>
            </Animated.View>
          </PinchGestureHandler>
        </Animated.View>
      </PanGestureHandler>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    ...StyleSheet.absoluteFillObject,
    backgroundColor: 'white',
  },
});

Read more comments on GitHub >

github_iconTop Results From Across the Web

Ugly Android performance with PanResponder #1064 - GitHub
I've optimized the rendering performance on Android quite a bit, found two low hanging fruits by profiling my latest example.
Read more >
React Native: Idea to Product in One Week - Medium
Using the research I did about designing for iOS and Android, we decided that this app is simple enough that it could be...
Read more >
Newest 'react-native-scrollview' Questions - Stack Overflow
I am working on React Native in which I want to stop multiline textinput component's scroll inside ScrollView. How we can achieve it?...
Read more >
Development - Thoughts from the folks at PROTOTYP
Requests on the frontend application can easily become a big and ugly ... Improving website performance by eliminating render-blocking CSS and JavaScript.
Read more >
Build React-native app in the right way. - DEV Community ‍ ‍
9.1 iOS; 9.2 Android; 9.3 Resources. 10 Performance (draft). 10.1 shouldComponentUpdate; 10.2 Resources. Resources ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found