动动小手学鸿蒙 HarmonyOS APP(01)

前言


通过在鸿蒙开发者官网学习了一些基础知识后,感觉可以动手操作一番来实际体验下鸿蒙开发了。 大致看过一遍《HarmonyOS第一课》,对整体的概念有了点了解。感觉陌生又熟悉,有安卓的味道,又有VUE的味道。

官方课程传送门

本篇的大致内容如下:

  1. 效果展示
  2. 知识点分解
  3. 具体实现
  4. 最后总结

一. 效果展示

二. 知识点分解

1. 基础组件使用

  • Tabs + TabContent

以Tabs组件为基础,其中有2个TAB分别展示了"首页"TAB和"项目"TAB,本例中项目TAB下无内容。仅作为展示Tabs组件所用。

Tabs通过页签进行内容视图切换的容器组件,每个页签对应一个内容视图。 需要配合TabContent组件使用,TabContent仅在Tabs中使用,对应一个切换页签的内容视图。

  • Swiper

首页顶部放置的Swiper组件, 滑块视图容器,提供子组件滑动轮播显示的能力。

  • Web

提供具有网页显示能力的Web组件,@ohos.web.webview提供web控制能力。

2. 页面跳转

@system.router (页面路由) 通过不同的uri访问不同的页面。

本例中,在点击Swiper组件内容或文章列表项后,跳转到应用内的Web页面。

3. 网络请求

@ohos.net.http (数据请求)

参考鸿蒙官方sample代码,对http数据请求进行了简单封装。可以进行get类型的请求发送。

4. 引入三方库

由于鸿蒙原生List控件并没有下拉刷新和上拉加载功能,并且在开发者官网给出支持下拉刷新和上拉加载的demo使用起来并不方便。所以本例中使用了三方库,该引入的三方库在实现列表下拉和上拉操作时会方便许多。

这也是三方库能够加速app开发的重要特性。 本例中就以引入该三方列表库来展示如何在鸿蒙开发中引入静态三方库。

三. 具体实现

首先需要建立首页结构。在MainPage中加入Tabs组件,在Tabs中加入TabContent.

ts 复制代码
@Builder TabBuilder(title: string, index: number, icon: Resource) {
  Column() {
    Image(icon)
      .width($r('app.float.mainPage_baseTab_size'))
      .height($r('app.float.mainPage_baseTab_size'))
      .fillColor(this.getTabBarColor(index))
    Text(title)
      .margin({ top: $r('app.float.mainPage_baseTab_top') })
      .fontSize($r('app.float.main_tab_fontSize'))
      .fontColor(this.getTabBarColor(index))
  }
  .justifyContent(FlexAlign.Center)
  .height($r('app.float.mainPage_barHeight'))
  .width(Constants.FULL_PARENT)
  .onClick(() => {
    this.currentTabIndex = index;
    this.tabsController.changeIndex(this.currentTabIndex)
  })
}

build() {
  Tabs({
    barPosition: BarPosition.End,
    controller: this.tabsController
  }) {
    // 首页
    TabContent() {
      Home()
    }
    .padding({ left: $r('app.float.mainPage_padding'), right: $r('app.float.mainPage_padding') })
    .backgroundColor($r('app.color.mainPage_backgroundColor'))
    .tabBar(this.TabBuilder(Constants.HOME_TITLE, Constants.HOME_TAB_INDEX
      , $r('app.media.ic_bottom_home')))

    // 项目
    TabContent() {
      Project()
    }
    .padding({ left: $r('app.float.mainPage_padding'), right: $r('app.float.mainPage_padding') })
    .backgroundColor($r('app.color.mainPage_backgroundColor'))
    .tabBar(this.TabBuilder(Constants.PROJECT_TITLE, Constants.PROJECT_TAB_INDEX
      , $r('app.media.ic_bottom_project')))
  }
  .width(Constants.FULL_PARENT)
  .backgroundColor(Color.White)
  .barHeight($r('app.float.mainPage_barHeight'))
  .barMode(BarMode.Fixed)
  .onChange((index: number) => {
    this.currentTabIndex = index;
  })
}

Home中由一个Banner自定义组件和ArticleList自定义组件构成

ts 复制代码
@Component
export default struct Home {
  build() {
    Stack() {
      ArticleList();
      Banner();
    }.alignContent(Alignment.Top)
  }
}

Banner组件中进行Banner数据请求并填充到Swiper组件中。

aboutToAppear生命周期中进行了请求发送。

