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

@State 状态详解

注意:不是所有的状态变量更改都会引起界面的刷新。
只有可以被框架观察到的修改才会引起界面刷新。
boolean、string、number类型时,可以观察到数值的变化
class或者Object时,只能观察自身的赋值的变化或者说第一层属性赋值的变化。
即Object.keys(observedObject) 返回的属性。
无法观察到第2层属性的变化


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 装饰的变量可以和父组件建立单向的同步关系
@Prop 装饰的变量是可变的,但是变化后不会同步回其父组件
父组件通过方法来更改传递给子组件的数据,子组件的ui界面会自动跟新

@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 ='更改为李四'
      })
    }
  }
}

@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(数字)

现在我们把顶部抽离成为一个组件

// @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组件。这个是规定

常见的属性

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

列表展示

@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

使用字体图标

阿里的字体图标库:https://www.iconfont.cn/manage/index
选择合适的字体图标,然后加入库

然后添加到项目

选择font css,下载到本地

复制下载下来的这个文件 iconfont.css
把这个文件放置在main/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'
  })

导出数据

export  class commentData{
  name:string
  constructor(name:string) {
    this.name = name
  }
}
export  const crateListData = ():commentData[] =>{
  let result:commentData[] = new Array;
   result = [
    new commentData('张三')
  ]
  return result
}
import {commentData, crateListData} from '../modeldata/commentData'
let listArr:commentData[] = crateListData()
console.log('listArr:',JSON.stringify(listArr) )

现在我们动态渲染一下数据

我们把数据稍微给改造一下哈
src/main/ets/modeldata/commentData.ets

export  class commentData{
  nickName:string
  userImgUrl:string
  cont:string
  constructor(nickName:string, userImgUrl:string, cont:string) {
    this.nickName = nickName
    this.userImgUrl = userImgUrl
    this.cont = cont
  }
}
export  const crateListData = ():commentData[] =>{
  let result:commentData[] = new Array;
   result = [
       new commentData('用户2345','https://p3-passport.byteacctimg.com/img/mosaic-legacy/3795/3047680722~130x130.awebp', '兄弟萌,到我啦,相亲帖。'),
       new commentData('起个什么名好呢','https://p9-passport.byteacctimg.com/img/user-avatar/98fd632fb172e17c564e253895d192bf~130x130.awebp', '兄弟萌,到我啦,相亲帖。'),
       new commentData('这是好事啊','https://p26-passport.byteacctimg.com/img/user-avatar/73fe06ddee6175af519e4e52763b1b95~130x130.awebp', '兄弟萌,到我啦,相亲帖。'),
     new commentData('一条爱吃屎的狗','https://p9-passport.byteacctimg.com/img/user-avatar/77982a236f0482ac6c1461f5f459daa5~130x130.awebp', '兄弟萌,到我啦,相亲帖。'),
     new commentData('Zssss修勾','https://p3-passport.byteacctimg.com/img/user-avatar/38583dcfd549fbfc5b4917788a108447~130x130.awebp', '兄弟萌,到我啦,相亲帖。'),
     new commentData('树上掉下来的虫','https://p3-passport.byteacctimg.com/img/mosaic-legacy/3795/3047680722~130x130.awebp', '兄弟萌,到我啦,相亲帖。'),
     new commentData('哎呦呵','https://p3-passport.byteacctimg.com/img/mosaic-legacy/3795/3047680722~130x130.awebp', '兄弟萌,到我啦,相亲帖。'),
     new commentData('Mask007','https://p3-passport.byteacctimg.com/img/mosaic-legacy/3795/3047680722~130x130.awebp', '兄弟萌,到我啦,相亲帖。'),
     new commentData('卧梅又闻花','https://p3-passport.byteacctimg.com/img/mosaic-legacy/3795/3047680722~130x130.awebp', '兄弟萌,到我啦,相亲帖。'),

  ]
  return result
}
import {commentData, crateListData} from '../modeldata/commentData'
struct Index {
  // 声明一个响应式数据,等会需要使用到
  @State commentListData:commentData[] = crateListData()
}

// 中间部分
  List(){ // 这个是列表组件
     ForEach(this.commentListData,(item:commentData,index:number)=>{
       // 列表组件中只能够放ListItem
       ListItem(){
         CenterList({
           itemObj: item // 现在我们把数据传递给这个组件
         })
       }
     })
  }.width('100%')
  .layoutWeight(1) // 让容器的高度自适应
  .backgroundColor('#fff')
  .listDirection(Axis.Vertical) // 垂直方向滚动
import { commentData } from "../modeldata/commentData"

