在React Native中实现一个Popover(气泡弹出框)组件,用于展示一些相关的信息,如提示、警告、错误等,同时支持自定义内容和位置

在React Native中实现一个Popover(气泡弹出框)组件,你可以使用第三方库,如react-native-popover-view,或者自己手动实现一个。这里将分别介绍这两种方法。

方法1:使用第三方库 react-native-popover-view

  1. 安装库

    首先,你需要安装react-native-popover-view库。在你的项目目录中运行以下命令:

    bash 复制代码
    npm install react-native-popover-view --save
    或者使用yarn
    yarn add react-native-popover-view
  2. 使用Popover组件

    安装完成后,你可以在React Native项目中这样使用Popover组件:

    javascript 复制代码
    import React from 'react';
    import { View, Text, TouchableOpacity } from 'react-native';
    import Popover from 'react-native-popover-view';
    import { PopoverButton } from 'react-native-popover-view/components'; // 引入PopoverButton组件
    
    const MyComponent = () => {
      return (
        <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
          <PopoverButton onPress={() => console.log('Button Pressed')}>
            <Text>点击我</Text>
          </PopoverButton>
          <Popover>
            <View style={{ width: 150, height: 100, backgroundColor: 'white', borderRadius: 5, padding: 10 }}>
              <Text>我是Popover内容</Text>
            </View>
          </Popover>
        </View>
      );
    };
    
    export default MyComponent;

方法2:手动实现Popover组件

如果你希望更灵活地控制Popover的样式和行为,或者想要完全自定义,你可以手动实现一个Popover组件。以下是一个基本的实现示例:

  1. 创建Popover组件

    javascript 复制代码
    import React, { useState } from 'react';
    import { View, Text, TouchableOpacity, Modal, StyleSheet } from 'react-native';
    
    const Popover = ({ children, content, onClose }) => {
      const [visible, setVisible] = useState(false);
    
      const openPopover = () => setVisible(true);
      const closePopover = () => {
        setVisible(false);
        if (onClose) onClose(); // 调用外部提供的关闭回调函数
      };
    
      return (
        <View>
          <TouchableOpacity onPress={openPopover}>{children}</TouchableOpacity>
          <Modal visible={visible} transparent animationType="none" onRequestClose={closePopover}>
            <TouchableOpacity style={styles.overlay} onPress={closePopover}>
              <View style={styles.popover}>{content}</View>
            </TouchableOpacity>
          </Modal>
        </View>
      );
    };
    
    const styles = StyleSheet.create({
      overlay: {
        flex: 1,
        backgroundColor: 'rgba(0,0,0,0.5)', // 半透明遮罩层背景色和透明度
        justifyContent: 'center',
        alignItems: 'center',
      },
      popover: {
        backgroundColor: 'white', // Popover背景色
        padding: 20, // 内边距
        borderRadius: 5, // 圆角边框
        elevation: 5, // 阴影效果(harmony)
        shadowColor: '000', // 阴影颜色(harmony)
        shadowOffset: { width: 0, height: 2 }, // 阴影偏移(harmony)
        shadowOpacity: 0.25, // 阴影透明度(harmony)
        shadowRadius: 3.84, // 阴影半径(harmony)
      },
    });

    使用这个组件:

    javascript 复制代码
    const MyComponent = () => {
      return (
        <Popover content={<Text>我是Popover内容</Text>}>
          <Text>点击我</Text>
        </Popover>
      );
    };

    这样你就可以根据需要自定义和扩展Popover的功能和样式了。手动实现的方式提供了更高的灵活性,但需要更多的代码来实现完整的功能。选择哪种方法取决于你的具体需求和偏好。


真实项目代码演示场景:

js 复制代码
// App.tsx
import React, { useState, useRef } from 'react';
import { 
  View, 
  Text, 
  StyleSheet, 
  ScrollView, 
  SafeAreaView,
  Image,
  Dimensions,
  TouchableOpacity,
  Modal,
  Pressable
} from 'react-native';

