ImageFit
.objectFit(ImageFit.Contain)

| 模式 | 保持比例 | 是否裁剪 | 放大策略 | 应用场景 | 描述 |
| Contain | 是 | 否 | 允许放大 | 完整展示图片 | 保持宽高比进行缩小或者放大,使得图片完全显示在显示边界内,居中显示。 |
| Cover | 是 | 是 | 允许放大 | 全屏背景 | 保持宽高比进行缩小或者放大,使得图片两边都大于或等于显示边界,居中显示。 |
| Fill | 否 | 否 | 强制拉伸 | 图形填充 | 不保持宽高比进行放大缩小,使得图片填充满显示边界。 |
| ScaleDown | 是 | 否 | 仅缩小 | 保护图片质量 | |
| Auto | 是 | 是 | 动态调整 | 响应式布局场景 | |
| none | 保持原有尺寸进行居中显示。 |
“判空防崩”的终极指北
| 写法 | 含义 | 风险点 (陷阱) | 最佳使用场景 |
| !x | 只要 x 是假值 (null, undefined, 0, "", false) 全为真 | 会误伤 0 和空字符串,导致逻辑错误 | 仅用于判断布尔值或确认 x 是对象 |
| null == x | x 是 null 或 undefined | 等同于 x == null,会发生隐式转换 | 首选,最简洁的通俗判空 |
| null === x | x 必须严格等于 null | 无法匹配 undefined | 明确知道后端接口会返回 null 且需区分 |
| undefined == x | x 是 null 或 undefined | 同 null == x | 几乎不用,不如 x == null 清晰 |
| undefined === x | x 必须严格等于 undefined | 无法匹配 null | 判断变量是否未赋值 (最精准) |
| x?.prop | 如果 x 有值取 prop,否则返回 undefined | 没风险,最安全 | UI 开发必备,防止调用属性崩掉 |
| x ?? 'def' | 如果 x 是 null/undefined,则取默认值 | 无风险 | 设置 UI 默认显示内容 |
注意:
-
日常 UI 开发: 统一使用 x == null 判空,x?.prop 取值,x ?? '默认值' 兜底。
-
严谨数据处理: 使用 === 区分 null 和 undefined。
-
绝对禁止: 在处理数字、布尔值或字符串时,严禁直接使用 !x 作为判空条件。
实例
| 变量 x 的值 | !x | x == null | x === null | x === undefined | x?.prop | x ?? 'def' |
| null | true | true | true | false | undefined | 'def' |
| undefined | true | true | false | true | undefined | 'def' |
| 0 | true | false | false | false | TypeError* | 0 |
| "" (空串) | true | false | false | false | TypeError* | "" |
| false | true | false | false | false | TypeError* | false |
| [] (空数组) | false | false | false | false | undefined | [] |
注意:
-
判断对象是否存在: if (data == null) —— 只有当它真的是空时才处理。
-
获取属性值: data?.name —— 如果对象空了,返回 undefined,UI 层通常会识别为“显示为空”,不会闪退。
-
设置默认值: data?.name ?? "暂无数据" —— 完美解决了 null、undefined 的情况,而且保留了 0 和 "" 和 false 的有效性
动画卡顿
设置帧率
expectedFrameRateRange: {
//期望帧率
min: 10,
max: 120,
expected: 60,
},
生命周期
场景1.安卓一个activityA配置的android:launchMode="singleTop"
startActivity(activityA.this,activityA.class)。它的生命周期是啥?
如果安卓startActivity(activityA.this,activityA.class)singleTop+onNewIntent的方式保留页面实例对象和页面上的成员变量缓存,鸿蒙应该怎么做呢?
鸿蒙:直接替换页面和携参传给下个页面
安卓生命周期触发顺序
情况一:Activity A 当前已经在任务栈的顶部
onNewIntent() → onRestart()→ onStart()→ onResume()
注意:只有当 Activity 处于 stopped 状态时才会调用 onRestart() 和 onStart()。
情况二:Activity A 不在任务栈顶部
onCreate()→ onStart()→ onResume()
鸿蒙生命周期触发顺序
- aboutToAppear先触发
当页面首次创建或从不可见变为可见时,会优先触发aboutToAppear生命周期1。此时组件已完成初始化但尚未渲染到界面。 - onReady后触发
在aboutToAppear执行后,当页面完成布局和渲染时触发onReady。此时可安全操作页面元素(如获取组件尺寸、绑定动画等)。
典型时序:
aboutToAppear→ 页面渲染 → onReady
对应安卓的场景1.的鸿蒙代码功能和时序是
replacePathByName+携参跳转->onReady拿到参数->rpc处理逻辑
@Prop与@ObjectLink的差异
@Prop和@ObjectLink都可以接收@Observed装饰的类对象实例。@Prop对对象进行深拷贝,修改深拷贝后的对象不会影响原对象及其关联的组件。@ObjectLink获取对象的引用,修改引用对象会影响原对象及其关联的组件。
下面的例子中,UserChild组件同时使用@Prop与@ObjectLink接收了来自父组件的@Observed装饰的类对象实例作为数据源。对该数据源对象的修改将同时影响@Prop与@ObjectLink装饰的变量。依次点击change @ObjectLink value按钮和change @Prop value按钮可以观察到:
- 修改@ObjectLink装饰的对象内容将影响数据源对象,并重新同步给@Prop,因此两个Text组件都将刷新。
- 修改@Prop装饰的对象内容仅影响使用该对象的Text2组件,不会影响数据源对象。
mvvm
今天cr代码发现俺用的不是mvvm,同事高情商💬:你这个viewmodel怎么没有效果捏。🤦然后俺发现是命名mvvm,实际是mvp。。。。。。用了好久的mvp。。。
💧打湿小爱玛,发誓要买大宝马
model层:数据模型(bean对象、实体类)、数据源(rpc、网络数据、缓存数据)
viewmodel层:获取数据和处理交互
view层:只做展示,即拿着viewmodel里面的数据展示。(page、view、compnent)
鸿蒙的mvvm体现在
1.stage模型。用@stage修饰的变量已经是mvvm了,该变量发生变化的时候,用到该变量的view节点数据会自动重新刷新。
2.父子组件数据传递。 父组件的变量使用@State修饰、子组件使用@Prop修饰,这样父组件的变量变化的时候,子组件拿到的值也会发生变化,如果子组件需要监听这个数据变化的时机来做操作,则可以再加一个@watch
3.@objectLink修饰model类,使用该对象的时候用@observed观测该model对象数据,这样model内部的属性发生变化的时候会触发使用观测的地方的数据刷新带动ui刷新
4.view直接调用viewmodel里面的属性,如果viewmodel里面的属性经过一系列用户操作和数据源变化后值发生变化,外部的view怎么能监听到该属性的变化然后来刷新ui呢?
使用@observedV2来修饰viewmodel,使用@trace来修饰属性。这样viewmodel里面的属性发生变化时,外面的view使用该属性的地方也会触发ui刷新

