案例开源地址:https://atomgit.com/nutpi/rn_openharmony_dogimg
请添加图片描述

沉浸式的图片体验

图库页面展示的是缩略图,用户点击后需要一个专门的页面来查看大图。这个页面要做到沉浸式,让用户专注于图片本身。

ImageViewPage 就是这样一个页面。黑色背景、全屏图片、底部操作栏,简洁但功能完整。

获取路由参数

页面需要知道要显示哪张图片,这个信息通过路由参数传递:

import {useRoute, useStore} from '../../hooks';
import type {DogImage} from '../../types';

export function ImageViewPage() {
  const {params} = useRoute<{image: DogImage}>();
  const img = params.image;

useRoute 是自定义 Hook,返回当前路由信息和参数。

泛型 <{image: DogImage}> 指定了参数的类型,这样 TypeScript 就知道 params.image 是什么结构。

从图库页面跳转时传的参数:

navigate('ImageView', {image: img})

这里直接传了整个 image 对象,包含 id、url 等信息。

收藏状态的获取

const {favoriteImages, toggleImage} = useStore();
const isFav = favoriteImages.includes(img.id);

favoriteImages 是收藏的图片 ID 数组。

toggleImage 是切换收藏状态的方法。

isFav 判断当前图片是否已收藏,用于显示不同的图标和文字。

屏幕尺寸的获取

import {Dimensions} from 'react-native';

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

在组件外部获取屏幕尺寸,避免每次渲染都调用。

这两个值用于计算图片的显示尺寸。

页面结构

return (
  <View style={s.container}>
    <Header title="图片详情" right={...} />
    <View style={s.imgBox}>
      <Image source={{uri: img.url}} style={s.img} resizeMode="contain" />
    </View>
    <View style={s.actions}>
      ...
    </View>
  </View>
);

三个部分:

  • Header:顶部导航栏
  • imgBox:图片展示区域
  • actions:底部操作栏

Header 的右侧按钮

<Header title="图片详情" right={
  <TouchableOpacity onPress={() => toggleImage(img.id)}>
    <Text style={s.fav}>{isFav ? '❤️' : '🤍'}</Text>
  </TouchableOpacity>
} />

Header 组件支持 right 属性,可以传入自定义的右侧内容。

这里放了一个收藏按钮,点击切换收藏状态。

图标根据 isFav 显示红心或白心。

图片展示区域

<View style={s.imgBox}>
  <Image 
    source={{uri: img.url}} 
    style={s.img} 
    resizeMode="contain" 
  />
</View>

resizeMode=“contain” 是关键属性。它让图片保持原始比例,完整显示在容器内,不会被裁剪。

其他可选值:

  • cover:填满容器,可能裁剪
  • stretch:拉伸填满,会变形
  • center:居中显示原始尺寸

对于查看大图的场景,contain 是最合适的。

图片容器样式

imgBox: {
  flex: 1, 
  justifyContent: 'center', 
  alignItems: 'center'
},

flex: 1 让容器占据除 Header 和底部操作栏之外的所有空间。

justifyContentalignItems 都设为 center,让图片在容器中居中显示。

图片尺寸计算

img: {width, height: height * 0.6},

宽度用屏幕宽度,高度用屏幕高度的 60%。

为什么不用 100%?因为要给 Header 和底部操作栏留空间。

配合 resizeMode="contain",图片会在这个区域内自适应显示。

底部操作栏

<View style={s.actions}>
  <TouchableOpacity style={s.action} onPress={() => toggleImage(img.id)}>
    <Text style={s.actionIcon}>{isFav ? '❤️' : '🤍'}</Text>
    <Text style={s.actionText}>{isFav ? '已收藏' : '收藏'}</Text>
  </TouchableOpacity>
  <TouchableOpacity style={s.action}>
    <Text style={s.actionIcon}>📤</Text>
    <Text style={s.actionText}>分享</Text>
  </TouchableOpacity>
  <TouchableOpacity style={s.action}>
    <Text style={s.actionIcon}>💾</Text>
    <Text style={s.actionText}>保存</Text>
  </TouchableOpacity>
</View>

三个操作按钮:收藏、分享、保存。

每个按钮都是图标 + 文字的组合。

收藏按钮的图标和文字都会根据状态变化。

操作栏样式

actions: {
  flexDirection: 'row', 
  backgroundColor: '#1a1a1a', 
  paddingVertical: 20, 
  paddingBottom: 36
},

flexDirection: ‘row’ 让三个按钮横向排列。

backgroundColor: ‘#1a1a1a’ 深灰色背景,比纯黑稍亮,有层次感。

paddingBottom: 36 底部多留一些空间,适配有底部安全区的设备。

单个操作按钮样式

action: {flex: 1, alignItems: 'center'},
actionIcon: {fontSize: 26, marginBottom: 4},
actionText: {fontSize: 12, color: '#fff'},

flex: 1 让三个按钮平分宽度。

alignItems: ‘center’ 让图标和文字居中。

图标用 26 号字,文字用 12 号,形成主次分明的视觉层次。

深色主题的设计

整个页面用深色背景:

container: {flex: 1, backgroundColor: '#000'},

深色背景有几个好处:

  • 突出图片:黑色背景让图片更醒目
  • 减少干扰:用户注意力集中在图片上
  • 护眼:在暗环境下看图片更舒适

这是图片查看类应用的常见设计。

收藏功能的双入口

页面上有两个收藏入口:

  1. Header 右侧的心形图标
  2. 底部操作栏的收藏按钮

两个入口调用的是同一个方法 toggleImage(img.id),状态是同步的。

为什么要两个入口?

Header 的按钮方便快速操作,不用滚动到底部。

底部按钮有文字说明,更直观,适合不熟悉图标含义的用户。

分享和保存功能

当前代码里分享和保存按钮只是占位,没有实际功能:

<TouchableOpacity style={s.action}>
  <Text style={s.actionIcon}>📤</Text>
  <Text style={s.actionText}>分享</Text>
</TouchableOpacity>

实现这些功能需要用到原生模块:

分享:调用系统分享面板,可以分享到微信、微博等。

保存:把图片下载到相册,需要存储权限。

这些功能在鸿蒙系统上的实现方式可能和 Android/iOS 不同,需要单独适配。

useRoute Hook 的实现原理

看看 useRoute 是怎么工作的:

export function useRoute<T = any>() {
  const [route, setRoute] = useState(navigator.getRoute());
  const [params, setParams] = useState<T>(navigator.getParams<T>());

  useEffect(() => {
    const unsubscribe = navigator.subscribe((r, p) => {
      setRoute(r);
      setParams((p || {}) as T);
    });
    return unsubscribe;
  }, []);

  return {route, params};
}

初始值从 navigator 获取当前路由和参数。

订阅变化:通过 subscribe 监听路由变化,更新本地状态。

返回值包含路由名和参数,供组件使用。

泛型 T 让调用方可以指定参数类型,获得类型提示。

图片加载的优化

当前实现比较简单,可以优化的点:

加载占位:图片加载前显示一个占位图或 Loading。

const [loaded, setLoaded] = useState(false);

<Image 
  source={{uri: img.url}} 
  onLoad={() => setLoaded(true)}
/>
{!loaded && <ActivityIndicator />}

加载失败处理:网络问题导致图片加载失败时显示错误提示。

<Image 
  source={{uri: img.url}} 
  onError={() => setError(true)}
/>

手势操作的扩展

图片查看页面通常支持手势操作:

双击放大:双击图片放大到 2 倍,再双击恢复。

捏合缩放:两指捏合调整图片大小。

拖动平移:放大后可以拖动查看不同区域。

这些功能需要用到手势系统,React Native 提供了 PanResponder 和 Animated API。

不过为了保持代码简洁和鸿蒙兼容性,当前版本没有实现这些高级功能。

图片信息展示

可以在页面上显示更多图片信息:

{img.breeds && img.breeds.length > 0 && (
  <View style={s.breedInfo}>
    <Text style={s.breedName}>{img.breeds[0].name}</Text>
  </View>
)}

如果图片关联了品种信息,可以显示品种名称,点击跳转到品种详情。

小结

图片查看页面的实现要点:

  • 路由参数:通过 useRoute 获取传递的图片数据
  • 沉浸式设计:深色背景突出图片内容
  • resizeMode:contain 模式保持图片比例
  • 多入口操作:Header 和底部都有收藏按钮
  • 响应式布局:用 Dimensions 获取屏幕尺寸

下一篇讲随机狗狗页面,每次刷新都能看到不同的狗狗,是个很有趣的功能。


欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