在这里插入图片描述

在跨端开发领域,React Native(以下简称RN)凭借“一次开发、多端复用”的优势,成为主流选择之一。而鸿蒙系统作为新兴的全场景分布式操作系统,RN通过@react-native-ohos/adapter适配器,可实现RN代码向鸿蒙原生组件的映射,无需大量修改即可完成跨端适配。

本文将针对一份完整的RN鸿蒙跨端瀑布流列表代码,按片段逐段解读,重点剖析懒加载图片、瀑布流布局、下拉刷新/上拉加载更多等核心功能的实现逻辑,以及RN代码在鸿蒙端的跨端适配要点,适合有RN基础、想入门鸿蒙跨端开发的开发者参考。


代码片段


// App.tsx
import React, { useState, useEffect, useCallback } from 'react';
import { 
  View, 
  Text, 
  StyleSheet, 
  ScrollView, 
  SafeAreaView,
  Image,
  Dimensions,
  TouchableOpacity,
  FlatList,
  RefreshControl,
  ActivityIndicator
} from 'react-native';

// Base64 Icons for list components
const LIST_ICONS = {
  loading: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFHmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDIgNzkuMTYwOTI0LCAyMDE3LzA3LzEzLTAxOjA2OjM5ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTAxLTAzVDE2OjU1OjQ5KzA4OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0wMS0wM1QxNjo1NjoxNCswODowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wMS0wM1QxNjo1NjoxNCswODowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoyZjA5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MmYwOWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6MmYwOWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDoyZjA5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHN0RXZ0OndoZW49IjIwMTktMDEtMDNUMTY6NTU6NDlaIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+Af/+/fz7+vn49/b19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwLCgkIBwYFBAMCAQAA//+gB5ZbAAAJRElEQVRoBe1ZaWxUVRQ+97030+k6U6Z0hk5npksptKW0bFJAWRB3UEFFjQuJKIoLJCZqYhRjNC5RFCMuaKJREhMXjAuKggsuRI0bGkEpWymltNBS2ulMZ9qZmfbe9+699715MzPTmdLS6STnfPPee+fc755zz3nnPcNwHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMx......',
  // 省略其他图标Base64编码
};


1. 核心依赖

该部分导入了RN核心组件与React基础Hooks,是RN鸿蒙跨端开发的基础,重点关注2个跨端适配关键点:

  • 组件兼容性:导入的View、Text、Image、FlatList等均为RN核心组件,在鸿蒙端会通过@react-native-ohos/adapter适配器,自动映射为鸿蒙原生组件(如View映射为鸿蒙ComponentImage映射为鸿蒙Image组件,FlatList映射为鸿蒙高性能列表组件ListContainer),无需额外修改代码即可实现跨端渲染。

  • 功能组件适配RefreshControl(下拉刷新)、ActivityIndicator(加载指示器)在鸿蒙端的适配的兼容性较好,RN原生API可直接复用;Dimensions用于获取设备屏幕尺寸,为跨端响应式布局提供支撑,鸿蒙手机、平板等不同尺寸设备均适用。

2. Base64图标定义:跨端资源加载的最优解

代码中将加载中、加载失败、点赞、评论等图标转为Base64编码,嵌入代码中而非通过网络或本地资源引入,这是RN鸿蒙跨端开发中资源适配的关键优化,核心优势有3点:

  • 规避跨端资源路径问题:RN原生开发中,本地资源需配置metro.config.js,而鸿蒙端的资源目录结构(如main_pagesresources)与RN原生不同,直接引用本地资源极易出现“资源找不到”的报错。Base64编码直接嵌入代码,无需依赖任何本地/网络资源,实现“一次编码,多端可用”。

  • 提升初始加载速度:图标作为列表组件的基础UI元素,Base64编码无需额外发起网络请求,可随组件一起渲染,避免因图标加载延迟导致的UI闪烁,尤其适配鸿蒙端对“启动速度”和“流畅度”的严苛要求。

  • 减小跨端打包复杂度:若使用本地图标,需为RN端、鸿蒙端分别放置对应资源并配置路径,增加打包体积和维护成本;Base64编码无需额外打包资源,简化跨端构建流程。

⚠️ 注意:Base64编码会增加代码体积,建议仅用于小图标(如本文中30x30px的状态图标);若图标较大(如启动图、Banner图),可采用“鸿蒙端本地资源+RN端本地资源”的双端适配方案,通过Platform.OS === 'harmony'判断环境,加载对应平台的资源。


列表项类型


// 列表项类型
interface ListItem {
  id: string;
  title: string;
  description: string;
  imageUrl: string;
  likes: number;
  comments: number;
  shares: number;
  author: string;
  timestamp: string;
  width: number;
  height: number;
}

使用TypeScript定义ListItem接口,规范列表项的数据结构,这在RN鸿蒙跨端开发中至关重要,核心价值体现在2点:

  • 类型安全,避免跨端数据异常:RN与鸿蒙端的JavaScript引擎存在细微差异,若数据类型不明确,可能出现“undefined转null”“数字转字符串”等异常。通过接口定义,强制约束每个字段的类型(如likes为数字类型、imageUrl为字符串类型),可提前规避跨端数据渲染异常。

  • 跨端开发协作效率提升:接口定义清晰了数据传递的规范,前端(RN)、后端、鸿蒙端开发者可基于同一接口开发,避免因数据结构不一致导致的联调问题;同时,TypeScript的类型提示的可减少代码错误,提升开发效率。

重点关注2个与UI渲染相关的字段:widthheight,用于计算图片宽高比,避免瀑布流布局中图片拉伸或裁剪,这是瀑布流布局的核心前提(后续会详细解读)。