5.如果在某些一大串操作后,viewmodel需要回抛行为变化给view咋整呢?
使用viewcontroller,自定义一层接口,回抛的时候抛到这个接口回调里,view持有这个controller可以接受到消息做ui变化。
RelativeContanier布局
类似于安卓的frameLayout和releativeLayout,中间子组件可以动态设置位于父布局的位置,也可以使用id来布局相对某个子组件的位置。比如有的子组件在父布局的左上角,有的在右上角,有的横竖居中,有的在底部居中。
stack只能设置整体子组件位置位于父布局的位置,子组件单独设置无效,因此要用relativeContanier
@Entry
@Component
struct Index {
build() {
Row() {
RelativeContainer() {
Row() {
Text('中心点水平垂直居中')
}
.justifyContent(FlexAlign.Center)
.height(100)
.width(100)
.backgroundColor('#0a59f7')
.align(Alignment.TopEnd)
.alignRules({
middle: { anchor: "__container__", align: HorizontalAlign.Center },
center: { anchor: "__container__", align: VerticalAlign.Center } // 水平 + 垂直居中
})
//.margin({ left: 50 })
Row() {
Text('左上')
}
.justifyContent(FlexAlign.Center)
.width(110)
.height(110)
.backgroundColor('#ff5700ae')
.alignRules({
top: { anchor: "__container__", align: VerticalAlign.Top }, //上边靠上
left: { anchor: "__container__", align: HorizontalAlign.Start } //右边靠右
})
Row() {
Text('右上')
}
.justifyContent(FlexAlign.Center)
.width(110)
.height(110)
.backgroundColor('#00ae9d')
.alignRules({
top: { anchor: "__container__", align: VerticalAlign.Top }, //上边靠上
right: { anchor: "__container__", align: HorizontalAlign.End } //右边靠右
})
Row() {
Text('中心点水平居中垂直居底')
}
.justifyContent(FlexAlign.Center)
.height(50)
.width(100)
.backgroundColor('#fff7710a')
.align(Alignment.TopEnd)
.alignRules({
middle: { anchor: "__container__", align: HorizontalAlign.Center },
bottom: { anchor: "__container__", align: VerticalAlign.Bottom } // 水平 + 垂直居底
})
}
.width(300).height(300)
.margin({ left: 50 })
.border({ width: 2, color: "#6699FF" })
}
.height('100%')
}
}
代码
https://gitee.com/flying-guy/harmony-practice/tree/master
TypeScript
变量声明