// Base64 Icons for popover components
const POPOVER_ICONS = {
  info: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFHmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDIgNzkuMTYwOTI0LCAyMDE3LzA3LzEzLTAxOjA2OjM5ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTAxLTAzVDE2OjQ1OjQ5KzA4OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0wMS0wM1QxNjo0NjoxNCswODowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wMS0wM1QxNjo0NjoxNCswODowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoyZjA5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MmYwOWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6MmYwOWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDoyZjA5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHN0RXZ0OndoZW49IjIwMTktMDEtMDNUMTY6NDU6NDlaIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+Af/+/fz7+vn49/b19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwLCgkIBwYFBAMCAQAA//+gB5ZbAAAJRElEQVRoBe1ZaWxUVRQ+97030+k6U6Z0hk5npksptKW0bFJAWRB3UEFFjQuJKIoLJCZqYhRjNC5RFCMuaKJREhMXjAuKggsuRI0bGkEpWymltNBS2ulMZ9qZmfbe9+699715MzPTmdLS6STnfPPee+fc755zz3nnPcNwHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMx......',
  warning: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFHmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDIgNzkuMTYwOTI0LCAyMDE3LzA3LzEzLTAxOjA2OjM5ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTAxLTAzVDE2OjQ2OjIwKzA4OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0wMS0wM1QxNjo0Njo0NSswODowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wMS0wM1QxNjo0Njo0NSswODowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoyZjE5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MmYxOWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6MmYxOWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDoyZjE5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHN0RXZ0OndoZW49IjIwMTktMDEtMDNUMTY6NDY6MjBaIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+Af/+/fz7+vn49/b19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwLCgkIBwYFBAMCAQAA//+gB5ZbAAAJRElEQVRoBe1ZaWxUVRQ+97030+k6U6Z0hk5npksptKW0bFJAWRB3UEFFjQuJKIoLJCZqYhRjNC5RFCMuaKJREhMXjAuKggsuRI0bGkEpWymltNBS2ulMZ9qZmfbe9+699715MzPTmdLS6STnfPPee+fc755zz3nnPcNwHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMx......',
  success: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFHmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDIgNzkuMTYwOTI0LCAyMDE3LzA3LzEzLTAxOjA2OjM5ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTAxLTAzVDE2OjQ2OjUxKzA4OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0wMS0wM1QxNjo0NzoxNiswODowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wMS0wM1QxNjo0NzoxNiswODowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoyZjI5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MmYyOWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6MmYyOWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDoyZjI5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHN0RXZ0OndoZW49IjIwMTktMDEtMDNUMTY6NDY6NTFaIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+Af/+/fz7+vn49/b19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwLCgkIBwYFBAMCAQAA//+gB5ZbAAAJRElEQVRoBe1ZaWxUVRQ+97030+k6U6Z0hk5npksptKW0bFJAWRB3UEFFjQuJKIoLJCZqYhRjNC5RFCMuaKJREhMXjAuKggsuRI0bGkEpWymltNBS2ulMZ9qZmfbe9+699715MzPTmdLS6STnfPPee+fc755zz3nnPcNwHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMx......',
  error: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFHmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDIgNzkuMTYwOTI0LCAyMDE3LzA3LzEzLTAxOjA2OjM5ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTAxLTAzVDE2OjQ3OjIyKzA4OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0wMS0wM1QxNjo0Nzo0OCswODowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wMS0wM1QxNjo0Nzo0OCswODowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoyZjM5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MmYzOWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6MmYzOWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDoyZjM5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHN0RXZ0OndoZW49IjIwMTktMDEtMDNUMTY6NDc6MjJaIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+Af/+/fz7+vn49/b19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwLCgkIBwYFBAMCAQAA//+gB5ZbAAAJRElEQVRoBe1ZaWxUVRQ+97030+k6U6Z0hk5npksptKW0bFJAWRB3UEFFjQuJKIoLJCZqYhRjNC5RFCMuaKJREhMXjAuKggsuRI0bGkEpWymltNBS2ulMZ9qZmfbe9+699715MzPTmdLS6STnfPPee+fc755zz3nnPcNwHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMx......',
  close: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFHmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDIgNzkuMTYwOTI0LCAyMDE3LzA3LzEzLTAxOjA2OjM5ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTAxLTAzVDE2OjQ3OjUzKzA4OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0wMS0wM1QxNjo0ODoxOCswODowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wMS0wM1QxNjo0ODoxOCswODowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoyZjQ5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MmY0OWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6MmY0OWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDoyZjQ5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHN0RXZ0OndoZW49IjIwMTktMDEtMDNUMTY6NDc6NTNaIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+Af/+/fz7+vn49/b19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwLCgkIBwYFBAMCAQAA//+gB5ZbAAAJRElEQVRoBe1ZaWxUVRQ+97030+k6U6Z0hk5npksptKW0bFJAWRB3UEFFjQuJKIoLJCZqYhRjNC5RFCMuaKJREhMXjAuKggsuRI0bGkEpWymltNBS2ulMZ9qZmfbe9+699715MzPTmdLS6STnfPPee+fc755zz3nnPcNwHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMx......',
  menu: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFHmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDIgNzkuMTYwOTI0LCAyMDE3LzA3LzEzLTAxOjA2OjM5ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTAxLTAzVDE2OjQ4OjI0KzA4OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0wMS0wM1QxNjo0ODo0OSswODowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wMS0wM1QxNjo0ODo0OSswODowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoyZjU5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MmY1OWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6MmY1OWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDoyZjU5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHN0RXZ0OndoZW49IjIwMTktMDEtMDNUMTY6NDg6MjRaIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+Af/+/fz7+vn49/b19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwLCgkIBwYFBAMCAQAA//+gB5ZbAAAJRElEQVRoBe1ZaWxUVRQ+97030+k6U6Z0hk5npksptKW0bFJAWRB3UEFFjQuJKIoLJCZqYhRjNC5RFCMuaKJREhMXjAuKggsuRI0bGkEpWymltNBS2ulMZ9qZmfbe9+699715MzPTmdLS6STnfPPee+fc755zz3nnPcNwHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMx......'
};

