鸿蒙5:知乎案例

目录

1. 知乎案例

1.1 拆解组件

1.2 新建Index.ets

1.3 评论列表 - @Param 父传子

1.4 底部回复按钮

1.5 实现点赞 - @Event 子传父

1.6 顶部的点赞

1.7 回复评论


1. 知乎案例

1.1 拆解组件

  • 新建components, 里面新建 NavBar和CommentItem

@Preview
  @ComponentV2
  struct NavBar {
    @Param title: string = '标题'

    build() {
      Row() {
        // 返回键
        Row() {
          Image($r('sys.media.ohos_ic_public_arrow_left'))
            .width(16)
            .height(16)
        }
        .width(30)
          .height(30)
          .borderRadius(15)
          .backgroundColor("#f4f4f4")
          .justifyContent(FlexAlign.Center)
          .margin({
            left: 20
          })

        Text(this.title)
          .layoutWeight(1)
          .textAlign(TextAlign.Center)
          .margin({
            right: 50
          })
      }
      .width('100%')
        .height(50)
        .border({
          color: "#f4f5f6",
          width: {
            bottom: 1
          }
        })
    }
  }

export { NavBar }

@Preview
  @ComponentV2
  struct CommentItem {
    build() {
      Row({ space: 10 }) {
        Image("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fsafe-img.xhscdn.com%2Fbw1%2F1bad8264-7428-44cf-a92d-3016a2de537b%3FimageView2%2F2%2Fw%2F1080%2Fformat%2Fjpg&refer=http%3A%2F%2Fsafe-img.xhscdn.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1711626934&t=5478cb3adef5d3e29e6952934797ca39")
          .width(40)
          .height(40)
          .borderRadius(20)

        Column({ space: 10 }) {
          Text("周杰伦")
            .fontColor("#303a43")
            .fontSize(18)
            .fontWeight(FontWeight.Bold)

          Text("黄河江最近一代都带蓝牙,意大利拌面必须使用42👌钢筋混凝土量子力学")
            .fontColor("#2f3642")
            .lineHeight(22)
          Row() {
            Text("10-21 .IP属地北京")
              .fontColor("#cacaca")
              .fontSize(12)

            Row({ space: 4 }) {
              Image($r("app.media.like"))
                .width(12)
                .height(12)
                .fillColor("#cacaca")

              Text("100")
                .fontColor("#cacaca")
                .fontSize(12)

            }
          }
          .justifyContent(FlexAlign.SpaceBetween)
            .width('100%')
        }
        .alignItems(HorizontalAlign.Start)
          .layoutWeight(1)

      }
      .alignItems(VerticalAlign.Top)
        .padding(20)
        .width('100%')
    }
  }

export { CommentItem }

1.2 新建Index.ets

import { CommentItem } from '../components/CommentItem'
import { NavBar } from '../components/NavBar'
@Entry
  @ComponentV2
  struct Index {
    build() {
      Column() {
        NavBar({ title: '评论回复'  })
        CommentItem()
        Divider().strokeWidth(6)
        Row() {
          Text("评论数50")
        }
        .width('100%')
          .height(50)
          .padding({
            left: 20
          })
          .border({
            color: '#f3f4f5',
            width: {
              bottom: 1
            }
          })
        ForEach([1,2,3,4,5,6], () => {
          CommentItem()
        })
      }
    }
  }

需要出现滚动区域。

使用了List组件,子组件必须有ListItem

List() {
  ForEach([1,2,3,4,5,6], () => {
    ListItem() {
      CommentItem()
    }
  })
}.layoutWeight(1)

1.3 评论列表 - @Param 父传子

  • 定义一个评论的interface
export interface ReplyItem {
  avatar: ResourceStr // 头像
  author: string   // 作者
  id: number  // 评论的id
  content: string // 评论内容
  time: string // 发表时间
  area: string // 地区
  likeNum: number // 点赞数量
  likeFlag: boolean | null // 当前用户是否点过赞
}

export class ReplyItemModel implements ReplyItem {
  id: number = 0
  avatar: string | Resource = ''
  author: string = ''
  content: string = ''
  time: string = ''
  area: string = ''
  likeNum: number = 0
  likeFlag: boolean | null = null

  constructor(model: ReplyItem) {
    this.id = model.id
    this.avatar = model.avatar
    this.author = model.author
    this.content = model.content
    this.time = model.time
    this.area = model.area
    this.likeNum = model.likeNum
    this.likeFlag = model.likeFlag
  }
}

  • 定义一个评论列表数据

因为我们需要的是class对象,所以每个对象都需要new一下