let msg: string = 'hello world'
console.log('msg: ' + msg)
let num: number = 21
console.log('num: ' + num)
let finished: boolean = true
console.log('finish: ' + finished)
let a: any = 'jack'
a = 20
console.log('any a: ' + a)
let p = {name: 'Jack', age: 21}
console.log(p.name)
console.log(p['age'])
let names: Array<string> = ['Jack', 'Rose']
let ages: number[] = [21, 18]
console.log(names[1])
console.log(ages[1])
条件控制


//定义数字
let num: number = 21
//判断是否是偶数
if(num % 2 === 0){
console.log(num + '是偶数')
} else {
console.log(num + '是奇数')
}
//判断是否是正数
if (num > 0) {
console.log(num + '是正数')
} else if (num < 0) {
console.log(num + '是负数')
} else {
console.log(num + '为0')
}
//判断变量是否有值
let data: any
if (data) {
console.log('数据存在:' + data)
} else {
console.log('数据不存在!')
}
let grade: string = 'C'
switch (grade) {
case 'A': {
console.log('优秀')
break
}
case 'B': {
console.log('合格')
break
}
case 'C': {
console.log('不合格')
break
}
default : {
console.log('优秀')
break
}
}
循环迭代

let names:Array<string> = ['Jack', 'Rose']
for (let i in names) {
console.log(i + ':' + names[i])
}
for (let n of names) {
console.log(n)
}
函数

//无返回值函数,返回值void可以省略
function sayHello(nam: string): void {
console.log('你好, ' + name + '!')
}
sayHello('Jack')
//有返回值函数
function sum(x: number, y: number): number {
return x + y
}
let result = sum(21, 18)
console.log('21 + 18 = ' + result)
//箭头函数
let sayHi = (name: string) => {
console.log('你好, ' + name + '!')
}
sayHi('Rose')
//可选参数,在参数后面加?,表示该参数是可选的
function sayHello(name?: string) {
//判断name是否有值,如果无值则给一个默认参数
name = name ? name : '陌生人'
console.log('你好, ' + name + '!')
}
sayHello('Jack')
sayHello()
//参数默认值,在参数后面赋值,表示参数默认值
//如果调用者没有传参,则使用默认值
function sayHello(name: string = '陌生人') {
console.log('你好, ' + name + '!')
}
sayHello('Jack')
sayHello()
类和接口

