react-native-modalbox icon indicating copy to clipboard operation
react-native-modalbox copied to clipboard

When I use coverScreen=true, shut off the modal screen will flicker at the top

Open MackJac opened this issue 5 years ago • 20 comments

MackJac avatar Aug 15 '18 03:08 MackJac

"react-native-modalbox": "^1.6.0" "react": "16.3.1", "react-native": "0.55.4",

MackJac avatar Aug 15 '18 03:08 MackJac

I also got this error

congdoan1 avatar Aug 16 '18 11:08 congdoan1

same issue! how to resolved?

meiqi1992 avatar Aug 17 '18 01:08 meiqi1992

same issue with IOS only

nes123 avatar Aug 20 '18 08:08 nes123

As a work around, do the following change in the index.js:

useNativeDriver: true => useNativeDriver: false

I hope it helps

nes123 avatar Aug 20 '18 09:08 nes123

same issue. any chance to be resolved without a work around?

colinluond avatar Aug 21 '18 07:08 colinluond

@nes123 thankyou,it's works,but the animation is not running well. hope to have a best solution

MackJac avatar Aug 22 '18 06:08 MackJac

same issue +1

herarya avatar Aug 24 '18 15:08 herarya

same issue +1

loveninteb avatar Aug 27 '18 10:08 loveninteb

same issue +1

ldhios avatar Sep 06 '18 02:09 ldhios

same issue +1

iOS react-native-cli: 2.0.1 react-native: 0.55.4 react-native-modalbox: 1.6.0

Uploaded a screencast here. flickering_modal.zip

UPDATE: On Android Real Device 8.0.0 works. no flickering occurred On iOS Real Device 11.4.1 works too

claudioviola avatar Sep 06 '18 12:09 claudioviola

Hey all,

setting useNativeDriver: false in index.js solved this for us. I've forked the repo and you can install from here:

~$: npm install git+ssh://github.com/jayhack/react-native-modalbox

Cheers! 🍻

jayhack avatar Sep 10 '18 19:09 jayhack

@jayhack solution worked for me. thanks! you saved my day. @maxs15 do you have any plans to merge this change?

nelson-glauber avatar Sep 26 '18 04:09 nelson-glauber

A similar issue on another modal package: https://github.com/react-native-community/react-native-modal/issues/92

I don't think useNativeDriver: false is a long term solution due to performance (which is already poor on low-powered android devices). I think it might ultimately be a react-native issue, though.

noahtallen avatar Nov 06 '18 15:11 noahtallen

A similar issue on another modal package: react-native-community/react-native-modal#92

I don't think useNativeDriver: false is a long term solution due to performance (which is already poor on low-powered android devices). I think it might ultimately be a react-native issue, though.

You think setting that is not okay? What should we do then? :/ i hate having that flickering, is super annoying.

msqar avatar Jan 28 '19 20:01 msqar

Seems that this is a react-native issue... in the react-native-modal github, they sent a fix for this. What should we do on modalbox? any idea guys?

msqar avatar Jan 28 '19 21:01 msqar

this should be the fix:


import React from 'react'; import PropTypes from 'prop-types'; import { View, StyleSheet, PanResponder, Animated, TouchableWithoutFeedback, Dimensions, Easing, BackHandler, Platform, Modal, Keyboard } from 'react-native';

const {height: SCREEN_HEIGHT, width: SCREEN_WIDTH} = Dimensions.get('window'); const styles = StyleSheet.create({ wrapper: { backgroundColor: 'white' }, transparent: { zIndex: 2, backgroundColor: 'rgba(0,0,0,0)' }, absolute: { position: 'absolute', top: 0, bottom: 0, left: 0, right: 0 } });

