记在前面
因无法直接使用chatgpt进行编程(悲,2024/10),故记录笔记,方便查阅,基于arkts语言
常用网址
TODO
显示类
组件->数据交互->渲染控制->组件封装->页面布局->页面导航(参数传递)
生命周期事件,用户交互事件
服务类
本地持久化,云端网络化,测试技巧
DevEco
环境配置
-
下载Huawei DevEco Studio软件,然后一路安装,记得勾选添加bin到path
-
项目创建 : 选择empty,然后一路默认
-
模拟器调试 : 右上方选择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 = '蜂窝网络'
}
}