华为鸿蒙开发笔记

记在前面

因无法直接使用chatgpt进行编程(悲,2024/10),故记录笔记,方便查阅,基于arkts语言

常用网址

官方文档链接

快速入门

开发者认证

codelabs

开发者激励

TODO

显示类

组件->数据交互->渲染控制->组件封装->页面布局->页面导航(参数传递)

生命周期事件,用户交互事件

服务类

本地持久化,云端网络化,测试技巧

DevEco

环境配置

  1. 注册华为开发者账号

  2. 下载Huawei DevEco Studio软件,然后一路安装,记得勾选添加bin到path

  3. 项目创建 : 选择empty,然后一路默认

  4. 模拟器调试 : 右上方选择device manager,登陆后跳转填写问卷调查后,等待审核通过,就可以下载鸿蒙模拟器进行测试了

目录结构

.clang-format :用于定义代码的格式化规则,保证项目中代码风格的一致性。

AppScope :用于定义应用程序作用域的相关文件或代码。通常与应用功能的模块化有关

.hvigor : 鸿蒙OS下的Hvigor是类似于Gradle的构建工具,这个目录存放Hvigor的相关配置文件。

hvigor : 存放Hvigor工具的相关文件,这些文件控制项目的构建和打包过程。

hvigorfile.ts :Hvigor配置的脚本文件,使用TypeScript编写,定义了项目构建流程。

build-profile.json5:存储项目构建时的配置,例如不同的构建环境或配置参数。

code-linter.json5:用于代码风格检查(Linter)的配置文件,定义项目的代码质量和规范标准。

local.properties :本地开发环境的配置文件,一般不上传至远程仓库,包含系统路径等本地配置信息。

oh-package-lock.json5 :确保项目中安装的依赖版本固定,避免不同环境中依赖版本不一致的问题。

oh-package.json5 :描述项目的依赖包、脚本和元数据,类似于npm的package.json

oh_modules 存放鸿蒙项目的依赖模块或库,类似于Node.js项目中的node_modules

中文

deveco是默认有中文包的,所以在市场里面搜不错,而应该在已安装里面搜索,然后启用就行了

测试

对ts进行单独测试

打开entry/src/test/LocalUnit.test.ets

将需要测试的代码附加再后面,然后就可以运行了,如下面测试代码

const first: string = "Hello World";
console.log(first);

图标与名称

在src/mian/module.json5里面的icon进行图标设置

在resoures/zh_CN里面的EntryAbility_label进行名称设置

常用组件

大小单位

如下,有两种设置方式

@Entry
@Component
struct Index {
    
    
  build() {
    
    
    Column({
    
     }) {
    
    
      Text()
        .width(360)
        .height(360)
        .backgroundColor(0x0000FF)
      Text()
        .width('100%')
        .height('50%')
        .backgroundColor(0x00FF00)
    }
  }
}

Text

Text('HarmonyOS')
  .fontColor(Color.Blue)
  .fontSize(40)
  .fontStyle(FontStyle.Italic)
  .fontWeight(FontWeight.Bold)
  .textAlign(TextAlign.Center)

使用资源文件

Text($r('app.string.EntryAbility_label'))

Image

资源图片

Image($r("app.media.image2"))
  .objectFit(ImageFit.Cover)
  .backgroundColor(0xCCCCCC)
  .width(100)
  .height(100)
  • Contain:保持宽高比进行缩小或者放大,使得图片完全显示在显示边界内。

  • Cover(默认值):保持宽高比进行缩小或者放大,使得图片两边都大于或等于显示边界。

  • Auto:自适应显示。

  • Fill:不保持宽高比进行放大缩小,使得图片充满显示边界。

  • ScaleDown:保持宽高比显示,图片缩小或者保持不变。

  • None:保持原有尺寸显示。

网络图片

Image('https://www.example.com/xxx.png')

为了成功加载网络图片,您需要在module.json5文件中申明网络访问权限。

{
    
    
    "module" : {
    
    
        "requestPermissions":[
           {
    
    
             "name": "ohos.permission.INTERNET"
           }
        ]
    }
}

TextInput

@Entry
@Component
struct Test {
    
    
  @State text: string = ''

  build() {
    
    
    Column() {
    
    
      TextInput({
    
     placeholder: '请输入账号' })
        .caretColor(Color.Blue)
        .onChange((value: string) => {
    
    
          this.text = value
        })
      Text(this.text)
    }
  }
}

