react native 弹框设计,兼容IOS和Android

1.实现的效果

- 点击按钮,出现弹框,最好有动画;

- 点击弹框周围,弹框消失;

- 点击Android返回键,弹框消失;

- 有“取消”和“确定”按钮,回调函数,巴拉巴拉巴。。。。

2.实现思路

- 弹框外层组件使用Modal,免得监听安卓返回键;

- 动画使用Animated;

- 最外层(绝对定位)存在一个宽高等于手机屏幕的activeOpacity=1的TouchableOpacity,点击能够使Modal消失;

3.源码

Popover.js

/**
 * 弹框 
 * @flow
 */

import React, { Component } from 'react';
import {
  Platform,
  StyleSheet,
  Text,
  View,
  BackHandler,
  Modal,
  Button,
  TouchableOpacity,
  Animated,
  Image,
  StatusBar
} from 'react-native';

import PropTypes from 'prop-types';
import theme from '../utils/theme';
const {screenWidth, screenHeight, segment} = theme;

export default class Popover extends Component {
  
  constructor(props){
    super(props);
    this.state = {
      show: false,
      pageX: 0,
      pageY: 0,
      height: 0,
      scaleAnimationValue: new Animated.Value(0)
    }
  }
  // 打开弹框
  showModal() {
    this.setState({
      show: true,
    });

    if(this.props.useAnimation){
      Animated.spring(this.state.scaleAnimationValue, {
          toValue: 1,
          duration: 200,
      }).start();
    }

  }
  // 关闭弹框
  onRequestClose() {
    if(this.props.useAnimation){
      this.state.scaleAnimationValue.setValue(0);
    }
    this.setState({
      show: false
    });
  }

  render() {
    const {hasCancelBtn, cancelCallback, title, content, ensureCallback, useAnimation, takePicsCallback, albumCallback, wordsCallback, hasWordBtn} = this.props;
    return (
      <View style={styles.modalContainer}>
        <Modal 
            animationType={"none"}
            transparent={true}
            visible={this.state.show}
            onRequestClose={() => this.onRequestClose()}>

          <TouchableOpacity 
            activeOpacity={1} 
            style={styles.modalContainer}
            onPress={() => this.onRequestClose()}
          >
            <View style={styles.outerContainer} />
          </TouchableOpacity>

          <Animated.View style={[styles.container, {transform: [{scale: useAnimation ? this.state.scaleAnimationValue : 1}]}]}>
            <View
                style={styles.promptContainer}
            >
              
              {
                this.props.onlyInChose ?
                <View style={styles.choseContainer}>
                  <TouchableOpacity activeOpacity={0.75} onPress={takePicsCallback}>
                    <View style={styles.choseItem}>
                      <Image source={require('../img/camera_small.png')} style={{width: theme.scaleSize(50),height: theme.scaleSize(40), resizeMode:'contain'}} />
                      <Text style={{color: '#343434',fontSize: theme.setSpText(32)}}>拍摄照片</Text>
                      <View style={{width: theme.scaleSize(48),height: theme.scaleSize(40)}} />
                    </View>
                  </TouchableOpacity>
                  <TouchableOpacity activeOpacity={0.75} onPress={albumCallback}>
                    <View style={[styles.choseItem, {borderBottomWidth: hasWordBtn ? segment.width : 0}]}>
                      <Image source={require('../img/page_small.png')} style={{width: theme.scaleSize(50),height: theme.scaleSize(40), resizeMode:'contain'}} />
                      <Text style={{color: '#343434',fontSize: theme.setSpText(32)}}>相册选择</Text>
                      <View style={{width: theme.scaleSize(48),height: theme.scaleSize(40)}} />
                    </View>
                  </TouchableOpacity>
                  {
                    hasWordBtn &&
                    <TouchableOpacity activeOpacity={0.75} onPress={wordsCallback}>
                      <View style={[styles.choseItem, {borderBottomWidth: 0}]}>
                        <Image source={require('../img/Text_small.png')} style={{width: theme.scaleSize(50),height: theme.scaleSize(40), resizeMode:'contain'}} />
                        <Text style={{color: '#343434',fontSize: theme.setSpText(32)}}>文字说说</Text>
                        <View style={{width: theme.scaleSize(48),height: theme.scaleSize(40)}} />
                      </View>
                    </TouchableOpacity>
                  }
                </View>
                :
                <View>
                  <View style={styles.titleContainer}>
                    <Text style={{color: '#333333',fontSize: theme.setSpText(38)}}>{title}</Text>
                  </View>

                  <View style={styles.contentContainer}>
                    <Text style={{color: '#343434',fontSize: theme.setSpText(34)}}>{content}</Text>
                  </View>
                </View>
              }
                
            </View>
            {
              this.props.onlyInChose ? 
              null
              :
              <View style={styles.buttonContainer}>
                {
                  hasCancelBtn && 
                  <TouchableOpacity
                      activeOpacity={0.75}
                      style={[styles.center, {flex: 4.5}]}
                      onPress={() => {
                        this._close();
                        // 取消后的事件
                        cancelCallback && cancelCallback()
                      }}
                  >
                      <Text style={{fontSize: theme.setSpText(38), color: '#666666'}}>取消</Text>
                  </TouchableOpacity>
                }
                <View style={[styles.line]}/>
                <TouchableOpacity
                    activeOpacity={0.75}
                    style={[styles.center, {flex: 4.5}]}
                    onPress={() => {
                        this.onRequestClose();
                        // 子组件传递数据到父组件 
                        ensureCallback && ensureCallback();
                    }}
                >
                    <Text style={{fontSize: theme.setSpText(38), color: '#3382C7'}}>确定</Text>
                </TouchableOpacity>
              </View>
            }
          </Animated.View>
        </Modal>
      </View>
    );
  }

