鸿蒙学习记录

简介

在自学鸿蒙开发的过程中,记录下自己遇到的问题以及解决办法,持续更新!!!

环境信息:

  • 系统:Mac 13.5 (22G74)
  • IDE信息:
    • DevEco Studio 3.1.1 Release
    • Build Version: 3.1.0.501, built on June 20, 2023

一、学习文档

1、HarmonyOS 第一课

2、鸿蒙开发者文档指南

3、鸿蒙开发者文档API参考

4、华为开发者论坛

5、鸿蒙开发基础认证和高级认证

6、鸿蒙应用发布


二、遇到的问题

1、DevEco Studio自动生成.js、.js.map文件问题

通过插件(ArkCompilerSupport)搞定

插件地址点我

参考博客

2、DevEco Studio无法自动监测到本地模拟器?

每次点击DeviceManager启动模拟器后,DevEco Studio都不会自动检测到模拟器问题,可以参考以下代码片段进行开发环境配置。

参考华为开发者社区论坛博客

# 鸿蒙脚本工具集
export PATH=/Users/XXX/HMOSProject/EnvInfo/sdk/openharmony/9/toolchains:$PATH
export PATH=/Users/XXX/HMOSProject/EnvInfo/ohpm/bin:$PATH
# HDC是为开发者提供HarmonyOS应用/服务的调试工具,为方便使用HDC工具,请为HDC端口号设置环境变量。
export HDC_SERVER_PORT=7035
launchctl setenv HDC_SERVER_PORT $HDC_SERVER_PORT
export HDC_SERVER_PORT

3、网络请求如何实现?

  • 方法1:第三方组件:axios
  • 方法2:系统API:http,封装示例如下:
import http from '@ohos.net.http';


import http from '@ohos.net.http';

// 标准网络请求返回数据结构
export interface  SJRequestConfig extends http.HttpRequestOptions{
  url?:string; // url参数
}

// 全局配置对象
export interface SJGlobalConfig {
  // baseUrl
  baseUrl?: string;
  // 是否使用缓存:默认为true
  usingCache?: boolean;
  // 全局header,默认值为 'content-type': 'application/json'
  header?: Object;
  // 读取超时时间:默认60000,即60s
  readTimeout?: number; // default is 60s
  // 连接超时时间:默认60000,即60s
  connectTimeout?: number; // default is 60s.
  // HTTP协议版本,默认由系统决定
  usingProtocol?: http.HttpProtocol;
}

// 标准网络请求返回数据结构
export class SJResponse<T> {
  status?: number;
  message?: string;
  data?: T;
}

// 网络库返回的错误对象
export class SJError<R> {
  code:Number; // HTTP错误码
  message?:string; // HTTP错误信息
  businessCode?:Number; // 业务code
  businessMessage?:string; // 业务message
  data?:R; //原始数据
}

export class SJHttp {
  request<T,R= string | Object | ArrayBuffer>(config:SJRequestConfig): Promise<T> {
    // 每一个httpRequest对应一个HTTP请求任务,不可复用
    let httpRequest = http.createHttp();
    // 请求参数配置
    let httpResponse = httpRequest.request(config.url,config)

    return new Promise((resolve,reject)=>{
      httpResponse.then((data: http.HttpResponse) => {
        let httpCode = data.responseCode;
        if (httpCode == 200) {
          // data.result为HTTP响应内容,可根据业务需要进行解析
          let resultStr = `${data.result}`
          let response:SJResponse<T> = JSON.parse(resultStr)
          if (response.status == 200) {
            resolve(response.data);
          } else {
            var sjErr:SJError<R> = new SJError();
            sjErr.code = httpCode;
            sjErr.message = "";
            sjErr.businessCode = response.status;
            sjErr.businessMessage = response.message;
            sjErr.data = data.result as R;
            reject(sjErr)
          }
        }
        var sjErr:SJError<R> = new SJError();
        sjErr.code = httpCode;
        sjErr.message = `${data.result}`;
        sjErr.businessCode = null;
        sjErr.businessMessage = null;
        sjErr.data = data.result as R;
        reject(sjErr)
      }).catch((err) => {
        console.info('error:' + JSON.stringify(err));
        // 取消订阅HTTP响应头事件
        httpRequest.off('headersReceive');
        // 当该请求使用完毕时,调用destroy方法主动销毁。
        httpRequest.destroy();

        var sjErr:SJError<R> = new SJError();
        sjErr.code = err.code;
        sjErr.message = err.message;
        sjErr.businessCode = null;
        sjErr.businessMessage = null;
        sjErr.data = null;
        reject(sjErr)
      })
    })
  }
}