@Local commentList: ReplyItemModel[] = [
  new ReplyItemModel({
    id: 1,
    avatar: 'https://picx.zhimg.com/027729d02bdf060e24973c3726fea9da_l.jpg?source=06d4cd63',
    author: '偏执狂-妄想家',
    content: '更何况还分到一个摩洛哥[惊喜]',
    time: '11-30',
    area: '海南',
    likeNum: 34,
    likeFlag: false
  }) ,
  new ReplyItemModel({
    id: 2,
    avatar: 'https://pic1.zhimg.com/v2-5a3f5190369ae59c12bee33abfe0c5cc_xl.jpg?source=32738c0c',
    author: 'William',
    content: '当年希腊可是把1:0发挥到极致了',
    time: '11-29',
    area: '北京',
    likeNum: 58,
    likeFlag: false
  }),
  new ReplyItemModel({
    id: 3,
    avatar: 'https://picx.zhimg.com/v2-e6f4605c16e4378572a96dad7eaaf2b0_l.jpg?source=06d4cd63',
    author: 'Andy Garcia',
    content: '欧洲杯其实16队球队打正赛已经差不多,24队打正赛意味着正赛阶段在小组赛一样有弱队。',
    time: '11-28',
    area: '上海',
    likeNum: 10,
    likeFlag: false
  }),
  new ReplyItemModel({
    id: 4,
    avatar: 'https://picx.zhimg.com/v2-53e7cf84228e26f419d924c2bf8d5d70_l.jpg?source=06d4cd63',
    author: '正宗好鱼头',
    content: '确实眼红啊,亚洲就没这种球队,让中国队刷',
    time: '11-27',
    area: '香港',
    likeNum: 139,
    likeFlag: false
  }),
  new ReplyItemModel({
    id: 5,
    avatar: 'https://pic1.zhimg.com/v2-eeddfaae049df2a407ff37540894c8ce_l.jpg?source=06d4cd63',
    author: '柱子哥',
    content: '我是支持扩大的,亚洲杯欧洲杯扩到32队,世界杯扩到64队才是好的,世界上有超过200支队伍,欧洲区55支队伍,亚洲区47支队伍,即使如此也就六成出现率',
    time: '11-27',
    area: '旧金山',
    likeNum: 29,
    likeFlag: false
  }),
  new ReplyItemModel({
    id: 6,
    avatar: 'https://picx.zhimg.com/v2-fab3da929232ae911e92bf8137d11f3a_l.jpg?source=06d4cd63',
    author: '飞轩逸',
    content: '禁止欧洲杯扩军之前,应该先禁止世界杯扩军,或者至少把亚洲名额一半给欧洲。',
    time: '11-26',
    area: '里约',
    likeNum: 100,
    likeFlag: false
  })
]

  • 在主页中渲染
List() {
  ForEach(this.commentList, (item: ReplyItemModel) => {
    ListItem() {
      CommentItem({ item })
    }
  })
}.layoutWeight(1)

  • CommentItem组件接收传入数据
import { ReplyItem, ReplyItemModel } from "../pages/Index"

@ComponentV2
  struct CommentItem {
    // 接收渲染的选项
    @Param item: ReplyItemModel = new ReplyItemModel({} as ReplyItem) // 初始值 只是为了语法不报错

    build() {
      Row({ space: 10 }) {
        Image(this.item.avatar)
          .width(40)
          .height(40)
          .borderRadius(20)

        Column({ space: 10 }) {
          Text(this.item.author)
            .fontColor("#303a43")
            .fontSize(18)
            .fontWeight(FontWeight.Bold)

          Text(this.item.content)
            .fontColor("#2f3642")
            .lineHeight(22)
          Row() {
            Text(`${this.item.time} .IP属地${this.item.area}`)
              .fontColor("#cacaca")
              .fontSize(12)

            Row({ space: 4 }) {
              Image($r("app.media.like"))
                .width(12)
                .height(12)
                .fillColor("#cacaca")

              Text(this.item.likeNum.toString())
                .fontColor("#cacaca")
                .fontSize(12)

            }
          }
          .justifyContent(FlexAlign.SpaceBetween)
            .width('100%')
        }
        .alignItems(HorizontalAlign.Start)
          .layoutWeight(1)

      }
      .alignItems(VerticalAlign.Top)
        .padding(20)
        .width('100%')
    }
  }

export { CommentItem }

  • 顶部组件同样需要new 对象传入过去
CommentItem({
  item: new ReplyItemModel({
    id: 999,
    author: '吴彦祖',
    avatar: $r("app.media.img"),
    likeNum: 10,
    likeFlag: false,
    time: '03-02',
    area: '北京',
    content: '人到了一定的年龄新陈代谢就慢了,吃了胖不吃瘦了皱纹就多,要靠锻炼 '
  })
})

1.4 底部回复按钮

封装底部的回复组件

