当前位置: 首页 > news >正文

ReactNative【实战系列教程】我的小红书 5 -- 文章详情(含轮播图 ImageSlider,点亮红心动画 Heart,嵌套评论等)

最终效果

在这里插入图片描述

安装依赖

npm i dayjs

用于对时间进行格式化

必备组件

轮播图 ImageSlider

https://blog.csdn.net/weixin_41192489/article/details/149224510

点亮红心动画 Heart

components/Heart.tsx

import AntDesign from "@expo/vector-icons/AntDesign";
import React, { useEffect, useRef, useState } from "react";
import { Animated, TouchableOpacity } from "react-native";
type Props = {value: boolean;onValueChanged?: (value: boolean) => void;size?: number;color?: string;
};
// eslint-disable-next-line react/display-name
export default (props: Props) => {const { value, onValueChanged, size = 20, color = "black" } = props;const [showState, setShowState] = useState<boolean>(false);const scale = useRef<Animated.Value>(new Animated.Value(0)).current;const alpha = useRef<Animated.Value>(new Animated.Value(0)).current;useEffect(() => {setShowState(value);}, [value]);const onHeartPress = () => {const newState = !showState;setShowState(newState);onValueChanged?.(newState);if (newState) {alpha.setValue(1);const scaleAnim = Animated.timing(scale, {toValue: 1.8,duration: 300,useNativeDriver: false,});const alphaAnim = Animated.timing(alpha, {toValue: 0,duration: 400,useNativeDriver: false,delay: 200,});Animated.parallel([scaleAnim, alphaAnim]).start();} else {scale.setValue(0);alpha.setValue(0);}};return (<TouchableOpacity onPress={onHeartPress}>{showState ? (<AntDesign name="heart" size={size} color="red" />) : (<AntDesign name="hearto" size={size} color={color} />)}<Animated.Viewstyle={{width: size,height: size,borderRadius: size / 2,borderWidth: size / 20,position: "absolute",borderColor: "#ff2442",transform: [{ scale: scale }],opacity: alpha,}}/></TouchableOpacity>);
};

获取路由参数

import { useLocalSearchParams } from "expo-router";
  const params = useLocalSearchParams();const { id } = params;

此种路由参数为路由添加的查询条件

router.push(`/articleDetail?id=${article.id}`);

代码实现

app/_layout.tsx

注册路由

              <Stack.Screenname="articleDetail"options={{ headerShown: false }}/>

app/articleDetail.tsx

文章详情页