export class SJRequest {
  // 单例对象
  static instance: SJRequest = new SJRequest();
  // 全局配置对象
  public globalConfig:SJGlobalConfig;

  constructor() {
    this.globalConfig = {
      usingCache:true,
      readTimeout:60000,
      connectTimeout:60000,
      usingProtocol:http.HttpProtocol.HTTP1_1,
    }
  }

  get<T>(config:SJRequestConfig): Promise<T> {
    config.method = http.RequestMethod.GET;
    return this.request(config);
  }

  post<T>(config:SJRequestConfig): Promise<T> {
    config.method = http.RequestMethod.POST;
    return this.request(config);
  }

  delete<T>(config:SJRequestConfig): Promise<T> {
    config.method = http.RequestMethod.DELETE;
    return this.request(config);
  }

  put<T>(config:SJRequestConfig): Promise<T> {
    config.method = http.RequestMethod.PUT;
    return this.request(config);
  }

  request<T>(config:SJRequestConfig): Promise<T> {
    this.handleGlobalConfig(config);
    let sjReq = new SJHttp();
    return sjReq.request<T>(config)
  }

  private isEmpty(obj:any):boolean{
    if (obj == undefined || obj == null) {
      return true;
    }
    return false;
  }

  private handleGlobalConfig(config:SJRequestConfig):SJRequestConfig{
    let globalConfig:SJGlobalConfig = this.globalConfig;
    // 1.处理url
    let url = config.url;
    let urlBase = globalConfig.baseUrl;

    let urlLower = url.toLowerCase();
    let isHttpUrl = urlLower.startsWith('http') || urlLower.startsWith('https');
    if (!isHttpUrl) {
      config.url = `${urlBase}${url}`
    }

    // 2、处理其他参数
    if (this.isEmpty(config.usingCache)) {
      config.usingCache = globalConfig.usingCache;
    }
    if (this.isEmpty(config.header)) {
      config.header = globalConfig.header;
    }
    if (this.isEmpty(config.readTimeout)) {
      config.readTimeout = globalConfig.readTimeout;
    }
    if (this.isEmpty(config.connectTimeout)) {
      config.connectTimeout = globalConfig.connectTimeout;
    }
    if (this.isEmpty(config.usingProtocol)) {
      config.usingProtocol = globalConfig.usingProtocol;
    }
    return config;
  }
}

export class SJRequestManager {
  // 全局配置
  static configGlobal(callback:(config:SJGlobalConfig)=>void){
    callback(SJRequest.instance.globalConfig);
  }

  static get<T>(config:SJRequestConfig): Promise<T> {
    return SJRequest.instance.get(config);
  }

  static post<T>(config:SJRequestConfig): Promise<T> {
    return SJRequest.instance.post(config);
  }

  static delete<T>(config:SJRequestConfig): Promise<T> {
    return SJRequest.instance.delete(config);
  }

  static put<T>(config:SJRequestConfig): Promise<T> {
    return SJRequest.instance.put(config);
  }

  static request<T>(config:SJRequestConfig): Promise<T> {
    return SJRequest.instance.request(config);
  }
}

4、List上拉加载,下拉刷新如何实现?

参考第三方组件: PullToRefresh

5、页面导航栏如何实现?

通过系统的Navigation组件即可实现

@Builder content(){
    Navigation(){
      Column() {
        this.appGridView()
        // LayoutGridDratEditPage()
      }
    }
    .mode(NavigationMode.Auto)
    .titleMode(NavigationTitleMode.Mini)
    .title('编辑应用')
    .menus(this.rightBtns())
  }

6、grid拖动编辑如何实现?有什么要注意的?

1)设置编辑模式属性为true

//设置Grid是否进入编辑模式,进入编辑模式可以拖拽Grid组件内部GridItem
.editMode(this.isEditable)

2)刚开始拖拽的时候,设置拖拽悬浮展示的UI

//第一次拖拽此事件绑定的组件时,触发回调
.onItemDragStart((event: ItemDragInfo, itemIndex: number) => {
   //设置拖拽过程中显示的图片
   return this.pixelMapBuilder()
})
//拖拽过程中展示的样式
@Builder pixelMapBuilder() {
   Column() {
     Text(this.text).gridItemStyle()
   }
}

