【鸿蒙 5.0 实战开发】(新闻阅读类)APP开发——体验流畅的首页信息流

📝往期推文全新看点(文中附带最新·鸿蒙全栈学习笔记)

🚩 鸿蒙应用开发与鸿蒙系统开发哪个更有前景?

🚩 嵌入式开发适不适合做鸿蒙南向开发?看完这篇你就了解了~

🚩 对于大前端开发来说,转鸿蒙开发究竟是福还是祸?

🚩 鸿蒙岗位需求突增!移动端、PC端、IoT到底该怎么选?

🚩 记录一场鸿蒙开发岗位面试经历~

📃 持续更新中……


简介

本场景解决方案主要面向于新闻类页面开发人员,指导开发者从零开始构建一个新闻类的首页面。主要包含地址选择、tabs和tabContent切换的动态图标和流畅动效、下拉刷新上拉加载、首页feed流等常见功能的实现及流畅体验。

整体场景介绍

主要介绍了用户操作应用的主要流程,展示了用户进入首页通过页签切换页面内容,上拉加载和下拉刷新页面,从首页地址进入地址选择页更换地址等功能效果。

  • 实现应用的主要流程图

  • 运行效果图

  • 操作流程

    1. 获取地理位置的权限;

    2. 点击位置信息,跳转地址页,可修改当前位置信息;

    3. 点击顶部页签或者滑动切换页面,页签同步切换;

    4. 点击底部页签切换页面,同步切换页签,触发页签切换的动画效果;

    5. 下拉刷新页面信息;

    6. 上拉加载页面信息;

    7. 点击右下角按钮回弹至顶部。

场景说明

适用范围

本场景主要适用于新闻类的应用首页,使用原生组件及三方库组件完成新闻首页与功能的实现。

场景优势

本场景可以带给用户更加流畅和便捷的首页体验。具体优势如下:

  1. 导航栏点击切换动效流畅,响应时延为51ms。
  2. 左右滑动切换动效流畅,响应时延为67ms。
  3. 地址选择页定位精确,选择目标城市更加便捷。
  4. 底部页签跳转精致流畅不丢帧,时延349ms。
  5. 具有上拉加载下拉刷新页面功能,动效回弹流畅不丢帧,下拉刷新响应时延为153ms,上拉加载响应时延为150ms。

场景分析

典型场景与实现方案

  • 实现方案如下表:
场景名称描述实现方案
导航栏切换动效流畅点击页签或者滑动切换页面,页签同步切换tab组件添加动画开始时触发事件
底部页签跳转精致流畅底部页签切换具有动画效果添加lottie动画
上拉加载下拉刷新上拉加载更多的新闻内容,下拉刷新整个页面,均具有加载动效pullToRefresh组件
首页feed流首页展示流畅图文列表使用LazyForEach对子组件进行渲染,实现懒加载功能
地址选择页提供地址选择,定位,地址首字母定位及模糊查询功能位置服务与AlphabetIndexer组件

场景实现

导航栏切换动效流畅

通过添加tab组件动效触发事件,实现页面内容切换与页签样式切换的同步触发效果。效果如图所示:

  • 动效触发事件节点

推荐使用onAnimationStart事件设置切换标签动效,使用onChange会导致页面切换后再触发动效导致效果延迟触发,使用onClick事件会与页面切换冲突。

// TabBar.ets
@Builder
TabBuilder(id: number, index: number) {
  Column() {
    Text(this.tabBarArray[id].name)
      // ...
  }
  .alignItems(HorizontalAlign.Start)
}

build() {
  Tabs({ barPosition: BarPosition.Start }) {
    ForEach(this.tabBarArray, (tabsItem: NewsTypeModel, index: number) => {
      TabContent() {
        // ...
      }
      // ...
    }, (item: NewsTypeModel) => JSON.stringify(item));
  }
  // ...
  .onAnimationStart((_index: number, targetIndex: number, _event: TabsAnimationEvent) => {
    this.currentIndex = targetIndex;
  })
}

底部页签跳转精致流畅

底部页签样式添加lottie动画使跳转精致流畅。效果如图所示:

  • 底部页签跳转功能时序图

  • TabBar集成lottie动画

Lottie是一个适用于OpenHarmony的动画库,它可以解析Adobe After Effects软件通过Bodymovin插件导出的json格式的动画,并在移动设备上进行本地渲染。支持动画的交互性,通过添加触摸事件与TabBar相结合可实现动态图标效果。

引入lottie三方库。