三、LazyImage 懒加载图片组件

懒加载图片组件


// 懒加载图片组件
const LazyImage = ({ source, style, placeholderColor = '#334155', errorColor = '#dc2626' }: { 
  source: { uri: string }; 
  style: any; 
  placeholderColor?: string;
  errorColor?: string;
}) => {
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(false);

  return (
    <View style={style}>
      {loading && !error && (
        <View style={[StyleSheet.absoluteFill, { backgroundColor: placeholderColor, justifyContent: 'center', alignItems: 'center' }]}>
<Image source={{ uri: LIST_ICONS.loading }} style={{ width: 30, height: 30, tintColor: '#94a3b8' }} />
        </View>
      )}
      {error && (
        <View style={[StyleSheet.absoluteFill, { backgroundColor: errorColor, justifyContent: 'center', alignItems: 'center' }]}>
         <Image source={{ uri: LIST_ICONS.error }} style={{ width: 30, height: 30, tintColor: '#ffffff' }} />
        </View>
      )}
      <Image
        source={source}
        style={StyleSheet.absoluteFill}
        onLoad={() => setLoading(false)}
        onError={() => {
          setLoading(false);
          setError(true);
        }}
        resizeMode="cover"
      />
    </View>
  );
};

LazyImage是整个代码的核心组件之一,封装了“加载中-加载成功-加载失败”的完整图片加载生命周期,实现图片懒加载(结合后续FlatList的视口监听),同时适配RN与鸿蒙双端,重点解读5个跨端关键技术点:

1. 组件接口

组件接收4个参数,均考虑了跨端复用的灵活性:

  • source: { uri: string }:统一接收网络图片地址,RN原生与鸿蒙端均支持该格式;若需适配本地图片,可扩展为source: { uri: string } | number,通过Platform.OS判断环境(鸿蒙端传入本地资源ID,RN端传入本地资源路径)。

  • style: any:接收外部样式,支持父组件自定义图片尺寸、圆角等样式,适配不同场景(如本文中瀑布流图片、图标等)。

  • placeholderColor、errorColor:可自定义占位背景色和错误背景色,适配不同APP的主题风格,跨端渲染效果一致。

2. 状态管理:

使用useState管理2个核心状态,逻辑跨端完全复用:

  • loading: boolean:控制“加载中”状态的显示,初始值为true,图片加载成功后设为false

  • error: boolean:控制“加载失败”状态的显示,初始值为false,图片加载失败时设为true

状态切换逻辑:图片开始加载(默认loading=true)→ 加载成功(onLoad触发,loading=false)→ 加载失败(onError触发,loading=false、error=true),双端均遵循该逻辑,保证用户体验一致。

3. 跨端UI空白

加载中、加载失败状态均使用View作为占位容器,结合StyleSheet.absoluteFill让占位视图完全覆盖图片容器,避免图片加载过程中出现UI空白。重点说明:

  • 加载中:显示Base64编码的加载图标,配合占位背景色,提升用户等待体验;

  • 加载失败:显示错误图标和红色背景,直观提示用户图片加载失败,后续可扩展“点击重试”功能(本文暂未实现,可参考后续优化建议)。

4. 图片渲染

核心图片渲染使用RNImage组件,跨端适配要点:

  • style={StyleSheet.absoluteFill}:让图片完全填充容器,配合父组件传入的style(尺寸、圆角等),实现自定义渲染效果;

  • resizeMode="cover":保证图片填充容器且不拉伸、不畸变,RN原生与鸿蒙端对resizeMode的支持完全一致(均支持cover、contain、stretch等),无需额外适配;

  • 事件回调:onLoad(加载成功)、onError(加载失败)是RNImage组件的原生事件,鸿蒙端通过适配器完全支持,无需修改回调逻辑。

5. 待结合FlatList视口监听

注意:当前LazyImage组件仅实现了“图片延迟加载”(加载完成前显示占位),真正的“懒加载”(仅当图片进入屏幕视口时才发起加载请求),需结合后续FlatList的onViewableItemsChangedFlatList的原生懒加载能力实现,这也是瀑布流列表性能优化的关键(后续解读)。

业务组件封装:ListItemComponent 列表项组件

列表项组件


// 列表项组件
const ListItemComponent = ({ item }: { item: ListItem }) => {
  const aspectRatio = item.width / item.height;
  const imageHeight = 200; // 固定高度
  const imageWidth = imageHeight * aspectRatio;

  return (
    <View style={styles.listItem}>
      <View style={styles.imageContainer}>
        <LazyImage
          source={{ uri: item.imageUrl }}
          style={{ width: imageWidth, height: imageHeight }}
        />
      </View>
      
      <View style={styles.contentContainer}>
        <Text style={styles.title}>{item.title}</Text>
        <Text style={styles.description}>{item.description}</Text>
        
        <View style={styles.authorInfo}>
          <Text style={styles.author}>作者: {item.author}</Text>
          <Text style={styles.timestamp}>{item.timestamp}</Text>
        </View>
        
        <View style={styles.actionsContainer}>
          <TouchableOpacity style={styles.actionButton}>
            <Image source={{ uri: LIST_ICONS.like }} style={styles.actionIcon} />
            <Text style={styles.actionText}>{item.likes}</Text>
          </TouchableOpacity>
          <TouchableOpacity style={styles.actionButton}>
            <Image source={{ uri: LIST_ICONS.comment }} style={styles.actionIcon} />
            <Text style={styles.actionText}>{item.comments}</Text>
          </TouchableOpacity>
          <TouchableOpacity style={styles.actionButton}>
            <Image source={{ uri: LIST_ICONS.share }} style={styles.actionIcon} />
            <Text style={styles.actionText}>{item.shares}</Text>
          </TouchableOpacity>
        </View>
      </View>
   </View>
  );
};