// 气泡弹出框组件
interface PopoverProps {
  visible: boolean;
  onClose: () => void;
  position: { x: number; y: number };
  content: React.ReactNode;
  placement?: 'top' | 'bottom' | 'left' | 'right';
  backgroundColor?: string;
  borderColor?: string;
}

const Popover: React.FC<PopoverProps> = ({
  visible,
  onClose,
  position,
  content,
  placement = 'top',
  backgroundColor = '#ffffff',
  borderColor = '#e2e8f0'
}) => {
  if (!visible) return null;

  const getPopoverStyle = () => {
    const baseStyle: any = {
      position: 'absolute',
      backgroundColor,
      borderRadius: 8,
      borderWidth: 1,
      borderColor,
      padding: 15,
      shadowColor: '#000',
      shadowOffset: { width: 0, height: 2 },
      shadowOpacity: 0.25,
      shadowRadius: 4,
      elevation: 5,
      maxWidth: 250,
      zIndex: 1000
    };

    switch (placement) {
      case 'bottom':
        return {
          ...baseStyle,
          top: position.y + 10,
          left: position.x - 125,
        };
      case 'left':
        return {
          ...baseStyle,
          top: position.y - 50,
          left: position.x - 270,
        };
      case 'right':
        return {
          ...baseStyle,
          top: position.y - 50,
          left: position.x + 10,
        };
      case 'top':
      default:
        return {
          ...baseStyle,
          bottom: Dimensions.get('window').height - position.y + 10,
          left: position.x - 125,
        };
    }
  };

  const getArrowStyle = () => {
    const baseStyle: any = {
      position: 'absolute',
      width: 0,
      height: 0,
      borderLeftWidth: 8,
      borderRightWidth: 8,
      borderBottomWidth: 8,
      borderLeftColor: 'transparent',
      borderRightColor: 'transparent',
      borderBottomColor: backgroundColor,
    };

    switch (placement) {
      case 'bottom':
        return {
          ...baseStyle,
          top: -8,
          left: 117,
          borderBottomColor: borderColor,
          borderTopWidth: 0,
        };
      case 'left':
        return {
          ...baseStyle,
          top: 42,
          right: -16,
          borderLeftColor: borderColor,
          borderRightWidth: 0,
          transform: [{ rotate: '90deg' }],
        };
      case 'right':
        return {
          ...baseStyle,
          top: 42,
          left: -16,
          borderRightColor: borderColor,
          borderLeftWidth: 0,
          transform: [{ rotate: '-90deg' }],
        };
      case 'top':
      default:
        return {
          ...baseStyle,
          bottom: -8,
          left: 117,
          borderTopColor: borderColor,
          borderBottomWidth: 0,
        };
    }
  };

  return (
    <Modal
      visible={visible}
      transparent={true}
      animationType="fade"
      onRequestClose={onClose}
    >
      <Pressable 
        style={styles.modalOverlay} 
        onPress={onClose}
      >
        <View style={getPopoverStyle()}>
          <View style={getArrowStyle()} />
          {content}
        </View>
      </Pressable>
    </Modal>
  );
};

