先放图:
首先下载第三方组件
npm i --save react-native-popover
此第三方组件是用老语法写的,直接运行如果报错的话 从node_modules文件件里面找个这个组件把里面的Popover.js的代码替换成如下代码:
/**
* Created by xiaowuzai on 2018/3/29.
*/
import React, { Component } from 'react';
import PropTypes from "prop-types";
import {
StyleSheet,
Dimensions,
Animated,
Text,
TouchableWithoutFeedback,
View,
Easing
} from 'react-native';
var noop = () => {};
var {height: SCREEN_HEIGHT, width: SCREEN_WIDTH} = Dimensions.get('window');
var DEFAULT_ARROW_SIZE = new Size(10, 5);
function Point(x, y) {
this.x = x;
this.y = y;
}
function Size(width, height) {
this.width = width;
this.height = height;
}
function Rect(x, y, width, height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
export default class Popover extends Component{
constructor(props){
super(props)
this.state={
contentSize: {},
anchorPoint: {},
popoverOrigin: {},
placement: 'auto',
isTransitioning: false,
scale: new Animated.Value(0),
translate: new Animated.ValueXY(),
fade: new Animated.Value(0)
}
this.computeAutoGeometry=this.computeAutoGeometry.bind(this)
this.computeGeometry=this.computeGeometry.bind(this)
this.measureContent=this.measureContent.bind(this)
this.computeTopGeometry=this.computeTopGeometry.bind(this)
this.computeBottomGeometry=this.computeBottomGeometry.bind(this)
this.computeLeftGeometry=this.computeLeftGeometry.bind(this)
this.computeRightGeometry=this.computeRightGeometry.bind(this)
this.getArrowSize=this.getArrowSize.bind(this)
this.getArrowColorStyle=this.getArrowColorStyle.bind(this)
this.getArrowRotation=this.getArrowRotation.bind(this)
this.getArrowDynamicStyle=this.getArrowDynamicStyle.bind(this)
this.getTranslateOrigin=this.getTranslateOrigin.bind(this)
this._startAnimation=this._startAnimation.bind(this)
this._startDefaultAnimation=this._startDefaultAnimation.bind(this)
this._getDefaultAnimatedStyles=this._getDefaultAnimatedStyles.bind(this)
this._getExtendedStyles=this._getExtendedStyles.bind(this)
}
measureContent(x) {
var {width, height} = x.nativeEvent.layout;
var contentSize = {width, height};
var geom = this.computeGeometry({contentSize});
var isAwaitingShow = this.state.isAwaitingShow;
this.setState(Object.assign(geom,
{contentSize, isAwaitingShow: undefined}), () => {
// Once state is set, call the showHandler so it can access all the geometry
// from the state
isAwaitingShow && this._startAnimation({show: true});
});
}
computeGeometry({contentSize, placement}) {
placement = placement || this.props.placement;
var options = {
displayArea: this.props.displayArea,
fromRect: this.props.fromRect,
arrowSize: this.getArrowSize(placement),
contentSize,
}
switch (placement) {
case 'top':
return this.computeTopGeometry(options);
case 'bottom':
return this.computeBottomGeometry(options);
case 'left':
return this.computeLeftGeometry(options);
case 'right':
return this.computeRightGeometry(options);
default:
return this.computeAutoGeometry(options);
}
}
computeTopGeometry({displayArea, fromRect, contentSize, arrowSize}) {
var popoverOrigin = new Point(
Math.min(displayArea.x + displayArea.width - contentSize.width,
Math.max(displayArea.x, fromRect.x + (fromRect.width - contentSize.width) / 2)),
fromRect.y - contentSize.height - arrowSize.height);
var anchorPoint = new Point(fromRect.x + fromRect.width / 2.0, fromRect.y);
return {
popoverOrigin,
anchorPoint,
placement: 'top',
}
}
computeBottomGeometry({displayArea, fromRect, contentSize, arrowSize}) {
var popoverOrigin = new Point(
Math.min(displayArea.x + displayArea.width - contentSize.width,
Math.max(displayArea.x, fromRect.x + (fromRect.width - contentSize.width) / 2)),
fromRect.y + fromRect.height + arrowSize.height);
var anchorPoint = new Point(fromRect.x + fromRect.width / 2.0, fromRect.y + fromRect.height);
return {
popoverOrigin,
anchorPoint,
placement: 'bottom',
}
}
computeLeftGeometry({displayArea, fromRect, contentSize, arrowSize}) {
var popoverOrigin = new Point(fromRect.x - contentSize.width - arrowSize.width,
Math.min(displayArea.y + displayArea.height - contentSize.height,
Math.max(displayArea.y, fromRect.y + (fromRect.height - contentSize.height) / 2)));
var anchorPoint = new Point(fromRect.x, fromRect.y + fromRect.height / 2.0);
return {
popoverOrigin,
anchorPoint,
placement: 'left',
}
}
computeRightGeometry({displayArea, fromRect, contentSize, arrowSize}) {
var popoverOrigin = new Point(fromRect.x + fromRect.width + arrowSize.width,
Math.min(displayArea.y + displayArea.height - contentSize.height,
Math.max(displayArea.y, fromRect.y + (fromRect.height - contentSize.height) / 2)));
var anchorPoint = new Point(fromRect.x + fromRect.width, fromRect.y + fromRect.height / 2.0);
return {
popoverOrigin,
anchorPoint,
placement: 'right',
}
}
computeAutoGeometry({displayArea, contentSize}) {
var placementsToTry = ['left', 'right', 'bottom', 'top'];
for (var i = 0; i < placementsToTry.length; i++) {
var placement = placementsToTry[i];
var geom = this.computeGeometry({contentSize: contentSize, placement: placement});
var {popoverOrigin} = geom;
if (popoverOrigin.x >= displayArea.x
&& popoverOrigin.x <= displayArea.x + displayArea.width - contentSize.width
&& popoverOrigin.y >= displayArea.y
&& popoverOrigin.y <= displayArea.y + displayArea.height - contentSize.height) {
break;
}
}
return geom;
}
getArrowSize(placement) {
var size = this.props.arrowSize;
switch(placement) {
case 'left':
case 'right':
return new Size(size.height, size.width);
default:
return size;
}
}
getArrowColorStyle(color) {
return { borderTopColor: color };
}
getArrowRotation(placement) {
switch (placement) {
case 'bottom':
return '180deg';
case 'left':
return '-90deg';
case 'right':
return '90deg';
default:
return '0deg';
}
}
getArrowDynamicStyle() {
var {anchorPoint, popoverOrigin} = this.state;
var arrowSize = this.props.arrowSize;
var width = arrowSize.width + 2;
var height = arrowSize.height * 2 + 2;
return {
left: anchorPoint.x - popoverOrigin.x - width / 2,
top: anchorPoint.y - popoverOrigin.y - height / 2,
width: width,
height: height,
borderTopWidth: height / 2,
borderRightWidth: width / 2,
borderBottomWidth: height / 2,
borderLeftWidth: width / 2,
}
}
getTranslateOrigin() {
var {contentSize, popoverOrigin, anchorPoint} = this.state;
var popoverCenter = new Point(popoverOrigin.x + contentSize.width / 2,
popoverOrigin.y + contentSize.height / 2);
return new Point(anchorPoint.x - popoverCenter.x, anchorPoint.y - popoverCenter.y);
}
componentWillReceiveProps(nextProps) {
var willBeVisible = nextProps.isVisible;
var {
isVisible,
} = this.props;
if (willBeVisible !== isVisible) {
if (willBeVisible) {
// We want to start the show animation only when contentSize is known
// so that we can have some logic depending on the geometry
this.setState({contentSize: {}, isAwaitingShow: true});
} else {
this._startAnimation({show: false});
}
}
}
_startAnimation({show}) {
var handler = this.props.startCustomAnimation || this._startDefaultAnimation;
handler({show, doneCallback: () => this.setState({isTransitioning: false})});
this.setState({isTransitioning: true});
}
_startDefaultAnimation({show, doneCallback}) {
var animDuration = 300;
var values = this.state;
var translateOrigin = this.getTranslateOrigin();
if (show) {
values.translate.setValue(translateOrigin);
}
var commonConfig = {
duration: animDuration,
easing: show ? Easing.out(Easing.back()) : Easing.inOut(Easing.quad),
}
Animated.parallel([
Animated.timing(values.fade, {
toValue: show ? 1 : 0,
...commonConfig,
}),
Animated.timing(values.translate, {
toValue: show ? new Point(0, 0) : translateOrigin,
...commonConfig,
}),
Animated.timing(values.scale, {
toValue: show ? 1 : 0,
...commonConfig,
})
]).start(doneCallback);
}
_getDefaultAnimatedStyles() {
// If there's a custom animation handler,
// we don't return the default animated styles
if (typeof this.props.startCustomAnimation !== 'undefined') {
return null;
}
var animatedValues = this.state;
return {
backgroundStyle: {
opacity: animatedValues.fade,
},
arrowStyle: {
transform: [
{
scale: animatedValues.scale.interpolate({
inputRange: [0, 1],
outputRange: [0, 1],
extrapolate: 'clamp',
}),
}
],
},
contentStyle: {
transform: [
{translateX: animatedValues.translate.x},
{translateY: animatedValues.translate.y},
{scale: animatedValues.scale},
],
}
};
}
_getExtendedStyles() {
var background = [];
var popover = [];
var arrow = [];
var content = [];
[this._getDefaultAnimatedStyles(), this.props].forEach((source) => {
if (source) {
background.push(source.backgroundStyle);
popover.push(source.popoverStyle);
arrow.push(source.arrowStyle);
content.push(source.contentStyle);
}
});
return {
background,
popover,
arrow,
content,
}
}
render() {
if (!this.props.isVisible && !this.state.isTransitioning) {
return null;
}
var {popoverOrigin, placement} = this.state;
var extendedStyles = this._getExtendedStyles();
var contentStyle = [styles.content, ...extendedStyles.content];
var arrowColor = StyleSheet.flatten(contentStyle).backgroundColor;
var arrowColorStyle = this.getArrowColorStyle(arrowColor);
var arrowDynamicStyle = this.getArrowDynamicStyle();
var contentSizeAvailable = this.state.contentSize.width;
// Special case, force the arrow rotation even if it was overriden
var arrowStyle = [styles.arrow, arrowDynamicStyle, arrowColorStyle, ...extendedStyles.arrow];
var arrowTransform = (StyleSheet.flatten(arrowStyle).transform || []).slice(0);
arrowTransform.unshift({rotate: this.getArrowRotation(placement)});
arrowStyle = [...arrowStyle, {transform: arrowTransform}];
return (
<TouchableWithoutFeedback onPress={this.props.onClose}>
<View style={[styles.container, contentSizeAvailable && styles.containerVisible ]}>
<Animated.View style={[styles.background, ...extendedStyles.background]}/>
<Animated.View style={[styles.popover, {
top: popoverOrigin.y,
left: popoverOrigin.x,
}, ...extendedStyles.popover]}>
<Animated.View style={arrowStyle}/>
<Animated.View ref='content' onLayout={this.measureContent} style={contentStyle}>
{this.props.children}
</Animated.View>
</Animated.View>
</View>
</TouchableWithoutFeedback>
);
}
};
Popover.defaultProps={
isVisible: false,
displayArea: new Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT),
arrowSize: DEFAULT_ARROW_SIZE,
placement: 'auto',
onClose: noop
}
var styles = StyleSheet.create({
container: {
opacity: 0,
top: 0,
bottom: 0,
left: 0,
right: 0,
position: 'absolute',
backgroundColor: 'transparent',
},
containerVisible: {
opacity: 1,
},
background: {
top: 0,
bottom: 0,
left: 0,
right: 0,
position: 'absolute',
//backgroundColor: 'rgba(0,0,0,0.5)',
},
popover: {
backgroundColor: 'transparent',
position: 'absolute',
shadowColor: 'black',
shadowOffset: {width: 0, height: 2},
shadowRadius: 2,
shadowOpacity: 0.8,
},
content: {
borderRadius: 3,
padding: 6,
backgroundColor: '#fff',
},
arrow: {
position: 'absolute',
borderTopColor: 'transparent',
borderRightColor: 'transparent',
borderBottomColor: 'transparent',
borderLeftColor: 'transparent',
},
});
下面是demo代码
/**
* Sample React Native App
* https://github.com/facebook/react-native
* @flow
*/
import React, {Component} from 'react';
import Popover from "react-native-popover"
import {
Platform,
StyleSheet,
Text,
View,
Image,
TouchableOpacity,
TouchableHighlight,
} from 'react-native';
import Toast, {DURATION} from 'react-native-easy-toast';
const instructions = Platform.select({
ios: 'Press Cmd+R to reload,\n' +
'Cmd+D or shake for dev menu',
android: 'Double tap R on your keyboard to reload,\n' +
'Shake or press menu button for dev menu',
});
const spinnerTextArray = ['关羽', '张飞', '马超', '黄忠', "赵云"]
type Props = {};
export default class App extends Component<Props> {
constructor(props) {
super(props);
this.state = {
//下拉列表是否可见
isVisible: false,
//下拉列表大小范围
spinnerRect: {},
}
}
//显示下拉列表
showSpinner() {
this.refs.spinner.measure((ox, oy, width, height, px, py) => {
this.setState({
isVisible: true,
spinnerRect: {x: px, y: py, width: width, height: height}
});
});
}
//隐藏下拉列表
closeSpinner() {
this.setState({
isVisible: false
});
}
//下拉列表每一行点击事件
onItemClick(spinnerItem) {
this.closeSpinner();
this.toast.show(spinnerItem, DURATION.LENGTH_SHORT);
}
render() {
return <View style={{flex: 1, alignItems: 'center'}}>
<TouchableOpacity
ref='spinner'
style={{flexDirection: 'row', alignItems: 'center', marginTop: 10}}
underlayColor='transparent'
onPress={() => this.showSpinner()}>
<Text>
点击可以弹出下拉菜单
</Text>
<Image source={require('./imgs/ic_tiaozhuan_down.png')}/>
</TouchableOpacity>
<Popover
//设置可见性
isVisible={this.state.isVisible}
//设置下拉位置
fromRect={this.state.spinnerRect}
placement="bottom"
//点击下拉框外范围关闭下拉框
onClose={() => this.closeSpinner()}
//设置内容样式
contentStyle={{opacity: 0.82, backgroundColor: '#343434'}}
style={{backgroundColor: 'red'}}>
<View style={{alignItems: 'center'}}>
{spinnerTextArray.map((result, i, arr) => {
return <TouchableHighlight key={i} onPress={() => this.onItemClick(arr[i])}
underlayColor='transparent'>
<Text
style={{fontSize: 18, color: 'white', padding: 8, fontWeight: '400'}}>
{arr[i]}
</Text>
</TouchableHighlight>
})
}
</View>
</Popover>
<Toast ref={toast => {
this.toast = toast
}}/>
</View>
}
}
此篇文章仅做备份,方便以后使用