概述

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

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

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

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

组件库安装

参考rn\_multidevice\_layout\_scenepkgrn\_hmfeatures

多设备断点

断点设计原理

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

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

多设备适配指导

在实际开发过程中,可以使用rn\_multidevice\_layout\_scenepkg的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鸿蒙特征动画组件

组件使用说明

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

组件导入方式

import GeometryView from 'rn_hmfeatures/src/';

组件API

场景案例

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

关键代码片段

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三个区域,实现折叠屏二分栏、三分栏在展开态、悬停态以及折叠屏悬停状态下折痕区避让,详情参考:rn\_multidevice\_layout\_scenepkg

组件导入方式

import { FoldSplitContainer } from 'rn_multidevice_layout_scenepkg/src';

FoldSplitContainer组件

ExpandedRegionLayoutOptions

HoverModeRegionLayoutOptions

FoldedRegionLayoutOptions

onHoverStatusChangeHandler

HoverModeStatus

ExtraRegionPosition

PresetSplitRatio

推荐使用方式

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

场景案例

实现效果

关键代码片段

这段代码是用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://gitee.com/openharmony-sig/rn\_multidevice\_layout\_scenepkg


HarmonyOS码上奇行
9.2k 声望3.3k 粉丝

欢迎关注 HarmonyOS 开发者社区:[链接]