export default class ModalBox extends React.PureComponent { static propTypes = { isOpen: PropTypes.bool, isDisabled: PropTypes.bool, startOpen: PropTypes.bool, backdropPressToClose: PropTypes.bool, swipeToClose: PropTypes.bool, swipeThreshold: PropTypes.number, swipeArea: PropTypes.number, position: PropTypes.string, entry: PropTypes.string, backdrop: PropTypes.bool, backdropOpacity: PropTypes.number, backdropColor: PropTypes.string, backdropContent: PropTypes.element, animationDuration: PropTypes.number, backButtonClose: PropTypes.bool, easing: PropTypes.func, coverScreen: PropTypes.bool, keyboardTopOffset: PropTypes.number, onClosed: PropTypes.func, onOpened: PropTypes.func, onClosingState: PropTypes.func };

static defaultProps = { startOpen: false, backdropPressToClose: true, swipeToClose: true, swipeThreshold: 50, position: 'center', backdrop: true, backdropOpacity: 0.5, backdropColor: 'black', backdropContent: null, animationDuration: 400, backButtonClose: false, easing: Easing.elastic(0.8), coverScreen: false, keyboardTopOffset: Platform.OS == 'ios' ? 22 : 0, useNativeDriver: true };

constructor(props) { super(props);

this.onBackPress = this.onBackPress.bind(this);
this.handleOpenning = this.handleOpenning.bind(this);
this.onKeyboardHide = this.onKeyboardHide.bind(this);
this.onKeyboardChange = this.onKeyboardChange.bind(this);
this.animateBackdropOpen = this.animateBackdropOpen.bind(this);
this.animateBackdropClose = this.animateBackdropClose.bind(this);
this.stopAnimateOpen = this.stopAnimateOpen.bind(this);
this.animateOpen = this.animateOpen.bind(this);
this.stopAnimateClose = this.stopAnimateClose.bind(this);
this.animateClose = this.animateClose.bind(this);
this.calculateModalPosition = this.calculateModalPosition.bind(this);
this.createPanResponder = this.createPanResponder.bind(this);
this.onViewLayout = this.onViewLayout.bind(this);
this.onContainerLayout = this.onContainerLayout.bind(this);
this.renderBackdrop = this.renderBackdrop.bind(this);
this.renderContent = this.renderContent.bind(this);
this.open = this.open.bind(this);
this.close = this.close.bind(this);

const position = props.startOpen
  ? new Animated.Value(0)
  : new Animated.Value(
      props.entry === 'top' ? -SCREEN_HEIGHT : SCREEN_HEIGHT
    );
this.state = {
  position,
  backdropOpacity: new Animated.Value(0),
  isOpen: props.startOpen,
  isAnimateClose: false,
  isAnimateOpen: false,
  swipeToClose: false,
  height: SCREEN_HEIGHT,
  width: SCREEN_WIDTH,
  containerHeight: SCREEN_HEIGHT,
  containerWidth: SCREEN_WIDTH,
  isInitialized: false,
  keyboardOffset: 0,
  pan: this.createPanResponder(position),
  hideContent: false,
};

// Needed for iOS because the keyboard covers the screen
if (Platform.OS === 'ios') {
  this.subscriptions = [
    Keyboard.addListener('keyboardWillChangeFrame', this.onKeyboardChange),
    Keyboard.addListener('keyboardDidHide', this.onKeyboardHide)
  ];
}

}

componentDidMount() { this.handleOpenning(); }

componentDidUpdate(prevProps) { if (this.props.isOpen != prevProps.isOpen) { this.handleOpenning(); } }

componentWillUnmount() { if (this.subscriptions) this.subscriptions.forEach(sub => sub.remove()); if (this.props.backButtonClose && Platform.OS === 'android') BackHandler.removeEventListener('hardwareBackPress', this.onBackPress); }

onBackPress() { this.close(); return true; }

handleOpenning() { if (typeof this.props.isOpen == 'undefined') return; if (this.props.isOpen) this.open(); else this.close(); }

/****************** ANIMATIONS **********************/

/*

  • The keyboard is hidden (IOS only) */ onKeyboardHide(evt) { this.setState({keyboardOffset: 0}); }

/*

  • The keyboard frame changed, used to detect when the keyboard open, faster than keyboardDidShow (IOS only) */ onKeyboardChange(evt) { if (!evt) return; if (!this.state.isOpen) return; const keyboardFrame = evt.endCoordinates; const keyboardHeight = this.state.containerHeight - keyboardFrame.screenY;
this.setState({keyboardOffset: keyboardHeight}, () => {
  this.animateOpen();
});

}

/*

  • Open animation for the backdrop, will fade in */ animateBackdropOpen() { if (this.state.isAnimateBackdrop && this.state.animBackdrop) { this.state.animBackdrop.stop(); } this.setState({isAnimateBackdrop: true});
let animBackdrop = Animated.timing(this.state.backdropOpacity, {
  toValue: 1,
  duration: this.props.animationDuration,
  easing: this.props.easing,
  useNativeDriver: this.props.useNativeDriver
}).start(() => {
  this.setState({
    isAnimateBackdrop: false,
    animBackdrop
  });
});

}

/*

  • Close animation for the backdrop, will fade out */ animateBackdropClose() { if (this.state.isAnimateBackdrop && this.state.animBackdrop) { this.state.animBackdrop.stop(); } this.setState({isAnimateBackdrop: true});
let animBackdrop = Animated.timing(this.state.backdropOpacity, {
  toValue: 0,
  duration: this.props.animationDuration,
  easing: this.props.easing,
  useNativeDriver: this.props.useNativeDriver
}).start(() => {
  this.setState({
    isAnimateBackdrop: false,
    animBackdrop
  });
});

}

/*

  • Stop opening animation */ stopAnimateOpen() { if (this.state.isAnimateOpen) { if (this.state.animOpen) this.state.animOpen.stop(); this.setState({isAnimateOpen: false}); } }

/*

  • Open animation for the modal, will move up */ animateOpen() { this.stopAnimateClose();
// Backdrop fadeIn
if (this.props.backdrop) this.animateBackdropOpen();

this.setState(
  {
    isAnimateOpen: true,
    isOpen: true
  },
  () => {
    requestAnimationFrame(() => {
      // Detecting modal position
      let positionDest = this.calculateModalPosition(
        this.state.containerHeight - this.state.keyboardOffset,
        this.state.containerWidth
      );
      if (
        this.state.keyboardOffset &&
        positionDest < this.props.keyboardTopOffset
      ) {
        positionDest = this.props.keyboardTopOffset;
      }
      let animOpen = Animated.timing(this.state.position, {
        toValue: positionDest,
        duration: this.props.animationDuration,
        easing: this.props.easing,
        useNativeDriver: this.props.useNativeDriver
      }).start(() => {
        this.setState({
          isAnimateOpen: false,
          animOpen,
          positionDest
        });
        if (this.props.onOpened) this.props.onOpened();
      });
    });
  }
);

}

/*

  • Stop closing animation */ stopAnimateClose() { if (this.state.isAnimateClose) { if (this.state.animClose) this.state.animClose.stop(); this.setState({hideContent: true}, () => this.setState({isAnimateClose: false}, () => { this.setState({hideContent: false}); }) ); } }

/*

  • Close animation for the modal, will move down */ animateClose() { this.stopAnimateOpen();
// Backdrop fadeout
if (this.props.backdrop) this.animateBackdropClose();

this.setState(
  {
    isAnimateClose: true,
    isOpen: false
  },
  () => {
    let animClose = Animated.timing(this.state.position, {
      toValue:
        this.props.entry === 'top'
          ? -this.state.containerHeight
          : this.state.containerHeight,
      duration: this.props.animationDuration,
      easing: this.props.easing,
      useNativeDriver: this.props.useNativeDriver
    }).start(() => {
      // Keyboard.dismiss();   // make this optional. Easily user defined in .onClosed() callback
      this.setState({hideContent: true}, () => {
      this.setState({
        isAnimateClose: false,
        animClose
      }, () => {
        this.setState({hideContent: false});
        /* Set the state to the starting position of the modal, preventing from animating where the swipe stopped */
        this.state.position.setValue(this.props.entry === 'top' ? -this.state.containerHeight : this.state.containerHeight);
      });
      });
      if (this.props.onClosed) this.props.onClosed();
    });
  }
);

}

/*

  • Calculate when should be placed the modal */ calculateModalPosition(containerHeight, containerWidth) { let position = 0;
if (this.props.position == 'bottom') {
  position = containerHeight - this.state.height;
} else if (this.props.position == 'center') {
  position = containerHeight / 2 - this.state.height / 2;
}
// Checking if the position >= 0
if (position < 0) position = 0;
return position;

}

/*

  • Create the pan responder to detect gesture
  • Only used if swipeToClose is enabled */ createPanResponder(position) { let closingState = false; let inSwipeArea = false;
const onPanStart = (evt, state) => {
  if (
    !this.props.swipeToClose ||
    this.props.isDisabled ||
    (this.props.swipeArea &&
      evt.nativeEvent.pageY - this.state.positionDest >
        this.props.swipeArea)
  ) {
    inSwipeArea = false;
    return false;
  }
  inSwipeArea = true;
  return true;
};

const animEvt = Animated.event([null, {customY: position}]);

const onPanMove = (evt, state) => {
  const newClosingState =
    this.props.entry === 'top'
      ? -state.dy > this.props.swipeThreshold
      : state.dy > this.props.swipeThreshold;
  if (this.props.entry === 'top' ? state.dy > 0 : state.dy < 0) return;
  if (newClosingState != closingState && this.props.onClosingState)
    this.props.onClosingState(newClosingState);
  closingState = newClosingState;
  state.customY = state.dy + this.state.positionDest;

  animEvt(evt, state);
};

const onPanRelease = (evt, state) => {
  if (!inSwipeArea) return;
  inSwipeArea = false;
  if (
    this.props.entry === 'top'
      ? -state.dy > this.props.swipeThreshold
      : state.dy > this.props.swipeThreshold
  ) {
    this.close();
  } else if (!this.state.isOpen) {
    this.animateOpen();
  }
};

return PanResponder.create({
  onStartShouldSetPanResponder: onPanStart,
  onPanResponderMove: onPanMove,
  onPanResponderRelease: onPanRelease,
  onPanResponderTerminate: onPanRelease
});

}

/*

  • Event called when the modal view layout is calculated */ onViewLayout(evt) { const height = evt.nativeEvent.layout.height; const width = evt.nativeEvent.layout.width;
// If the dimensions are still the same we're done
let newState = {};
if (height !== this.state.height) newState.height = height;
if (width !== this.state.width) newState.width = width;
this.setState(newState);

if (this.onViewLayoutCalculated) this.onViewLayoutCalculated();

}

/*

  • Event called when the container view layout is calculated */ onContainerLayout(evt) { const height = evt.nativeEvent.layout.height; const width = evt.nativeEvent.layout.width;
// If the container size is still the same we're done
if (
  height == this.state.containerHeight &&
  width == this.state.containerWidth
) {
  this.setState({isInitialized: true});
  return;
}

if (this.state.isOpen || this.state.isAnimateOpen) {
  this.animateOpen();
}

if (this.props.onLayout) this.props.onLayout(evt);
this.setState({
  isInitialized: true,
  containerHeight: height,
  containerWidth: width
});

}

/*

  • Render the backdrop element */ renderBackdrop() { let backdrop = null;
if (this.props.backdrop) {
  backdrop = (
    <TouchableWithoutFeedback
      onPress={this.props.backdropPressToClose ? this.close : null}>
      <Animated.View
        importantForAccessibility="no"
        style={[styles.absolute, {opacity: this.state.backdropOpacity}]}>
        <View
          style={[
            styles.absolute,
            {
              backgroundColor: this.props.backdropColor,
              opacity: this.props.backdropOpacity
            }
          ]}
        />
        {this.props.backdropContent || []}
      </Animated.View>
    </TouchableWithoutFeedback>
  );
}

return backdrop;

}

renderContent() { const size = { height: this.state.containerHeight, width: this.state.containerWidth }; const offsetX = (this.state.containerWidth - this.state.width) / 2;

return (
  <Animated.View
    onLayout={this.onViewLayout}
    style={[
      styles.wrapper,
      size,
      this.props.style,
      {
        transform: [
          {translateY: this.state.position},
          {translateX: offsetX}
        ]
      }
    ]}
    {...this.state.pan.panHandlers}>
    {this.props.children}
  </Animated.View>
);

}

/*

  • Render the component */ render() { const visible = this.state.isOpen || this.state.isAnimateOpen || this.state.isAnimateClose;
if (!visible) return <View />;

const content = (
  <View
    importantForAccessibility="yes"
    accessibilityViewIsModal={true}
    style={[styles.transparent, styles.absolute]}
    pointerEvents={'box-none'}>
    <View
      style={{flex: 1}}
      pointerEvents={'box-none'}
      onLayout={this.onContainerLayout}>
      {visible && this.renderBackdrop()}
      {visible && this.renderContent()}
    </View>
  </View>
);

if (!this.props.coverScreen) return content;

return (
  <Modal
    onRequestClose={() => {
      if (this.props.backButtonClose) {
        this.close();
      }
    }}
    supportedOrientations={[
      'landscape',
      'portrait',
      'portrait-upside-down'
    ]}
    transparent
    visible={visible}
    hardwareAccelerated={true}>
    {this.state.hideContent ? null : content}
  </Modal>
);

}

/****************** PUBLIC METHODS **********************/

open() { if (this.props.isDisabled) return; if ( !this.state.isAnimateOpen && (!this.state.isOpen || this.state.isAnimateClose) ) { this.onViewLayoutCalculated = () => { this.animateOpen(); if (this.props.backButtonClose && Platform.OS === 'android') BackHandler.addEventListener('hardwareBackPress', this.onBackPress); this.onViewLayoutCalculated = null; }; this.setState({isAnimateOpen: true}); } }

close() { if (this.props.isDisabled) return; if ( !this.state.isAnimateClose && (this.state.isOpen || this.state.isAnimateOpen) ) { this.animateClose(); if (this.props.backButtonClose && Platform.OS === 'android') BackHandler.removeEventListener('hardwareBackPress', this.onBackPress); } } }


nes123 avatar Dec 18 '19 11:12 nes123

Same issue. any updates?

Dylan0916 avatar Feb 25 '20 07:02 Dylan0916

Same issue

"react": "16.13.1", "react-native": "0.62.0", "react-native-modalbox": "2.0.0",

khoatran808 avatar Apr 03 '20 14:04 khoatran808

@nes123 why not creating a PR instead?

mehmetnyarar avatar Mar 04 '21 21:03 mehmetnyarar