Button

点击事件

@Entry
@Component
struct Test {
    
    
  @State num: number = 0

  build() {
    
    
    Column() {
    
    
      Button('click me', {
    
     type: ButtonType.Capsule, stateEffect: true })
      .onClick(() => {
    
    
        this.num+=1
        })
      Text(this.num.toString())
    }
  }
}

type用于定义按钮样式三种Capsule,Normal,Circle

stateEffect用于设置按钮按下时是否开启切换效果,当状态置为false时,点击效果关闭,默认值为true。

图标按钮

@Entry
@Component
struct Test {
    
    

  build() {
    
    
    Column() {
    
    
      Button({
    
     type: ButtonType.Circle, stateEffect: true }) {
    
    
        // Image($r('app.media.startIcon'))
        //   .width(30)
        //   .height(30)

        SymbolGlyph($r('sys.symbol.ohos_folder_badge_plus'))
          .fontSize(45)
      }
      .width(55)
      .height(55)
      .backgroundColor(0x00FF00)
    }
  }
}

LoadingProgress

LoadingProgress()
    .color(Color.Blue)
    .height(60)
    .width(60)

Foreach

接口如下

ForEach(
  arr: Array,
  itemGenerator: (item: any, index: number) => void,
  keyGenerator?: (item: any, index: number) => string
)

简单应用如下,

@Entry
@Component
struct Index {
    
    
  @State simpleList: Array<string> = ['one', 'two', 'three'];

  build() {
    
    
    Column() {
    
    
      ForEach(this.simpleList, (item: string,index:number) => {
    
    
        Text(`${
      
      index} : `+item)
          .fontSize(50)
      }, (item: string) => item)
    }
  }
}

List

如下,需要先用List,再使用ListItem指定显示项

@Entry
@Component
struct Index {
    
    
  @State results:number[] = [1,2,3,4,5,6]

  build() {
    
    
    List() {
    
    
      ForEach(this.results,(item:number)=>{
    
    
        ListItem(){
    
    
          Text(item.toString())
            .fontSize(30)
            .width('100%')
            .height('30%')
            .borderWidth(1)
            .borderColor(0x000000)
        }
      })
    }
  }
}

弹窗

简单提醒

基于模态窗口

