@State 状态详解
当被 @State 声明的boolean、string、number类型时。
数据发生变化,界面会自动更新。
当声明的是class或者Object时,只能观察到第一层属性赋值的变化。界面会自动更新。
即Object.keys(observedObject) 返回的属性。无法观察到第2层属性的变化
注意:不是所有的状态变量更改都会引起界面的刷新,
只有被框架观察到的修改才会引起界面刷新
@States声明的变量发生变化界面自动更新
interface addressType {
details: string;
}
interface Person {
userName:string
address: addressType
}
@Entry
@Component
struct Index {
@State Person:Person = {
userName: '张三',
address:{
details:'地球中的某一个地方'
}
}
build() {
Column(){
Text('地址是:'+this.Person.address.details).fontColor(Color.Pink)
Button('更改它的值').margin({ top:10}).onClick(()=>{
// 这种更改会失败,因为观察不到第2层属性的赋值变化。不会跟新
// this.Person.address.details = '在xxx省xx市阳光小区'
// 正确做法,这样就可以观察到第1层属性赋值的变化.ui正常更新
this.Person.address = {
details: '在xxx省xx市阳光小区'
}
})
}
}
}
@Prop-父子单向传递更改
@Prop 装饰的变量可以和父组件建立单向的同步关系
父组件通过方法来更改传递给子组件的数据,数据变化后,子组件的ui界面会自动跟新
如果你在子组件中更改@Prop 装饰的变量,变化后不会同步回其父组件。
@Prop父组件单向更改数据,界面自动更新
@Component
struct ChildComponent {
// @Prop 装饰的变量可以和父组件建立单向的同步关系,相当于props中的属性值
@Prop userName:string
// 使用箭头函数的就是成员函数。可以被外部覆盖
changeName = () => {
}
build() {
// 使用传递过来的参数
Text('用户名:'+this.userName).backgroundColor(Color.Pink).width('60%').padding(10)
}
}
@Entry
@Component
struct Index {
@State fatherName :string='张三'
build() {
Column(){
ChildComponent({
// 给 userName 赋值默认值
userName: this.fatherName
})
Button('更改值').margin({ top:10 }).onClick(()=>{
this.fatherName ='更改为李四'
})
}
}
}
有机智的小伙伴会发现,如果点击后业务逻辑比较复杂。
会写很多很多代码,这些代码放在UI界面中不好。
如果可以把这些业务逻辑单独抽离出去就好了。
这,这,说的有道理,可以抽离出去。
将onClick点击方法中的业务逻辑抽离出去
@Component
struct ChildComponent {
// @Prop 装饰的变量可以和父组件建立单向的同步关系,相当于props中的属性值
@Prop userName:string
// 使用箭头函数的就是成员函数。可以被外部覆盖
changeName = () => {
}
build() {
// 使用传递过来的参数
Text('用户名:'+this.userName).backgroundColor(Color.Pink).width('60%').padding(10)
}
}
@Entry
@Component
struct Index {
// 把原来的业务放在这个函数中
changeFatherNameValue(){
this.fatherName ='更改为李四'
}
@State fatherName :string='张三'
build() {
Column(){
ChildComponent({
// 给 userName 赋值默认值
userName: this.fatherName
})
Button('更改值').margin({ top:10 }).onClick(()=>{
// 这里由原来的业务逻辑变成之间调用函数
this.changeFatherNameValue()
})
}
}
}
@Prop-子组件调用父组件的方法
import { MyComCustomComponent } from '../components/HiCom'
@Component
struct ChildComponent {
// @Prop 装饰的变量可以和父组件建立单向的同步关系,相当于props中的属性值
@Prop userName:string
// 使用箭头函数的就是成员函数。可以被外部覆盖
changeName = () => { }
build() {
// 使用传递过来的参数
Column(){
Text('用户名:'+this.userName).backgroundColor(Color.Pink).width('60%').padding(10)
Button('更改值').margin({ top:10 }).onClick(()=>{
// 子组件调用父组件传递过来的方法。这样就可以做到更改父子组件数据同步了
this.changeName()
})
}
}
}
@Entry
@Component
struct Index {
@State fatherName :string='张三'
build() {
Column(){
Text('父组件展示的名称:' +this.fatherName)
ChildComponent({
// 给 userName 赋值默认值
userName: this.fatherName,
// 子组件调用这个方法的时候,就会更改父组件中的数据,同时数据传递给子组件,达到修改子组件中的数据
// 需要注意:这里必须要使用箭头函数,this的指向问题
// 使用箭头函数,指向上一级作用域中的this,也就是这个组件中的this
changeName:()=>{
this.fatherName ='更改为李四'
console.log('更改名称', this.fatherName)
}
})
}
}
}
布局:顶部和底部高度固定,中间自适应
@Entry
@Component
struct Index {
build() {
Column(){
// 顶部部分
Row(){
}.width('100%').height(50).backgroundColor('#ccc')
// 中间部分
Column(){
}.width('100%')
.layoutWeight(1) // 让容器的高度自适应
.backgroundColor('#cac')
// 底部部分
Row(){
}.width('100%').height(50).backgroundColor('#aa1')
}.width('100%').height('100%')
}
}
1,撑满整个屏幕
Column(){}.width('100%').height('100%')
2,中间部分自适应布局(layoutWeight)。
设置 layoutWeight属性的元素,将会与兄弟元素按照权重进行分配主轴剩余的空间。
或者说:自适应布局会先把固定空间排列好,剩下的空间会按照权重来进行分配。
语法是:组件.layoutWeight(数字)
因此,中间会自适应
抽离成为组件
等会我们这个文件的代码会越来越多
为了以后好维护,我们会把顶部,中间,底部的抽离为组件。
现在我们把顶部的区域抽离成为组件
现在我们把顶部抽离成为一个组件
// @Extend 对某个具体的组件进行抽取
@Extend(Button)
function ButtonFn(isSelect:boolean){
.width(46)
.height(30)
.fontSize(12)
.padding({left:4, right:4})
.backgroundColor(isSelect ? '#fff' : '#f7f8fa')
.fontColor(isSelect ? '#2f2e33' : '#8e9298')
.border({width:1, color:isSelect ? '#e4e5e6' : '#f7f8fa'})
}
// 使用@Component说明这个是一个组件
@Component
struct HeaderCom{
@State selectActive:boolean = true
build() {
Row(){
Text('评论76').fontSize(22).fontWeight(400)
Row(){
Button('最新',{ stateEffect:false }).ButtonFn(this.selectActive).onClick(()=>{
this.selectActive = true
})
Button('最热',{ stateEffect:false }).ButtonFn(!this.selectActive).onClick(()=>{
this.selectActive = false
})
}.backgroundColor('#f7f8fa')
.borderRadius(20)
}.width('100%').height(50)
.justifyContent(FlexAlign.SpaceBetween)
.padding({left:10, right:10, top:6, bottom:6})
}
}
// 使用默认导出导出这个组件
export default HeaderCom
import HeaderCom from '../components/HeaderCom'
@Entry
@Component
struct Index {
build() {
Column(){
// 顶部部分
HeaderCom().backgroundColor('#fff')
// 中间部分
Column(){
}.width('100%')
.layoutWeight(1) // 让容器的高度自适应
.backgroundColor('#fff')
// 底部部分
Row(){
}.width('100%').height(50).backgroundColor('#aa1')
}.width('100%').height('100%').backgroundColor(Color.Red)
}
}
List 列表组件
列表是一种容器,当列表项达到一定数量,超过List容器组件大小时,自动滚动
List 组件下只能够放ListItem组件。这个是规定.
如果我们的列表中的数据量较多时,采用懒加载、缓存列表项、动态预加载等。
List组件的更多详细内容可以看这里:
developer.huawei.com/consumer/cn…
developer.huawei.com/consumer/cn…
常见的属性
1,主轴方向(垂直) listDirection(Axis.Horizontal)
2,交叉轴布局 lanes(列数,间距)
3,列对齐方式 alignListItem(ListItemAlign.Center)
4,滚动条状态 scrollBar(BarState.Auto)
BarState.Auto 滑动的时候展示滚动条,不滑动的时候就不展示。 Off不展示滚动条。 On一直展示滚动条
5,分割线样式 divider({ ... })
主轴方向:listDirection(Axis.Horizontal)水平
List(){ // 这个是列表组件
ForEach(Array.from({length:12}),()=>{
// 列表组件中只能够放ListItem
ListItem(){
Row(){
}.width(330).height(90).backgroundColor(Color.Gray).margin({bottom:10,left:10})
}
})
}.width('100%')
.layoutWeight(1) // 让容器的高度自适应
.backgroundColor('#fff')
.listDirection(Axis.Horizontal) // 水平方向滚动
交叉轴布局 lanes(列数,间距)
List(){ // 这个是列表组件
ForEach(Array.from({length:12}),()=>{
// 列表组件中只能够放ListItem
ListItem(){
Row(){
}.width('100%').height(90).backgroundColor(Color.Gray).margin({bottom:10,left:10})
}
})
}.width('100%')
.layoutWeight(1) // 让容器的高度自适应
.backgroundColor('#fff')
.listDirection(Axis.Vertical) // 垂直方向滚动
.lanes(2,40) // 2列,左右2侧间距40。需要注意的是:这个列数和我们设置的宽度有关哈
分割线的颜色divider
// 中间部分
List(){ // 这个是列表组件
ForEach(Array.from({length:12}),()=>{
// 列表组件中只能够放ListItem
ListItem(){
Row(){
}.width(340).height(90).backgroundColor(Color.Gray).margin({bottom:10,left:10})
}
})
}.width('100%')
.layoutWeight(1) // 让容器的高度自适应
.backgroundColor('#fff')
.listDirection(Axis.Vertical) // 垂直方向滚动
.lanes(1,10) // 2列,左右2侧间距40。需要注意的是:这个列数和我们设置的宽度有关哈
.scrollBar(BarState.Auto) // 滑动的时候就会展示滚动条
.divider({ // 分割线的颜色
strokeWidth:3,
color:Color.Red,
startMargin:12, // 左边线距离最左边的间距
endMargin:12 // 右边线距离最右边的间距
})
如何让Column下的2个Row组件水平居左
@Component
struct CenterList{
build() {
Column(){
Row(){
Image($r('app.media.meinv')).width(40).aspectRatio(1).borderRadius(40)
Text('这周也很开心').margin({left:12})
}
Row(){
Text('昏是非结不可吗,上一代人觉得不结婚不生小孩没有盼头,我是觉').width('100%')
}
}
}
}
export default CenterList
使用 width('100%').justifyContent(FlexAlign.Start)来处理
@Component
struct CenterList{
build() {
Column(){
Row(){
Image($r('app.media.meinv')).width(40).aspectRatio(1).borderRadius(40)
Text('这周也很开心').margin({left:12})
}.width('100%').justifyContent(FlexAlign.Start).margin({bottom:8}).padding({left:10,right:10})
Row(){
Text('昏是非结不可吗,上一代人觉得不结婚不生小孩没有盼头,我是觉').width('100%')
}.width('100%').justifyContent(FlexAlign.Start).margin({bottom:14}).padding({left:10,right:10})
}
}
}
export default CenterList
还可以使用alignItems(HorizontalAlign.Start)水平左对齐
@Component
struct CenterList{
build() {
Column(){
Row(){
Image($r('app.media.meinv')).width(40).aspectRatio(1).borderRadius(40)
Text('这周也很开心').margin({left:12})
}.margin({bottom:8}).padding({left:10,right:10})
Row(){
Text('昏是非结不可吗,上一代人觉得不结婚不生小孩没有盼头,我是觉').width('100%')
}.margin({bottom:14}).padding({left:10,right:10})
}.alignItems(HorizontalAlign.Start) // 水平左对齐
}
}
export default CenterList
列表展示
我们的父组件使用List组件来装下面的内容。
父组件的代码就不贴出来了...,因为当时忘记啦,后面我们会贴出来的
@Component
struct CenterList{
build() {
Column(){
Row(){
Image($r('app.media.meinv')).width(40).aspectRatio(1).borderRadius(40)
Text('这周也很开心').margin({left:12})
}.margin({bottom:8}).padding({left:10,right:10})
Row(){
Text('昏是非结不可吗,上一代人觉得不结婚不生小孩没有盼头,我是觉').width('100%')
}.margin({bottom:14}).padding({left:10,right:10})
Row(){
Text('4小时前')
Row(){
Image($r('app.media.xiaoxi')).width(26).margin({right:4})
Image($r('app.media.zhichi')).width(26)
}
}
// 2端对齐哈的前提需要设置宽度为100%
.width('100%').justifyContent(FlexAlign.SpaceBetween)
.padding({left:10,right:10})
}.alignItems(HorizontalAlign.Start) // 水平左对齐
}
}
export default CenterList
字体图标的下载
为啥要使用字体图标,1,非常好用,方便。2,不会失真
阿里的字体图标库:www.iconfont.cn/manage/inde…
选择合适的字体图标,然后加入库
然后添加到项目
选择font css,下载到本地
把这个文件iconfont.css放置在main/ets文件夹下
引入字体图标这个库
需要注意的是:如果你在鸿蒙项目的src/main/resources/base/media目录下有iconfont.ttf
就会造成冲突,解决办法:把这个目录下的iconfont.ttf删除掉。
多说一句:字体图标文件不要放在src/main/resources/base/media目录下,放在main/ets目录下。
因为:路径的话是以当前的ets来计算的
字体图标的使用
引入字体图标这个库
import font from '@ohos.font'
在构建页面的时候使用
@Entry
@Component
struct Index {
// 在加载Index页面的时候就进行注册
aboutToAppear(): void { // 生命周期函数,组件在加载的时候就会自动去调用这个方法
// 1,注册字体
font.registerFont({
familyName:'myfont',
// 路径的话是以当前的ets来计算的。注意这里需要写成/开头。而不能是 ../
familySrc:'/myfont/fonts/iconfont.ttf'
})
}
build() {
Column(){
// \u ==>表示使用Unicode
// e66c ==> 表示的是这个图标。
Text('\ue66c').fontFamily('myfont').fontSize(30).fontColor('red')
}
}
}
尾部的评论
@Component
struct FooterCom {
build() {
Column(){
Row(){
TextInput({
placeholder:'请输入评论'
}).backgroundColor('#ccc').width('70%').margin({left:10}) // Color.Transparent 无色
Row(){
Text('\ue62a').fontFamily('myfont').fontSize(30)
Text('\ue6a1').fontFamily('myfont').fontSize(30).margin({left:10})
}
}.width('100%').justifyContent(FlexAlign.SpaceBetween)
}
}
}
export default FooterCom
// 使用组件
Row(){
FooterCom().width('100%')
}.width('100%').height(56).width('100%').padding({left:10, right:10}).border({
width: {
top:1
},
color:'#e4e6eb'
})
已经简单的列表加上评论页面就算完成啦,是不是很简单呀。
尾声
到这里并没有结束小伙伴们
下一篇我们将动态渲染列表内容,进行点赞,发表自己的内容等
如果你觉得我写的不错的话,点个赞,收藏,发布自己宝贵的意见。