@ComponentV2
  struct ReplyInput {
    @Local content: string = ""

    build() {
      Row({ space: 10 }) {
        TextInput({ text: $$this.content, placeholder: '~请留下您的神评论' })
          .layoutWeight(1)
          .height(40)
        Button("发布")
      }
      .padding({ left: 10, right: 10 })
        .width('100%')
        .height(60)
    }
  }

export { ReplyInput }

组件在主页中使用

List() {
  ForEach(this.commentList, (item: ReplyItemModel) => {
    ListItem() {
      CommentItem({ item })
    }
  })
}.layoutWeight(1)
ReplyInput()  // 在此刻显示

1.5 实现点赞 - @Event 子传父
 

子组件如何调用父组件的函数

  • 子组件要声明一个函数

@Event test: () => void = () => {}  // test:变量类型 = 初始值

子里面,可以调用 this.test()

  • 父组件需要给子组件传入这个参数

Child({
  test: () => {
    this.fatherFn()
  }
})

  • 实现点赞

@Event changeLike: () => void = () => {}
  • 注册点击事件

Row({ space: 4 }) {
  Image($r("app.media.like"))
    .width(12)
    .height(12)
    .fillColor(this.item.likeFlag ? Color.Red : "#cacaca")

  Text(this.item.likeNum.toString())
    .fontColor(this.item.likeFlag ? Color.Red :"#cacaca")
    .fontSize(12)

}.onClick(() => {
  this.changeLike()
})

  • 父组件定义函数,传入函数

changeLike (item: ReplyItemModel) {
  // 需要拿到点击的数据 拿到数据更新数据即可
  if(item.likeFlag) {
    // 点过赞
    item.likeNum--
  }
  else {
    // 没有点过赞
    item.likeNum++
  }
  item.likeFlag = !item.likeFlag // 取反
}
  • 传入子组件

   List() {
        ForEach(this.commentList, (item: ReplyItemModel) => {
          ListItem() {
            CommentItem({ item, changeLike: () => {
              this.changeLike(item)
            } })
          }
        })
      }.layoutWeight(1)

  • 添加 @ObervedV2@Trace装饰
@ObservedV2
  export class ReplyItemModel implements ReplyItem {
    id: number = 0
    avatar: string | Resource = ''
    author: string = ''
    content: string = ''
    time: string = ''
    area: string = ''
    @Trace likeNum: number = 0
    @Trace likeFlag: boolean | null = null

    constructor(model: ReplyItem) {
      this.id = model.id
      this.avatar = model.avatar
      this.author = model.author
      this.content = model.content
      this.time = model.time
      this.area = model.area
      this.likeNum = model.likeNum
      this.likeFlag = model.likeFlag
    }
  }

1.6 顶部的点赞

将顶部的数据抽提出来

 

@Local topItem: ReplyItemModel = new ReplyItemModel({
  id: 999,
  author: '吴彦祖',
  avatar: $r("app.media.img"),
  likeNum: 10,
  likeFlag: false,
  time: '03-02',
  area: '北京',
  content: '人到了一定的年龄新陈代谢就慢了,吃了胖不吃瘦了皱纹就多,要靠锻炼'
})

  • 赋值
CommentItem({
  item: this.topItem, changeLike: () => {
    this.changeLike(this.topItem)
  }
})

1.7 回复评论

  • 底部输入组件双向绑定
@ComponentV2
  struct ReplyInput {
    @Local content: string = ""
    @Event publishComment: (content: string) => void = () => {}
    build() {
      Row({ space: 10 }) {
        TextInput({ text: $$this.content, placeholder: '~请留下您的神评论' })
          .layoutWeight(1)
          .height(40)
          .onSubmit(() => {
            // 键盘的确定事件
            if(this.content) {
              this.publishComment(this.content)
              this.content = ""
            }
          })
        Button("发布")
          .onClick(() => {
            if(this.content) {
              this.publishComment(this.content)
              this.content = ""
            }
          })
      }
      .padding({ left: 10, right: 10 })
        .width('100%')
        .height(60)
    }
  }

export { ReplyInput }

调用父组件传入的publishComment的方法
父组件实现的方法

addComment(content: string) {
  this.commentList.unshift(new ReplyItemModel({
    id: Math.random(),
    avatar: 'https://foruda.gitee.com/avatar/1705232317138324256/1759638_itcast_panpu_1705232317.png',
    author: '帅鹏',
    content,
    time: `${(new Date().getMonth() + 1).toString().padStart(2, "0")}-${new Date().getDate()
                                                                        .toString()
                                                                        .padStart(2, "0")}`,
    area: '上海',
    likeNum: 0,
    likeFlag: false
  }))
}



 

  • 实现传入方法
ReplyInput({
  publishComment: (content: string) => {
    this.addComment(content)
  }
}) // 在此刻显示

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值