You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Native实现FlatList项点击弹出带背景的详情Modal

实现方案:将优惠券详情改为弹窗展示

需求说明

当前在HomeScreen.js中通过FlatList渲染Cupons组件,点击列表项时会通过navigation跳转到CuponDetails页面。需要修改为点击列表项后弹出弹窗展示详情,弹窗背景保留HomeScreen原界面。

修改步骤

1. 修改HomeScreen.js

在父组件中统一管理弹窗状态和选中的优惠券数据,添加Modal组件承载详情内容:

import { StatusBar } from 'expo-status-bar';
import { Button, Image, Modal, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import {FontAwesome} from "@expo/vector-icons"
import { TextInput } from 'react-native-gesture-handler';
import TopHeading from './home_components/TopHeading';
import TopSlider from './home_components/TopSlider';
import { FlatList } from 'react-native';
import Cupons from './home_components/Cupons';
import { useState, useEffect } from 'react';
import CuponDetails from './CuponDetails';

export default function HomeScreen  () {
  const [dataa= [], setData] = useState([]);
  const [isModalVisible, setIsModalVisible] = useState(false);
  const [selectedCoupon, setSelectedCoupon] = useState(null);

  useEffect(() => {
    fetch('https://newsapi.org/v2/everything?q=Pakistan&from=2022-06-26&sortBy=publishedAt&apiKey=******************')
    .then(response => response.json())
    .then(data => {
      setData(data.articles || []);
    })
    .catch(err => console.error('请求失败:', err));
  }, []);

  const common = [
    {name: "Test-1", url: require("../assets/image.jpg")},
    {name: "Test-2", url: require("../assets/image.jpg")},
    {name: "Test-3", url: require("../assets/image.jpg")},
    {name: "Test-4", url: require("../assets/image.jpg")},
    {name: "Test-5", url: require("../assets/image.jpg")},
  ];

  const cupon= [
    {url: require("../assets/image2.jpg"), head: "Dabdob", desc: "Offer: 10%"},
    {url: require("../assets/image2.jpg"), head: "Noon", desc: "Offer: cashback 10%"},
    {url: require("../assets/image2.jpg"), head: "Deraah", desc: "Offer: Free Shipping"},
    {url: require("../assets/image2.jpg"), head: "Al-Qassim Health Water", desc: "Offer: 11%"},
    {url: require("../assets/image2.jpg"), head: "Danube", desc: "( SR 10 (3 times use"},
    {url: require("../assets/image2.jpg"), head: "Styli Shop", desc: "Offer: 10%"},
    {url: require("../assets/image2.jpg"), head: "Naseem", desc: "Offer: 15%-10%"},
    {url: require("../assets/image2.jpg"), head: "Shgardi", desc: "Offer: 25%"},
    {url: require("../assets/image2.jpg"), head: "Click Toys", desc: "Offer: 10%"},
    {url: require("../assets/image2.jpg"), head: "Nanaa", desc: "Offer: 50% cash back"},
  ];

  const navigation = useNavigation();

  const openModal = (couponData) => {
    setSelectedCoupon(couponData);
    setIsModalVisible(true);
  };

  const closeModal = () => {
    setIsModalVisible(false);
    setSelectedCoupon(null);
  };

  return (
    <View style={styles.container}>
      {/* 原头部内容 */}
      <View style={styles.header}>
          <View style={styles.innerContainer}>
              <Image  style={styles.logo} source={require("../assets/mainLogo.png")} />
              <View style={styles.searchbarContainer}>
                  <FontAwesome 
                  color="orange" 
                  style={{marginTop:12, paddingLeft:5}} 
                  size={15} 
                  name='search' />
                  <TextInput 
                  style={styles.searchBar} 
                  placeholder='Search..'/>
                  </View>
              </View>
              </View>
      {/* 原轮播内容 */}
      <View style={{alignSelf: "stretch", borderColor: "red", height:160, justifyContent:"space-between", alignItems: "center",marginHorizontal:20 }}>
        <TopHeading />
        <FlatList 
        showsHorizontalScrollIndicator={false}
        bounces
        data={common} 
        renderItem = {({item, index})=>{
            return <TopSlider heading= {item.name}  img={item.url} index={index} />
        }}
        horizontal  />
      </View>
      {/* 优惠券列表 */}
      <View style={{flex: 1, marginBottom: 95,marginHorizontal: 0}}>
        <FlatList 
        showsVerticalScrollIndicator={false}
        data={dataa} 
        renderItem = {({item})=>{
            return  <Cupons 
                      imgUrl={item.urlToImage} 
                      name={item.author} 
                      title={item.title} 
                      desc={item.description} 
                      content={item.content} 
                      onCouponPress={() => openModal(item)} 
                    />
        }}
        />
      </View>

      {/* 详情弹窗 */}
      <Modal
        visible={isModalVisible}
        animationType="slide"
        transparent={true}
        onRequestClose={closeModal}
      >
        <View style={styles.modalOverlay}>
          <View style={styles.modalContent}>
            <TouchableOpacity style={styles.closeButton} onPress={closeModal}>
              <FontAwesome name="times" size={20} color="#333" />
            </TouchableOpacity>
            {selectedCoupon && (
              <CuponDetails 
                ImgUrl={selectedCoupon.urlToImage}
                Name={selectedCoupon.author}
                Desc={selectedCoupon.description}
                Title={selectedCoupon.title}
                Content={selectedCoupon.content}
              />
            )}
          </View>
        </View>
      </Modal>
      
      <StatusBar style={'auto'} />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "white",
    alignItems: "center"
  },
  header:{
    borderWidth: 1,
    height: 130,
    backgroundColor: "rgb(32, 68, 124)",
    alignSelf: "stretch",
    alignItems: 'center', 
  },
  logo:{
    width: 40,
    height: 40,
    tintColor: "white",
  },
  innerContainer:{
    flexDirection: "row",
    marginTop: 70,
    alignSelf: "stretch",
    justifyContent: "space-between",
    marginHorizontal: 20,
    alignItems: "center",
    height: 50,
  },
  searchbarContainer:{
    flexDirection: "row",
    backgroundColor: "white",
    borderRadius: 20,
    width: 300,
    height: 40,
  },
  searchBar:{
    paddingLeft: 5,
    width: 280,
  },
  modalOverlay: {
    flex: 1,
    backgroundColor: 'rgba(0,0,0,0.5)',
    justifyContent: 'center',
    alignItems: 'center',
  },
  modalContent: {
    width: '90%',
    maxHeight: '90%',
    backgroundColor: 'white',
    borderRadius: 15,
    overflow: 'hidden',
    position: 'relative',
  },
  closeButton: {
    position: 'absolute',
    top: 15,
    right: 15,
    zIndex: 10,
    backgroundColor: '#f5f5f5',
    borderRadius: 15,
    padding: 5,
  }
});

