ReactNative常用组件之Popover弹窗控件的使用

先放图:
这里写图片描述

首先下载第三方组件

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>
    }
}

此篇文章仅做备份,方便以后使用

猜你喜欢

转载自blog.csdn.net/lantiankongmo/article/details/80843481