Toast
- 在common文件夹下新建Toast文件夹,把这里面的两个文件放进去(Toast)
- Toast.js
// Toast.js
'use strict';
import React, { Component } from 'react';
import { View } from 'react-native';
import Overlay from '../Overlay/Overlay';
import ToastView from './ToastView';
export default class Toast extends Overlay {
static ToastView = ToastView;
static defaultDuration = 'short';
static defaultPosition = 'center';
static messageDefaultDuration = 'short';
static messageDefaultPosition = 'bottom';
static show(options) {
let { duration, ...others } =
options && typeof options === 'object' ? options : {};
let key = super.show(<this.ToastView {...others} />);
if (typeof duration !== 'number') {
switch (duration) {
case 'long':
duration = 3500;
break;
default:
duration = 2000;
break;
}
}
setTimeout(() => this.hide(key), duration);
return key;
}
static message(
text,
duration = this.messageDefaultDuration,
position = this.messageDefaultPosition,
) {
return this.show({ text, duration, position });
}
static success(
text,
duration = this.defaultDuration,
position = this.defaultPosition,
) {
return this.show({ text, duration, position, icon: 'success' });
}
static fail(
text,
duration = this.defaultDuration,
position = this.defaultPosition,
) {
return this.show({ text, duration, position, icon: 'fail' });
}
static smile(
text,
duration = this.defaultDuration,
position = this.defaultPosition,
) {
return this.show({ text, duration, position, icon: 'smile' });
}
static sad(
text,
duration = this.defaultDuration,
position = this.defaultPosition,
) {
return this.show({ text, duration, position, icon: 'sad' });
}
static info(
text,
duration = this.defaultDuration,
position = this.defaultPosition,
) {
return this.show({ text, duration, position, icon: 'info' });
}
static stop(
text,
duration = this.defaultDuration,
position = this.defaultPosition,
) {
return this.show({ text, duration, position, icon: 'stop' });
}
}
- ToastView.js
// ToastView.js
'use strict';
import React from 'react';
import PropTypes from 'prop-types';
import { View, Image, Text } from 'react-native';
import Theme from '../../../themes/Theme';
import Overlay from '../Overlay/Overlay';
export default class ToastView extends Overlay.View {
static propTypes = {
...Overlay.View.propTypes,
text: PropTypes.oneOfType([
PropTypes.element,
PropTypes.string,
PropTypes.number,
]),
icon: PropTypes.oneOfType([
PropTypes.element,
PropTypes.shape({ uri: PropTypes.string }), //{uri: 'http://...'}
PropTypes.number, //require('path/to/image')
PropTypes.oneOf([
'none',
'success',
'fail',
'smile',
'sad',
'info',
'stop',
]),
]),
position: PropTypes.oneOf(['top', 'bottom', 'center']),
};
static defaultProps = {
...Overlay.View.defaultProps,
overlayOpacity: 0,
overlayPointerEvents: 'none',
closeOnHardwareBackPress: false,
position: 'center',
};
get overlayPointerEvents() {
let { overlayPointerEvents, modal } = this.props;
return modal ? 'auto' : overlayPointerEvents;
}
buildStyle() {
let { style, position } = this.props;
style = [
{
paddingLeft: Theme.toastScreenPaddingLeft,
paddingRight: Theme.toastScreenPaddingRight,
paddingTop: Theme.toastScreenPaddingTop,
paddingBottom: Theme.toastScreenPaddingBottom,
justifyContent:
position === 'top'
? 'flex-start'
: position === 'bottom'
? 'flex-end'
: 'center',
alignItems: 'center',
},
].concat(super.buildStyle());
return style;
}
renderIcon() {
let { icon } = this.props;
if (!icon && icon !== 0) {
return null;
}
let image;
if (!React.isValidElement(icon)) {
let imageSource;
if (typeof icon === 'string') {
switch (icon) {
case 'success':
imageSource = require('../../../asserts/icons/success.png');
break;
case 'fail':
imageSource = require('../../../asserts/icons/fail.png');
break;
case 'smile':
imageSource = require('../../../asserts/icons/smile.png');
break;
case 'sad':
imageSource = require('../../../asserts/icons/sad.png');
break;
case 'info':
imageSource = require('../../../asserts/icons/info.png');
break;
case 'stop':
imageSource = require('../../../asserts/icons/stop.png');
break;
default:
imageSource = null;
break;
}
} else {
imageSource = icon;
}
image = (
<Image
style={{
width: Theme.toastIconWidth,
height: Theme.toastIconHeight,
tintColor: Theme.toastIconTintColor,
}}
source={imageSource}
/>
);
} else {
image = icon;
}
return (
<View
style={{
paddingTop: Theme.toastIconPaddingTop,
paddingBottom: Theme.toastIconPaddingBottom,
}}
>
{image}
</View>
);
}
renderText() {
let { text } = this.props;
if (typeof text === 'string' || typeof text === 'number') {
text = (
<Text
style={{ color: Theme.toastTextColor, fontSize: Theme.toastFontSize }}
>
{text}
</Text>
);
}
return text;
}
renderContent() {
let contentStyle = {
backgroundColor: Theme.toastColor,
paddingLeft: Theme.toastPaddingLeft,
paddingRight: Theme.toastPaddingRight,
paddingTop: Theme.toastPaddingTop,
paddingBottom: Theme.toastPaddingBottom,
borderRadius: Theme.toastBorderRadius,
alignItems: 'center',
};
return (
<View style={contentStyle}>
{this.renderIcon()}
{this.renderText()}
</View>
);
}
}
Overlay
- 因为Toast需要Overlay,所以还得把Overlay也引入
- 在common文件夹下新建文件夹Overlay
- Overlay.js
// Overlay.js
'use strict';
import React, { Component } from 'react';
import { View } from 'react-native';
import TopView from './TopView';
import OverlayView from './OverlayView';
import OverlayPullView from './OverlayPullView';
import OverlayPopView from './OverlayPopView';
import OverlayPopoverView from './OverlayPopoverView';
export default class Overlay {
static View = OverlayView;
static PullView = OverlayPullView;
static PopView = OverlayPopView;
static PopoverView = OverlayPopoverView;
// base props
// style: ViewPropTypes.style,
// modal: PropTypes.bool,
// animated: PropTypes.bool,
// overlayOpacity: PropTypes.number,
// overlayPointerEvents: ViewPropTypes.pointerEvents,
static show(overlayView) {
let key;
let onDisappearCompletedSave = overlayView.props.onDisappearCompleted;
let element = React.cloneElement(overlayView, {
onDisappearCompleted: () => {
TopView.remove(key);
onDisappearCompletedSave && onDisappearCompletedSave();
},
});
key = TopView.add(element);
return key;
}
static hide(key) {
TopView.remove(key);
}
static transformRoot(transform, animated, animatesOnly = null) {
TopView.transform(transform, animated, animatesOnly);
}
static restoreRoot(animated, animatesOnly = null) {
TopView.restore(animated, animatesOnly);
}
}
// OverlayPopoverView.js
'use strict';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { View, Dimensions, Platform, StatusBar } from 'react-native';
import OverlayView from './OverlayView';
import Popover from '../Popover/Popover';
export default class OverlayPopoverView extends OverlayView {
static propTypes = {
...OverlayView.propTypes,
popoverStyle: Popover.propTypes.style,
fromBounds: PropTypes.shape({
x: PropTypes.number.isRequired,
y: PropTypes.number.isRequired,
width: PropTypes.number,
height: PropTypes.number,
}).isRequired,
direction: PropTypes.oneOf(['down', 'up', 'right', 'left']),
autoDirection: PropTypes.bool, //down -> up, or right -> left
directionInsets: PropTypes.number,
align: PropTypes.oneOf(['start', 'center', 'end']),
alignInsets: PropTypes.number,
showArrow: PropTypes.bool,
paddingCorner: Popover.propTypes.paddingCorner,
};
static defaultProps = {
...OverlayView.defaultProps,
overlayOpacity: 0,
direction: 'down',
autoDirection: true,
align: 'end',
showArrow: true,
};
constructor(props) {
super(props);
Object.assign(this.state, {
fromBounds: props.fromBounds,
popoverWidth: null,
popoverHeight: null,
});
this.defaultDirectionInsets = 0;
}
componentDidUpdate(prevProps, prevState, snapshot) {
super.componentDidUpdate &&
super.componentDidUpdate(prevProps, prevState, snapshot);
if (
JSON.stringify(this.props.fromBounds) !==
JSON.stringify(this.state.fromBounds)
) {
this.setState({ fromBounds: this.props.fromBounds });
}
}
updateFromBounds(bounds) {
this.setState({ fromBounds: bounds });
}
onPopoverLayout(e) {
if (
Platform.OS === 'android' &&
(this.state.popoverWidth !== null || this.state.popoverHeight != null)
) {
//android calls many times...
return;
}
let { width, height } = e.nativeEvent.layout;
if (
width !== this.state.popoverWidth ||
height !== this.state.popoverHeight
) {
this.setState({ popoverWidth: width, popoverHeight: height });
}
}
buildPopoverStyle() {
let { fromBounds, popoverWidth, popoverHeight } = this.state;
let {
popoverStyle,
direction,
autoDirection,
directionInsets,
align,
alignInsets,
showArrow,
arrow,
} = this.props;
if (popoverWidth === null || popoverHeight === null) {
popoverStyle = []
.concat(popoverStyle)
.concat({ position: 'absolute', left: 0, top: 0, opacity: 0 });
if (!showArrow) {
arrow = 'none';
} else {
switch (direction) {
case 'right':
arrow = 'left';
break;
case 'left':
arrow = 'right';
break;
case 'up':
arrow = 'bottom';
break;
default:
arrow = 'top';
break;
}
}
return { popoverStyle, arrow };
}
let screenWidth = Dimensions.get('window').width;
let screenHeight = Dimensions.get('window').height;
let { x, y, width, height } = fromBounds ? fromBounds : {};
if (!x && x !== 0) {
x = screenWidth / 2;
}
if (!y && y !== 0) {
y = screenHeight / 2;
}
if (!width) {
width = 0;
}
if (!height) {
height = 0;
}
if (!directionInsets && directionInsets !== 0) {
directionInsets = this.defaultDirectionInsets;
}
if (!alignInsets) {
alignInsets = 0;
}
//auto direction
let ph = popoverHeight + directionInsets;
let pw = popoverWidth + directionInsets;
switch (direction) {
case 'right':
if (autoDirection && x + width + pw > screenWidth && x >= pw) {
direction = 'left';
}
break;
case 'left':
if (autoDirection && x + width + pw <= screenWidth && x < pw) {
direction = 'right';
}
break;
case 'up':
if (autoDirection && y + height + ph <= screenHeight && y < ph) {
direction = 'down';
}
break;
default:
if (autoDirection && y + height + ph > screenHeight && y >= ph) {
direction = 'up';
}
break;
}
//calculate popover top-left position and arrow type
let px, py;
switch (direction) {
case 'right':
px = x + width + directionInsets;
arrow = 'left';
break;
case 'left':
px = x - popoverWidth - directionInsets;
arrow = 'right';
break;
case 'up':
py = y - popoverHeight - directionInsets;
arrow = 'bottom';
break;
default:
py = y + height + directionInsets;
arrow = 'top';
break;
}
if (direction == 'down' || direction == 'up') {
switch (align) {
case 'start':
px = x - alignInsets;
arrow += 'Left';
break;
case 'center':
px = x + width / 2 - popoverWidth / 2;
break;
default:
px = x + width - popoverWidth + alignInsets;
arrow += 'Right';
break;
}
} else if (direction == 'right' || direction == 'left') {
switch (align) {
case 'end':
py = y + height - popoverHeight + alignInsets;
arrow += 'Bottom';
break;
case 'center':
py = y + height / 2 - popoverHeight / 2;
break;
default:
py = y - alignInsets;
arrow += 'Top';
break;
}
}
popoverStyle = [].concat(popoverStyle).concat({
position: 'absolute',
left: px,
top: py,
});
if (!showArrow) {
arrow = 'none';
}
return { popoverStyle, arrow };
}
renderContent(content = null) {
let { paddingCorner, children } = this.props;
let { popoverStyle, arrow } = this.buildPopoverStyle();
return (
<Popover
style={popoverStyle}
arrow={arrow}
paddingCorner={paddingCorner}
onLayout={(e) => this.onPopoverLayout(e)}
>
{content ? content : children}
</Popover>
);
}
}
// OverlayPopView.js
'use strict';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Animated, View, ViewPropTypes } from 'react-native';
import OverlayView from './OverlayView';
export default class OverlayPopView extends OverlayView {
static propTypes = {
...OverlayView.propTypes,
type: PropTypes.oneOf(['zoomOut', 'zoomIn', 'custom']),
containerStyle: ViewPropTypes.style,
customBounds: PropTypes.shape({
x: PropTypes.number.isRequired,
y: PropTypes.number.isRequired,
width: PropTypes.number.isRequired,
height: PropTypes.number.isRequired,
}),
};
static defaultProps = {
...OverlayView.defaultProps,
type: 'zoomOut',
animated: true,
};
constructor(props) {
super(props);
this.viewLayout = { x: 0, y: 0, width: 0, height: 0 };
Object.assign(this.state, {
opacity: new Animated.Value(1),
translateX: new Animated.Value(0),
translateY: new Animated.Value(0),
scaleX: new Animated.Value(1),
scaleY: new Animated.Value(1),
showed: false,
});
}
get appearAnimates() {
let animates = super.appearAnimates;
let duration = 200;
animates = animates.concat([
Animated.timing(this.state.opacity, {
toValue: 1,
duration,
useNativeDriver: false,
}),
Animated.timing(this.state.translateX, {
toValue: 0,
duration,
useNativeDriver: false,
}),
Animated.timing(this.state.translateY, {
toValue: 0,
duration,
useNativeDriver: false,
}),
Animated.timing(this.state.scaleX, {
toValue: 1,
duration,
useNativeDriver: false,
}),
Animated.timing(this.state.scaleY, {
toValue: 1,
duration,
useNativeDriver: false,
}),
]);
return animates;
}
get disappearAnimates() {
let animates = super.disappearAnimates;
let duration = 200;
let ft = this.fromTransform;
animates = animates.concat([
Animated.timing(this.state.opacity, {
toValue: 0,
duration,
useNativeDriver: false,
}),
Animated.timing(this.state.translateX, {
toValue: ft.translateX,
duration,
useNativeDriver: false,
}),
Animated.timing(this.state.translateY, {
toValue: ft.translateY,
duration,
useNativeDriver: false,
}),
Animated.timing(this.state.scaleX, {
toValue: ft.scaleX,
duration,
useNativeDriver: false,
}),
Animated.timing(this.state.scaleY, {
toValue: ft.scaleY,
duration,
useNativeDriver: false,
}),
]);
return animates;
}
get appearAfterMount() {
return false;
}
get fromBounds() {
let { type, customBounds } = this.props;
let bounds;
if (type === 'custom' && !customBounds) {
console.error(
'OverlayPopView: customBounds can not be null when type is "custom"',
);
}
if (type === 'custom' && customBounds) {
bounds = customBounds;
} else {
let zoomRate = type === 'zoomIn' ? 0.3 : 1.2;
let { x, y, width, height } = this.viewLayout;
bounds = {
x: x - (width * zoomRate - width) / 2,
y: y - (height * zoomRate - height) / 2,
width: width * zoomRate,
height: height * zoomRate,
};
}
return bounds;
}
get fromTransform() {
let fb = this.fromBounds;
let tb = this.viewLayout;
let transform = {
translateX: fb.x + fb.width / 2 - (tb.x + tb.width / 2),
translateY: fb.y + fb.height / 2 - (tb.y + tb.height / 2),
scaleX: fb.width / tb.width,
scaleY: fb.height / tb.height,
};
return transform;
}
appear(animated = this.props.animated) {
if (animated) {
let { opacity, translateX, translateY, scaleX, scaleY } = this.state;
let ft = this.fromTransform;
opacity.setValue(0);
translateX.setValue(ft.translateX);
translateY.setValue(ft.translateY);
scaleX.setValue(ft.scaleX);
scaleY.setValue(ft.scaleY);
}
super.appear(animated);
}
onLayout(e) {
this.viewLayout = e.nativeEvent.layout;
if (!this.state.showed) {
this.setState({ showed: true });
this.appear();
}
}
renderContent(content = null) {
let { containerStyle, children } = this.props;
let { opacity, translateX, translateY, scaleX, scaleY } = this.state;
containerStyle = [
{
backgroundColor: 'rgba(0, 0, 0, 0)',
minWidth: 1,
minHeight: 1,
},
]
.concat(containerStyle)
.concat({
opacity: this.state.showed ? opacity : 0,
transform: [{ translateX }, { translateY }, { scaleX }, { scaleY }],
});
return (
<Animated.View
style={containerStyle}
pointerEvents="box-none"
onLayout={(e) => this.onLayout(e)}
>
{content ? content : children}
</Animated.View>
);
}
}
// OverlayPullView.js
'use strict';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Animated, View, ViewPropTypes } from 'react-native';
import Theme from '../../../themes/Theme';
import TopView from './TopView';
import OverlayView from './OverlayView';
export default class OverlayPullView extends OverlayView {
static propTypes = {
...OverlayView.propTypes,
side: PropTypes.oneOf(['top', 'bottom', 'left', 'right']),
containerStyle: ViewPropTypes.style,
rootTransform: PropTypes.oneOfType([
PropTypes.oneOf(['none', 'translate', 'scale']),
PropTypes.arrayOf(
PropTypes.shape({
translateX: PropTypes.number,
translateY: PropTypes.number,
scaleX: PropTypes.number,
scaleY: PropTypes.number,
}),
),
]),
};
static defaultProps = {
...OverlayView.defaultProps,
side: 'bottom',
animated: true,
rootTransform: 'none',
};
constructor(props) {
super(props);
this.viewLayout = { x: 0, y: 0, width: 0, height: 0 };
Object.assign(this.state, {
marginValue: new Animated.Value(0),
showed: false,
});
}
get appearAnimates() {
let animates = super.appearAnimates;
animates.push(
Animated.spring(this.state.marginValue, {
toValue: 0,
friction: 9,
useNativeDriver: false,
}),
);
return animates;
}
get disappearAnimates() {
let animates = super.disappearAnimates;
animates.push(
Animated.spring(this.state.marginValue, {
toValue: this.marginSize,
friction: 9,
useNativeDriver: false,
}),
);
return animates;
}
get appearAfterMount() {
return false;
}
get marginSize() {
let { side } = this.props;
if (side === 'left' || side === 'right') {
return -this.viewLayout.width;
} else {
return -this.viewLayout.height;
}
}
get rootTransformValue() {
let { side, rootTransform } = this.props;
if (!rootTransform || rootTransform === 'none') {
return [];
}
let transform;
switch (rootTransform) {
case 'translate':
switch (side) {
case 'top':
return [{ translateY: this.viewLayout.height }];
case 'left':
return [{ translateX: this.viewLayout.width }];
case 'right':
return [{ translateX: -this.viewLayout.width }];
default:
return [{ translateY: -this.viewLayout.height }];
}
break;
case 'scale':
return [
{ scaleX: Theme.overlayRootScale },
{ scaleY: Theme.overlayRootScale },
];
default:
return rootTransform;
}
}
appear(animated = this.props.animated) {
if (animated) {
this.state.marginValue.setValue(this.marginSize);
}
super.appear(animated);
let { rootTransform } = this.props;
if (rootTransform && rootTransform !== 'none') {
TopView.transform(this.rootTransformValue, animated);
}
}
disappear(animated = this.props.animated) {
let { rootTransform } = this.props;
if (rootTransform && rootTransform !== 'none') {
TopView.restore(animated);
}
super.disappear(animated);
}
onLayout(e) {
this.viewLayout = e.nativeEvent.layout;
if (!this.state.showed) {
this.setState({ showed: true });
this.appear();
}
}
buildStyle() {
let { side } = this.props;
let sideStyle;
//Set flexDirection so that the content view will fill the side
switch (side) {
case 'top':
sideStyle = {
flexDirection: 'column',
justifyContent: 'flex-start',
alignItems: 'stretch',
};
break;
case 'left':
sideStyle = {
flexDirection: 'row',
justifyContent: 'flex-start',
alignItems: 'stretch',
};
break;
case 'right':
sideStyle = {
flexDirection: 'row',
justifyContent: 'flex-end',
alignItems: 'stretch',
};
break;
default:
sideStyle = {
flexDirection: 'column',
justifyContent: 'flex-end',
alignItems: 'stretch',
};
}
return super.buildStyle().concat(sideStyle);
}
renderContent(content = null) {
let { side, containerStyle, children } = this.props;
let contentStyle;
switch (side) {
case 'top':
contentStyle = { marginTop: this.state.marginValue };
break;
case 'left':
contentStyle = { marginLeft: this.state.marginValue };
break;
case 'right':
contentStyle = { marginRight: this.state.marginValue };
break;
default:
contentStyle = { marginBottom: this.state.marginValue };
}
contentStyle.opacity = this.state.showed ? 1 : 0;
containerStyle = [
{
backgroundColor: Theme.defaultColor,
},
]
.concat(containerStyle)
.concat(contentStyle);
return (
<Animated.View style={containerStyle} onLayout={(e) => this.onLayout(e)}>
{content ? content : children}
</Animated.View>
);
}
}
// OverlayView.js
'use strict';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import ReactNative, {
StyleSheet,
Animated,
View,
PanResponder,
Platform,
ViewPropTypes,
} from 'react-native';
import Theme from '../../../themes/Theme';
import KeyboardSpace from '../KeyboardSpace/KeyboardSpace';
export default class OverlayView extends Component {
static propTypes = {
style: ViewPropTypes.style,
modal: PropTypes.bool,
animated: PropTypes.bool,
overlayOpacity: PropTypes.number,
overlayPointerEvents: ViewPropTypes.pointerEvents,
autoKeyboardInsets: PropTypes.bool,
closeOnHardwareBackPress: PropTypes.bool, //android only
onAppearCompleted: PropTypes.func,
onDisappearCompleted: PropTypes.func,
onCloseRequest: PropTypes.func, //(overlayView)
};
static defaultProps = {
modal: false,
animated: false,
overlayPointerEvents: 'auto',
autoKeyboardInsets: false,
closeOnHardwareBackPress: true,
};
constructor(props) {
super(props);
this.panResponder = PanResponder.create({
onStartShouldSetPanResponder: (e, gestureState) => true,
onPanResponderGrant: (e, gestureState) =>
(this.touchStateID = gestureState.stateID),
onPanResponderRelease: (e, gestureState) =>
this.touchStateID == gestureState.stateID ? this.closeRequest() : null,
});
this.state = {
overlayOpacity: new Animated.Value(0),
};
}
componentDidMount() {
this.appearAfterMount && this.appear();
if (Platform.OS === 'android') {
let BackHandler = ReactNative.BackHandler
? ReactNative.BackHandler
: ReactNative.BackAndroid;
this.backListener = BackHandler.addEventListener(
'hardwareBackPress',
() => {
if (this.props.closeOnHardwareBackPress) {
this.closeRequest();
return true;
} else {
return false;
}
},
);
}
}
componentWillUnmount() {
this.removeBackListener();
}
removeBackListener() {
if (this.backListener) {
this.backListener.remove();
this.backListener = null;
}
}
get overlayOpacity() {
let { overlayOpacity } = this.props;
return overlayOpacity || overlayOpacity === 0
? overlayOpacity
: Theme.overlayOpacity;
}
get appearAnimates() {
let duration = 200;
let animates = [
Animated.timing(this.state.overlayOpacity, {
toValue: this.overlayOpacity,
duration,
useNativeDriver: false,
}),
];
return animates;
}
get disappearAnimates() {
let duration = 200;
let animates = [
Animated.timing(this.state.overlayOpacity, {
toValue: 0,
duration,
useNativeDriver: false,
}),
];
return animates;
}
get appearAfterMount() {
return true;
}
get overlayPointerEvents() {
//override in Toast
return this.props.overlayPointerEvents;
}
appear(animated = this.props.animated, additionAnimates = null) {
if (animated) {
this.state.overlayOpacity.setValue(0);
Animated.parallel(
this.appearAnimates.concat(additionAnimates),
).start((e) => this.appearCompleted());
} else {
this.state.overlayOpacity.setValue(this.overlayOpacity);
this.appearCompleted();
}
}
disappear(animated = this.props.animated, additionAnimates = null) {
if (animated) {
Animated.parallel(
this.disappearAnimates.concat(additionAnimates),
).start((e) => this.disappearCompleted());
this.state.overlayOpacity.addListener((e) => {
if (e.value < 0.01) {
this.state.overlayOpacity.stopAnimation();
this.state.overlayOpacity.removeAllListeners();
}
});
} else {
this.disappearCompleted();
}
}
appearCompleted() {
let { onAppearCompleted } = this.props;
onAppearCompleted && onAppearCompleted();
}
disappearCompleted() {
let { onDisappearCompleted } = this.props;
onDisappearCompleted && onDisappearCompleted();
}
close(animated = this.props.animated) {
if (this.closed) {
return true;
}
this.closed = true;
this.removeBackListener();
this.disappear(animated);
return true;
}
closeRequest() {
let { modal, onCloseRequest } = this.props;
if (onCloseRequest) {
onCloseRequest(this);
} else if (!modal) {
this.close();
}
}
buildStyle() {
let { style } = this.props;
style = [{ backgroundColor: 'rgba(0, 0, 0, 0)', flex: 1 }].concat(style);
return style;
}
renderContent() {
return this.props.children;
}
render() {
let { autoKeyboardInsets } = this.props;
return (
<View style={styles.screen} pointerEvents={this.overlayPointerEvents}>
<Animated.View
style={[
styles.screen,
{ backgroundColor: '#000', opacity: this.state.overlayOpacity },
]}
{...this.panResponder.panHandlers}
/>
<View style={this.buildStyle()} pointerEvents="box-none">
{this.renderContent()}
</View>
{autoKeyboardInsets ? <KeyboardSpace /> : null}
</View>
);
}
}
var styles = StyleSheet.create({
screen: {
backgroundColor: 'rgba(0, 0, 0, 0)',
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
},
});
// TopView.js
'use strict';
import React, { Component, PureComponent } from 'react';
import {
StyleSheet,
AppRegistry,
DeviceEventEmitter,
View,
Animated,
} from 'react-native';
import PropTypes from 'prop-types';
import Theme from '../../../themes/Theme';
let keyValue = 0;
export default class TopView extends Component {
static add(element) {
let key = ++keyValue;
DeviceEventEmitter.emit('addOverlay', { key, element });
return key;
}
static remove(key) {
DeviceEventEmitter.emit('removeOverlay', { key });
}
static removeAll() {
DeviceEventEmitter.emit('removeAllOverlay', {});
}
static transform(transform, animated, animatesOnly = null) {
DeviceEventEmitter.emit('transformRoot', {
transform,
animated,
animatesOnly,
});
}
static restore(animated, animatesOnly = null) {
DeviceEventEmitter.emit('restoreRoot', { animated, animatesOnly });
}
constructor(props) {
super(props);
this.state = {
elements: [],
translateX: new Animated.Value(0),
translateY: new Animated.Value(0),
scaleX: new Animated.Value(1),
scaleY: new Animated.Value(1),
};
this.handlers = [];
}
static contextTypes = {
registerTopViewHandler: PropTypes.func,
unregisterTopViewHandler: PropTypes.func,
};
static childContextTypes = {
registerTopViewHandler: PropTypes.func,
unregisterTopViewHandler: PropTypes.func,
};
getChildContext() {
let { registerTopViewHandler, unregisterTopViewHandler } = this.context;
if (!registerTopViewHandler) {
registerTopViewHandler = (handler) => {
this.handlers.push(handler);
};
unregisterTopViewHandler = (handler) => {
for (let i = this.handlers.length - 1; i >= 0; --i) {
if (this.handlers[i] === handler) {
this.handlers.splice(i, 1);
return true;
}
}
return false;
};
}
return { registerTopViewHandler, unregisterTopViewHandler };
}
get handler() {
return this.handlers.length > 0
? this.handlers[this.handlers.length - 1]
: this;
}
componentDidMount() {
let { registerTopViewHandler } = this.context;
if (registerTopViewHandler) {
registerTopViewHandler(this);
return;
}
DeviceEventEmitter.addListener('addOverlay', (e) => this.handler.add(e));
DeviceEventEmitter.addListener('removeOverlay', (e) =>
this.handler.remove(e),
);
DeviceEventEmitter.addListener('removeAllOverlay', (e) =>
this.handler.removeAll(e),
);
DeviceEventEmitter.addListener('transformRoot', (e) =>
this.handler.transform(e),
);
DeviceEventEmitter.addListener('restoreRoot', (e) =>
this.handler.restore(e),
);
}
componentWillUnmount() {
let { unregisterTopViewHandler } = this.context;
if (unregisterTopViewHandler) {
unregisterTopViewHandler(this);
return;
}
DeviceEventEmitter.removeAllListeners('addOverlay');
DeviceEventEmitter.removeAllListeners('removeOverlay');
DeviceEventEmitter.removeAllListeners('removeAllOverlay');
DeviceEventEmitter.removeAllListeners('transformRoot');
DeviceEventEmitter.removeAllListeners('restoreRoot');
}
add(e) {
let { elements } = this.state;
elements.push(e);
this.setState({ elements });
}
remove(e) {
let { elements } = this.state;
for (let i = elements.length - 1; i >= 0; --i) {
if (elements[i].key === e.key) {
elements.splice(i, 1);
break;
}
}
this.setState({ elements });
}
removeAll(e) {
let { elements } = this.state;
this.setState({ elements: [] });
}
transform(e) {
let { translateX, translateY, scaleX, scaleY } = this.state;
let { transform, animated, animatesOnly } = e;
let tx = 0,
ty = 0,
sx = 1,
sy = 1;
transform.map((item) => {
if (item && typeof item === 'object') {
for (let name in item) {
let value = item[name];
switch (name) {
case 'translateX':
tx = value;
break;
case 'translateY':
ty = value;
break;
case 'scaleX':
sx = value;
break;
case 'scaleY':
sy = value;
break;
}
}
}
});
if (animated) {
let animates = [
Animated.spring(translateX, {
toValue: tx,
friction: 9,
useNativeDriver: false,
}),
Animated.spring(translateY, {
toValue: ty,
friction: 9,
useNativeDriver: false,
}),
Animated.spring(scaleX, {
toValue: sx,
friction: 9,
useNativeDriver: false,
}),
Animated.spring(scaleY, {
toValue: sy,
friction: 9,
useNativeDriver: false,
}),
];
animatesOnly
? animatesOnly(animates)
: Animated.parallel(animates).start();
} else {
if (animatesOnly) {
let animates = [
Animated.timing(translateX, {
toValue: tx,
duration: 1,
useNativeDriver: false,
}),
Animated.timing(translateY, {
toValue: ty,
duration: 1,
useNativeDriver: false,
}),
Animated.timing(scaleX, {
toValue: sx,
duration: 1,
useNativeDriver: false,
}),
Animated.timing(scaleY, {
toValue: sy,
duration: 1,
useNativeDriver: false,
}),
];
animatesOnly(animates);
} else {
translateX.setValue(tx);
translateY.setValue(ty);
scaleX.setValue(sx);
scaleY.setValue(sy);
}
}
}
restore(e) {
let { translateX, translateY, scaleX, scaleY } = this.state;
let { animated, animatesOnly } = e;
if (animated) {
let animates = [
Animated.spring(translateX, {
toValue: 0,
friction: 9,
useNativeDriver: false,
}),
Animated.spring(translateY, {
toValue: 0,
friction: 9,
useNativeDriver: false,
}),
Animated.spring(scaleX, {
toValue: 1,
friction: 9,
useNativeDriver: false,
}),
Animated.spring(scaleY, {
toValue: 1,
friction: 9,
useNativeDriver: false,
}),
];
animatesOnly
? animatesOnly(animates)
: Animated.parallel(animates).start();
} else {
if (animatesOnly) {
let animates = [
Animated.timing(translateX, {
toValue: 0,
duration: 1,
useNativeDriver: false,
}),
Animated.timing(translateY, {
toValue: 0,
duration: 1,
useNativeDriver: false,
}),
Animated.timing(scaleX, {
toValue: 1,
duration: 1,
useNativeDriver: false,
}),
Animated.timing(scaleY, {
toValue: 1,
duration: 1,
useNativeDriver: false,
}),
];
animatesOnly(animates);
} else {
translateX.setValue(0);
translateY.setValue(0);
scaleX.setValue(1);
scaleY.setValue(1);
}
}
}
render() {
let { elements, translateX, translateY, scaleX, scaleY } = this.state;
let transform = [{ translateX }, { translateY }, { scaleX }, { scaleY }];
return (
<View style={{ backgroundColor: Theme.screenColor, flex: 1 }}>
<Animated.View style={{ flex: 1, transform: transform }}>
<PureView>{this.props.children}</PureView>
</Animated.View>
{elements.map((item, index) => {
return (
<View
key={'topView' + item.key}
style={styles.overlay}
pointerEvents="box-none"
>
{item.element}
</View>
);
})}
</View>
);
}
}
var styles = StyleSheet.create({
overlay: {
backgroundColor: 'rgba(0, 0, 0, 0)',
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
},
});
class PureView extends PureComponent {
render() {
return <View style={{ flex: 1 }}>{this.props.children}</View>;
}
}
if (!AppRegistry.registerComponentOld) {
AppRegistry.registerComponentOld = AppRegistry.registerComponent;
}
AppRegistry.registerComponent = function (appKey, componentProvider) {
class RootElement extends Component {
render() {
let Component = componentProvider();
return (
<TopView>
<Component {...this.props} />
</TopView>
);
}
}
return AppRegistry.registerComponentOld(appKey, () => RootElement);
};
自己创建的Toast等待中的提示
import React from "react";
import { ActivityIndicator } from "react-native";
import {Toast,Theme } from "teaset";
let customKey = null;
// 显示转圈圈
Toast.showLoading=(text)=> {
if (customKey) return;
customKey = Toast.show({
// 转圈圈里面的文本
text,
// ActivityIndicator 就是那个转圈圈的
icon: <ActivityIndicator size='large' color={Theme.toastIconTintColor} />,
position: 'center',
duration: 100000,
});
}
// 隐藏转圈圈
Toast.hideLoading=()=> {
if (!customKey) return;
Toast.hide(customKey);
customKey = null;
}
export default Toast;
// ————————————————————————————————————————————————————————————————————————
// 示例:
// 在react-native中想要看到一些弹窗提示,把调试关掉ctrl + m
// Toast.message('Taooo', 10000);
// Toast.message("验证码不正确",2000,"center");
如何使用刚才导入的Toast
- 有message、success、fail、smile、sad、info、stop、这几种提示类型
- 示例
import Toast from '../...'
// 位置有'top', 'bottom', 'center'这几种
Toast.message('提示文字信息', '提示时长', '位置');