3)拖拽到目标gridItem时,根据条件交换数组中的元素

//绑定此事件的组件可作为拖拽释放目标,当在本组件范围内停止拖拽行为时,触发回调
//itemIndex为拖拽起始位置,insertIndex为拖拽插入位置
.onItemDrop((event: ItemDragInfo, itemIndex: number, insertIndex: number, isSuccess: boolean) => {
  let isValidInserIndex = insertIndex < this.numbers.length;
  if (isSuccess && insertIndex < this.numbers.length) {
  // if (isSuccess && isValidInserIndex) {
    //不支持拖拽到已有内容以外的位置
    this.changeIndex(itemIndex, insertIndex)
  } else {
    Logger.warn('griditem交换失败');
  }
})

//交换数组中元素位置
changeIndex(index1: number, index2: number) {
  Logger.warn('交换前:' + this.numbers.toString());
  [this.numbers[index1], this.numbers[index2]] = [this.numbers[index2], this.numbers[index1]];
  Logger.warn('交换后:' + this.numbers.toString());
}

4)完整代码如下:

@Extend(Grid) function gridStyle() {
  .columnsTemplate('1fr 1fr 1fr')
  .columnsGap(10)
  .rowsGap(10)
  .width('100%')
  .backgroundColor(0xFAEEE0)
  .height('100%')
}

@Extend(Text) function gridItemStyle() {
  .fontSize(16)
  .backgroundColor(0xF9CF93)
  .width(80)
  .height(80)
  .textAlign(TextAlign.Center)
}

@Entry
@Component
struct LayoutGridDratEditPage {
  @State numbers: String[] = ['1','2','3','4','5','6','7','8','9','10']
  scroller: Scroller = new Scroller()
  @State text: string = 'drag'
  // 是否是编辑模式
  @State isEditable: boolean = true

  //拖拽过程中展示的样式
  @Builder pixelMapBuilder() {
    Column() {
      Text(this.text).gridItemStyle()
    }
  }

  aboutToAppear() {
    // for (let i = 1;i <= 15; i++) {
    //   this.numbers.push(i.toString())
    // }
    Logger.warn('初始化后:' + this.numbers.toString());
  }

  //交换数组中元素位置
  changeIndex(index1: number, index2: number) {
    Logger.warn('交换前:' + this.numbers.toString());
    [this.numbers[index1], this.numbers[index2]] = [this.numbers[index2], this.numbers[index1]];
    Logger.warn('交换后:' + this.numbers.toString());
  }

  build() {
      Column() {
        this.dragDemo()
        // this.animalTest()
      }
  }

  @Builder dragDemo() {
    Column({ space: 5 }) {
      Text(this.isEditable?'编辑模式':'非编辑模式').fontSize(28).fontWeight(FontWeight.Bold)
        .onClick(()=>{
          this.isEditable = !this.isEditable;
        })
      Grid(this.scroller) {
        ForEach(this.numbers, (day: string,index:number) => {
          GridItem() {
            Stack(){
              Text(day).gridItemStyle()
                // .rotate({ angle: this.rotateAngle })
                .onTouch((event: TouchEvent) => {
                  if (event.type === TouchType.Down) {
                    this.text = day
                  }
                })
              if (this.isEditable) {
                Image($r('app.media.btn_Closed')).width(15).height(15).onClick(()=>{
                  // Array
                  Logger.warn('移除前:' + this.numbers.toString());
                  this.numbers.splice(index,1)
                  Logger.warn('移除后:' + this.numbers.toString());
                })
              }
            }.alignContent(Alignment.TopEnd)
          }
        })
      }
      .gridStyle()
      .onScrollIndex((first: number) => {
        console.info(first.toString())
      })
      //设置Grid是否进入编辑模式,进入编辑模式可以拖拽Grid组件内部GridItem
      .editMode(this.isEditable)
      //第一次拖拽此事件绑定的组件时,触发回调
      .onItemDragStart((event: ItemDragInfo, itemIndex: number) => {
        //设置拖拽过程中显示的图片
        return this.pixelMapBuilder()
      })
      //绑定此事件的组件可作为拖拽释放目标,当在本组件范围内停止拖拽行为时,触发回调
      //itemIndex为拖拽起始位置,insertIndex为拖拽插入位置
      .onItemDrop((event: ItemDragInfo, itemIndex: number, insertIndex: number, isSuccess: boolean) => {
        let isValidInserIndex = insertIndex < this.numbers.length;
        if (isSuccess && insertIndex < this.numbers.length) {
        // if (isSuccess && isValidInserIndex) {
          //不支持拖拽到已有内容以外的位置
          this.changeIndex(itemIndex, insertIndex)
        } else {
          Logger.warn('griditem交换失败');
        }
      })
    }
    .width('100%')
  }
}