//定义枚举
enum Msg {
HI = 'hi',
HELLO = 'hello'
}
//定义接口,抽象方法接受枚举参数
interface A {
say(msg: Msg):void
}
//实现接口
class B implements A {
say(msg: Msg): void {
console.log(msg + ', I am B')
}
}
//初始化对象
let a:A = new B()
//调用方法,传递枚举参数
a.say(Msg.HI)
//定义矩形
class Rectangle {
//成员变量
private width: number
private length: number
//构造函数
constructor(width: number, length: number) {
this.width = width
this.length = length
}
//成员方法
public area(): number {
return this.width + this.length
}
}
//定义正方形
class Square extends Rectangle {
constructor(side: number) {
//调用父类构造
super(side, side)
}
}
let s = new Square(10)
console.log('正方形面积为:' + s.area())
模块开发

//rectangle.ts
//定义矩形类,并通过export导出
export class Rectangle {
//成员变量
public width: number
public length: number
//构造函数
constructor(width: number, length: number) {
this.width = width
this.length = length
}
}
//定义工具方法,求矩形面积,并通过export导出
export function area(rec: Rectangle): number{
return rec.width * rec.length
}
//index.ts
//通过import语法导入,from后面写文件的地址
import {Rectangle, area} from '../rectangle'
//创建Rectangle对象
let r = new Rectangle(10, 20)
//调用area方法
console.log('面积为:' + area(r))
代码定义

@Entry
@Component
struct Index {
@State message: string = 'Hello World'
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
.fontColor('#36D')
.onClick(() => {
this.message = 'Hello ArkTS!'
})
}
.width('100%')
}
.height('100%')
}
}

ArkUI组件

Image

申请权限
TextInput

Text

Button

Slider

Row和Column






循环控制


List


自定义组件



状态管理
@State
@Prop和@Link
@Provide和@Consume
@Observed和@ObjectLink
@State装饰器

任务统计案例
@Prop、@Link、@Provide、@Consume

@Observed和@ObjectLink
- 解决嵌套对象里面属性变更无法触发数据刷新和数组里面的元素对象数据变更无法触发数据刷新的问题。
- 子组件调用父组件里面的方法,把父组件里面的方法作为参数传进来,用bind的方式把父组件的this传递给子组件。

页面路由



属性动画和显示动画


组件转场动画

实现摇杆功能
State模型

基本概念

应用配置文件


UIAbility生命周期

页面及组件生命周期

pushurl:不会销毁界面
页面1通过pushurl的方式跳转到页面2生命周期:页面2 about to appear -》页面1 on page hide -》页面2 on page show
按系统返回按钮(会销毁界面)从页面2回到页面1生命周期:页面2 on back press -》 页面2 on page hide -》页面1 on page show -》 页面2 about to disappear
replace:会销毁界面
页面1通过replace的方式跳转到页面2生命周期:页面1 about to disappear -》 页面2 about to appear -》页面2 on page show
子组件只有创建和销毁,没有显示和隐藏的生命周期
页面里面增加子组件a,生命周期:about to appear
页面里面删除子组件a,生命周期:about to disappear
如果子组件是数组,用的for循环渲染,如果数组元素发生变化,会去检查哪些发生变化,对发生变化的元素重新渲染,重新渲染时会把之前的所有元素删除再重新创建,则生命周期会全部销毁后重新渲染。解决方式:给for循环加一个唯一标识。
UIAbility的启动模式


singleton模式:重复创建时实际只会创建一个ability实例
standard模式:每次创建都会创建新实例,创建新实例后旧实例依然存在,可以实现多开的效果
margin模式:与standard类似,每次创建都会创建新实例,创建新实例后旧实例会被移除
specified模式:每个ability实例可以设置key标识,启动ability时指定key,存在key相同实例直接被拉起,不存在就直接创建新实例。可以重复利用以前创建好的实例,也可以给不同业务相同ability创建不同的实例
数据持久化
用户首选项


关系型数据库

初始化数据库

增、删、改数据

查询数据

网络连接

http请求数据


三方库axios
简介

下载安装ohpm

下载安装axios

使用axios


5449

被折叠的 条评论
为什么被折叠?