对于组件的 aboutToAppear 和 aboutToDisappear 在开发过程中会比较常用到。

aboutToAppear

aboutToAppear?(): void

aboutToAppear函数在创建自定义组件的新实例后,在执行其build函数之前执行。允许在aboutToAppear函数中改变状态变量,更改将在后续执行build函数中生效。

aboutToDisappear

aboutToDisappear?(): void

aboutToDisappear函数在自定义组件析构销毁之前执行。不允许在aboutToDisappear函数中改变状态变量,特别是@Link变量的修改可能会导致应用程序行为不稳定。

点击Banner项时将进行页面跳转

ts 复制代码
aboutToAppear() {
  HomeViewModel.getHomeBanner(Constants.GET_HOME_BANNER).then((data: HomeBannerItemBean[]) => {
    this.bannerData = data;
  }).catch((err: string | Resource) => {
    promptAction.showToast({
      message: err,
      duration: Constants.ANIMATION_DURATION
    });
  });
}

build() {
  Column() {
    Swiper(this.swiperController) {
      ForEach(this.bannerData, (banner: HomeBannerItemBean) => {
        Image(banner.imagePath).borderRadius($r('app.float.home_swiper_borderRadius')).onClick(() => {
          router.pushUrl({
            url: 'pages/WebPage',
            params: {
              title: banner.title,
              src: banner.url
            }
          }, router.RouterMode.Single)
        })
      }, (img: Resource) => JSON.stringify(img.id))
    }
    .margin({top: $r('app.float.home_swiper_margin')})
    .autoPlay(true)
    .width(Constants.FULL_PARENT)
    .height($r('app.float.main_swiper_height'))
  }
}

文章列表实现, 也是在aboutToAppear中进行数据请求。可以看到这里使用的三方组件ListView很轻松的实现了目标功能。

ts 复制代码
aboutToAppear() {
  this.getHomeArticleList(true);
}

@Builder
itemLayout(item, index) {
  ArticleItem({articleData: item})
}

getHomeArticleList(reset: boolean) {
  HomeViewModel.getHomeArticleList(this.currentPage, this.pageSize, Constants.GET_HOME_ARTICLE_LIST)
    .then((data: ArticleDataBean) => {
    if (data.curPage < data.pageCount) {
      this.currentPage++;
      this.hasMore = true;
    }  else {
      this.hasMore = false;
    }
    if (reset) {
      this.articleData = data.datas;
    } else {
      this.articleData = this.articleData.concat(data.datas);
    }
  }).catch((err: string | Resource) => {
    promptAction.showToast({ message: err});
  })
}

build() {
  ListView({
    items: this.articleData, //数据源 数组
    itemLayout: (item, index) => this.itemLayout(item, index),
    controller: this.controller, //控制器,负责关闭下拉和上拉
    marginHeader: 160,
    onRefresh: () => {
      //下拉刷新
      this.getHomeArticleList(true);
      this.controller.finishRefresh()
    },
    onLoadMore: () => {
      //上拉加载
      this.getHomeArticleList(false);
      this.controller.finishLoadMore()
    }
  })
}

新建WebPage, 封装Web组件实现网页的展示。 WebPage接受src参数作为页面url, 接受title参数作为标题栏文字。

ts 复制代码
import router from '@ohos.router';
import web_webview from '@ohos.web.webview'
import Constants from '../common/Constants';

@Entry
@Component
struct WebPage {
  @State src: string = router.getParams()?.['src'];
  @State title: string = router.getParams()?.['title'];

  controller: web_webview.WebviewController = new web_webview.WebviewController();

  build() {
    Column() {
      PageTitle({ titleName: this.title })

      Divider()
        .strokeWidth('1px')
        .color($r('sys.color.ohos_id_color_list_separator'))

      Web({
        src: this.src, controller: this.controller
      }).javaScriptAccess(true)
    }
  }
}

@Component
struct PageTitle {
  private titleName: string

  build() {
    Row() {
      Image($r('app.media.back'))
        .width(20)
        .height(20)
        .onClick(() => {
          router.back()
        })
      Text(this.titleName)
        .fontSize(Constants.PAGE_TITLE_TEXT_SIZE)
        .width(Constants.PAGE_TITLE_TEXT_WIDTH)
        .maxLines(Constants.PAGE_TITLE_TEXT_MAX_LINES)
        .textOverflow({overflow: TextOverflow.Ellipsis})
        .margin({ left: 20 })
    }
    .padding(12)
    .width('100%')
  }
}

别忘记在pages配置中添加WebPage页面