// 主应用组件
const App = () => {
  const [popoverVisible, setPopoverVisible] = useState(false);
  const [popoverPosition, setPopoverPosition] = useState({ x: 0, y: 0 });
  const [currentPlacement, setCurrentPlacement] = useState<'top' | 'bottom' | 'left' | 'right'>('top');
  const buttonRef = useRef<View>(null);

  const showPopover = (placement: 'top' | 'bottom' | 'left' | 'right') => {
    setCurrentPlacement(placement);
    buttonRef.current?.measure((fx, fy, width, height, px, py) => {
      setPopoverPosition({ x: px + width / 2, y: py + height / 2 });
      setPopoverVisible(true);
    });
  };

  const hidePopover = () => {
    setPopoverVisible(false);
  };

  const popoverContent = (
    <View style={styles.popoverContent}>
      <View style={styles.popoverHeader}>
        <Image source={{ uri: POPOVER_ICONS.info }} style={styles.popoverIcon} />
        <Text style={styles.popoverTitle}>操作提示</Text>
        <TouchableOpacity onPress={hidePopover} style={styles.popoverCloseButton}>
          <Image source={{ uri: POPOVER_ICONS.close }} style={styles.popoverCloseIcon} />
        </TouchableOpacity>
      </View>
      <Text style={styles.popoverText}>
        这是一个气泡弹出框,用于显示额外的信息或操作选项。您可以点击不同的按钮来查看不同方向的弹出效果。
      </Text>
      <View style={styles.popoverActions}>
        <TouchableOpacity style={styles.popoverActionButton}>
          <Text style={styles.popoverActionText}>了解更多</Text>
        </TouchableOpacity>
        <TouchableOpacity style={[styles.popoverActionButton, styles.popoverActionSecondary]}>
          <Text style={styles.popoverActionTextSecondary}>关闭</Text>
        </TouchableOpacity>
      </View>
    </View>
  );

  return (
    <SafeAreaView style={styles.container}>
      <View style={styles.header}>
        <Text style={styles.headerTitle}>气泡弹出框组件演示</Text>
        <Text style={styles.headerSubtitle}>多种方向弹出效果展示</Text>
      </View>
      
      <ScrollView contentContainerStyle={styles.contentContainer}>
        <View style={styles.section}>
          <Text style={styles.sectionTitle}>不同方向弹出示例</Text>
          <View style={styles.buttonContainer}>
            <TouchableOpacity 
              style={[styles.actionButton, styles.topButton]} 
              onPress={() => showPopover('top')}
            >
              <Text style={styles.buttonText}>向上弹出</Text>
            </TouchableOpacity>
            
            <View style={styles.middleRow}>
              <TouchableOpacity 
                style={[styles.actionButton, styles.leftButton]} 
                onPress={() => showPopover('left')}
              >
                <Text style={styles.buttonText}>向左弹出</Text>
              </TouchableOpacity>
              
              <View 
                ref={buttonRef} 
                style={styles.centerTarget}
              >
                <Text style={styles.targetText}>点击目标</Text>
              </View>
              
              <TouchableOpacity 
                style={[styles.actionButton, styles.rightButton]} 
                onPress={() => showPopover('right')}
              >
                <Text style={styles.buttonText}>向右弹出</Text>
              </TouchableOpacity>
            </View>
            
            <TouchableOpacity 
              style={[styles.actionButton, styles.bottomButton]} 
              onPress={() => showPopover('bottom')}
            >
              <Text style={styles.buttonText}>向下弹出</Text>
            </TouchableOpacity>
          </View>
        </View>
        
        <View style={styles.featuresSection}>
          <Text style={styles.featuresTitle}>功能特性</Text>
          <View style={styles.featureList}>
            <View style={styles.featureItem}>
              <Text style={styles.featureBullet}>•</Text>
              <Text style={styles.featureText}>支持四个方向弹出(上、下、左、右)</Text>
            </View>
            <View style={styles.featureItem}>
              <Text style={styles.featureBullet}>•</Text>
              <Text style={styles.featureText}>自定义样式和颜色主题</Text>
            </View>
            <View style={styles.featureItem}>
              <Text style={styles.featureBullet}>•</Text>
              <Text style={styles.featureText}>响应式设计和动画效果</Text>
            </View>
            <View style={styles.featureItem}>
              <Text style={styles.featureBullet}>•</Text>
              <Text style={styles.featureText}>支持复杂内容展示</Text>
            </View>
          </View>
        </View>
        
        <View style={styles.usageSection}>
          <Text style={styles.usageTitle}>使用说明</Text>
          <Text style={styles.usageText}>
            气泡弹出框组件可用于显示额外信息、操作菜单或用户提示。
            通过指定弹出方向和位置,可以灵活适应不同的界面布局需求。
          </Text>
        </View>
      </ScrollView>
      
      <View style={styles.footer}>
        <Text style={styles.footerText}>© 2023 气泡弹出框组件. All rights reserved.</Text>
      </View>
      
      <Popover
        visible={popoverVisible}
        onClose={hidePopover}
        position={popoverPosition}
        content={popoverContent}
        placement={currentPlacement}
      />
    </SafeAreaView>
  );
};