ListItemComponent是基于LazyImage的业务封装,用于渲染瀑布流列表中的单个图片项,核心是“图片比例适配+UI样式封装”,同时实现点赞、评论、分享等交互功能,跨端适配重点关注3点:

1. 图片比例适配:

瀑布流布局的关键是“图片宽高比一致,避免布局错乱”,代码中通过以下逻辑实现跨端一致的比例适配:

  • 计算宽高比:aspectRatio = item.width / item.height,基于接口返回的图片原始宽高,确保比例准确;

  • 固定高度+动态宽度:设置图片固定高度imageHeight = 200(单位dp),动态计算宽度imageWidth = imageHeight * aspectRatio,保证图片比例不畸变。

跨端适配优势:鸿蒙端屏幕尺寸多样(手机、平板、智慧屏等),固定高度+动态宽度的方式,可避免因屏幕宽度变化导致的图片拉伸或裁剪,同时保证瀑布流两列布局的均匀分布(后续结合FlatList的numColumns={2}实现)。

⚠️ 优化点:可扩展为“根据屏幕宽度动态计算图片高度”,如const imageHeight = (width - 40) / 2 / aspectRatiowidth为屏幕宽度,40为左右间距),让图片项在不同屏幕尺寸下保持一致的布局比例。

2. UI样式封装:

ListItemComponent的样式通过外部StyleSheet定义(后续解读样式部分),跨端适配重点关注2点:

  • 样式属性兼容性:代码中使用的样式属性(borderRadius、overflow、flexDirection等),均是RN与鸿蒙端兼容的属性;鸿蒙端对borderRadius的支持较好,但部分旧版本鸿蒙系统对borderRadiusoverflow: 'hidden'的兼容性不佳,可能出现圆角失效的情况,可通过给图片容器添加overflow: 'hidden'并结合鸿蒙原生样式优化。

  • 文本样式适配Text组件的fontSize、color、fontWeight等属性,RN原生与鸿蒙端完全兼容;但字体家族(fontFamily)适配需注意——鸿蒙端默认字体与RN原生不同,可通过Platform.OS === 'harmony'判断环境,加载对应平台的字体文件,保证跨端文本显示一致。

3. 交互功能

组件底部实现了点赞、评论、分享三个交互按钮,使用RNTouchableOpacity组件(鸿蒙端映射为鸿蒙原生Button组件,支持点击反馈),跨端适配要点:

  • TouchableOpacityonPress事件,跨端完全复用,无需修改逻辑;

  • 图标使用Base64编码的LIST_ICONS,跨端加载无差异;tintColor用于修改图标颜色,鸿蒙端与RN原生均支持该属性;

  • 可扩展点:当前仅实现了UI展示,可添加点击事件逻辑(如点赞数递增、跳转评论页等),逻辑跨端完全复用,无需额外适配。


主组件核心逻辑


// 主应用组件
const App = () => {
  const [listData, setListData] = useState<ListItem[]>([]);
  const [refreshing, setRefreshing] = useState(false);
  const [loadingMore, setLoadingMore] = useState(false);
  const [page, setPage] = useState(1);
  const [hasMore, setHasMore] = useState(true);

  // 模拟加载数据
  const loadData = async (pageNum: number, isRefresh = false) => {
    if (isRefresh) {
      setListData([]);
    }
    
    // 模拟API请求延迟
    await new Promise(resolve => setTimeout(resolve, 1000));
    
    // 生成模拟数据
    const newData: ListItem[] = Array.from({ length: 10 }, (_, i) => ({
      id: `${pageNum * 10 + i}`,
      title: `瀑布流内容 ${pageNum * 10 + i + 1}`,
      description: `这是第 ${pageNum * 10 + i + 1} 个瀑布流项目的详细描述,展示了瀑布流布局的效果。`,
      imageUrl: `https://picsum.photos/seed/${pageNum * 10 + i}/600/400`,
      likes: Math.floor(Math.random() * 100),
      comments: Math.floor(Math.random() * 50),
      shares: Math.floor(Math.random() * 30),
      author: `作者${String.fromCharCode(65 + (pageNum * 10 + i) % 26)}`,
      timestamp: `${Math.floor(Math.random() * 12) + 1}小时前`,
      width: 600,
      height: 400
    }));
    
    if (isRefresh) {
      setListData(newData);
    } else {
      setListData(prev => [...prev, ...newData]);
    }
    
    // 模拟没有更多数据的情况
    if (pageNum >= 5) {
      setHasMore(false);
    }
  };

  // 初始加载
  useEffect(() => {
    loadData(1);
  }, []);

  // 下拉刷新
  const onRefresh = useCallback(async () => {
    setRefreshing(true);
    await loadData(1, true);
    setPage(1);
    setRefreshing(false);
  }, []);

  // 加载更多
  const loadMore = useCallback(async () => {
    if (loadingMore || !hasMore) return;
    
    setLoadingMore(true);
    await loadData(page + 1);
    setPage(prev => prev + 1);
    setLoadingMore(false);
  }, [page, loadingMore, hasMore]);

  // 渲染部分省略,后续单独解读
};

App组件是整个应用的入口,负责数据管理、页面布局、列表渲染,核心实现了“初始加载、下拉刷新、上拉加载更多”三大功能,同时管理列表相关的状态,跨端适配重点关注4个核心技术点:

1. 状态管理:

