RN OpenHarmony特征&多设备适配最佳实践

fox280
发布于 2025-1-22 15:47
9641浏览
0收藏

1.概述

现有的伙伴应用使用RN框架开发的历史页面多且冗杂。为达到鸿蒙原生一多体验,所有页面若均使用ArkUI原生框架重新开发耗时长、成本高,不可行。针对此问题,本文将主要提供一套RN多设备响应式组件及方案:

1)一套基于RN的鸿蒙特征动画组件库,在RN页面实现鸿蒙特征动画UI效果;

2)一套基于RN的一多高阶组件库,在RN页面实现折叠屏悬停避让分栏的UI效果;

首先介绍组件及效果说明,再分别结合组件效果提供对应场景的开发案例,最终提供示例代码指导实际开发。

组件库安装

参考​​rn\_multidevice\_layout\_scenepkg​​​ 和 ​​rn\_hmfeatures​​。

2.多设备断点

2.1断点设计原理

RN断点是基于鸿蒙多设备封装的一套断点机制,通过设置断点,让开发者可以结合窗口宽度去实现不同的页面布局效果。

断点以应用窗口宽度为基准,将应用窗口在宽度维度上分成了几个不同的区间即不同的断点,默认提供的断点区间如下所示。


断点名称

取值范围(px)

xs

[0, 320)

sm

[320, 600)

md

[600, 840)

lg

[840, 1440)

xl

[1440, +∞)

2.2多设备适配指导

在实际开发过程中,可以使用​​rn\_multidevice\_layout\_scenepkg​​的setBreakpoints自定义断点的区间,也可以使用上述默认的断点区间。使用useBreakpointValue时,只需将屏幕断点所对应的参数传入useBreakpointValue,当屏幕断点发生变化时,该hook会根据当前断点的类型返回所对应的数据。

具体示例如下所示:

自定义断点区间

  // 自定义断点区间,可选
  useEffect(() => {
    setBreakpoints({
      base: 320,
      md: 768,
      lg: 1024,
    });
  });
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

使用断点hook并获取不同断点下的属性值

  const color = useBreakpointValue({
    base: 'red',
    xs: 'blue',
    sm: 'green',
    md: 'yellow',
    lg: 'purple',
    xl: 'orange',
  });

  return (
    <Text style={{ color }}>Responsive Color Text</Text>
  );
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

3.RN鸿蒙特征动画组件

3.1组件使用说明

​rn\_hmfeatures​​​鸿蒙特征动画组件GeometryView,基于ArkUI的geometryTransition接口,实现了鸿蒙特征动画效果,详细介绍可参考:​​使用geometryTransition共享元素转场​​。

组件导入方式

import GeometryView from 'rn_hmfeatures/src/';
  • 1.

组件API


名称

类型

必填

说明

geometryViewID

string

设置转场动效ID

onGeometryViewClick

DirectEventHandler

点击回调函数

3.2场景案例

点击歌单页当前播放音乐控件,跳转音乐播放页,触发一镜到底的转场效果。

RN OpenHarmony特征&多设备适配最佳实践-鸿蒙开发者社区

RN OpenHarmony特征&多设备适配最佳实践-鸿蒙开发者社区

3.2.1关键代码片段

1、将歌单页中的当前播放音乐控件设置共享元素ID,监听点击事件并发送消息至ArkUI侧。(SampleTurboModule为自定义TurboModule,执行调用ArkUI侧发送消息方法)

    return (
        <GeometryView
            style={styles.container}
            geometryViewID={'test'}   // 设置共享元素ID
            onGeometryViewClick={() => {
                SampleTurboModule.pushStringToHarmony('pages/MusicPlay', 1);
            }}>
            <Image source={require('../../../../asset/cover.png')} style={[styles.albumCover, { marginLeft: itemLeft }]} />
            <View style={styles.songInfo}>
                <Text style={styles.songTitle}>{song.title}</Text>
                <Text style={styles.songArtist}>{song.artist}</Text>
            </View>
            <View style={{ flex: 1 }} />
            {controlIcons}
        </GeometryView>
    );
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