2. 修改Cupons.js

移除导航跳转逻辑,改为调用父组件传递的点击回调:

import { Image, StyleSheet, Text, TouchableOpacity, View } from 'react-native';

export default function Cupons({imgUrl, name, desc, title, content, onCouponPress}) {
  
  return (
    <TouchableOpacity onPress={onCouponPress}>
      <View style= {styles.container}>
        <View style={styles.topContainer}>
          <View style= {styles.imageContainer}>
            <Image resizeMode='cover' style={styles.image} source={{uri: `${imgUrl}`}}/>
          </View>
          <View style={styles.textContainer}>
            <Text style={{fontWeight: "bold", fontSize: 16}} >{name}</Text>
            <Text style={{fontSize:16, color: "grey", overflow: "hidden", padding:5}}>{title}</Text>
          </View>
        </View>
        <Text style={{alignSelf: "center",  fontSize:20, fontWeight: "bold"}}>Description</Text>
        <Text 
          style={{marginHorizontal:10,  
          marginVertical:10, 
          height: "auto", 
          overflow: "scroll",
          fontSize: 14,
          padding:5, 
          color: "grey",
          alignItems:"center", 
          justifyContent: "center" }}>
          {desc}
        </Text>
      </View>
    </TouchableOpacity>
  );
}

const styles = StyleSheet.create({
  container: {
    backgroundColor: "rgb(250,250,250)",
    height: "auto",
    margin: 10,
    width: 350,
    borderRadius: 10,
    elevation: 5,
    shadowColor: "rgb(100,100,255)",
    position: "relative",
    overflow: "hidden",
    justifyContent: "center",
  },
  topContainer:{
    margin: 10,
    alignItems: "center",
    width: "auto",
    height: "auto",
    justifyContent: "center",
  },
  imageContainer:{
    borderRadius: 10,
    height: 60,
    width:80,
    elevation: 10,
    shadowColor: "rgb(100,100,255)",
    marginBottom: 10,
  },
  image:{
    height:70,
    width: 80,
    borderRadius: 10,
    marginRight: 20,
    marginBottom: 10,
  },
  textContainer:{
    margin: 5,
    height:"auto",
    width:300,
    justifyContent: "space-between",
    alignItems: 'center'
  }
});

3. 修改CuponDetails.js

移除路由依赖,直接接收父组件传递的详情数据:

import { Image, ScrollView, StyleSheet, Text, View } from 'react-native';

export default function CuponDetails({ImgUrl, Name, Desc, Title, Content}) {
  return (
    <ScrollView style={{backgroundColor: "white", flex:1}}>
      <View style= {styles.container}> 
        <View style = {styles.imageContainer}>
          <Image style={{width:250, height: 200,}} resizeMode='cover' source={{uri: `${ImgUrl}`}} />
        </View>
        <View style={styles.headingContainer}>
          <Text style={{fontWeight: "bold", fontSize: 18}}>{Name}</Text>
          <Text style={{fontWeight: "normal", fontSize: 14,}}>{Title}</Text>
        </View>
        <View style={styles.headingContainer}>
          <Text style={{fontWeight: "bold", fontSize: 18}}>Description</Text>
          <Text style={{fontWeight: "normal", fontSize: 14,}}>{Desc}</Text>
        </View>
        <View style={styles.headingContainer}>
          <Text style={{fontWeight: "bold", fontSize: 18}}>Content</Text>
          <Text style={{fontWeight: "normal", fontSize: 14,}}>{Content}</Text>
        </View>
      </View>
    </ScrollView>   
  );
}

const styles = StyleSheet.create({
  container: {
    alignItems: "center",
    backgroundColor: "white",
    paddingTop: 50,
    paddingBottom: 20,
  },
  imageContainer:{
    height: 200,
    width: 250,
    marginBottom: 10,
    borderRadius: 10,
    overflow: "hidden",
    elevation: 2,
    shadowColor: "rgb(100,100,255)",
  },
  headingContainer:{
    width: "90%",
    marginVertical: 10,
    height: "auto",
    alignItems: "center",
    borderRadius: 15,
    padding: 8,
    elevation: 2,
    shadowColor: "rgb(100,100,255)",
    backgroundColor: "white"
  }
})

核心说明

  1. 统一状态管理:弹窗显示状态和选中数据放在HomeScreen中,避免子组件重复维护状态。
  2. 透明弹窗效果:通过transparent={true}和半透明遮罩层,实现背景保留原界面的视觉效果。
  3. 组件解耦:Cupons只负责渲染列表项,点击逻辑交给父组件;CuponDetails只负责渲染详情,不再依赖路由参数。

内容的提问来源于stack exchange,提问作者Ozelea

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 21:57:35