该配置位于ets/resources/base/profile/main_pages.json

ts 复制代码
{
  "src": [
    "pages/MainPage",
    "pages/WebPage"
  ]
}

网络请求方法封装, 实际项目中可以考虑换成其他三方库来实现网络请求。以实现Header拦截,error拦截等操作,以及其他如post类型等其他常见需求。

本例中使用的开放接口来自WanAndroid大神的无私奉献,此例中仅使用了2个接口。大家如有兴趣可以参考并自行实现全部接口。

感谢鸿洋大佬的WanAndroid网站提供的 传送门

ts 复制代码
/**
 * Initiates an HTTP request to a given URL.
 *
 * @param url URL for initiating an HTTP request.
 * @param params Params for initiating an HTTP request.
 */
export function httpRequestGet(url: string): Promise<ResponseResult> {
  let httpRequest = http.createHttp();
  let responseResult = httpRequest.request(url, {
    method: http.RequestMethod.GET,
    readTimeout: Constants.HTTP_READ_TIMEOUT,
    header: {
      'Content-Type': ContentType.JSON
    },
    connectTimeout: Constants.HTTP_READ_TIMEOUT,
    extraData: {}
  });
  let serverData: ResponseResult = new ResponseResult();
  // Processes the data and returns.
  return responseResult.then((value: http.HttpResponse) => {
    if (value.responseCode === Constants.HTTP_CODE_200) {
      // Obtains the returned data.
      let result = `${value.result}`;
      let resultJson: ResponseResult = JSON.parse(result);
      if (resultJson.errorCode === Constants.SERVER_CODE_SUCCESS) {
        serverData.data = resultJson.data;
      }
      serverData.errorCode = resultJson.errorCode;
      serverData.errorMsg = resultJson.errorMsg;
    } else {
      serverData.errorMsg = `${$r('app.string.http_error_message')}&${value.responseCode}`;
    }
    return serverData;
  }).catch(() => {
    serverData.errorMsg = $r('app.string.http_error_message');
    return serverData;
  })
}

介绍下三方库的引入方法:

目前可以使用静态包下载后拷贝到项目中的方法来实现引入三方库。 首先在entry目录下新建一个文件夹,比如libs。将下载的har包复制进去。 然后打开entry目录下的oh-package.json5文件。在dependencies字段中填入引入的har包的地址。

完成之后进行一次sync操作。引入的三方静态包将会被以代码形式加入到项目中。

此三方列表组件详细使用和介绍见原文: 传送门

四. 最后总结

Demo地址: github.com/kainbro/HaW...

鸿蒙基础UI组件使用+页面跳转+网络数据请求+三方组件引入,这一套基础操作在着手去写App时是必不可少的。 熟练掌握后便可继续探索鸿蒙开发。刚开始还比较粗糙请多包涵!

相关推荐
爱桥代码的程序媛2 小时前
鸿蒙OpenHarmony【轻量系统芯片移植案例】标准系统方案之瑞芯微RK3568移植案例
嵌入式硬件·harmonyos·鸿蒙·鸿蒙系统·移植·openharmony·鸿蒙开发
AORO_BEIDOU2 小时前
防爆手机+鸿蒙系统,遨游通讯筑牢工业安全基石
5g·安全·智能手机·信息与通信·harmonyos
小强在此17 小时前
【基于开源鸿蒙(OpenHarmony)的智慧农业综合应用系统】
华为·开源·团队开发·智慧农业·harmonyos·开源鸿蒙
PlumCarefree21 小时前
基于鸿蒙API10的RTSP播放器(四:沉浸式播放窗口)
华为·harmonyos
中关村科金1 天前
中关村科金推出得助音视频鸿蒙SDK,助力金融业务系统鸿蒙化提速
华为·音视频·harmonyos
小强在此1 天前
基于OpenHarmony(开源鸿蒙)的智慧医疗综合应用系统
华为·开源·团队开发·健康医疗·harmonyos·开源鸿蒙
奔跑的露西ly1 天前
【鸿蒙 HarmonyOS NEXT】popup弹窗
华为·harmonyos
OH五星上将2 天前
OpenHarmony(鸿蒙南向开发)——轻量和小型系统三方库移植指南(一)
嵌入式硬件·移动开发·harmonyos·openharmony·鸿蒙开发·鸿蒙移植
codes234577892 天前
鸿蒙开发之ArkTS 界面篇 一
harmonyos·arkts·harmonyos next·deveco-studio·鸿蒙界面·鸿蒙界面入门·鸿蒙 index.ets