5)注意事项

  • griditem最好设置宽高,否则可能拖拽不成功,具体现象就是onItemDrop方法中的insertIndex为-1,isSuccess为false

7、子组件占满主轴方向剩余区域如何设置?

  • 方法1:在row和column的子组件中设置layoutWeight
Column() {
      Row({space:10}){
        Image($r('app.media.logo')).width(90).height(90)
        Column(){
          Text(title).fontSize('14').width('100%')
          Blank()
          Row(){
            Text('我是按钮前文')
            Blank()
            Button('立即查看',{type:ButtonType.Capsule}).width(80).height(35).onClick(()=>{
              Logger.debug('当前索引值:'+ index.toString() + ',当前item:' + title)
            })
          }
          .width('100%')
          .backgroundColor(Color.Orange)
        }
        .layoutWeight(1) // 占满主轴方向剩余空间
        .height(80)
        .justifyContent(FlexAlign.SpaceBetween)
        .backgroundColor(Color.Pink)
      }
      .width('100%')
      .padding(10)

      Divider().color('#F2F2F2').strokeWidth(1).margin({left:10,right:10})
    }.width('100%')
    
  • 方法2:使用flext布局,设置flexGrow

      Flex({direction:FlexDirection.Row,alignItems:ItemAlign.Center}){
        Flex(){
          Stack(){
            Image($r('app.media.logo')).width(90).height(90)
          }.width(90).height(90)
          // .backgroundColor(Color.Blue)
        }
        // .flexBasis(120)
        .width(120)
        // .backgroundColor(Color.Green)

        Flex({direction:FlexDirection.Column,justifyContent:FlexAlign.SpaceBetween,alignItems:ItemAlign.Start}){
          Text(title).fontSize('14')
          Flex({direction:FlexDirection.Row,justifyContent:FlexAlign.SpaceBetween,alignItems:ItemAlign.Center}){
            Text('按钮前文案')
            Button('立即查看',{type:ButtonType.Capsule}).width(80).height(35).onClick(()=>{
              Logger.debug('当前索引值:'+ index.toString() + ',当前item:' + title)
            })
          }
          .backgroundColor(Color.Orange)
        }
        .flexGrow(2) // column占满剩余空间
        // .flexShrink(1)
        .backgroundColor(Color.Pink)
        .height(80)
        .margin({left:10})
      }
      .width('100%')
      .padding(10)
      // .backgroundColor(Color.Green)
      // 2、最下面的横线
      Divider().color('#F2F2F2').strokeWidth(1).margin({left:10,right:10})
    }.width('100%')


二、常用快捷键

以下快捷键是能提效的,部分快捷键是我自定义的,大家自行参考。

1、com+shift+j【自定义成Xcode一致】

快速打开当前文件左侧文件树位置


image.png

2、com+7

打开structure

3、OPT+F7

提供Find Usages代码引用查找功能,帮助开发者快速查看某个对象(变量、函数或者类等)被引用的地方,用于后续的代码重构,可以极大的提升开发者的开发效率。

4、com+[ 和 com+]

代码后退和代码前进

5、两次shift

全局搜索

6、com+/

代码注释

7、com+左右箭头

某行代码,光标移到行首和行尾

8、com+shift+上下方向键

某行代码上移和下移

9、com+点击(某个属性或者方法)

快速跳转到定义的位置,一般可以配合com+[com+]使用

10、com+删除按钮

快速删除某行

11、com+D

复制某行或者某段代码

12、com+上下箭头【自定义的,默认没有】

快速滚动到文件顶部和底部<BR>
需要自定义快捷键,在自定义快捷键界面中的Scroll To TopScroll To Bottom自定义快捷键


三、待解决问题?

1、网络库绕过https自签名证书如何实现?

2、网络库封装之code status问题?

3、网络库封装之全局配置无效的问题?

4、网络库封装之异常问题?

5、web组件绕过https自签名证书如何实现?

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,392评论 5 470
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,258评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,417评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,992评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,930评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,199评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,652评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,327评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,463评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,382评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,432评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,118评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,704评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,787评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,999评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,476评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,057评论 2 341

推荐阅读更多精彩内容