ohpm install @ohos/lottie

准备lottie动画资源,建议放置到Entry目录下的common文件夹下(放置本模块中,使用相对路径无法读取)。

导入lottie模块。

// Home.ets
import lottie from '@ohos/lottie';

使用RenderingContext在Canvas组件上进行绘制,声明CanvasRenderingContext2D变量,RenderingContextSettings用来配置CanvasRenderingContext2D对象的参数表明canvas是否开启抗锯齿。

// Home.ets
private renderingSettings1: RenderingContextSettings = new RenderingContextSettings(true);
private canvasRenderingContext1: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.renderingSettings1);

定义所需数据类型的接口,初始化变量,接口中需要包含资源路径信息和CanvasRenderingContext2D。

// Home.ets
interface TabBarOption {
  index: number;
  text: ResourceStr;
  name: string;
  path: string;
  canvasRenderingContext: CanvasRenderingContext2D;
}

实现动画播放方法。

// Home.ets
lottieController(): void {
  if (this.currentBottomIndex === 0) {
    lottie.stop();
    lottie.play(this.tabBarOption1.name);
  }
  if (this.currentBottomIndex === 1) {
    lottie.stop();
    lottie.play(this.tabBarOption2.name);
  }
  if (this.currentBottomIndex === 2) {
    lottie.stop();
    lottie.play(this.tabBarOption3.name);
  }
}

在TabBar样式中实现子组件Canvas。

// Home.ets
Canvas(tabBarOption.canvasRenderingContext)
  .width($r('app.float.canvas_size'))
  .height($r('app.float.canvas_size'))
  .onReady(() => {
    lottie.loadAnimation({
      container: tabBarOption.canvasRenderingContext,
      renderer: 'canvas',
      loop: false,
      autoplay: false,
      name: tabBarOption.name,
      path: tabBarOption.path,
    });
    this.lottieController();
  })

在tab组件的onAnimationStart事件中调用播放方法。

// Home.ets
.onAnimationStart((index: number, targetIndex: number, event: TabsAnimationEvent) => {
  this.currentBottomIndex = targetIndex;
  this.lottieController();
})

上拉加载下拉刷新

通过三方库组件pullToRefresh实现下拉刷新页面,上拉加载更多数据效果。效果如图所示:

  • 上拉加载下拉刷新功能时序图

  • pullToRefresh组件

使用第三方库pullToRefresh组件,将列表组件、绑定的数据对象和scroller对象包含进去,并添加上滑与下拉方法。支持lazyForEarch的数据作为数据源,使用的List组件需要设置edgeEffect属性为(EdgeEffect.None)。

引入三方库pullToRefresh。

ohpm install @ohos/pulltorefresh

// PullToRefreshNews.ets
import { PullToRefresh } from '@ohos/pulltorefresh/index';

准备数据源,本场景使用本地资源模拟效果,资源文件在resources/rawfile目录下,实际情况可替换为从网络获取。

// PullToRefreshNews.ets
const MOCK_DATA_FILE_ONE_DIR: string = getContext(this).resourceManager.getStringSync($r('app.string.mock1').id);
const MOCK_DATA_FILE_TWO_DIR: string = getContext(this).resourceManager.getStringSync($r('app.string.mock2').id);

将列表组件、绑定的数据对象和scroller对象包含进去,并添加上滑与下拉方法。

// PullToRefreshNews.ets
PullToRefresh({
  // 必传项,列表组件所绑定的数据
  data: $newsData,
  // 必传项,需绑定传入主体布局内的列表或宫格组件
  scroller: this.scroller,
  // 必传项,自定义主体布局,内部有列表或宫格组件
  customList: () => {
    // 一个用@Builder修饰过的UI方法
    this.getListView();
  },
  // 下拉刷新回调
  onRefresh: () => {
    return new Promise<string>((resolve, reject) => {
      // ...
      }, NEWS_REFRESH_TIME);
    });
  },
  // 上滑加载回调
  onLoadMore: () => {
    return new Promise<string>((resolve, reject) => {
      // ...
    });
  },
  customLoad: null,
  customRefresh: null,
})

首页feed流

通过懒加载实现首页feed流快速渲染与流畅滑动。效果如图所示:

  • 懒加载

新闻应用列表数据往往会达到上万条,针对这类大量数据加载的长列表应用,使用懒加载解决一次性加载长列表数据耗时长、占用过多资源的问题,可以提升页面响应速度。

创建需要加载的数据源。