const { width, height } = Dimensions.get('window');

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f8fafc',
  },
  header: {
    backgroundColor: '#ffffff',
    paddingTop: 20,
    paddingBottom: 25,
    paddingHorizontal: 20,
    borderBottomWidth: 1,
    borderBottomColor: '#e2e8f0',
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.05,
    shadowRadius: 4,
    elevation: 2,
  },
  headerTitle: {
    fontSize: 26,
    fontWeight: '700',
    color: '#0f172a',
    textAlign: 'center',
    marginBottom: 5,
  },
  headerSubtitle: {
    fontSize: 15,
    color: '#64748b',
    textAlign: 'center',
  },
  contentContainer: {
    padding: 20,
  },
  section: {
    marginBottom: 30,
  },
  sectionTitle: {
    fontSize: 22,
    fontWeight: '700',
    color: '#0f172a',
    marginBottom: 20,
    paddingLeft: 10,
    borderLeftWidth: 4,
    borderLeftColor: '#3b82f6',
  },
  buttonContainer: {
    alignItems: 'center',
  },
  middleRow: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
    width: '100%',
    marginVertical: 30,
  },
  actionButton: {
    backgroundColor: '#3b82f6',
    paddingHorizontal: 20,
    paddingVertical: 12,
    borderRadius: 8,
    minWidth: 100,
    alignItems: 'center',
  },
  topButton: {
    marginBottom: 30,
  },
  bottomButton: {
    marginTop: 30,
  },
  leftButton: {
    marginRight: 20,
  },
  rightButton: {
    marginLeft: 20,
  },
  buttonText: {
    fontSize: 16,
    fontWeight: '600',
    color: '#ffffff',
  },
  centerTarget: {
    width: 100,
    height: 100,
    backgroundColor: '#f1f5f9',
    borderRadius: 50,
    justifyContent: 'center',
    alignItems: 'center',
    borderWidth: 2,
    borderColor: '#94a3b8',
  },
  targetText: {
    fontSize: 16,
    fontWeight: '600',
    color: '#64748b',
  },
  featuresSection: {
    backgroundColor: '#ffffff',
    borderRadius: 16,
    padding: 20,
    marginBottom: 30,
    borderWidth: 1,
    borderColor: '#e2e8f0',
  },
  featuresTitle: {
    fontSize: 20,
    fontWeight: '700',
    color: '#0f172a',
    marginBottom: 15,
    textAlign: 'center',
  },
  featureList: {
    paddingLeft: 10,
  },
  featureItem: {
    flexDirection: 'row',
    alignItems: 'center',
    marginBottom: 12,
  },
  featureBullet: {
    fontSize: 18,
    color: '#3b82f6',
    marginRight: 10,
  },
  featureText: {
    fontSize: 16,
    color: '#334155',
    flex: 1,
  },
  usageSection: {
    backgroundColor: '#ffffff',
    borderRadius: 16,
    padding: 20,
    borderWidth: 1,
    borderColor: '#e2e8f0',
  },
  usageTitle: {
    fontSize: 20,
    fontWeight: '700',
    color: '#0f172a',
    marginBottom: 15,
    textAlign: 'center',
  },
  usageText: {
    fontSize: 16,
    color: '#334155',
    lineHeight: 24,
    textAlign: 'center',
  },
  footer: {
    paddingVertical: 15,
    alignItems: 'center',
    borderTopWidth: 1,
    borderTopColor: '#e2e8f0',
    backgroundColor: '#ffffff',
  },
  footerText: {
    fontSize: 14,
    color: '#64748b',
    fontWeight: '500',
  },
  // Popover Styles
  modalOverlay: {
    flex: 1,
    backgroundColor: 'transparent',
  },
  popoverContent: {
    width: 250,
  },
  popoverHeader: {
    flexDirection: 'row',
    alignItems: 'center',
    marginBottom: 10,
  },
  popoverIcon: {
    width: 20,
    height: 20,
    tintColor: '#3b82f6',
    marginRight: 8,
  },
  popoverTitle: {
    fontSize: 18,
    fontWeight: '700',
    color: '#0f172a',
    flex: 1,
  },
  popoverCloseButton: {
    padding: 5,
  },
  popoverCloseIcon: {
    width: 16,
    height: 16,
    tintColor: '#94a3b8',
  },
  popoverText: {
    fontSize: 15,
    color: '#64748b',
    lineHeight: 22,
    marginBottom: 15,
  },
  popoverActions: {
    flexDirection: 'row',
    justifyContent: 'flex-end',
  },
  popoverActionButton: {
    backgroundColor: '#3b82f6',
    paddingHorizontal: 15,
    paddingVertical: 8,
    borderRadius: 6,
    marginLeft: 10,
  },
  popoverActionSecondary: {
    backgroundColor: '#f1f5f9',
  },
  popoverActionText: {
    fontSize: 14,
    fontWeight: '600',
    color: '#ffffff',
  },
  popoverActionTextSecondary: {
    fontSize: 14,
    fontWeight: '600',
    color: '#64748b',
  },
});

