鸿蒙@State和List列表组件和列表渲染(一)

58 阅读9分钟

@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'
  })

已经简单的列表加上评论页面就算完成啦,是不是很简单呀。

尾声

到这里并没有结束小伙伴们
下一篇我们将动态渲染列表内容,进行点赞,发表自己的内容等
如果你觉得我写的不错的话,点个赞,收藏,发布自己宝贵的意见。