// PullToRefreshNews.ets
@State newsData: NewsDataSource = new NewsDataSource();

创建子组件。

// PullToRefreshNews.ets
@Component
struct newsItem {
  // ...
}

使用LazyForEach对子组件进行渲染。

// PullToRefreshNews.ets
List({ space: CommonConstants.LIST_SPACE, scroller: this.scroller }) {
  LazyForEach(this.newsData, (item: NewsData) => {
    ListItem() {
      newsItem({
        // ...
      })
    }
    // ...
  }, (item: NewsData, index?: number) => JSON.stringify(item) + index);
}

地址选择页

使用原生位置服务实现定位功能,AlphabetIndexer组件实现地址首字母定位导航条。效果如图所示:

  • 地址选择页效果功能时序图

  • 位置服务与索引条导航

开启手机位置服务功能,使用位置服务提供GNSS定位、网络定位、逆地理编码的功能获取当前地理位置信息,通过AlphabetIndexer组件实现首字母快速定位城市的索引条导航。

申请权限,API9及之后的版本,需要申请ohos.permission.APPROXIMATELY_LOCATION或者同时申请ohos.permission.APPROXIMATELY_LOCATION和ohos.permission.LOCATION;无法单独申请ohos.permission.LOCATION。

// module.json5
"requestPermissions": [
  {
    "name": "ohos.permission.APPROXIMATELY_LOCATION",
    "reason": "$string:approximately_location_desc",
    "usedScene": {
      "abilities": [
        "EntryAbility"
      ],
      "when": "always"
    }
  },
  {
    "name": "ohos.permission.LOCATION",
    "reason": "$string:location_desc",
    "usedScene": {
      "abilities": [
        "EntryAbility"
      ],
      "when": "always"
    }
  }
],

申请用户安全权限。

// Index.ets
onPageShow(): void {
  abilityAccessCtrl.createAtManager().requestPermissionsFromUser(getContext(), [
    'ohos.permission.LOCATION', 'ohos.permission.APPROXIMATELY_LOCATION']).then(() => {
    if (this.status) {
      geoLocationManager.getCurrentLocation(locationChange);
      this.status = false;
    }
  });
}

导入位置服务。

// Index.ets
import { geoLocationManager } from '@kit.LocationKit';

获取地理位置信息,进行逆地理编码获取当前位置。

// Index.ets
let locationChange: (err: BusinessError, location: geoLocationManager.Location) => void = (err, location) => {
  if (err) {
    hilog.error(0x00000, 'locationChanger: err=', JSON.stringify(err));
  }
  if (location) {
    let reverseGeocodeRequest: geoLocationManager.ReverseGeoCodeRequest = {
      'latitude': location.latitude,
      'longitude': location.longitude,
      'maxItems': CommonConstants.MAX_ITEMS
    };
    geoLocationManager.getAddressesFromLocation(reverseGeocodeRequest, (err, data) => {
      if (data) {
        hilog.info(0x00000, 'getAddressesFromLocation: data=', JSON.stringify(data));
        if (data[0].locality !== undefined) {
          AppStorage.setOrCreate('local', data[0].locality.replace(/"/g, '').slice(0, -1));
          AppStorage.setOrCreate('currentLocal', data[0].locality.replace(/"/g, '').slice(0, -1));
        }
      }
    });
  }
};

使用AlphabetIndexer组件实现导航条,通过AlphabetIndexer组件onSelect事件获取选中索引值,通过Scroller的scrollToIndex方法滑动到指定索引位置。

// CityView.ets
AlphabetIndexer({arrayValue: TAB_VALUE, selected: this.stabIndex })
  .height(CommonConstants.FULL_PERCENT)
  .selectedColor($r('app.color.alphabet_select_color'))
  .popupColor($r('app.color.alphabet_pop_color'))
  .selectedBackgroundColor($r('app.color.alphabet_selected_bgc'))
  .popupBackground($r('app.color.alphabet_pop_bgc'))
  .popupPosition({ x: $r('app.integer.pop_position_x'), y: $r('app.integer.pop_position_y') })
  .usingPopup(true)
  .selectedFont({size: $r('app.integer.select_font'), weight: FontWeight.Bolder })
  .popupFont({ size: $r('app.integer.pop_font'), weight: FontWeight.Bolder })
  .alignStyle(IndexerAlign.Right)
  .itemSize(CommonConstants.ITEM_SIZE)
  .onSelect((tabIndex: number) => {
    this.scroller.scrollToIndex(tabIndex);
  })
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值