import Heart from "@/components/Heart";
import { ImageSlider } from "@/components/slidePager";
import AntDesign from "@expo/vector-icons/AntDesign";
import Entypo from "@expo/vector-icons/Entypo";
import EvilIcons from "@expo/vector-icons/EvilIcons";
import dayjs from "dayjs";
import { useLocalSearchParams } from "expo-router";
import { useEffect, useState } from "react";
import {Dimensions,Image,ScrollView,StyleSheet,Text,TextInput,TouchableOpacity,View,
} from "react-native";
export default function Index() {const [height, setHeight] = useState<number>(300);const { width: SCREEN_WIDTH } = Dimensions.get("window");const userInfo = {avatar:"https://gips1.baidu.com/it/u=705524004,2611014536&fm=3074&app=3074&f=PNG?w=2048&h=2048",};const detail = {avatarUrl:"https://img2.baidu.com/it/u=902203086,3868774028&fm=253&app=138&f=JPEG?w=500&h=500",userName: "张三",images: ["https://img0.baidu.com/it/u=1168912228,741609775&fm=253&fmt=auto&app=120&f=JPEG?w=1422&h=800","https://gips3.baidu.com/it/u=1014935733,598223672&fm=3074&app=3074&f=PNG?w=1440&h=2560","https://img0.baidu.com/it/u=2779316202,721068756&fm=253&app=120&f=JPEG?w=1422&h=800",],title: "让我抱抱,一起温暖,真的好治愈",desc: "家里鸡蛋不知道怎么吃的,可以去试试这个火腿肠炒蛋,\n我妈说居然这么好吃。\n香辣过瘾真的忒下饭了,做法鸡蛋,你们也去安排起来。",tag: ["每秒都值得记录", "治愈系", "治愈猫咪"],dateTime: "2023-02-27 12:20",location: "广东",favoriteCount: 1098,collectionCount: 208,isFavorite: false,isCollection: false,comments: [{userName: "胖胖胖",avatarUrl:"https://img0.baidu.com/it/u=2875122849,1736556673&fm=253&app=138&f=JPEG?w=500&h=500",message:"果断关注,这个博主做的菜真实色香味俱全。每天跟着学做菜,我要做大厨了,啊哈哈~",dateTime: "2023-02-28 07:23",location: "成都",favoriteCount: 45,isFavorite: false,children: [{userName: "小飞飞爱猫咪",avatarUrl:"https://img2.baidu.com/it/u=1922178401,3519789815&fm=253&app=138&f=JPEG?w=500&h=500",message: "安排起来,这个做法很简单的哦。",dateTime: "2023-02-28 13:23",location: "广东",favoriteCount: 1,isFavorite: false,},],},{userName: "左半",avatarUrl:"https://img2.baidu.com/it/u=3118961545,1128223138&fm=253&app=138&f=JPEG?w=500&h=500",message: "看着真有食欲",dateTime: "2023-02-28 09:09",location: "福建",favoriteCount: 2,isFavorite: false,children: undefined,},],};useEffect(() => {if (!detail?.images) {return;}const firstImg = detail?.images[0];Image.getSize(firstImg, (width: number, height: number) => {const showHeight = (SCREEN_WIDTH * height) / width;setHeight(showHeight);});// eslint-disable-next-line react-hooks/exhaustive-deps}, []);const params = useLocalSearchParams();const { id } = params;// 此处省略访问接口获取文章详情console.log(id);const renderImages = () => {const { images } = detail;if (!images?.length) {return null;}const data: any[] = detail.images.map((i) => {return { img: i };});return (<View style={{ paddingBottom: 30 }}><ImageSliderdata={data}autoPlay={false}closeIconColor="white"caroselImageStyle={{ height }}indicatorContainerStyle={{ bottom: -40 }}activeIndicatorStyle={styles.activeDot}inActiveIndicatorStyle={styles.inActiveDot}/></View>);};const renderInfo = () => {const tags = detail.tag?.map((i) => `# ${i}`).join(" ");return (<><Text style={styles.articleTitleTxt}>{detail.title}</Text><Text style={styles.descTxt}>{detail.desc}</Text><Text style={styles.tagsTxt}>{tags}</Text><Text style={styles.timeAndLocationTxt}>{detail.dateTime} {detail.location}</Text><View style={styles.line} /></>);};const renderComments = () => {const count = detail.comments?.length || 0;const styles = StyleSheet.create({commentsCountTxt: {fontSize: 14,color: "#666",marginTop: 20,marginLeft: 16,},inputLayout: {width: "100%",padding: 16,flexDirection: "row",alignItems: "center",},userAvatarImg: {width: 32,height: 32,borderRadius: 16,resizeMode: "cover",},commentInput: {flex: 1,height: 40,borderRadius: 16,marginLeft: 12,backgroundColor: "#f0f0f0",color: "#333",paddingVertical: 4,paddingHorizontal: 12,},commentsContainer: {paddingHorizontal: 16,paddingTop: 16,paddingBottom: 32,},commentItem: {width: "100%",flexDirection: "row",},cAvatar: {width: 36,height: 36,resizeMode: "cover",borderRadius: 18,},contentLayout: {flex: 1,marginHorizontal: 12,},nameTxt: {fontSize: 12,color: "#999",},messageTxt: {fontSize: 14,color: "#333",marginTop: 6,},timeLocationTxt: {fontSize: 12,color: "#bbb",},countLayout: {alignItems: "center",},fCount: {fontSize: 12,color: "#666",marginTop: 2,},divider: {marginLeft: 50,marginRight: 0,height: StyleSheet.hairlineWidth,backgroundColor: "#eee",marginVertical: 16,},});return (<><Text style={styles.commentsCountTxt}>{count ? `共 ${count} 条评论` : "暂无评论"}</Text><View style={styles.inputLayout}><Imagestyle={styles.userAvatarImg}source={{ uri: userInfo.avatar }}/><TextInputstyle={styles.commentInput}placeholder="说点什么吧,万一火了呢~"placeholderTextColor={"#bbb"}/></View>{!!count && (<View style={styles.commentsContainer}>{detail.comments?.map((i: ArticleComment, index: number) => {return (<View key={`${index}`} style={{}}><View style={styles.commentItem}><Imagestyle={styles.cAvatar}source={{ uri: i.avatarUrl }}/><View style={styles.contentLayout}><Text style={styles.nameTxt}>{i.userName}</Text><Text style={styles.messageTxt}>{i.message}<Text style={styles.timeLocationTxt}>{dayjs(i.dateTime).format("MM-DD")} {i.location}</Text></Text>{!!i.children?.length &&i.children.map((j: ArticleComment, subIndex: number) => {return (<Viewkey={`${index}-${subIndex}`}style={[styles.commentItem,{ marginTop: 12, width: SCREEN_WIDTH - 80 },]}><Imagestyle={[styles.cAvatar,{ width: 32, height: 32 },]}source={{ uri: j.avatarUrl }}/><View style={styles.contentLayout}><Text style={styles.nameTxt}>{j.userName}</Text><Text style={styles.messageTxt}>{j.message}<Text style={styles.timeLocationTxt}>{dayjs(j.dateTime).format("MM-DD")}{" "}{j.location}</Text></Text></View><View style={styles.countLayout}><Heart size={20} value={j.isFavorite} /><Text style={styles.fCount}>{j.favoriteCount}</Text></View></View>);})}</View><View style={styles.countLayout}><Heart size={20} value={i.isFavorite} /><Text style={styles.fCount}>{i.favoriteCount}</Text></View></View><View style={styles.divider} /></View>);})}</View>)}</>);};const renderBottom = () => {return (<View style={styles.bottomLayout}><View style={styles.bottomEditLayout}><AntDesign name="edit" size={20} color="#333" /><TextInputstyle={styles.bottomCommentInput}placeholder="说点什么"placeholderTextColor={"#333"}/></View><Heart size={22} value={detail.isFavorite} color="grey" /><Text style={styles.bottomCount}>{detail.favoriteCount}</Text><AntDesign name="staro" size={22} color="grey" /><Text style={styles.bottomCount}>{detail.collectionCount}</Text><AntDesign name="message1" size={22} color="grey" /><Text style={styles.bottomCount}>{detail.comments?.length || 0}</Text></View>);};return (<View style={styles.page}><View style={styles.topBar}><TouchableOpacityonPress={() => {console.log("返回");}}><Entypo name="chevron-left" size={24} color="black" /></TouchableOpacity><Image style={styles.avatarImg} source={{ uri: detail.avatarUrl }} /><Text style={styles.nameTxt}>{detail.userName}</Text><TouchableOpacityonPress={() => {console.log("关注");}}><Text style={styles.followBtn}>关注</Text></TouchableOpacity><TouchableOpacityonPress={() => {console.log("转发");}}><EvilIconsstyle={styles.shareIcon}name="external-link"size={32}color="black"/></TouchableOpacity></View><ScrollView showsVerticalScrollIndicator={false}>{renderImages()}{renderInfo()}{renderComments()}</ScrollView>{renderBottom()}</View>);
}
const styles = StyleSheet.create({page: {backgroundColor: "white",width: "100%",height: "100%",},bottomLayout: {width: "100%",height: 64,flexDirection: "row",alignItems: "center",paddingHorizontal: 16,borderTopWidth: 1,borderTopColor: "#eee",},bottomEditLayout: {height: 40,flex: 1,backgroundColor: "#f0f0f0",borderRadius: 20,flexDirection: "row",alignItems: "center",paddingHorizontal: 12,marginRight: 12,},bottomCommentInput: {height: "100%",paddingTop: 6,paddingLeft: 6,color: "#333",textAlignVertical: "center",paddingVertical: 0,},bottomCount: {fontSize: 14,color: "grey",marginLeft: 6,marginRight: 8,},shareIcon: {marginBottom: 4,},articleTitleTxt: {fontSize: 18,color: "#333",fontWeight: "bold",paddingHorizontal: 16,},descTxt: {fontSize: 15,color: "#333",marginTop: 6,paddingHorizontal: 16,},tagsTxt: {fontSize: 15,color: "#305090",marginTop: 6,paddingHorizontal: 16,},timeAndLocationTxt: {fontSize: 12,color: "#bbb",marginVertical: 16,marginLeft: 16,},line: {marginHorizontal: 16,height: StyleSheet.hairlineWidth,backgroundColor: "#eee",},topBar: {flexDirection: "row",alignItems: "center",padding: 10,},avatarImg: {width: 30,height: 30,resizeMode: "cover",borderRadius: 50,margin: 10,},nameTxt: {fontSize: 14,color: "#999",marginLeft: 6,flex: 1,},followBtn: {fontSize: 12,color: "red",borderWidth: 1,borderColor: "red",borderRadius: 10,paddingVertical: 4,paddingHorizontal: 12,marginRight: 10,},activeDot: {width: 6,height: 6,backgroundColor: "#ff2442",borderRadius: 3,},inActiveDot: {width: 6,height: 6,backgroundColor: "#c0c0c0",borderRadius: 3,},
});
http://www.dtcms.com/a/271459.html

相关文章:

  • 【三维重建】一、设备分类
  • 优化 ECharts 多条折线:折线数据不完整导致的X轴日期错乱问题
  • 【面试精讲】I2C 子系统核心结构与常见问题深度解析
  • 【PTA数据结构 | C语言版】一元多项式求导
  • Redis-哨兵选取主节点流程
  • 操作系统核心技术剖析:从Android驱动模型到鸿蒙微内核的国产化实践
  • HashMap的Get(),Put()源码解析
  • CTFHub————Web{信息泄露[备份文件下载(网站源码、bak文件)]}
  • 微服务架构中数据一致性保证机制深度解析
  • [Backlog] 核心协调器 | 终端用户界面(TUI)实现 | 多分支任务冲突解决 | 测试验证体系
  • vue2中使用xgplayer播放流视频
  • 方差、协方差和协方差矩阵
  • 软件编码规范、运行时错误、安全漏洞与缺陷:解析及库博的检测能力
  • Dify 文本语意识别与自动补全工作流
  • VD6052系列 30V 200mA的低功耗稳压器芯片 适用12V/24V供电MCU
  • 腾讯0708面试手撕题:严格递增数字分割
  • 17-C#封装,继承,多态与重载
  • git配置密钥
  • MTK-系统设置Settings 开机累计时长源码分析
  • AI芯片产品经理:算力革命的架构师
  • Mysql底层专题(七)MVCC多版本并发控制机制
  • STM32-定时器输入捕获
  • 高级LoRA:面向垂直领域LLM的实战微调指南——LoRA合并、续训、堆叠,Checkpoint管理
  • 佰力博PEAI压电分析仪-精准测量压电材料d33系数
  • RAG实战指南 Day 11:文本分块策略与最佳实践
  • HCIP(综合实验)
  • 腾讯位置商业授权未来驾车ETA(批量)
  • Fluent许可配置常见问题
  • ARM汇编编程(AArch64架构)课程 - 第8章:控制流与循环
  • 数字化管理新趋势:权限分级看板如何筑牢安全防线