Button('删除')
    .onClick(() => {
    
    
    if (this.selectedCardList.length == 0) {
    
    
        AlertDialog.show(
            {
    
    
                message: '已经删完了喔',
                autoCancel: true,
                alignment: DialogAlignment.Bottom,
            })
    } 

简单提醒2

onBackPress()
  {
    
    
    if (this.isShowToast()) {
    
    
      prompt.showToast(
        {
    
    
          message: $r('app.string.prompt_text'),
          duration: 1000
        });
      this.clickBackTimeRecord = new Date().getTime();
      return true;
    }
    return false;
  }

样式复用

使用@Style装饰,但只能在本文件中复用,说实话有点鸡肋

组件封装

class Article {
    
    
  id: string;
  title: string;
  brief: string;

  constructor(id: string, title: string, brief: string) {
    
    
    this.id = id;
    this.title = title;
    this.brief = brief;
  }
}

@Entry
@Component
struct ArticleListView {
    
    
  @State isListReachEnd: boolean = false;
  @State articleList: Array<Article> = []

  aboutToAppear(): void {
    
    
    for (let i = 1; i <= 4; i++) {
    
    
      const id = i.toString().padStart(3, '0'); // 格式化ID,如 '001', '002', etc.
      const title = `${
      
      i}篇文章`;
      const content = '文章简介内容';
      this.articleList.push(new Article(id, title, content));
    }
  }

  loadMoreArticles() {
    
    
    this.articleList.push(new Article('007', '加载的新文章', '文章简介内容'));
  }

  build() {
    
    
    Column({
    
     space: 5 }) {
    
    
      List() {
    
    
        ForEach(this.articleList, (item: Article) => {
    
    
          ListItem() {
    
    
            ArticleCard({
    
     article: item })
              .margin({
    
     top: 20 })
          }
        }, (item: Article) => item.id)
      }
      .padding(20)
      .scrollBar(BarState.Off)
    }
    .width('100%')
    .height('100%')
    .backgroundColor(0xF1F3F5)
  }
}

@Component
struct ArticleCard {
    
    
  @Prop article: Article;

  build() {
    
    
    Row() {
    
    
      Image($r('app.media.startIcon'))
        .width(80)
        .height(80)
        .margin({
    
     right: 20 })

      Column() {
    
    
        Text(this.article.title)
          .fontSize(20)
          .margin({
    
     bottom: 8 })
        Text(this.article.brief)
          .fontSize(16)
          .fontColor(Color.Gray)
          .margin({
    
     bottom: 8 })
      }
      .alignItems(HorizontalAlign.Start)
      .width('80%')
      .height('100%')
    }
    .padding(20)
    .borderRadius(12)
    .backgroundColor('#FFECECEC')
    .height(120)
    .width('100%')
    .justifyContent(FlexAlign.SpaceBetween)
  }
}

布局

动画

动态样式

多态样式

@Entry
@Component
struct Index {
    
    
  @State focusedIndex: number = -1; // 用于存储当前焦点项的索引
  @State items: string[] = ["Item 1", "Item 2", "Item 3", "Item 4"];


  build() {
    
    
    Column(){
    
    
      //focus
      List() {
    
    
        ForEach(this.items, (item:string, index) => {
    
    
          ListItem(){
    
    
            Text(item)
              .focusable(true)
              .onClick(() => {
    
    
                this.focusedIndex = index; // 设置当前焦点项的索引
              })
              .backgroundColor(this.focusedIndex === index ? Color.Blue : Color.Transparent) // 焦点态样式
              .padding(10)
              .borderRadius(5)
          }
        })
      }
      
      //press
      Button('Button1')
        .stateStyles({
    
    
          pressed: {
    
    
            .backgroundColor(Color.Green)
          },
          normal: {
    
    
            .backgroundColor(Color.Red)
          }
        })
      
      //disable
      Button('Button2')
        .enabled(false)
        .stateStyles({
    
    
          disabled: {
    
    
            .backgroundColor(Color.Grey)
          }
        })
    }
  }
}

导航

页面跳转,以及传参

Main Page

import {
    
     DetailPage } from './DetailPage';

@Entry
@Component
struct MainPage {
    
    
  @Provide('pageInfos') pageInfos: NavPathStack = new NavPathStack()

  build() {
    
    
    Column() {
    
    
      Navigation(this.pageInfos) {
    
    
        Button('Next Page')
          .align(Alignment.Center)
          .margin({
    
     top: 100 })
          .borderRadius(12)
          .onClick(() => {
    
    
            this.pageInfos.pushPathByName("DetailPage",123)
          });
      }
      .title("Main Page")
      .navDestination(this.PageMap);
    }
  }

  @Builder
  PageMap(name: string) {
    
    
    if (name === "DetailPage") {
    
    
      DetailPage(); // 调用详情页面
    }
  }
}

Detail Page

@Component
export struct DetailPage {
    
    
  @Consume('pageInfos') pageInfos: NavPathStack;

  build() {
    
    
    NavDestination() {
    
    
      Column() {
    
    
        Text(this.pageInfos.getParamByName("DetailPage").toString())
          .fontSize(30)
          .margin({
    
     top: 20 })
      }
    }
    .title("Detail Page")
    .onBackPressed(() => {
    
    
      this.pageInfos.pop();
      return true;
    });
  }
}

位图

解码

资源文件

下面给出了资源文件和rawfile的两种方法

import {
    
     image } from '@kit.ImageKit';

async function createBitmapFromImage(resourcePath: Resource): Promise<PixelMap | null> {
    
    
  try {
    
    
    const img = await getContext().resourceManager.getMediaContent(resourcePath);
    const imageSource: image.ImageSource = image.createImageSource(img.buffer.slice(0));

    const decodingOptions: image.DecodingOptions = {
    
    
      editable: true,
      desiredPixelFormat: 3,
    };
    
    const pixelMap = await imageSource.createPixelMap(decodingOptions);
    return pixelMap;
  } catch (err) {
    
    
    return null;
  }
}

async function createBitmapFromRawfile(picName:string): Promise<PixelMap | null> {
    
    
  try {
    
    
    const rawFileDescriptor = await getContext().resourceManager.getRawFd(picName);
    const imageSource: image.ImageSource = image.createImageSource(rawFileDescriptor);

    const decodingOptions: image.DecodingOptions = {
    
    
      editable: true,
      desiredPixelFormat: 3,
    };
    
    const pixelMap = await imageSource.createPixelMap(decodingOptions);
    return pixelMap;

  } catch (err) {
    
    
    return null;
  }
}


@Entry
@Component
struct Index {
    
    
  @State pixelMap: PixelMap | null = null;

  async loadImage() {
    
    
    // const newPixelMap = await createBitmapFromImage($r("app.media.startIcon"));
    const newPixelMap = await createBitmapFromRawfile('test.jpg')
    //因为类型判断,所以这里应该新建一个,再操作完成后再赋值给状态变量
    if (newPixelMap) {
    
    
      newPixelMap.opacity(0.5);
      this.pixelMap = newPixelMap;
    }
  }


  build() {
    
    
    Column(){
    
    
      Button('click me')
        .onClick(()=>{
    
    
          this.loadImage();
        })
      if (this.pixelMap) {
    
    
        Image(this.pixelMap)
          .width(100)
          .height(100)
          .autoResize(true)
      } else {
    
    
        Text("Loading image...");
      }
    }
  }
}

沙箱文件

还没研究.todo

位图处理

生命周期事件

页面生命周期

被@Entry装饰的组件生命周期

onPageShow:页面每次显示时触发一次,包括路由过程、应用进入前台等场景
onPageHide:页面每次隐藏时触发一次,包括路由过程、应用进入前后台等场景
onBackPress:当用户点击返回按钮时触发。

组件生命周期

被@Component装饰的自定义组件的生命周期

aboutToAppear:组件即将出现时回调该接口,具体时机为在创建自定义组件的新实例后,在执行其build()函数之前执行。

aboutToDisappear:在自定义组件即将析构销毁时执行。

onAppear:是每个组件的属性方法,在该组件显示时触发此回调。

设备信息

屏幕信息

// 获取屏幕分辨率
getDisplayInfo() {
    
    
  // 获取默认显示器
  const defaultDisplay = display.getDefaultDisplaySync()
  // 获取屏幕高度
  this.displayHeight = defaultDisplay.height
  // 获取屏幕宽度
  this.displayWidth = defaultDisplay.width
  // 获取屏幕刷新率
  this.displayRefreshRate = defaultDisplay.refreshRate
  // 获取像素密度
  this.displayDensityDPI = defaultDisplay.densityDPI
}

在使用previewer获取到的信息都是0,所以在开发的时候先设定定值

// private  deviceWidth:number = display.getDefaultDisplaySync().width;
// private  deviceHeight:number = display.getDefaultDisplaySync().height;

private  deviceWidth:number = 1080;
private  deviceHeight:number = 2340;

网络状态

网络权限

在module.json5中配置网络权限

 "requestPermissions": [
      {
    
    
        "name": "ohos.permission.GET_NETWORK_INFO"// 获取网络信息权限
      }
    ],

IP

//获取 IP 地址
getIPAddress() {
    
    
  // 获取默认网络
  const netHandle = connection.getDefaultNetSync()
  // 获取网络连接信息
  const connectionProperties = connection.getConnectionPropertiesSync(netHandle)
  // 提取链路信息
  const linkAddress = connectionProperties.linkAddresses?.[0]
  if (linkAddress) {
    
    
    // 提取 IP 地址
    this.IPAddress = linkAddress.address.address
  }
}

网络类型

// 获取网络类型
getConnection() {
    
    
  const hasDefaultNet = connection.hasDefaultNetSync() // 是否有默认网络
  // 没有网络
  if (!hasDefaultNet) {
    
    
    this.netBearType = '无网络连接'
  } else {
    
    
    this.getConnectionNetBearType()
  }
}

// 获取网络类型
getConnectionNetBearType() {
    
    
  // 1. 获取默认网络
  const defaultNet = connection.getDefaultNetSync()
  // 2. 获取网络能力信息
  const netCapabilities = connection.getNetCapabilitiesSync(defaultNet)
  // 3. 判断网络类型
  if (netCapabilities.bearerTypes.includes(connection.NetBearType.BEARER_ETHERNET)) {
    
    
    this.netBearType = '以太网网络'
  } else if (netCapabilities.bearerTypes.includes(connection.NetBearType.BEARER_WIFI)) {
    
    
    this.netBearType = 'WIFI网络'
  } else if (netCapabilities.bearerTypes.includes(connection.NetBearType.BEARER_CELLULAR)) {
    
    
    this.netBearType = '蜂窝网络'
  }
}

猜你喜欢

转载自blog.csdn.net/killsime/article/details/143108605