  _close() {
    this.onRequestClose();
  }

  ensureCallback() {
    this.props.navigation.goBack(null);
  }

}

Popover.propTypes = {
  hasCancelBtn: PropTypes.bool,      // 是否保留“取消”按钮
  onlyInChose: PropTypes.bool,       // 是否应用在发布说说时
  cancelCallback: PropTypes.func,    // 点击取消后的回调
  ensureCallback: PropTypes.func,    // 点击确定后的回调
  wordsCallback: PropTypes.func,     // 点击发布说说回调
  albumCallback: PropTypes.func,     // 选取相册回调
  takePicsCallback: PropTypes.func,  // 拍摄照片回调
  title: PropTypes.string,           // 提示标题
  content: PropTypes.string,         // 提示内容文字
  useAnimation: PropTypes.bool,      // 是否应用动画
  hasWordBtn: PropTypes.bool         // 是否包含说说
}
Popover.defaultProps = {
  hasCancelBtn: true,
  title: '未定义标题',
  content: '未定义内容',
  onlyInChose: false,
  useAnimation: false,
  hasWordBtn: true
}

const styles = StyleSheet.create({
  modalContainer: {
    position: 'absolute',
    width: screenWidth,
    zIndex: -1,
    height: Platform.OS == 'android' ? screenHeight - StatusBar.currentHeight : screenHeight - 20,
  },
  outerContainer: {
    position: 'absolute',
    width: screenWidth,
    height: screenHeight,
    backgroundColor: 'rgba(1, 1, 1, 0.5)'
  },
  container: {
    width: theme.scaleSize(660),
    backgroundColor: '#fff',
    borderRadius: 8,
    justifyContent: 'center',
    alignItems: 'center',
    position: 'absolute',
    top: theme.scaleSize(410),
    left: (screenWidth - theme.scaleSize(660)) / 2,
  },
  promptContainer: {
    width: theme.scaleSize(660),
    alignItems: 'center',
  },
  choseContainer:{

  },
  choseItem:{
    height: theme.scaleSize(120),
    width: theme.scaleSize(660),
    paddingHorizontal: theme.scaleSize(160),
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    borderBottomWidth: segment.width,
    borderBottomColor: '#999999',
  },
  titleContainer:{
    width: theme.scaleSize(660),
    height: theme.scaleSize(116),
    borderBottomColor: '#F2F2F2',
    borderBottomWidth: segment.width,
    justifyContent: 'center',
    alignItems: 'center'
  },
  contentContainer:{
    width: theme.scaleSize(660),
    height: theme.scaleSize(246),
    padding:theme.scaleSize(40),
    // justifyContent: 'center',
    alignItems: 'center'
  },
  buttonContainer: {
    height: theme.scaleSize(100),
    width: theme.scaleSize(660),
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'center',
    borderTopWidth: segment.width,
    borderColor: '#999999'
  },
  center: {
    alignItems: 'center',
    justifyContent: 'center',
  },
  line: {
    height: theme.scaleSize(100),
    width: segment.width,
    backgroundColor: '#999999'
  }
});


效果图:
1212121212.gif
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,552评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,666评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,519评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,180评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,205评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,344评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,781评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,449评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,635评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,467评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,515评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,217评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,775评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,851评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,084评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,637评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,204评论 2 341

推荐阅读更多精彩内容