使用useState管理5个核心状态,逻辑跨端完全复用,无需修改:

  • listData: ListItem[]:存储瀑布流列表数据,初始值为空数组;

  • refreshing: boolean:控制下拉刷新状态,true表示正在刷新;

  • loadingMore: boolean:控制上拉加载更多状态,true表示正在加载;

  • page: number:当前页码,用于加载更多时请求下一页数据;

  • hasMore: boolean:是否还有更多数据,用于控制加载更多的显示/隐藏。

跨端适配优势:React Hooks(useState、useEffect、useCallback)在鸿蒙端完全支持,状态管理逻辑无需任何修改,可直接复用RN原生代码;若为复杂跨端应用,可扩展为Redux、MobX等状态管理库,RN与鸿蒙端均支持这些库(通过适配包),实现“一次状态定义,多端共享”。

2. 数据加载逻辑:

loadData方法封装了数据加载的核心逻辑,模拟了真实接口请求的流程,跨端适配要点:

  • 请求逻辑复用:方法接收pageNum(页码)和isRefresh(是否下拉刷新)两个参数,适配“初始加载、下拉刷新、加载更多”三种场景,逻辑跨端完全复用;

  • 模拟API请求:使用setTimeout模拟接口请求延迟,真实开发中可替换为fetchaxios(RN原生与鸿蒙端均支持fetch,鸿蒙端也支持axios,需安装对应依赖);

  • 数据格式统一:生成的模拟数据严格遵循ListItem接口定义,保证跨端数据类型一致;图片地址使用https://picsum.photos(免费图片接口),跨端均可正常加载;

  • 无更多数据处理:当页码pageNum >= 5时,设置hasMore = false,停止加载更多,适配真实业务场景中“数据加载完毕”的逻辑。

3. 下拉刷新:

下拉刷新功能通过RN原生RefreshControl组件实现,结合onRefresh方法,跨端适配要点:

  • useCallback优化:onRefresh方法使用useCallback包裹,避免因组件重新渲染导致的方法重复创建,提升跨端性能(尤其鸿蒙端对渲染性能要求较高);

  • 跨端兼容性:RefreshControl组件在鸿蒙端通过适配器完全支持,refreshing(刷新状态)、colors(刷新指示器颜色)、tintColor(指示器颜色)等属性,跨端渲染效果一致;

  • 刷新逻辑:下拉刷新时,重置页码为1,重新加载第一页数据,清空原有列表,实现“刷新重置”的效果,逻辑跨端复用。

4. 上拉加载更多:

上拉加载更多功能通过FlatList的onEndReachedonEndReachedThreshold属性实现,跨端适配要点:

  • onEndReached:列表滚动到底部时触发,调用loadMore方法加载下一页数据;

  • onEndReachedThreshold={0.1}:设置列表滚动到距离底部10%的位置时,触发加载更多,提升用户体验(避免用户滚动到最底部才开始加载);

  • 防重复加载:通过loadingMorehasMore状态判断,避免多次触发加载更多(如用户快速滚动到底部时);

  • 鸿蒙端适配:FlatList在鸿蒙端映射为原生ListContainer组件,onEndReached事件的触发时机与RN原生一致,无需修改逻辑,保证跨端体验统一。

FlatList 瀑布流

FlatList渲染部分


return (
  <SafeAreaView style={styles.container}>
    <View style={styles.header}>
      <Text style={styles.headerTitle}>瀑布流列表</Text>
      <Text style={styles.headerSubtitle}>懒加载图片瀑布流布局</Text>
    </View><FlatList
      data={listData}
      renderItem={({ item }) => <ListItemComponent item={item} />}
      keyExtractor={(item) => item.id}
      numColumns={2}
      contentContainerStyle={styles.listContainer}
      showsVerticalScrollIndicator={false}
      refreshControl={
        <RefreshControl
          refreshing={refreshing}
          onRefresh={onRefresh}
          colors={['#3b82f6']}
          tintColor="#3b82f6"
        />
      }
      onEndReached={loadMore}
      onEndReachedThreshold={0.1}
      ListFooterComponent={
        loadingMore ? (
          <View style={styles.loadingMoreContainer}>
            <ActivityIndicator size="small" color="#3b82f6" />
            <Text style={styles.loadingMoreText}>加载更多...</Text>
          </View>
        ) : hasMore ? null : (
          <View style={styles.noMoreContainer}>
            <Text style={styles.noMoreText}>没有更多数据了</Text>
          </View>
        )
      }
    />
    
    {/* 功能特性、使用说明、底部信息省略,后续解读 */}
  </SafeAreaView>
);

FlatList是RN实现瀑布流布局的核心组件,也是跨端高性能列表渲染的关键——在鸿蒙端,FlatList通过适配器映射为鸿蒙原生ListContainer组件(鸿蒙原生高性能列表组件,支持视口内渲染、组件复用),无需修改代码即可实现跨端高性能列表,重点解读5个跨端关键技术点:

1. 瀑布流布局实现:numColumns={2}

通过numColumns={2}属性,直接实现两列瀑布流布局,这是RN FlatList实现瀑布流的最简方式,跨端适配要点:

  • 布局均匀性:结合contentContainerStyle={styles.listContainer}(设置列表内边距)和listItem样式(设置列表项间距),保证两列布局均匀,跨端显示一致;

  • 鸿蒙端适配:numColumns属性在鸿蒙端完全支持,映射为ListContainergridCount属性,实现两列网格布局,与RN原生效果一致;

  • 优势:相比ScrollView(一次性渲染所有子组件),FlatList支持“视口内渲染”(仅渲染屏幕可见区域的列表项)和“组件复用”,大幅降低内存占用,提升跨端性能(尤其列表数据较多时,如本文中每页10条,共5页50条数据)。

2. 核心属性