export default App;

这段React Native气泡弹出框组件实现了一个灵活的浮动提示系统,其核心原理是通过模态框和绝对定位在屏幕指定位置显示内容。组件通过传入的position坐标和placement方向参数,动态计算弹出框和箭头的具体位置,通过样式组合实现不同方向的定位效果。

在鸿蒙系统适配方面,这段代码面临着根本性的架构差异。React Native的气泡弹窗依赖于Modal组件创建全屏透明层,然后通过View的绝对定位实现具体位置的显示。而鸿蒙的ArkUI框架提供了Popup组件作为系统级的气泡弹窗实现。鸿蒙的Popup组件采用声明式配置方式,开发者只需设置目标组件和弹出位置,系统会自动处理弹窗的显示和定位,无需手动计算坐标偏移量。

鸿蒙的Popup组件在底层直接调用系统窗口管理服务,能够实现更稳定的显示效果和更好的性能表现。特别是当需要处理复杂的定位逻辑时,鸿蒙的系统级实现能够避免JavaScript计算带来的性能损耗。

手势处理机制方面,React Native通过Pressable组件处理触摸事件,而鸿蒙的Popup组件内置了完整的手势识别系统,能够自动处理点击外部关闭、滑动消失等交互行为,无需开发者手动实现这些逻辑。

动画系统也存在显著差异,React Native的动画需要通过animationType属性配置,而鸿蒙的Popup组件提供了更丰富的动画效果选项,包括淡入淡出、缩放、滑动等多种动画类型。

资源管理方面,React Native通过URI加载图标资源,而鸿蒙使用ResourceManager统一管理应用资源,这种差异导致图片加载路径需要重新设计。


打包

接下来通过打包命令npn run harmony将reactNative的代码打包成为bundle,这样可以进行在开源鸿蒙OpenHarmony中进行使用。

打包之后再将打包后的鸿蒙OpenHarmony文件拷贝到鸿蒙的DevEco-Studio工程目录去:

最后运行效果图如下显示:

相关推荐
ohyeah8 小时前
深入理解 React 中的 useRef:不只是获取 DOM 元素
前端·react.js
低保和光头哪个先来8 小时前
场景6:对浏览器内核的理解
开发语言·前端·javascript·vue.js·前端框架
ji_shuke9 小时前
canvas绘制拖拽箭头
开发语言·javascript·ecmascript
2501_946244789 小时前
Flutter & OpenHarmony OA系统设置页面组件开发指南
开发语言·javascript·flutter
cz追天之路10 小时前
华为机考 ------ 识别有效的IP地址和掩码并进行分类统计
javascript·华为·typescript·node.js·ecmascript·less·css3
l1t10 小时前
DeepSeek总结的算法 X 与舞蹈链文章
前端·javascript·算法
千寻girling11 小时前
面试官 : “ 说一下 localhost 和127.0.0.1 的区别 ? ”
前端·javascript·面试
YAY_tyy11 小时前
Turfjs+Three.js:地理数据的三维建模应用
前端·javascript·3d·arcgis·turfjs
@淡 定11 小时前
DDD领域事件详解:抽奖系统实战
开发语言·javascript·网络
汐泽学园12 小时前
基于Vue的幼儿绘本阅读启蒙网站设计与实现
前端·javascript·vue.js