@Component
struct CenterList{
  @Prop itemObj:commentData
  build() {
    Column(){
      Row(){
        Image(this.itemObj.userImgUrl).width(40).aspectRatio(1).borderRadius(40)
        Text(this.itemObj.nickName).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

实现点赞功能

import { commentData } from "../modeldata/commentData"

@Component
struct CenterList{
  @Prop itemObj:commentData
  // 定义的点击喜欢的方法,这个方法将会被父组件的的方法给重写
  onLickClick=()=>{}
  build() {
    Column(){
      Row(){
        Image(this.itemObj.userImgUrl).width(40).aspectRatio(1).borderRadius(40)
        Text(this.itemObj.nickName).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})
          Row(){
            Image($r('app.media.zhichi')).width(26)
            Text(this.itemObj.like.toString())
          }.onClick(()=>{
            // 到门口的时候就会调用这个方法
            this.onLickClick()
          })
        }
      }
      // 2端对齐哈的前提需要设置宽度为100%
      .width('100%').justifyContent(FlexAlign.SpaceBetween)
      .padding({left:10,right:10})

    }.alignItems(HorizontalAlign.Start) // 水平左对齐
  }
}

export  default  CenterList
import HeaderCom from '../components/HeaderCom'
import CenterList from '../components/CenterList'
import FooterCom from '../components/FooterCom'
import font from '@ohos.font'
import {commentData, crateListData} from '../modeldata/commentData'

@Entry
@Component
struct Index {
  // 声明一个响应式数据
  @State commentListData:commentData[] = crateListData()
  // fatherClick(){
  //   AlertDialog.show({
  //     message:'121'
  //   })
  // }
  // 在加载Index页面的时候就进行注册
  aboutToAppear(): void { // 生命周期函数,组件在加载的时候就会自动去调用这个方法
     //   1,注册字体
    font.registerFont({
      familyName:'myfont',
      // 路径的话是以当前的ets来计算的。注意这里需要写成/开头。而不能是 ../
      familySrc:'/myfont/fonts/iconfont.ttf'
    })

  }

  build() {

    Column(){

     // 顶部部分
     HeaderCom().backgroundColor('#fff')

    // 中间部分
    List(){ // 这个是列表组件
       ForEach(this.commentListData,(item:commentData,index:number)=>{
         // 列表组件中只能够放ListItem
         ListItem(){
           CenterList({
             itemObj: item,
             onLickClick: (item:commentData)=>{
               // 错误的更新方式
               // const newItemObj:commentData = this.commentListData[index]
               // newItemObj.like +=1
               // this.commentListData[index] = newItemObj

               // 正确的更新方式
               // Array.splice(从第几项开始删除,删除几个,替换的项1,替换的项2,...)
               const newItemObj:commentData = this.commentListData[index]
               newItemObj.like+=1
               this.commentListData.splice(index,1,newItemObj)
             }
           })
         }
       })
    }.width('100%')
    .layoutWeight(1) // 让容器的高度自适应
    .backgroundColor('#fff')
    .listDirection(Axis.Vertical) // 垂直方向滚动

    //  底部部分
    Row(){
      FooterCom().width('100%')
    }.width('100%').height(56).width('100%').padding({left:10, right:10}).border({
      width: {
        top:1
      },
      color:'#e4e6eb'
    })

    }.width('100%').height('100%')
  }
}

@State绑定的数据默认是单向

@Component
struct FooterCom {
  @State inputValue:string ='1'
  build() {
    Column(){
      Row(){
        TextInput({
          placeholder:'请输入评论',
          text:this.inputValue
        }).backgroundColor('#ccc').width('70%').margin({left:10})
          .onSubmit(()=>{
            AlertDialog.show({
              message: this.inputValue
            })
          })
        // 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

怎么把它变成双向的值呢?

我们需要在text:后面新增2个$$就可以了。

TextInput({
   placeholder:'请输入评论',
   text:$$this.inputValue
})

实现评论功能

@Component
struct FooterCom {
  @State inputValue:string =''
  // 父组件等会会给它覆盖掉。新增评论
  addCommentFn = (cont:string)=>{}
  build() {
    Column(){
      Row(){
        TextInput({
          placeholder:'请输入评论',
          text:$$this.inputValue
        }).backgroundColor('#ccc').width('70%').margin({left:10})
          .onSubmit(()=>{
            this.addCommentFn(this.inputValue)
          })
        // 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({
        addCommentFn:(commentValue:string)=>{
            const addItem:commentData = new commentData(
              '我的div丢了肿么办',
              'https://p9-passport.byteacctimg.com/img/user-avatar/473f3e32c874a82e2cf6f844ba77e362~130x130.awebp',
              commentValue,
              1,
              'user1992'
            )
            this.commentListData.unshift(addItem)
        }
      }).width('100%')
    }.width('100%').height(56).width('100%').padding({left:10, right:10}).border({
      width: {
        top:1
      },
      color:'#e4e6eb'
    })

把评论方法抽离出去

addCommentFn(commentValue:string){
    const addItem:commentData = new commentData(
      '我的div丢了肿么办',
      'https://p9-passport.byteacctimg.com/img/user-avatar/473f3e32c874a82e2cf6f844ba77e362~130x130.awebp',
      commentValue,
      1,
      'user1992'
    )
    this.commentListData.unshift(addItem)
  }

addCommentFn:(commentValue:string)=>{
    this.addCommentFn(commentValue)
}

posted @ 2025-06-26 12:03  南风晚来晚相识  阅读(62)  评论(0)    收藏  举报