概述

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

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

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

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

组件库安装

参考@hadss/react_native_adaptive_layout@hadss/react_native_geometry_transition

多设备断点

断点设计原理

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

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

断点名称

取值范围(px

xs

[0, 320)

sm

[320, 600)

md

[600, 840)

lg

[840, 1440)

xl

[1440, +∞)

多设备适配指导

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

具体示例如下所示:

自定义断点区间

  // 自定义断点区间,可选
  useEffect(() => {
    setBreakpoints({
      base: 320,
      md: 768,
      lg: 1024,
    });
  });

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

  const color = useBreakpointValue({
    base: 'red',
    xs: 'blue',
    sm: 'green',
    md: 'yellow',
    lg: 'purple',
    xl: 'orange',
  });
 
  return (
    <Text style={{ color }}>Responsive Color Text</Text>
  );

    RN鸿蒙特征动画组件

    组件使用说明

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

    组件导入方式

    import GeometryView from '@hadss/react_native_geometry_transition/src/';

    组件API

    名称

    类型

    必填

    说明

    geometryViewID

    string

    设置转场动效ID

    onGeometryViewClick

    DirectEventHandler

    点击回调函数

    场景案例

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

    关键代码片段

    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>
        );

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

      aboutToAppear() {
        emitter.on({ eventId: 1 }, () => {
          animateTo({ duration: 700, curve: Curve.Friction }, () => {
            this.navPathStack.pushPath({ name: 'MusicPlayPage' });
          });
        });
      }

    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)
      }
    }

      RN折叠屏适配组件

      组件使用说明

      RN折叠屏适配组件FoldSplitContainer,包含primary、secondary、extra三个区域,实现折叠屏二分栏、三分栏在展开态、悬停态以及折叠屏悬停状态下折痕区避让,详情参考:@hadss/react_native_adaptive_layout

      组件导入方式

      import { FoldSplitContainer } from '@hadss/react_native_adaptive_layout/src';

      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) => 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}
        />

      场景案例

      实现效果

      fold

      expanded

      hover

      关键代码片段

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

      1. secondRender函数

      • 这个函数返回一个渲染组件,该组件包含多个View和Image组件,用于显示音乐的封面、标题、艺术家名、播放进度条、控制按钮(如上一曲、下一曲、播放/暂停、重复等)和一些额外的图标。
      • View组件用于布局,Image组件用于显示图标或音乐封面。
      • Slider组件用于显示播放进度条,用户可以通过拖动来改变播放位置。
      • TouchableOpacity组件用于创建可点击的图标。

      2. extraRender函数

      • 这个函数用于显示歌词信息。

      3. 布局选项

      • expandedLayoutOptions、foldedRegionLayoutOptions和hoverModeLayoutOptions定义了不同的布局选项,用于控制组件在展开、折叠和悬停模式下的布局。

      4. 返回的组件

      • 最后,代码返回一个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>
        );

      示例代码

      基于RN框架的多设备开发sample示例代码地址:https://gitcode.com/openharmony-sig/rn_multidevice_layout_scenepkg/tree/master/samples/example

      推荐内容
      点击阅读全文
      Logo

      讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

      更多推荐