FlatList的核心属性均支持跨端复用,重点说明:

  • data={listData}:数据源,接收ListItem[]类型数据,跨端数据格式一致;

  • renderItem:渲染单个列表项,调用ListItemComponent组件,跨端组件复用;

  • keyExtractor={(item) => item.id}:设置列表项唯一标识,避免RN渲染警告,鸿蒙端同样需要唯一标识,保证组件复用正常;

  • showsVerticalScrollIndicator={false}:隐藏垂直滚动条,跨端视觉效果一致;

  • ListFooterComponent:列表底部组件,用于显示“加载更多”或“没有更多数据”提示,跨端完全复用,适配不同加载状态。

3. 列表底部组件:

ListFooterComponent根据loadingMorehasMore状态,动态渲染不同的底部提示,跨端适配要点:

  • 加载中:显示ActivityIndicator(加载指示器)和“加载更多…”文本,ActivityIndicator组件跨端完全支持,size(尺寸)、color(颜色)属性可自定义;

  • 没有更多数据:显示“没有更多数据了”文本,提示用户数据加载完毕;

  • 样式适配:底部组件的样式(如内边距、文本颜色)与整体主题一致,跨端视觉效果统一。

4. SafeAreaView:

页面根容器使用SafeAreaView,而非普通View,这是跨端刘海屏、状态栏适配的关键:

  • RN原生:SafeAreaView用于适配iOS刘海屏、Android状态栏,避免内容被刘海屏或状态栏遮挡;

  • 鸿蒙端:SafeAreaView通过适配器,自动适配鸿蒙系统的状态栏、导航栏高度,无需额外计算,适配鸿蒙手机、平板等不同设备的刘海屏、水滴屏。


代码片段


// 功能特性section
<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>
</View>

// 使用说明section
<View style={styles.usageSection}>
  <Text style={styles.usageTitle}>使用说明</Text>
  <Text style={styles.usageText}>
    瀑布流列表适用于图片展示、商品列表等场景。
    通过懒加载技术优化性能,提升用户体验。
  </Text>
</View>

// 底部信息
<View style={styles.footer}>
  <Text style={styles.footerText}>© 2023 瀑布流列表. All rights reserved.</Text>
</View>

这部分是静态UI组件,用于展示应用的功能特性、使用说明和底部版权信息,核心是“跨端UI样式复用”,适配要点:

  • 布局逻辑复用:使用View嵌套实现section布局,flexDirection: 'row'实现功能特性项的横向排列,逻辑跨端完全复用;

  • 样式统一:静态UI的样式与列表、头部样式保持一致,使用深色主题(backgroundColor: '#1e293b'),文本颜色、间距等属性跨端渲染一致;

  • 文本适配Text组件的textAlign: 'center'(居中对齐)、lineHeight(行高)等属性,跨端支持一致,保证文本显示美观;

  • 鸿蒙端适配:静态UI组件无复杂交互,仅涉及基础布局和文本渲染,RN原生代码可直接复用,无需任何修改,适配鸿蒙端的显示规范。


全局样式定义


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

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#0f172a',
  },
  header: {
    backgroundColor: '#1e293b',
    paddingTop: 20,
    paddingBottom: 25,
    paddingHorizontal: 20,
    borderBottomWidth: 1,
    borderBottomColor: '#334155',
  },
  // 省略其他样式定义
  listItem: {
    backgroundColor: '#1e293b',
    margin: 5,
    borderRadius: 12,
    overflow: 'hidden',
    borderWidth: 1,
    borderColor: '#334155',
  },
  // 省略其他样式定义
});

StyleSheet是RN跨端样式管理的核心,通过StyleSheet.create定义全局样式,相比普通对象样式,具有“性能优化、样式校验”的优势,跨端适配重点关注3点:

1. 动态尺寸计算:

通过Dimensions.get('window')获取屏幕宽高(width为屏幕宽度,height为屏幕高度),用于动态计算组件尺寸(如列表项宽度),核心优势:

  • 跨端适配不同屏幕尺寸:鸿蒙端设备屏幕尺寸多样(手机、平板等),动态尺寸计算可避免固定尺寸导致的布局错乱,实现“一次计算,多端适配”;

  • 示例:列表项宽度计算(前文ListItemComponent中),基于屏幕宽度动态计算,保证两列瀑布流布局均匀。

2. 样式属性

代码中使用的样式属性均为RN与鸿蒙端兼容的属性,重点规避以下不兼容属性:

  • RN原生支持shadowColor、shadowOffset、shadowOpacity等阴影属性,鸿蒙端不支持,可通过Platform.OS === 'ios'/'android'单独设置,鸿蒙端使用elevation属性实现阴影效果;

  • 鸿蒙端支持backgroundBlur等模糊效果,RN原生需通过第三方组件实现,可通过条件渲染适配双端;

  • 本文中使用的flex、backgroundColor、borderRadius、padding等属性,跨端完全兼容,无需适配。

3. 主题样式统一:

