鸿蒙开发ArkTS基础快速学习
说明
阅读本篇文章,适合一些了解过前端开发以及掌握前端框架的读者。
ArkTS介绍
ArkTS语言在TS语言基础上扩展了声明式UI,组件化,状态管理等功能。
官方推荐两种开发模式开发鸿蒙,一种是基于js扩展的类web范式,另一种是基于TS扩展的声明式UI范式(也就是使用ArkTS开发)。
ArkTs的代码基本结构
Index.ets示例文件
js
@Entry
@Component
struct Index {
@State message: string = 'Hello World'
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
}
.width('100%')
}
.height('100%')
}
}
js
@Entry // 装饰器
@Component // 装饰器
struct Hello{ // Hello属于自定义组件
// @State表示 装饰器
@State myText:string = "World";
build(){ // UI描述
Column(){ //系统组件
Text('Hello ${this.myText}')
.fontSize(50)
Divder()
Button('Click me')
.onClick(()=>{ // 事件方法
this.myText = 'ArkUI'
})
.height(50) // 属性方法
.width(100)
.margin({ top : 20 })
}
}
}
ArkTS自定义组件
首先,创建一个Stage的项目,也就是ArkTS项目。
在devcoe studio 编辑中依次点击:File》Create Project 》next 》Project name:ArkTSApplication(项目名字自己去);Model:Stage》Finish
创建完成项目之后,编辑器会自动打开index.ets文件
js
// index.ets 初始文件
import MyComponent1 from './MyComponent1'
@Entry // 入口组件装饰器,一个ets文件中,只能使用依次@Entry
@Component // 组件装饰器 ,表示以下内容是一个组件 它只能装饰用struct关键字声明的结构;注意:一个组件也只能有一个@Component装饰器
struct Index {
// @State 是一个状态装饰器,只要这里的状态发生变化,整个ui就会进行刷新
@State message: string = 'Hello World'
build() { // build方法必须被实现,是用来描述这个组件的UI
Row() { // 表示一行 此时占满了整个页面
Column() { // 表示一列
Text(this.message) //Text组件 表示文本组件 其中this.message来自于上面的 @State message
.fontSize(50) // 设置Text组件的字体大小
.fontWeight(FontWeight.Bold) // 设置Text组件的字体粗心
// 使用内部自定义组件
MyComponent()
// 使用外部自定义组件
MyComponent1()
MyComponent1({title:"首页"}) // 会将title传递到自定义组件中的@State title
MyComponent1({title:'发现'})
}
.width('100%') // 设置Column的高度100%,也就是设置列的高度为100%
}
.height('100%') // 设置Row的高度100%,也就是设置行的高度为100%
}
}
// 自定义组件部分,内容往下看
再上面的基础上,自己自定义一个组件,还是在index.ets文件中
js
// 省略上面部分代码
// 自定义组件
@Component
struct MyComponent{
build(){
Row(){
Button("按钮")
}
}
}
单独定义组件
当组件比较复杂的时候,就定义在单独的一个文件中。
在src/main/pages中新建一个 MyComponent1.ets
创建文件:在pages目录上右键》new》page,输入文件名即可。
js
@Component
// 使用 export default 将当前组件导出去,让其他文件能够导入该组件
export default struct MyComponent1{
@State title:string = "按钮1"
build(){
Row(){
Button(this.title)
}
}
}
build函数注意事项
1.一个build函数中,入口组件只能有一个Row()组件[根组件],而且还得是个容器组件,比如说Image()就是非容器组件。
2.在非入口组件中,build方法的根组件可以是非容器组件,案例如下
js
@Component
export default struct MyComponent1{
@State title:string = "按钮1"
build(){
// 非入口组件中,根组件可以是非容器组件
Image($r('app.media.icon'))
}
}
3.无论是入口组件还是非入口组件 都不能使用ForEach作为根组件。
4.bulid函数中不能定义本地变量,或者打印输出;变量和打印输出要在bulid函数的上方定义。
5.bulid函数中,不允许调用普通方法:this.方法();
6.使用@Builder修饰的函数,就成了组件,也就可以在build函数中使用,案例见如下:
js
@Builder doRender(){
// 被@Builder修饰过的函数,返回值是个组件
Text("使用Builder修饰的函数")
}
build(){
Row(){
this.deRender()
}
}
7.builder函数中的组件中内部变量可以使用 this.函数名
js
@State title:string = "按钮"
doName(){
return this.title;
}
build(){
Row(){
Button(this.doName())
}
}
8.build函数中可以使用判断语句if;但是不能用switch语句
java
private score:number = 78;
build(){
Row(){
if(this.score>=90){
Text("优秀")
}else if(this.score >= 80){
Text("良好")
}else if(this.score >= 70){
Text("及格")
}else{
Text("不及格")
}
}
}
9.build函数中不能使用表达式
js
// 这种方式不可以,只能用if判断语句
this.score >= 60 > Text("及格") : Text("不及格")
组件样式设置
js
组件(){}.样式 = 样式值
js
build(){
row(){
}
// 下面这些都是给row组件设置的样式
.width('80%')
.height('500px')
.backgroundColor('#ff0')
.margin({top:"50px",left:'50px'})
}
生命周期
页面生命周期
onPageShow:
页面每次显示时触发一次,包括路由过程、应用进入前台等场景。
onPageHide:
页面每次隐藏时触发一次,包括路由过程、应用进入后台等场景。
onBackPress:
当用户点击返回按钮时触发。
组件生命周期
aboutToAppear:
组件即将出现时回调该接口,具体时机为在创建自定义组件的新实例后,在执行其build(0函数之前执行。
aboutToDisappear
:在自定义组件析构销毁之前执行
生命周期函数
首先在pages文件夹下新建一个页面,就叫LifePages.ets
js
import router from '@ohos.router'
@Entry
@Component
struct LifePage {
@State childShow : boolean = true
aboutToAppear(){
console.info("组件将要显示")
}
onPageShow(){
console.info("组件显示")
}
onBackPress(){
console.log("按返回键")
}
onPageHide(){
console.info("页面隐藏")
}
aboutToDisappear(){
console.info("页面将要被销毁")
}
build() {
Row(){
Column() {
// this.showChild为true,创建Child子组件,执行Child aboutToAppear
if (this.childShow) {
Child()
}
// this.showChild为false,删除Child子组件,执行Child aboutToDisappear
Button('delete Child').onClick(() => {
this.childShow = false;
})
Button("跳转到其他页面").onClick(() =>{
router.replaceUrl({
url:"pages/Index"
})
})
}
.width("100%")
}
.height("100%")
}
}
@Component
struct Child{
aboutToAppear(){
console.info("child about appear")
}
aboutToDisappear(){
console.info(("child about to disappear"))
}
build(){
Text("child component")
.fontSize(100)
.backgroundColor('#f00')
}
}
函数式组件@Bulider装饰器
使用 @Bulider修饰的函数,被称为函数式组件,这种方式修饰的组件增加了组件的复用性。
局部函数式组件
js
// 这种函数式组件,只能在当前页面使用,不能外部使用
@Bulider 函数式组件名(){
Text("函数式组件")
}
首先,在pages/下创建一个新页面,就叫BuliderPage.ets
js
@Entry
@Component
struct BuliderPage {
@State message: string = 'Hello World'
// 使用 @Bulider修饰的函数,被称为函数式组件
@Bulider MyFunctionComponent(){
Text("函数式组件")
}
// 函数式组件可以有多个
@Bulider MyFunctionComponent2(text){
// text就是值传递,当text被赋值后,利用事件来触发修改值,组件的UI时不会刷新的
// 如果想要达到刷新,就要用到@State修饰的变量
Column(){
// Text("函数式组件2")
// 组件也可以传参
Text(text)
}.width("400px")
.backgroundColor("#f00")
}
build() {
Row() {
Column() {
// 使用自定义的函数式组件
this.MyFunctionComponent()
this.MyFunctionComponent2(this.message)
}
.width('100%')
}
.height('100%')
}
}
全局函数式组件
js
// 定义全局函数式组件
// 参数:值传递;引用传递
@Bulider function GlobalFunctionComponent($$:{param1:string}){
// GlobalFunctionComponent(param1) 当param1被赋值后,利用事件来触发修改值,组件UI不会刷新
// 当使用 $$:{param1:string} 这种方式就可以刷新UI组件
Column(){
// Text("全局函数式组件")
Text($$.param1)
}
.width("400px")
.height("200px")
.backgroundColor("@00f")
}
@Entry
// .........省略下面部分代码.........
Bulid(){
Row(){
Column(){
// 直接调用全局组件,不需要用this
// this.message来自于省略部分的代码
// 传参方式: {参数名称:"值"}
GlobalFunctionComponent({parm1:this.message})
Button("更新message").onClick(()=>{
this.message = "你好,世界";
})
}
}
}
完整代码
js
// 全局函数式组件
@Builder function GlobalFunctionComponent(param1){
// 这种传递参数的方式,属于值传递,也就是说
// 当param1被赋值过,利用事件来触发修改值,是不会自动刷新组件的UI的,触发被赋值的参数被@State修饰
Column(){
Text(param1)
}
.width("400px")
.height("200px")
.backgroundColor("#00f")
}
@Builder function GlobalFunctionComponent2($$:{param1:string}){
// $$:{参数名:string} 这种传递参数的方式,属于引用传递,也就是说
// 当param1被赋值过,利用事件来触发修改值,是会自动刷新组件的UI的
Column(){
Text($$.param1)
}
.width("400px")
.height("200px")
.backgroundColor("#00f")
}
@Entry
@Component
struct BuliderPage{
@State message: string = 'Hello World'
public text:string = "组件的传值"
// 定义函数式组件
@Builder MyFunctionComponent(){
Text("局部函数式组件")
}
// 定义多个函数式组件,并且能够传参
@Builder MyFunctionComponent2(text){
// 这种传参的方式叫做值传递,也就是说,当text被赋值过后,利用事件来触发修改值,不会刷新组件UI
// 想要刷新组件的UI,那么在调用该组件,传递参数的时候,传的参数得是被@State修饰过的变量
Text(text)
}
build() {
Row() {
Column() {
// 使用局部函数式组件
this.MyFunctionComponent()
// 使用可以传参的函数式组件
this.MyFunctionComponent2(this.message)
// 使用全局函数式组件(不需要用this),带参数,会不刷新ui
GlobalFunctionComponent(this.message)
// 使用全局函数式组件(不需要用this),带参数刷新的
// 传参方式: {参数名称:"值"}
GlobalFunctionComponent2({param1:this.message})
// 尝试使用事件来修改组件中的值
Button("修改值").onClick(()=>{
this.message = "修改组件传递的值"
})
// 此时,就可以发现,当点击Button的时候
// GlobalFunctionComponent中的参数值不会变化
// GlobalFunctionComponent2中的参数值会发生变化
}
.width('100%')
}
.height('100%')
}
}
插槽
@BuilderParans装饰器定义插槽
插槽本身无意义,只是在页面布局中占个空位置而已,后期再往整个空位置里插入组件。
通俗的来讲,就是,在页面布局中,提前给组件留个空位,后期再补上
首先创建一个新页面,BuilderParamPage
BuliderParamPage.ets
js
// 自定义组件
@Component
struct MyHeader{
// 1. 定义插槽
@BuilderParam leftSlot:() => void
@BuilderParam rightSlot:() => void
// 2.将插槽放入容器中
build(){
Row(){
Column(){
// 放入插槽
this.leftSlot()
}
.width("33.33%")
Column(){
Text("标题")
}
.width("33.33%")
Column(){
// 放入插槽
this.rightSlot()
}
.width("33.33%")
}
}
}
// 主入口函数
@Entry
@Component
struct BuilderParamPage{
@State message:string = "hello world"
// 4.定义函数式组件
@Builder leftContent(){
Text("返回")
}
@Builder rightContent(){
Text("更多")
}
@Builder leftContent2(){
Text("back")
}
@Builder rightContent2(){
Text("more")
}
build(){
Column(){
// 5. 使用带插槽的自定义组件
MyHeader({leftSlot:this.leftContent,rightSlot:this.rightContent})
MyHeader({leftSlot:this.leftContent2,rightSlot:this.rightContent2})
// 上述两行代码,通俗的来讲:
// 将第39行的组件插入到13行的插槽,将第43行组件插入到22行的插槽中
// 将第47行的组件插入到13行的插槽,将第51行组件插入到22行的插槽中
}
.height("100%")
}
}
尾随闭包的方式使用插槽
js
// 1. 自定义组件
@Component
struct Book{
// 2. 定义了插槽
@BuilderParam title:() => void;
build(){
Column(){
// 3.将插槽放入容器中
this.title()
}
.width("400px")
.height("600px")
.backgroundColor("#ccf")
.margin({top:"20px"})
}
}
// 主入口函数
@Entry
@Component
struct BuilderParamPage{
@State message:string = "hello world"
build(){
Column(){
// 使用插槽
Book(){
// 相当于 把这个组件插入到第8行的插槽中
Text("三国演义")
}
Book(){
// 相当于 把这个组件插入到第8行的插槽中
Text("西游记")
Column(){
Text("作者:吴承恩")
}
}
}
.height("100%")
}
}
@Style装饰器定义样式
@Styles可以将多个重复样式提炼出来,封装成一个方法,然后组件调用这个方法,就能获得该样式。
@Styles支持 局部样式 和 全局样式
局部组件样式
具体案例
首先,新建一个页面,就叫StylePage.ets
js
@Entry
@Component
struct StylePage {
@State message: string = 'Hello World'
@State color:Color = Color.Black;
// 定义局部组件样式
@Styles block(){
// @Styles block(){} 括号中不支持传参
.width("400px")
.height("400px")
// 此时this.color 就是黑色
.backgroundColor(this.color)
}
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
}
.block() // 使用局部组件样式
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
}
.block() // 使用局部组件样式
Button("改变颜色").onClick(()=>{
this.color = Color.blue;
})
}
.height('100%')
}
}
全局组件样式
js
// 定义全局组件样式
@Styles function commomStyles(){
.height("200px")
.width("400px")
.backgroundColor(Color.Green)
}
@Entry
@Component
struct StylePage {
@State message: string = 'Hello World'
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
}
.commomStyles() // 使用全局组件样式
}
.height('100%')
}
}
@State装饰器定义响应式数据
组件的状态,当被@State修饰后的变量数据更新了,整个UI组件就会跟着更新。
代码案例
首先新建一个页面,就叫StatePage.ets
js
@Entry
@Component
struct StatePage {
@State message: string = 'Hello World'
count:number = 1;
build() {
Column() {
Text(this.count.toString())
Button("increase").onClick(()=>{
this.count++
})
// 可以发现,当点击按钮的时候,没有被@State修饰的count变量,页面上的数字永远是1,不会更新
// 当给count添加@State(@State count:Number =1) ,页面上的数字就会更新了
}
.height('100%')
}
}
@Prop装饰器
使用@Prop装饰器可以实现组件外部向内部传输数据
@Prop装饰器只能实现组件外部向内部传递数据,是单向的。
而且,组件内部对数据的修改,不会影响到组件外部
代码案例
首先新建一个页面,就叫PropPage.ets
js
// 定义一个子组件
@Component
struct GoodsItem{
// 接受外部组件传进来的title和price参数
@Prop title:string;
@Prop price:number;
build(){
Column(){
Text(`商品名称:${this.title}`)
Text(`商品价格:${this.price}`)
// 这里的this.price--;只会影响该组件内部的price,不会影响到父组件
.onClick(()=>{
this.price--;
})
.fontSize(24)
}
}
}
// 定义接口
interface Good{
title:string; // 接口属性
price:number;
}
@Entry
@Component
struct PropPage {
@State message: string = 'Hello World'
@State price :number = 0
@State goodList:Good[] = [
{
title:'手机',price:666
},
{
title:'平板',price:777
},
{
title:'电脑',price:888
},
]
build() {
Column() {
Text(`父组件的price:${this.price}`).fontSize(50)
// 将字符串手机 和 price变量 传递给GoodsItem组件
GoodsItem({title:"手机",price:this.price})
// 使用ForEach循环遍历数组
// ForEach(数组,变量=>{回调函数})
ForEach(this.goodList,item =>{
GoodsItem({title:item.title,price:item.price})
})
Button("父组件修改price").onClick(()=>{
this.price++;
})
}
}
}
@Link装饰器
@Link装饰器可以实现父子双向同步,也就是说可以实现父组件和子组件的数据同步
代码案例
tsx
// 定义子组件
@Component
struct Child{
// 1. 使用@link修饰变量
@Link author:string;
build(){
Text(`作者:${this.author}`)
.onClick(()=>{
// 当点击作者文本的时候,作者发生改变
this.author = "李四";
})
}
}
@Entry
@Component
struct Index {
@State message: string = 'Hello World'
// 2.定义与@Link修饰的相同变量名
@State author:string = "张三"
build() {
Column() {
Text(`父组件中的author:${this.author}`)
// 3.往子组件里传值 要使用 $变量
Child({author:$author})
Button("父组件")
.onClick(()=>{
this.author = "王五"
})
}
.height('100%')
}
}
// 总结:
// 此时,父子组件就实现了双向数据绑定,当子组件中的author发生变化的时候,父组件的author也跟着发生变化
// 当父组件中的author发生变化的时候,子组件的author也跟着发生变化
LocalStorage本地存储
StoragePage可以提供少量数据的本地存储。
@LocalStorageProp:UI中变量和LocalStorage单向同步。被@LocalStorageProp装饰的变量发生改变,不会影响LocalStorage中变量的值。但是LocalStorage中变量的值发生变化会同步被@LocalStorageProp装饰的变量。
@LocalStorageLink:UI中变量和LocalStorage双向同步。
代码案例
首先,新建一个页面,就叫StoragePage.ets
tsx
// 1. 创建LocalStorage实例
let storage = new LocalStorage({"count":100})
// 创建子组件
@Component
struct Child{
// 这里的count与new LocalStorage中的count保持一致
@LocalStorageLink("count") childCount : number =1;
@LocalStorageProp("count") childProp : number =1;
build(){
Column(){
Button(`子组件来自localStorage的数据+1:${this.childCount}`)
.onClick(()=>{
this.childCount++;
})
Button(`子组件更新LocalStorageProp的数据+2:${this.childProp}`)
.onClick(()=>{
this.childProp+=2;
})
}
}
}
@Entry
@Component
struct StoragePage{
@State message:string = "Hello World"
@LocalStorageLink("count") parentCount : number =1;
@LocalStorageProp("count") parentProp : number =1;
build(){
Row(){
Column(){
// 子组件
Child()
Text(`父组件来自localSotrage的数据${this.parentCount}`)
.fontSize(50)
.fontWeight(FontWeight.Bold)
Button("父组件更新storage-1").onClick(()=>{
this.parentCount--
})
Button("父组件更新storage prop-2").onClick(()=>{
this.parentProp-=2;
})
}
.width("100%")
}
.height("100%")
}
}
条件渲染
新建一个页面,就叫ifPage
tsx
@Entry
@Component
struct IfPage{
@State score:number = 10;
build(){
Column(){
Text(`分数:${this.score}`).fontSize(24)
Button("更新分数").onClick(()=>{
this.score += 10;
})
if(this.score < 60){
Text("不及格").fontSize(24)
}else if(this.score >=60 && this.score < 70){
Text("及格").fontSize(24)
}else if(this.score >=70 && this.score <90){
Text("良好").fontSize(24)
}else if(this.score>=90){
Text("优秀").fontSize(24)
}
}
}
}
列表渲染
新建一个页面,就叫ListPage
tsx
@Entry
@Component
struct ListPage{
@State list:number[] = [11,22,33,66,88]
build(){
Column({space:10}){
// item表示元素本身
ForEach(this.list,(item:string)=>{
Row(){
Text(`内容:${item}`).fontSize(24).fontColor(Color.White)
}
.height(100)
.backgroundColor(Color.Blue)
.padding(20)
},(item:string)=>item)
}.width('100%').height('100%')
}
}
列表应用案例
新建一个页面,就叫BookListPage
tsx
interface Book{
id:number,
title:string,
price:number,
num:number;
}
// 定义图书组件
@Component
struct BookItem{
@Prop title:string;
@Prop price:number;
@Prop num:number;
build(){
Row(){
Column(){
Image($r('app.media.icon')).width(100).height(100)
}.width('40%')
Column(){
Row(){
Text(`图书名称:${this.title}`).fontSize(18)
}
Row(){
Text(`图书价格:${this.price}`).fontSize(14).fontColor(Color.Red)
}
Row(){
Button("-").onClick(()=>{
this.num--;
})
Text(`${this.num}`).fontSize(18).margin(10)
Button("+").onClick(()=>{
this.num++;
})
}
}.width("60%")
}
.backgroundColor("#eee")
.width("90%")
.margin({left:'5%'})
}
}
@Entry
@Component
struct BookListPage {
@State bookList: Book[] = [
{
id: 0,
title: "三国演义",
price: 89.9,
num:1
},
{
id: 1,
title: "水浒传",
price: 79.9,
num:2
},
{
id: 2,
title: "西游记",
price: 99.9,
num:5
},
{
id: 3,
title: "红楼梦",
price: 109.9,
num:7
}
]
build() {
Column({ space: 10 }) {
ForEach(this.bookList, (item: Book) => {
BookItem({title:item.title,price:item.price,num:item.num})
}, (item: Book) => item.id + '')
}
}
}
线性布局
行布局
新建一个文件,文件名就叫RowPage.ets
行布局也称水平布局
typescript
@Entry
@Component
struct RowPage{
build(){
// 行容器元素,子元素水平排列
Row(){
Text("水平排列").width("20%").backgroundColor(Color.Yellow)
Row(){
}.width("20%").height(100).backgroundColor(Color. Red)
Row(){
}.width("20%").height(100).backgroundColor(Color. Green)
Row(){
}.width("20%").height(100).backgroundColor(Color. Blue)
}.height(200)
.width("100%")
.backgroundColor("#ccc")
.justifyContent(FlexAlign.Center)
.alignItems(VerticalAlign.Center)
}
}
列布局
列布局也称垂直布局
新建一个文件,文件名就叫ColumnPage.ets
typescript
@Entry
@Component
struct ColumnPage {
build() {
// 列容器元素,子元素在垂直方向
Column({space:50}){
Text("垂直排列")
Row(){
Text("111")
Text("222")
Text("333")
}.width(200).height(200).backgroundColor(Color.Red)
Column(){
Text("111")
Text("222")
Text("333")
}.width(200).height(200).backgroundColor(Color.Green)
}.width("100%")
.height("100%")
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Start)
}
}
StackLayout层叠布局
StackLayout层叠布局可以实现元素重叠,类似于css中的绝对定位
新建一个文件,文件名就叫StackLayoutPage.ets
typescript
@Entry
@Component
struct StackLayoutPage {
build() {
Stack({alignContent:Alignment.Start}){
Row(){
}.width(300).height(300).backgroundColor(Color.Pink)
Text("stack layout").width(200).height(200).backgroundColor(Color.Yellow).zIndex(2)
Button("按钮").zIndex(3)
}.width("100%").height(500).backgroundColor(Color.Gray)
}
}
Flex弹性布局
新建一个文件,文件名就叫FlexPage.ets
typescript
@Entry
@Component
struct FlexPage {
build() {
// direction:FlexDirection.Row 横向排列
// wrap:FlexWrap.Wrap 换行
Flex({direction:FlexDirection.Row,wrap:FlexWrap.Wrap}){
Text("box1").width(100).height(100).backgroundColor(Color.Red)
Text("box2").width(100).height(100).backgroundColor(Color.Red)
Text("box3").width(100).height(100).backgroundColor(Color.Red)
Text("box4").width(100).height(100).backgroundColor(Color.Red)
Text("box5").width(100).height(100).backgroundColor(Color.Red)
}
}
}
Flex的主轴和交叉轴的排列
typescript
@Entry
@Component
struct FlexPage {
build() {
// justifyContent:FlexAlign.Center左右居中
// alignItems:ItemAlign.End 靠下排列
Flex({justifyContent:FlexAlign.Center,alignItems:ItemAlign.End}){
Text("box1").width(100).height(100).backgroundColor(Color.Red)
Text("box2").width(100).height(100).backgroundColor(Color.Red)
Text("box3").width(100).height(100).backgroundColor(Color.Red)
}.height(500).backgroundColor(Color.Gray)
}
}
Flex布局综合练习
首先,新建一个LayoutPage.ets文件
typescript
@Entry
@Component
struct LayoutPage {
@State apps: string[] = ['app1','app2','app3','app4','app5','app6','app7','app8']
@State app2:string[] = ["微信","电话","短信"]
build() {
// 使用Stack层叠布局
Stack({ alignContent: Alignment.Bottom }) {
Flex({ wrap: FlexWrap.Wrap }) {
ForEach(this.apps, (item: string) => {
Text(item)
.width(100)
.height(100)
.backgroundColor("#ccc")
.borderRadius(10)
.margin(10)
.textAlign(TextAlign.Center)
}, item => item)
}.width("100%").height("100%")
Flex({ justifyContent: FlexAlign.SpaceBetween }) {
ForEach(this.app2, (item: string) => {
Text(item)
.width(100)
.height(100)
.backgroundColor(Color.Green)
.borderRadius(10)
.textAlign(TextAlign.Center)
}, item => item)
}
.width("90%")
.height(100)
.backgroundColor("#ccc")
.borderRadius(10)
.margin({ bottom: 10 })
}
}
}
综合项目练习
项目准备
新建一个项目,项目名字就叫jaofei
在pages目录下创建一个文件夹,名叫components
将项目所需要的图片复制到src/main/resources/rawfile目录下
具体代码
1.在components目录下创建一个文件,就叫MyHeader.ets
typescript
// MyHeader.ets 文件
@Component
export default struct MyHeader{
build(){
Row(){
Column(){
// Image组件读取src/main/resources/base/media下的文件:Image($r('app.media.图片名称(不需要带后缀)'))
Image($r('app.media.shuifei')).width(16).height(16)
}.width(57)
// 第二列
Column(){
Text("缴费记录")
}
}.height(44)
.width("100%")
.backgroundColor("#F7F8FA")
}
}
Index.ets文件
typescript
import MyHeader from './components/MyHeader'
import JFItem from './components/JFItem'
@Entry
@Component
struct Index {
@State message: string = 'Hello World'
@State list: Object[] = [
{
icon:'shuifei.png',
title:"水费",
date:"2022-01-09 14:30:20",
price:"100.00"
},
{
icon:'dianfei.png',
title:"电费",
date:"2022-01-09 14:30:20",
price:"100.00"
},
{
icon:'fangzu.png',
title:"房租",
date:"2022-01-09 14:30:20",
price:"100.00"
},
{
icon:'wuye.png',
title:"物业费",
date:"2022-01-09 14:30:20",
price:"100.00"
}
]
build() {
Column(){
MyHeader()
Flex(){
Column(){
Text("缴费类型").height(56)
}.width('50%')
Column(){
Text("全部时间").height(56)
}.width('50%')
}
.height(56)
.width('100%')
.linearGradient({
direction:GradientDirection.Top,
colors:[['#F7F8FA',0],['#fff',1]]
})
ForEach(this.list,(item) =>{
JFItem({
icon:item.icon,
title:item.title,
date:item.date,
price:item.price
})
}, item=> item.icon)
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
}
}
在component文件夹下创建一个新组件,就叫JFItem.ets
typescript
// JFItem.ets
@Component
export default struct JFItem{
@Prop icon:string = $r('app.media.dianfei');
@Prop title:string;
@Prop date:string;
@Prop price:string;
build(){
Row(){
// dianfei是media文件夹下的dainfei.png图片 访问src/main/resources/rawfile下的资源,就用$rawfile('图片路径')
Image($rawfile(this.icon)).width(56).height(56).margin({left:16,right:10})
Column(){
Text(this.title).textAlign(TextAlign.Start)
Text(this.date)
}
.alignItems(HorizontalAlign.Start)
Text(`${this.price}元`)
.width(60)
.fontSize(14)
.margin({left:10})
}
.width(345)
.height(90)
.borderRadius(5)
.shadow({offsetX:0,offsetY:4,radius:20,color:"#ccc"})
.margin({top:10})
}
}
ArkUI内置组件
按钮组件
Button组件
typescript
@Entry
@Component
struct ButtonPage {
@State message: string = 'Hello World'
build() {
Row() {
Column() {
// 按钮组件
// Normal方形按钮
// Capsule 圆角按钮
// Circle 圆形按钮
// stateEffect:false 取消按钮点击效果
Button("按钮",{type:ButtonType.Normal,stateEffect:true})
// 给方形按钮添加样式
.borderRadius(10)
.backgroundColor(0xfff000)
.fontColor(0x0000ff)
.fontSize(100)
Button("按钮",{type:ButtonType.Capsule})
Button("按钮",{type:ButtonType.Circle})
Button({type:ButtonType.Normal,stateEffect:true}){
Row(){
Image($r('app.media.loading')).width(30).height(30).margin({right:10})
Text("正在加载").fontSize(24).fontColor(0xffffff)
}.padding(10)
}
.width(200)
.borderRadius(5)
Button(){
Image($r('app.media.delete')).width(50).height(50)
}.padding(20)
.onClick(()=>{
console.info("按钮被点击")
})
}
}
}
}
单选按钮
Radio组件
typescript
@Entry
@Component
struct RadioPage {
@State message: string = 'Hello World'
@State gender:string = "男"
build() {
Column(){
Row(){
Text(`性别:${this.gender}`).fontSize(24)
}
Row() {
// 单选按钮
Text("男: ")
// checked(true) 默认选中
// .onChange() 给单选按钮绑定事件
Radio({value:'男',group:'gender'}).checked(true)
.width(50).height(50)
.onChange((isCheck:Boolean)=>{
if(isCheck){
// 按钮被选中
this.gender = "男"
}
})
Text("女: ")
Radio({value:'女',group:'gender'})
.onChange((isCheck:Boolean)=>{
if(isCheck){
// 按钮被选中
this.gender = "女"
}
})
}
}
}
}
多选按钮
Toggle组件
typescript
@Entry
@Component
struct TogglePage{
@State message: string = 'Hello World'
@State hobbies: string[] = ["唱歌"]
@State isOpen:boolean = true;
build() {
Column() {
Row() {
Text(`爱好:${this.hobbies.toString()}`)
}
Row() {
// 多选按钮
// isOn:true 默认选中
Text("爱好").fontSize(24)
Text("唱歌:")
Toggle({ type: ToggleType.Checkbox, isOn: true })
.onChange((isOn: boolean) => {
if (isOn) {
this.hobbies.push('唱歌')
} else {
this.hobbies = this.hobbies.filter(item => item != "唱歌")
}
})
Text("跳舞:")
Toggle({ type: ToggleType.Checkbox, isOn: false })
.onChange((isOn: boolean) => {
if (isOn) {
this.hobbies.push('跳舞')
} else {
this.hobbies = this.hobbies.filter(item => item != "跳舞")
}
})
Text("打篮球:")
Toggle({ type: ToggleType.Checkbox, isOn: false })
.onChange((isOn: boolean) => {
if (isOn) {
this.hobbies.push('打篮球')
} else {
this.hobbies = this.hobbies.filter(item => item != "打篮球")
}
})
}
Row(){
Text(`是否打开:${this.isOpen}`)
// 开关按钮
Toggle({type:ToggleType.Switch,isOn:true})
.switchPointColor(Color.Red)//未选中
.selectedColor(Color.Pink) // 选中
.onChange((isOn:boolean)=>{
this.isOpen = isOn
})
}
Row(){
Toggle({type:ToggleType.Button}){
Text("是否打开")
}.selectedColor(Color.Pink)
}
}
}
}
输入框组件
typescript
@Entry
@Component
struct InputPage {
@State message: string = 'Hello World'
build() {
Column() {
// 输入框组件
TextInput({placeholder:"请输入"})
.width(300)
.height(50)
.backgroundColor(Color.Green)
.fontSize(24)
.fontColor(Color.White)
// 密码输入框
TextInput().type(InputType.Password)
// 数字输入框
TextInput().type(InputType.Number)
// 获取输入框的内容
TextInput().onChange((value:string)=>{
console.info(value)
})
// 多行输入框
TextArea().width(200).height(100)
.backgroundColor(0xcccccc)
// 获取多行输入框的值
.onChange((value:string)=>{
console.info(value)
})
}
}
}
注册表单案例
typescript
@Entry
@Component
struct RegistPage {
@State message: string = 'Hello World'
build() {
Column() {
Row(){
Text("姓名:")
TextInput().width(300)
.height(50)
}
Row(){
Text("密码:")
TextInput()
.width(300)
.type(InputType.Password)
.height(50)
}.margin({top:10})
Row(){
Button("提交")
.width(300)
.onClick(()=>{
})
}
}
}
}
页面跳转
页面跳转有两种方式,一种是用pushUrl跳转,另一种是用replaceUrl进行跳转。
使用Index.ets页面,新建一个ListPage.ets页面
Index.ets
typescript
// 导入路由对象
import router from '@ohos.router'
@Entry
@Component
struct Index {
@State message: string = 'Hello World'
build() {
Column() {
Button("跳转到list页面").onClick(()=>{
// pushUrl会在当前页面添加一个页面
// 下一页可以实现上一页返回操作
router.pushUrl({
url:"pages/ListPage"
})
})
Button().onClick(()=>{
// replaceUrl直接替换当前页面
// 下一页无法实现返回操作
router.replaceUrl({
url:"Pages/ListPage"
})
})
}
}
}
ListPage.ets
typescript
import router from '@ohos.router'
@Entry
@Component
struct ListPage {
@State message: string = 'Hello World'
build() {
Column() {
Text("list 页面").fontSize(32)
Button("返回").onClick(()=>{
// 返回到用pushUrl跳转过来的页面
router.back()
})
}
}
}
路由跳转练习
新建DetailPage.ets页面
typescript
import router from '@ohos.router'
@Entry
@Component
struct DetailPage {
@State title: string = 'Hello World'
@State price: number = 0
// 当页面一开始加载就会自动执行onPageShow函数
onPageShow(){
// 获取路由传来的参数
const params = router.getParams()
this.title = params['title']
this.price = params['price']
}
build() {
Column() {
Button("返回").onClick(()=>{
router.back()
})
Text("详情页").fontSize(24)
Text(`名称${this.title}`)
Text(`名称${this.price}`)
}
}
}
ListPage.ets
typescript
import router from '@ohos.router'
@Entry
@Component
struct ListPage {
@State message: string = 'Hello World'
@State list:Object[] = [
{
id:"1",
title:"三国演义",
price:78.8
},
{
id:"2",
title:"西游记",
price:78.8
},
{
id:"3",
title:"红楼梦",
price:78.8
},
{
id:"4",
title:"水浒传",
price:78.8
}
]
build() {
Column() {
Text("list 页面").fontSize(32)
Button("返回").onClick(()=>{
// 返回到用pushUrl跳转过来的页面
router.back()
})
Column(){
ForEach(this.list,(item)=>{
Row(){
Text(item.title)
}.height(50).width("100%").backgroundColor(0xeeeeee).margin({top:10})
.onClick(()=>{
router.pushUrl({
url:"pages/DetailPage",
params:item
})
})
},(item)=>item.id)
}.width("100%")
}.width("100%").height("100%")
}
}
模板文件待删除
typescript
@Entry
@Component
struct TextPage {
@State message: string = 'Hello World'
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
}
.width('100%')
}
.height('100%')
}
}
综合练习
做一个todolist,待办任务。
新建一个工程项目,就叫TodoList。
在pages目录下,新建一个TodoList.ets文件
typescript
// TodoList.ets
// 导入Todos文件
import Todos from './components/Todos'
import {ObjectData,ListData} from '../viewmodel/DataModel'
@Entry
@Component
struct TodoList {
// 所有的列表
@State myLists: Array<ObjectData> = []
// 当前要渲染的列表
@State curList:Array<ObjectData> = []
@State inputText:string = ""
aboutToAppear(){
this.myLists = new ListData().getList()
this.curList = this.myLists;
}
build() {
Stack({alignContent:Alignment.Bottom}){
Row() {
Column() {
Text("任务列表")
.fontSize(30)
.fontWeight(FontWeight.Bold)
.margin({top:20})
Row({space:20}){
Button("全部").backgroundColor(Color.Red)
.onClick(()=>{
this.curList = this.myLists
})
Button("已完成").backgroundColor(Color.Blue)
.onClick(()=>{
// 使用过滤器filter来过滤出status为true的对象
this.curList = this.myLists.filter(item=>item.status)
})
Button("未完成").backgroundColor(Color.Blue)
.onClick(()=>{
// 使用过滤器filter来过滤出status为false的对象
this.curList = this.myLists.filter(item=>!item.status)
})
}.margin({top:20})
// 调用外部组件
// 调用Array<ObjectData>类型数据,要用$
Todos({lists:$curList})
}
.width("100%")
.height("100%")
.alignItems(HorizontalAlign.Center)
}
.height('100%')
.backgroundColor(0xcccccc)
.alignItems(VerticalAlign.Top)
// 输入框
Row(){
// text:this.inputText 表示将this.inputText的值显示到输入框中
TextInput({placeholder:"请输入待办任务",text:this.inputText})
.width("60%")
.height(50)
.backgroundColor(Color.White)
.onChange(value=>{
this.inputText = value
})
Button("添加")
.width("30%")
.height(50)
.margin({left:20})
.onClick(()=>{
if(this.inputText){
this.myLists.push(new ObjectData(this.myLists.length+1,this.inputText,false))
// 清空输入框
this.inputText = ""
}
})
}
.margin({bottom:20})
}
}
}
在pages目录下新建一个文件夹,就叫components,然后再在该目录下新建一个Todos.ets文件
tsx
// Todos.ets
import {ObjectData,ListData} from '../../viewmodel/DataModel'
@Component
export default struct Todos{
@Link lists:Array<ObjectData>;
scroller:Scroller = new Scroller()
@Builder
taskItem(item:ObjectData){
Row(){
// 使用复选框组件
Toggle({type:ToggleType.Checkbox,isOn:item.status})
.onChange(event => {
// 找出当前被点击的对象
const curTodo = this.lists.find(todo => todo.id === item.id)
curTodo.status = event;
})
Text(item.task)
.margin({left:20})
}
.width("90%")
.height(50)
.padding({left:20})
.margin({top:20})
.backgroundColor(Color.White)
.borderRadius(20)
}
build(){
// Scroll使用滚动组件,实现页面滚动
Scroll(this.scroller){
Column(){
ForEach(this.lists,(item)=>{
this.taskItem(item)
},item=>item.id)
}
}
.scrollable(ScrollDirection.Vertical) // 设置滚动方向为垂直滚动
.scrollBar(BarState.On) // 设置开启滚动条
.scrollBarColor(Color.Gray) // 设置滚动条颜色
.scrollBarWidth(10) //设置滚动条宽度
.edgeEffect(EdgeEffect.Spring)
.margin({bottom:200})
}
}
在ets文件夹下新建一个目录,就叫viewmodel,然后在该文件夹下新建一个文件,就叫DataModel.ets
typescript
// DataModel.ets
export class ObjectData{
id:number;
task:string;
status:boolean;
constructor(id:number,task:string,status:boolean){
this.id = id;
this.task = task;
this.status = status
}
}
export class ListData{
private lists:Array<ObjectData> = [
new ObjectData(1,"吃饭",true)
new ObjectData(2,"唱歌",false)
new ObjectData(3,"写作业",false)
]
getList(){
retrun this.lists
}
}