Modal Animation Types

Summary:
Currently the Modal component uses the slide up / down animation for presenting and hiding the Modal with no options. This PR gives users a choice to use a fade in / out animation or the current slide animation (slide is the default). Android and iOS.

![](http://g.recordit.co/nfJSg487Ox.gif)  ![](http://g.recordit.co/QHGDuUFbPy.gif)

I've updated the UIExplorer and documentation.

![image](https://cloud.githubusercontent.com/assets/4265163/14743130/0bd8282c-086e-11e6-93eb-3d344431337d.png)

Thanks!
Closes https://github.com/facebook/react-native/pull/7156

Differential Revision: D3237809

Pulled By: javache

fb-gh-sync-id: 813e56ada8b19990dc5018527dc3a81b2c8b349a
fbshipit-source-id: 813e56ada8b19990dc5018527dc3a81b2c8b349a
This commit is contained in:
Jesse Sessler 2016-04-28 15:59:11 -07:00 committed by Facebook Github Bot 1
parent b5f14ea8f1
commit 2bb1c263db
10 changed files with 88 additions and 26 deletions

View File

@ -66,7 +66,7 @@ var Button = React.createClass({
var ModalExample = React.createClass({
getInitialState() {
return {
animated: true,
animationType: 'none',
modalVisible: false,
transparent: false,
};
@ -76,8 +76,8 @@ var ModalExample = React.createClass({
this.setState({modalVisible: visible});
},
_toggleAnimated() {
this.setState({animated: !this.state.animated});
_setAnimationType(type) {
this.setState({animationType: type});
},
_toggleTransparent() {
@ -91,18 +91,21 @@ var ModalExample = React.createClass({
var innerContainerTransparentStyle = this.state.transparent
? {backgroundColor: '#fff', padding: 20}
: null;
var activeButtonStyle = {
backgroundColor: '#ddd'
};
return (
<View>
<Modal
animated={this.state.animated}
animationType={this.state.animationType}
transparent={this.state.transparent}
visible={this.state.modalVisible}
onRequestClose={() => {this._setModalVisible(false)}}
>
<View style={[styles.container, modalBackgroundStyle]}>
<View style={[styles.innerContainer, innerContainerTransparentStyle]}>
<Text>This modal was presented {this.state.animated ? 'with' : 'without'} animation.</Text>
<Text>This modal was presented {this.state.animationType === 'none' ? 'without' : 'with'} animation.</Text>
<Button
onPress={this._setModalVisible.bind(this, false)}
style={styles.modalButton}>
@ -111,10 +114,17 @@ var ModalExample = React.createClass({
</View>
</View>
</Modal>
<View style={styles.row}>
<Text style={styles.rowTitle}>Animated</Text>
<Switch value={this.state.animated} onValueChange={this._toggleAnimated} />
<Text style={styles.rowTitle}>Animation Type</Text>
<Button onPress={this._setAnimationType.bind(this, 'none')} style={this.state.animationType === 'none' ? activeButtonStyle : {}}>
none
</Button>
<Button onPress={this._setAnimationType.bind(this, 'slide')} style={this.state.animationType === 'slide' ? activeButtonStyle : {}}>
slide
</Button>
<Button onPress={this._setAnimationType.bind(this, 'fade')} style={this.state.animationType === 'fade' ? activeButtonStyle : {}}>
fade
</Button>
</View>
<View style={styles.row}>

View File

@ -16,6 +16,7 @@ const PropTypes = require('ReactPropTypes');
const React = require('React');
const StyleSheet = require('StyleSheet');
const View = require('View');
const deprecatedPropType = require('deprecatedPropType');
const requireNativeComponent = require('requireNativeComponent');
const RCTModalHostView = requireNativeComponent('RCTModalHostView', null);
@ -35,7 +36,11 @@ const RCTModalHostView = requireNativeComponent('RCTModalHostView', null);
*/
class Modal extends React.Component {
static propTypes = {
animated: PropTypes.bool,
animated: deprecatedPropType(
PropTypes.bool,
'Use the `animationType` prop instead.'
),
animationType: PropTypes.oneOf(['none', 'slide', 'fade']),
transparent: PropTypes.bool,
visible: PropTypes.bool,
onRequestClose: Platform.OS === 'android' ? PropTypes.func.isRequired : PropTypes.func,
@ -55,9 +60,18 @@ class Modal extends React.Component {
backgroundColor: this.props.transparent ? 'transparent' : 'white',
};
let animationType = this.props.animationType;
if (!animationType) {
// manually setting default prop here to keep support for the deprecated 'animated' prop
animationType = 'none';
if (this.props.animated) {
animationType = 'slide';
}
}
return (
<RCTModalHostView
animated={this.props.animated}
animationType={animationType}
transparent={this.props.transparent}
onRequestClose={this.props.onRequestClose}
onShow={this.props.onShow}
@ -88,4 +102,4 @@ const styles = StyleSheet.create({
}
});
module.exports = Modal;
module.exports = Modal;

View File

@ -16,7 +16,7 @@
@interface RCTModalHostView : UIView <RCTInvalidating>
@property (nonatomic, assign, getter=isAnimated) BOOL animated;
@property (nonatomic, copy) NSString *animationType;
@property (nonatomic, assign, getter=isTransparent) BOOL transparent;
@property (nonatomic, copy) RCTDirectEventBlock onShow;

View File

@ -82,7 +82,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:coder)
- (void)dismissModalViewController
{
if (_isPresented) {
[_modalViewController dismissViewControllerAnimated:self.animated completion:nil];
[_modalViewController dismissViewControllerAnimated:[self hasAnimationType] completion:nil];
_isPresented = NO;
}
}
@ -93,7 +93,13 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:coder)
if (!_isPresented && self.window) {
RCTAssert(self.reactViewController, @"Can't present modal view controller without a presenting view controller");
[self.reactViewController presentViewController:_modalViewController animated:self.animated completion:^{
if ([self.animationType isEqualToString:@"fade"]) {
_modalViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
} else if ([self.animationType isEqualToString:@"slide"]) {
_modalViewController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
}
[self.reactViewController presentViewController:_modalViewController animated:[self hasAnimationType] completion:^{
if (_onShow) {
_onShow(nil);
}
@ -123,6 +129,11 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:coder)
return _modalViewController.modalPresentationStyle == UIModalPresentationCustom;
}
- (BOOL)hasAnimationType
{
return ![self.animationType isEqualToString:@"none"];
}
- (void)setTransparent:(BOOL)transparent
{
_modalViewController.modalPresentationStyle = transparent ? UIModalPresentationCustom : UIModalPresentationFullScreen;

View File

@ -62,7 +62,7 @@ RCT_EXPORT_MODULE()
[_hostViews removeAllObjects];
}
RCT_EXPORT_VIEW_PROPERTY(animated, BOOL)
RCT_EXPORT_VIEW_PROPERTY(animationType, NSString)
RCT_EXPORT_VIEW_PROPERTY(transparent, BOOL)
RCT_EXPORT_VIEW_PROPERTY(onShow, RCTDirectEventBlock)

View File

@ -55,9 +55,9 @@ public class ReactModalHostManager extends ViewGroupManager<ReactModalHostView>
view.dismiss();
}
@ReactProp(name = "animated")
public void setAnimated(ReactModalHostView view, boolean animated) {
view.setAnimated(animated);
@ReactProp(name = "animationType")
public void setAnimationType(ReactModalHostView view, String animationType) {
view.setAnimationType(animationType);
}
@ReactProp(name = "transparent")

View File

@ -56,7 +56,7 @@ public class ReactModalHostView extends ViewGroup {
private DialogRootViewGroup mHostView;
private @Nullable Dialog mDialog;
private boolean mTransparent;
private boolean mAnimated;
private String mAnimationType;
// Set this flag to true if changing a particular property on the view requires a new Dialog to
// be created. For instance, animation does since it affects Dialog creation through the theme
// but transparency does not since we can access the window to update the property.
@ -131,8 +131,8 @@ public class ReactModalHostView extends ViewGroup {
mTransparent = transparent;
}
protected void setAnimated(boolean animated) {
mAnimated = animated;
protected void setAnimationType(String animationType) {
mAnimationType = animationType;
mPropertyRequiresNewDialog = true;
}
@ -162,8 +162,10 @@ public class ReactModalHostView extends ViewGroup {
// Reset the flag since we are going to create a new dialog
mPropertyRequiresNewDialog = false;
int theme = R.style.Theme_FullScreenDialog;
if (mAnimated) {
theme = R.style.Theme_FullScreenDialogAnimated;
if (mAnimationType.equals("fade")) {
theme = R.style.Theme_FullScreenDialogAnimatedFade;
} else if (mAnimationType.equals("slide")) {
theme = R.style.Theme_FullScreenDialogAnimatedSlide;
}
mDialog = new Dialog(getContext(), theme);

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_shortAnimTime"
android:interpolator="@android:anim/accelerate_interpolator"
android:fromAlpha="0.0"
android:toAlpha="1.0"
/>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_shortAnimTime"
android:interpolator="@android:anim/accelerate_interpolator"
android:fromAlpha="1.0"
android:toAlpha="0.0"
/>

View File

@ -8,13 +8,24 @@
<item name="android:windowBackground">@android:color/transparent</item>
</style>
<style name="Theme.FullScreenDialogAnimated" parent="Theme.FullScreenDialog">
<item name="android:windowAnimationStyle">@style/DialogAnimation</item>
<style name="Theme.FullScreenDialogAnimatedSlide" parent="Theme.FullScreenDialog">
<item name="android:windowAnimationStyle">@style/DialogAnimationSlide</item>
</style>
<style name="DialogAnimation">
<style name="Theme.FullScreenDialogAnimatedFade" parent="Theme.FullScreenDialog">
<item name="android:windowAnimationStyle">@style/DialogAnimationFade</item>
</style>
<style name="DialogAnimationSlide">
<item name="android:windowEnterAnimation">@anim/slide_up</item>
<item name="android:windowExitAnimation">@anim/slide_down</item>
</style>
<style name="DialogAnimationFade">
<item name="android:windowEnterAnimation">@anim/fade_in</item>
<item name="android:windowExitAnimation">@anim/fade_out</item>
</style>
</resources>