2、ArkUI侧监听跳转事件,在animateTo闭包内执行页面跳转逻辑

  aboutToAppear() {
    emitter.on({ eventId: 1 }, () => {
      animateTo({ duration: 700, curve: Curve.Friction }, () => {
        this.navPathStack.pushPath({ name: 'MusicPlayPage' });
      });
    });
  }
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

3、音乐播放页设置与歌单页一致的共享元素ID

@Component
export default struct MusicPlayPage {
  private instance: RNInstance = LoadManager.instance;
  private bundlePath = 'bundle/musicplay.harmony.bundle';
  private moduleName = 'MusicPlay';
  @StorageLink('isMetroAvailable') isMetroAvailable: boolean = false;
  @Consume('navPathStack') navPathStack: NavPathStack;
  aboutToAppear() {
    emitter.on({ eventId: 2 }, () => {
      animateTo({ duration: 700, curve: Curve.Friction }, () => {
        this.navPathStack.pop();
      });
    });
  }
  aboutToDisappear() {
    emitter.off(2);
  }
  build() {
    NavDestination() {
      if (this.isMetroAvailable) {
        MetroBaseRN({
          moduleName: this.moduleName,
        })
          .align(Alignment.Top)
      } else if (this.instance) {
        BaseRN({
          rnInstance: this.instance,
          moduleName: this.moduleName,
          bundlePath: this.bundlePath,
        }).align(Alignment.Top)
      }
    }
    .geometryTransition('test')  // 设置共享元素ID
    .hideTitleBar(true)
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.

4.RN折叠屏适配组件

4.1组件使用说明

RN折叠屏适配组件FoldSplitContainer,包含primary、secondary、extra三个区域,实现折叠屏二分栏、三分栏在展开态、悬停态以及折叠屏悬停状态下折痕区避让,详情参考:​​rn\_multidevice\_layout\_scenepkg​​。

组件导入方式

import { FoldSplitContainer } from 'rn_multidevice_layout_scenepkg/src';
  • 1.

FoldSplitContainer组件


Name

Description

Type

Platform

primary

主要区域回调函数。

function

OpenHarmony

secondary

次要区域回调函数。

function

OpenHarmony

extra

扩展区域回调函数,不传入的情况,没有对应区域。

function

OpenHarmony

expandedLayoutOptions

展开态布局信息。

ExpandedRegionLayoutOptions

OpenHarmony

hoverModeLayoutOptions

悬停态布局信息。

HoverModeRegionLayoutOptions

OpenHarmony

foldedLayoutOptions

折叠态布局信息。

FoldedRegionLayoutOptions

OpenHarmony

onHoverStatusChange

折叠屏进入或退出悬停模式时触发的回调函数。

onHoverStatusChangeHandler

OpenHarmony

ExpandedRegionLayoutOptions


Name

Description

Type

Platform

isExtraRegionPerpendicular

扩展区域是否从上到下贯穿整个组件,当且仅当extra有效时此字段才生效。默认值:true。

boolean

OpenHarmony

verticalSplitRatio

主要区域与次要区域之间的高度比例。默认值:PresetSplitRatio.LAYOUT_1V1。

number

OpenHarmony

horizontalSplitRatio

主要区域与扩展区域之间的宽度比例,当且仅当extra有效时此字段才生效。默认值:PresetSplitRatio.LAYOUT_3V2。

number

OpenHarmony

extraRegionPosition

扩展区域的位置信息,当且仅当isExtraRegionPerpendicular = false有效时此字段才生效。默认值:ExtraRegionPosition.top。

ExtraRegionPosition

OpenHarmony

HoverModeRegionLayoutOptions


Name

Description

Type

Platform

showExtraRegion

可折叠屏幕在半折叠状态下是否显示扩展区域。默认值:false。

boolean

OpenHarmony

horizontalSplitRatio

主要区域与扩展区域之间的宽度比例,当且仅当extra有效时此字段才生效。默认值:PresetSplitRatio.LAYOUT_3V2。

number

OpenHarmony

extraRegionPosition

扩展区域的位置信息,当且仅当showExtraRegion时此字段才生效。默认值:ExtraRegionPosition.top。

ExtraRegionPosition

OpenHarmony

FoldedRegionLayoutOptions


Name

Description

Type

Platform

verticalSplitRatio

主要区域与次要区域之间的高度比例。默认值:PresetSplitRatio.LAYOUT_1V1。

number

OpenHarmony

onHoverStatusChangeHandler


Name

Description

Type

Platform

callback

折叠屏进入或退出悬停模式时触发的回调函数。

(status: HoverModeStatus) =&gt; void

OpenHarmony

HoverModeStatus


Name

Description

Type

Platform

foldStatus

设备的折叠状态。

FoldStatus

OpenHarmony

isHoverMode

app当前是否处于悬停态。

boolean

OpenHarmony

ExtraRegionPosition


Name

Description

Value

top

扩展区域在组件上半区域。

1

bottom

扩展区域在组件下半区域。

2

PresetSplitRatio


Name

Description

Value

LAYOUT_1V1

1:1比例。

1/1

LAYOUT_3V2

3:2比例。

3/2

LAYOUT_2V3

2:3比例。

2/3

推荐使用方式

  const primaryRender = () => (
    <View>
      <Text> 此区域为primary
    </View>
  );
  const secondRender = () => (
    <View>
      <Text> 此区域为second
    </View>
  );
  const extraRender = () => (
    <View>
      <Text> 此区域为extra
    </View>
  );
  
  const expandedLayoutOptions: ExpandedRegionLayoutOptions = {
    isExtraRegionPerpendicular: true,
    verticalSplitRatio: PresetSplitRatio.LAYOUT_1V1,
    horizontalSplitRatio: PresetSplitRatio.LAYOUT_1V1,
  };
  const foldedRegionLayoutOptions: FoldedRegionLayoutOptions = {
    verticalSplitRatio: PresetSplitRatio.LAYOUT_1V1,
  };
  const hoverModeLayoutOptions: HoverModeRegionLayoutOptions = {
    horizontalSplitRatio: PresetSplitRatio.LAYOUT_1V1,
    showExtraRegion: true,
  };
  <FoldSplitContainer
    primary={primaryRender()}
    secondary={secondRender()}
    extra={extraRender()}
    expandedLayoutOptions={expandedLayoutOptions}
    foldedLayoutOptions={foldedRegionLayoutOptions}
    hoverModeLayoutOptions={hoverModeLayoutOptions}
  />
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.

4.2.场景案例

4.2.1实现效果

fold

expanded

hover

RN OpenHarmony特征&多设备适配最佳实践-鸿蒙开发者社区

RN OpenHarmony特征&多设备适配最佳实践-鸿蒙开发者社区



RN OpenHarmony特征&多设备适配最佳实践-鸿蒙开发者社区

RN OpenHarmony特征&多设备适配最佳实践-鸿蒙开发者社区


RN OpenHarmony特征&多设备适配最佳实践-鸿蒙开发者社区

RN OpenHarmony特征&多设备适配最佳实践-鸿蒙开发者社区

4.2.2关键代码片段

这段代码是用React Native编写的,用于构建一个音乐播放器界面:

  1. secondRender函数
  • 这个函数返回一个渲染组件,该组件包含多个View和Image组件,用于显示音乐的封面、标题、艺术家名、播放进度条、控制按钮(如上一曲、下一曲、播放/暂停、重复等)和一些额外的图标。
  • View组件用于布局,Image组件用于显示图标或音乐封面。
  • Slider组件用于显示播放进度条,用户可以通过拖动来改变播放位置。
  • TouchableOpacity组件用于创建可点击的图标。
  1. extraRender函数
  • 这个函数用于显示歌词信息。
  1. 布局选项
  • expandedLayoutOptions、foldedRegionLayoutOptions和hoverModeLayoutOptions定义了不同的布局选项,用于控制组件在展开、折叠和悬停模式下的布局。
  1. 返回的组件
  • 最后,代码返回一个ImageBackground组件,该组件使用一个模糊背景图片,并在其上层叠加FoldSplitContainer组件。FoldSplitContainer组件使用前面定义的primaryRender、secondRender和extraRender函数来渲染其主要、次要和额外的内容部分。

代码示例如下:

const secondRender = () => (
    <View style={{flex: 1, alignItems: 'center'}}>
      <View style={styles.message}>
        <View>
          <Text style={styles.title}>{title}</Text>
          <Text style={styles.artist}>{artist}</Text>
        </View>
        <Image
          source={require('../../../asset/likes.svg')}
          style={styles.imageGrey}
        />
      </View>
      <View style={styles.slider}>
        <Slider
          style={{width: '100%'}}
          minimumValue={0}
          maximumValue={duration}
          value={position}
          minimumTrackTintColor="#e8e1e1"
          maximumTrackTintColor="#784949"
          thumbStyle={{opacity: 0}}
          onValueChange={(val: number) => {
            seekTo(val);
          }}
        />
      </View>
      <View style={styles.controls}>
        <Text style={styles.text}>{formatTime(position)}</Text>
        <Text style={styles.text}>{formatTime(duration)}</Text>
      </View>
      <View style={styles.container}>
        <Image
          source={require('../../../asset/repeat.svg')}
          style={styles.imageGrey}
        />
        <TouchableOpacity onPress={skipToPrevious}>
          <Image
            source={require('../../../asset/left.svg')}
            style={styles.image}
          />
        </TouchableOpacity>
        <TouchableOpacity onPress={togglePlayPause}>
          <Image
            source={
              playState === State.Playing
                ? require('../../../asset/pause.svg')
                : require('../../../asset/play.svg')
            }
            style={styles.imagePlay}
          />
        </TouchableOpacity>
        <TouchableOpacity onPress={skipToNext}>
          <Image
            source={require('../../../asset/forward_end_fill.svg')}
            style={styles.image}
          />
        </TouchableOpacity>
        <Image
          source={require('../../../asset/music_note_list.svg')}
          style={styles.imageGrey}
        />
      </View>
      <View style={styles.container}>
        <Image
          source={require('../../../asset/share_play.svg')}
          style={styles.imageGrey}
        />
        <Image
          source={require('../../../asset/bell.svg')}
          style={styles.imageGrey}
        />
        <Image
          source={require('../../../asset/arrow_down_circle.svg')}
          style={styles.imageGrey}
        />
        <Image
          source={require('../../../asset/dot.svg')}
          style={styles.imageGrey}
        />
      </View>
    </View>
  );
  const extraRender = () => (
    <View style={styles.extra}>
      <Text
        style={{
          marginTop: isHover ? 70 : 0,
          marginRight: isPad ? 200 : 0,
          fontSize: 23,
          color: '#ffffff',
        }}>
        此歌曲为纯音乐,请您欣赏
      </Text>
    </View>
  );
  const expandedLayoutOptions: ExpandedRegionLayoutOptions = {
    isExtraRegionPerpendicular: true,
    verticalSplitRatio: PresetSplitRatio.LAYOUT_1V1,
    horizontalSplitRatio: PresetSplitRatio.LAYOUT_1V1,
  };
  const foldedRegionLayoutOptions: FoldedRegionLayoutOptions = {
    verticalSplitRatio: PresetSplitRatio.LAYOUT_1V1,
  };
  const hoverModeLayoutOptions: HoverModeRegionLayoutOptions = {
    horizontalSplitRatio: 0.66,
    showExtraRegion: true,
  };
  return (
    <ImageBackground
      source={require('../../../asset/blur.png')}
      style={{width: '100%', height: '100%'}}>
      <View style={{position: 'absolute', width: '100%', alignItems: 'center'}}>
        <FoldSplitContainer
          primary={primaryRender()}
          secondary={secondRender()}
          extra={extraRender()}
          expandedLayoutOptions={expandedLayoutOptions}
          foldedLayoutOptions={foldedRegionLayoutOptions}
          hoverModeLayoutOptions={hoverModeLayoutOptions}
        />
    </ImageBackground>
  );
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.

5.示例代码

基于RN框架的多设备开发sample示例代码地址:​​https://gitee.com/openharmony-sig/rn\_multidevice\_layout\_scenepkg​

分类
收藏
回复
举报


回复
    相关推荐