代码采用深色主题,统一设置背景色(#0f172a#1e293b)、文本颜色(#f1f5f9#94a3b8)、边框颜色(#334155),跨端适配优势:

  • 避免鸿蒙端主题适配问题:鸿蒙端支持系统主题切换(深色/浅色),若APP使用固定主题,统一的样式可避免主题切换导致的视觉错乱;

真实演示案例代码:

// App.tsx
import React, { useState, useEffect, useCallback } from 'react';
import { 
  View, 
  Text, 
  StyleSheet, 
  ScrollView, 
  SafeAreaView,
  Image,
  Dimensions,
  TouchableOpacity,
  FlatList,
  RefreshControl,
  ActivityIndicator
} from 'react-native';

// Base64 Icons for list components
const LIST_ICONS = {
  loading: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFHmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDIgNzkuMTYwOTI0LCAyMDE3LzA3LzEzLTAxOjA2OjM5ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTAxLTAzVDE2OjU1OjQ5KzA4OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0wMS0wM1QxNjo1NjoxNCswODowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wMS0wM1QxNjo1NjoxNCswODowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoyZjA5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MmYwOWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6MmYwOWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDoyZjA5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHN0RXZ0OndoZW49IjIwMTktMDEtMDNUMTY6NTU6NDlaIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+Af/+/fz7+vn49/b19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwLCgkIBwYFBAMCAQAA//+gB5ZbAAAJRElEQVRoBe1ZaWxUVRQ+97030+k6U6Z0hk5npksptKW0bFJAWRB3UEFFjQuJKIoLJCZqYhRjNC5RFCMuaKJREhMXjAuKggsuRI0bGkEpWymltNBS2ulMZ9qZmfbe9+699715MzPTmdLS6STnfPPee+fc755zz3nnPcNwHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMx......',
  error: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFHmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDIgNzkuMTYwOTI0LCAyMDE3LzA3LzEzLTAxOjA2OjM5ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTAxLTAzVDE2OjU2OjIwKzA4OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0wMS0wM1QxNjo1Njo0NSswODowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wMS0wM1QxNjo1Njo0NSswODowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoyZjE5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MmYxOWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6MmYxOWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDoyZjE5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHN0RXZ0OndoZW49IjIwMTktMDEtMDNUMTY6NTY6MjBaIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+Af/+/fz7+vn49/b19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwLCgkIBwYFBAMCAQAA//+gB5ZbAAAJRElEQVRoBe1ZaWxUVRQ+97030+k6U6Z0hk5npksptKW0bFJAWRB3UEFFjQuJKIoLJCZqYhRjNC5RFCMuaKJREhMXjAuKggsuRI0bGkEpWymltNBS2ulMZ9qZmfbe9+699715MzPTmdLS6STnfPPee+fc755zz3nnPcNwHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMx......',
  refresh: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFHmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDIgNzkuMTYwOTI0LCAyMDE3LzA3LzEzLTAxOjA2OjM5ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTAxLTAzVDE2OjU2OjUxKzA4OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0wMS0wM1QxNjo1NzoxNiswODowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wMS0wM1QxNjo1NzoxNiswODowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoyZjI5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MmYyOWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6MmYyOWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDoyZjI5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHN0RXZ0OndoZW49IjIwMTktMDEtMDNUMTY6NTY6NTFaIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+Af/+/fz7+vn49/b19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwLCgkIBwYFBAMCAQAA//+gB5ZbAAAJRElEQVRoBe1ZaWxUVRQ+97030+k6U6Z0hk5npksptKW0bFJAWRB3UEFFjQuJKIoLJCZqYhRjNC5RFCMuaKJREhMXjAuKggsuRI0bGkEpWymltNBS2ulMZ9qZmfbe9+699715MzPTmdLS6STnfPPee+fc755zz3nnPcNwHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMx......',
  like: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFHmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDIgNzkuMTYwOTI0LCAyMDE3LzA3LzEzLTAxOjA2OjM5ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTAxLTAzVDE2OjU3OjIyKzA4OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0wMS0wM1QxNjo1Nzo0OCswODowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wMS0wM1QxNjo1Nzo0OCswODowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoyZjM5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MmYzOWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6MmYzOWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDoyZjM5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHN0RXZ0OndoZW49IjIwMTktMDEtMDNUMTY6NTc6MjJaIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+Af/+/fz7+vn49/b19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwLCgkIBwYFBAMCAQAA//+gB5ZbAAAJRElEQVRoBe1ZaWxUVRQ+97030+k6U6Z0hk5npksptKW0bFJAWRB3UEFFjQuJKIoLJCZqYhRjNC5RFCMuaKJREhMXjAuKggsuRI0bGkEpWymltNBS2ulMZ9qZmfbe9+699715MzPTmdLS6STnfPPee+fc755zz3nnPcNwHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMx......',
  comment: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFHmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDIgNzkuMTYwOTI0LCAyMDE3LzA3LzEzLTAxOjA2OjM5ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTAxLTAzVDE2OjU3OjUzKzA4OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0wMS0wM1QxNjo1ODoxOCswODowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wMS0wM1QxNjo1ODoxOCswODowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoyZjQ5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MmY0OWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6MmY0OWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDoyZjQ5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHN0RXZ0OndoZW49IjIwMTktMDEtMDNUMTY6NTc6NTNaIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+Af/+/fz7+vn49/b19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwLCgkIBwYFBAMCAQAA//+gB5ZbAAAJRElEQVRoBe1ZaWxUVRQ+97030+k6U6Z0hk5npksptKW0bFJAWRB3UEFFjQuJKIoLJCZqYhRjNC5RFCMuaKJREhMXjAuKggsuRI0bGkEpWymltNBS2ulMZ9qZmfbe9+699715MzPTmdLS6STnfPPee+fc755zz3nnPcNwHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMx......',
  share: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFHmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDIgNzkuMTYwOTI0LCAyMDE3LzA3LzEzLTAxOjA2OjM5ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTAxLTAzVDE2OjU4OjI0KzA4OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0wMS0wM1QxNjo1ODo0OSswODowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wMS0wM1QxNjo1ODo0OSswODowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoyZjU5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MmY1OWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6MmY1OWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDoyZjU5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHN0RXZ0OndoZW49IjIwMTktMDEtMDNUMTY6NTg6MjRaIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+Af/+/fz7+vn49/b19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwLCgkIBwYFBAMCAQAA//+gB5ZbAAAJRElEQVRoBe1ZaWxUVRQ+97030+k6U6Z0hk5npksptKW0bFJAWRB3UEFFjQuJKIoLJCZqYhRjNC5RFCMuaKJREhMXjAuKggsuRI0bGkEpWymltNBS2ulMZ9qZmfbe9+699715MzPTmdLS6STnfPPee+fc755zz3nnPcNwHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMx......'
};

// 列表项类型
interface ListItem {
  id: string;
  title: string;
  description: string;
  imageUrl: string;
  likes: number;
  comments: number;
  shares: number;
  author: string;
  timestamp: string;
  width: number;
  height: number;
}

// 懒加载图片组件
const LazyImage = ({ source, style, placeholderColor = '#334155', errorColor = '#dc2626' }: { 
  source: { uri: string }; 
  style: any; 
  placeholderColor?: string;
  errorColor?: string;
}) => {
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(false);

  return (
    <View style={style}>
      {loading && !error && (
        <View style={[StyleSheet.absoluteFill, { backgroundColor: placeholderColor, justifyContent: 'center', alignItems: 'center' }]}>
          <Image source={{ uri: LIST_ICONS.loading }} style={{ width: 30, height: 30, tintColor: '#94a3b8' }} />
        </View>
      )}
      {error && (
        <View style={[StyleSheet.absoluteFill, { backgroundColor: errorColor, justifyContent: 'center', alignItems: 'center' }]}>
          <Image source={{ uri: LIST_ICONS.error }} style={{ width: 30, height: 30, tintColor: '#ffffff' }} />
        </View>
      )}
      <Image
        source={source}
        style={StyleSheet.absoluteFill}
        onLoad={() => setLoading(false)}
        onError={() => {
          setLoading(false);
          setError(true);
        }}
        resizeMode="cover"
      />
    </View>
  );
};

// 列表项组件
const ListItemComponent = ({ item }: { item: ListItem }) => {
  const aspectRatio = item.width / item.height;
  const imageHeight = 200; // 固定高度
  const imageWidth = imageHeight * aspectRatio;

  return (
    <View style={styles.listItem}>
      <View style={styles.imageContainer}>
        <LazyImage
          source={{ uri: item.imageUrl }}
          style={{ width: imageWidth, height: imageHeight }}
        />
      </View>
      
      <View style={styles.contentContainer}>
        <Text style={styles.title}>{item.title}</Text>
        <Text style={styles.description}>{item.description}</Text>
        
        <View style={styles.authorInfo}>
          <Text style={styles.author}>作者: {item.author}</Text>
          <Text style={styles.timestamp}>{item.timestamp}</Text>
        </View>
        
        <View style={styles.actionsContainer}>
          <TouchableOpacity style={styles.actionButton}>
            <Image source={{ uri: LIST_ICONS.like }} style={styles.actionIcon} />
            <Text style={styles.actionText}>{item.likes}</Text>
          </TouchableOpacity>
          <TouchableOpacity style={styles.actionButton}>
            <Image source={{ uri: LIST_ICONS.comment }} style={styles.actionIcon} />
            <Text style={styles.actionText}>{item.comments}</Text>
          </TouchableOpacity>
          <TouchableOpacity style={styles.actionButton}>
            <Image source={{ uri: LIST_ICONS.share }} style={styles.actionIcon} />
            <Text style={styles.actionText}>{item.shares}</Text>
          </TouchableOpacity>
        </View>
      </View>
    </View>
  );
};

// 主应用组件
const App = () => {
  const [listData, setListData] = useState<ListItem[]>([]);
  const [refreshing, setRefreshing] = useState(false);
  const [loadingMore, setLoadingMore] = useState(false);
  const [page, setPage] = useState(1);
  const [hasMore, setHasMore] = useState(true);

  // 模拟加载数据
  const loadData = async (pageNum: number, isRefresh = false) => {
    if (isRefresh) {
      setListData([]);
    }
    
    // 模拟API请求延迟
    await new Promise(resolve => setTimeout(resolve, 1000));
    
    // 生成模拟数据
    const newData: ListItem[] = Array.from({ length: 10 }, (_, i) => ({
      id: `${pageNum * 10 + i}`,
      title: `瀑布流内容 ${pageNum * 10 + i + 1}`,
      description: `这是第 ${pageNum * 10 + i + 1} 个瀑布流项目的详细描述,展示了瀑布流布局的效果。`,
      imageUrl: `https://picsum.photos/seed/${pageNum * 10 + i}/600/400`,
      likes: Math.floor(Math.random() * 100),
      comments: Math.floor(Math.random() * 50),
      shares: Math.floor(Math.random() * 30),
      author: `作者${String.fromCharCode(65 + (pageNum * 10 + i) % 26)}`,
      timestamp: `${Math.floor(Math.random() * 12) + 1}小时前`,
      width: 600,
      height: 400
    }));
    
    if (isRefresh) {
      setListData(newData);
    } else {
      setListData(prev => [...prev, ...newData]);
    }
    
    // 模拟没有更多数据的情况
    if (pageNum >= 5) {
      setHasMore(false);
    }
  };

  // 初始加载
  useEffect(() => {
    loadData(1);
  }, []);

  // 下拉刷新
  const onRefresh = useCallback(async () => {
    setRefreshing(true);
    await loadData(1, true);
    setPage(1);
    setRefreshing(false);
  }, []);

  // 加载更多
  const loadMore = useCallback(async () => {
    if (loadingMore || !hasMore) return;
    
    setLoadingMore(true);
    await loadData(page + 1);
    setPage(prev => prev + 1);
    setLoadingMore(false);
  }, [page, loadingMore, hasMore]);

  return (
    <SafeAreaView style={styles.container}>
      <View style={styles.header}>
        <Text style={styles.headerTitle}>瀑布流列表</Text>
        <Text style={styles.headerSubtitle}>懒加载图片瀑布流布局</Text>
      </View>
      
      <FlatList
        data={listData}
        renderItem={({ item }) => <ListItemComponent item={item} />}
        keyExtractor={(item) => item.id}
        numColumns={2}
        contentContainerStyle={styles.listContainer}
        showsVerticalScrollIndicator={false}
        refreshControl={
          <RefreshControl
            refreshing={refreshing}
            onRefresh={onRefresh}
            colors={['#3b82f6']}
            tintColor="#3b82f6"
          />
        }
        onEndReached={loadMore}
        onEndReachedThreshold={0.1}
        ListFooterComponent={
          loadingMore ? (
            <View style={styles.loadingMoreContainer}>
              <ActivityIndicator size="small" color="#3b82f6" />
              <Text style={styles.loadingMoreText}>加载更多...</Text>
            </View>
          ) : hasMore ? null : (
            <View style={styles.noMoreContainer}>
              <Text style={styles.noMoreText}>没有更多数据了</Text>
            </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 style={styles.featureItem}>
            <Text style={styles.featureBullet}></Text>
            <Text style={styles.featureText}>丰富的Base64图标库</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>
      
      <View style={styles.footer}>
        <Text style={styles.footerText}>© 2023 瀑布流列表. All rights reserved.</Text>
      </View>
    </SafeAreaView>
  );
};

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

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#0f172a',
  },
  header: {
    backgroundColor: '#1e293b',
    paddingTop: 20,
    paddingBottom: 25,
    paddingHorizontal: 20,
    borderBottomWidth: 1,
    borderBottomColor: '#334155',
  },
  headerTitle: {
    fontSize: 26,
    fontWeight: '700',
    color: '#f1f5f9',
    textAlign: 'center',
    marginBottom: 5,
  },
  headerSubtitle: {
    fontSize: 15,
    color: '#94a3b8',
    textAlign: 'center',
  },
  listContainer: {
    padding: 10,
  },
  listItem: {
    backgroundColor: '#1e293b',
    margin: 5,
    borderRadius: 12,
    overflow: 'hidden',
    borderWidth: 1,
    borderColor: '#334155',
  },
  imageContainer: {
    position: 'relative',
  },
  contentContainer: {
    padding: 12,
  },
  title: {
    fontSize: 16,
    fontWeight: '700',
    color: '#f1f5f9',
    marginBottom: 6,
  },
  description: {
    fontSize: 14,
    color: '#cbd5e1',
    lineHeight: 20,
    marginBottom: 10,
  },
  authorInfo: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    marginBottom: 10,
  },
  author: {
    fontSize: 12,
    color: '#94a3b8',
  },
  timestamp: {
    fontSize: 12,
    color: '#94a3b8',
  },
  actionsContainer: {
    flexDirection: 'row',
    justifyContent: 'space-around',
    paddingTop: 10,
    borderTopWidth: 1,
    borderTopColor: '#334155',
  },
  actionButton: {
    flexDirection: 'row',
    alignItems: 'center',
  },
  actionIcon: {
    width: 16,
    height: 16,
    tintColor: '#94a3b8',
    marginRight: 4,
  },
  actionText: {
    fontSize: 12,
    color: '#94a3b8',
  },
  loadingMoreContainer: {
    flexDirection: 'row',
    justifyContent: 'center',
    alignItems: 'center',
    paddingVertical: 20,
  },
  loadingMoreText: {
    marginLeft: 10,
    color: '#94a3b8',
    fontSize: 14,
  },
  noMoreContainer: {
    paddingVertical: 20,
    alignItems: 'center',
  },
  noMoreText: {
    color: '#94a3b8',
    fontSize: 14,
  },
  featuresSection: {
    backgroundColor: '#1e293b',
    borderRadius: 16,
    padding: 20,
    margin: 15,
    borderWidth: 1,
    borderColor: '#334155',
  },
  featuresTitle: {
    fontSize: 20,
    fontWeight: '700',
    color: '#f1f5f9',
    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: '#cbd5e1',
    flex: 1,
  },
  usageSection: {
    backgroundColor: '#1e293b',
    borderRadius: 16,
    padding: 20,
    margin: 15,
    borderWidth: 1,
    borderColor: '#334155',
  },
  usageTitle: {
    fontSize: 20,
    fontWeight: '700',
    color: '#f1f5f9',
    marginBottom: 15,
    textAlign: 'center',
  },
  usageText: {
    fontSize: 16,
    color: '#cbd5e1',
    lineHeight: 24,
    textAlign: 'center',
  },
  footer: {
    paddingVertical: 15,
    alignItems: 'center',
    borderTopWidth: 1,
    borderTopColor: '#334155',
    backgroundColor: '#1e293b',
  },
  footerText: {
    fontSize: 14,
    color: '#94a3b8',
    fontWeight: '500',
  },
});

export default App;

在这里插入图片描述


打包

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

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述
本文介绍了React Native(RN)在鸿蒙系统上的跨端开发实现,重点解析了瀑布流列表的核心功能。通过@react-native-ohos/adapter适配器,RN代码可直接映射为鸿蒙原生组件,实现"一次开发,多端复用"。文章详细讲解了懒加载图片、瀑布流布局和下拉刷新等功能的实现逻辑,并强调了Base64图标在跨端资源加载中的优势。该方案适合有RN基础、希望快速适配鸿蒙的开发者,能有效降低多端适配成本,提升开发效率。

欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