【鸿蒙Harmony 最新版本编译器5.0版本】实现评论区demo以及点赞功能、排序功能、评论功能。


效果展示

在这里插入图片描述

说在前面的

大家在听音乐或者晒短视频的时候,是不是经常会习惯性地点开评论区看看呢,下面是我两个常用软件中的评论区,今天我们试着来复刻一个看起来差不多一个评论区。
请添加图片描述

请添加图片描述

iconfont字体图标使用

iconfont字体图标使用方法见此文

总体分析

通过一些评论区的案例,我们发现大部分评论区都有着相似的布局和功能

  1. 首先是一个固定的头部组件如下图
    在这里插入图片描述

  2. 接着是可以滑动的评论区正文部分,如下图
    请添加图片描述

  3. 最后位于屏幕最下侧的一个组件,一般都具有评论功能和选择emoji的入口,如下图所示
    在这里插入图片描述

部分分析

通过总体分析,我们知道一个评论区主要由三部分组成,分别是:头部组件、滑动组件、尾部组件,接下来我们具体组件具体分析一下。

头部组件

头部组件实现思路

一眼看过去就知道需要使用到Row容器,然后是一个text组件用来声明本页主题,接下来就是两中不同的排序方式(热度顺序和时间顺序)
在这里插入图片描述

头部组件实现代码
@Extend(Button)
//最新&最热两个按钮的代码
function fancyButton (isOn: boolean){
  .width(46)
  .height(32)
  .fontSize(12)
  .padding({left:5,right:5})
  .backgroundColor(isOn ? '#fff' : '#F7F8FA')
  .border({width:1,color:'#e4e5e6'})
  .fontColor(isOn ? '#2f2e33' : '#8e9298')
}

@Component
export struct InfoTop{
  @State isOn:boolean = true
  onSort = (type:number)=>{}
  build() {
    Row(){
      Text('全部评论')
        .padding(10)
        .fontSize(26)
        .fontWeight(FontWeight.Bold)
      Blank()
      Row(){
        Button('最新',{stateEffect:false})
          .fancyButton(this.isOn)
          .onClick(()=>{
            this.isOn = true;
            //onSort参数为0时表示按照时间顺序排列
            this.onSort(0);
          })

        Button('最热',{stateEffect:false})
          .fancyButton(!this.isOn)
          .onClick(()=>{
            this.isOn = false;
             //onSort参数为1时表示按照热度顺序排列
            this.onSort(1);
          })

      }
      .margin(10)
      .border({width:1,color:'#e4e5e6'})
      .height(32)
      .width(92)
      .borderRadius(16)
      .backgroundColor('#F7F8FA')
    }.backgroundColor(Color.White)
    .justifyContent(FlexAlign.Center)
    .width('100%')
    .height(60)
  }
}

滑动组件

滑动组件实现思路

一个评论页面的主要组成部分就是这个可滑动的部分,所以我们想到这里采用List容器,并考虑到数据的量比较大,采用ForEach来进行滑动组件中不同评论的渲染。
再进一步看到评论的组成部分:用户头像、用户昵称、用户等级、评论内容、评论时间以及对该评论的点赞功能和点赞数量的显示,效果图如下:在这里插入图片描述

滑动组件代码部分(包括点赞功能的实现)
  • 入口组件中使用List容器和ForEach进行布局和渲染
  //中部
      List(){
        ForEach(this.InfoItem1,(item:CommentData,index:number)=>{
          ListItem(){
           //列表项组件
            InfoItem({itemObj:item});
          }
          .padding(10)
        })
      }
      .width('100%')
      .layoutWeight(1)//让容器高度自适应
      .backgroundColor(Color.White)
      .listDirection(Axis.Vertical)
      .scrollBar(BarState.Auto)
      .lanes(1,5)
      .alignListItem(ListItemAlign.Center)
  • 单个评论的组成部分代码
//此处为引入数据源
import {CommentData} from '../model/CommentData'
@Component
export struct InfoItem{
//这个boolean类型判断是否点过赞
  @State stage_praise:boolean = false;
  //实现父子双向传递
  @ObjectLink itemObj :CommentData
  build() {
    Column(){
      Row(){
        //头像、昵称、等级
        Image(this.itemObj.avater)
          .width(30)
          .aspectRatio(1)
          .borderRadius(15)

        Text(this.itemObj.name)
            .fontSize(16)
            .fontColor(Color.Gray)
          .margin(10)

        Image(this.itemObj.levelIcon)
          .width(30)
          .aspectRatio(1)

      }.width('100%')
      .justifyContent(FlexAlign.Start)
      .margin(10)
      //评论内容
      Row(){
        Text(this.itemObj.commentText)
          .fontSize(16)
          .fontColor(Color.Black)
      }.width('80%')

      //日期、点赞互动
      Row(){
        Text(this.itemObj.timeString)
          .fontSize(12)
          .fontColor(Color.Gray)
          .margin(10)

        Blank()

        if(this.itemObj.isLike) {
          Image($r('app.media.selected'))
            .width(14)
            .aspectRatio(1)
            .onClick(()=>{
              this.itemObj.likeNum-=1
              this.itemObj.isLike = false;
            })
        }
        else{
          Image($r('app.media.unselect'))
            .width(14)
            .aspectRatio(1)
            .onClick(()=>{
              this.itemObj.likeNum+=1
              this.itemObj.isLike = true;
            })
        }

        Text(this.itemObj.likeNum.toString())
          .fontColor(this.itemObj.isLike ? Color.Red : Color.Gray)
          .margin(5)
          .fontSize(14)
          .onClick(()=>{
            this.itemObj.isLike = !this.itemObj.isLike;
          })


      }.width('95%')

    }.width('100%')
  }
}

尾部组件

尾部组件实现思路

感觉和头部组件很相似,所以都是Row容器,接着需要使用TextInput组件来实现输入文字功能,最后插入一个emoji表情文字图片即可。
在这里插入图片描述

尾部组件代码

@Component

export struct InfoBottom{
  @State txt:string = '123'
  onSubmitComment = (content:string) =>{}


  build() {
    Row(){
      Row() {
      //此处将iconfont中的图标设置成了文字格式
        Text('\ue607')
          .fontFamily('myfont')
          .fontSize(22)
          .aspectRatio(1)
          .fontColor(Color.Gray)
          .margin({left:10})
	//输入框组件
        TextInput({placeholder:'写评论...', text:$$this.txt})
          .layoutWeight(1)
          .height(40)
          .backgroundColor(Color.Transparent)
          .borderRadius(20)
          .onSubmit(()=>{
            //这里不能直接添加,需要调用父组件传递过来的方法
            this.onSubmitComment(this.txt)
          })

      }.backgroundColor('#f5f6f5')
      .height(30)
      .layoutWeight(1)
      .borderRadius(15)
      .margin(20)

      Text('\ue616')
        .fontFamily('myfont')
        .fontSize(26)
        .fontColor(Color.Gray)
        .margin(20)
        .onClick(()=>{
          AlertDialog.show({message:'暂无emoji可用'})
        })

     /* Text('\ue666')
        .fontFamily('myfont')
        .fontSize(26)
        .fontColor(Color.Gray)
        .margin(20)*/

    }
    .width('100%')
    .height(60)
    .backgroundColor(Color.White)
  }
}

评论功能的实现

//处理提交
  handleSubmit(content:string){
  //直接new一个CommentData类然后传参
    const newItem:CommentData = new CommentData(
      $r("app.media.avater1"),'我',3,0,content,false,new Date().getTime()
    )
    //最后将自己的评论加到数据的前面
    this.InfoItem1 = [newItem,...this.InfoItem1]
  }

总体代码

import {InfoTop} from '../constants/InfoTop'
import {InfoItem} from '../constants/InfoItem'
import font from '@ohos.font';
import { InfoBottom } from '../constants/InfoBottom';
import {CommentData,createListRange} from '../model/CommentData'

@Entry
@Component
struct Index {
  @State InfoItem1:CommentData[] = createListRange()
//处理提交
  handleSubmit(content:string){
    const newItem:CommentData = new CommentData(
      $r("app.media.avater1"),'我',3,0,content,false,new Date().getTime()
    )
    this.InfoItem1 = [newItem,...this.InfoItem1]
  }

  //处理排序  0 最新 time时间戳    1 最热 likenum点赞数
  handleSort(type:number){
    if(type===0){
        this.InfoItem1.sort((a,b)=>{
          return b.time - a.time
      })
    }
    else{
      this.InfoItem1.sort((a,b)=>{
        return b.likeNum - a.likeNum;
      })
    }
  }

  //一加载Index入口页面,就进行注册
  aboutToAppear(): void {
    //注册字体
    font.registerFont({
      familyName:'myfont',
      familySrc:'/fonts/iconfont.ttf'
    })
    //this.handleSort(0)
  }
  build() {
    Column() {
      //头部
      InfoTop({
        onSort:(type:number)=>{
          this.handleSort(type)
        }
      })
      //中部
      List(){
        ForEach(this.InfoItem1,(item:CommentData,index:number)=>{
          ListItem(){
           //列表项组件
            InfoItem({itemObj:item});
          }
          .padding(10)
        })
      }
      .width('100%')
      .layoutWeight(1)//让容器高度自适应
      .backgroundColor(Color.White)
      .listDirection(Axis.Vertical)
      .scrollBar(BarState.Auto)
      .lanes(1,5)
      .alignListItem(ListItemAlign.Center)
      //底部
     InfoBottom({
       //监听
       onSubmitComment:(content:string)=>{
         this.handleSubmit(content);
       }
     })
    }
  }
}



@Extend(Button)
function fancyButton (isOn: boolean){
  .width(46)
  .height(32)
  .fontSize(12)
  .padding({left:5,right:5})
  .backgroundColor(isOn ? '#fff' : '#F7F8FA')
  .border({width:1,color:'#e4e5e6'})
  .fontColor(isOn ? '#2f2e33' : '#8e9298')
}

@Component
export struct InfoTop{
  @State isOn:boolean = true
  onSort = (type:number)=>{}
  build() {
    Row(){
      Text('全部评论')
        .padding(10)
        .fontSize(26)
        .fontWeight(FontWeight.Bold)
      Blank()
      Row(){
        Button('最新',{stateEffect:false})
          .fancyButton(this.isOn)
          .onClick(()=>{
            this.isOn = true;
            this.onSort(0);
          })

        Button('最热',{stateEffect:false})
          .fancyButton(!this.isOn)
          .onClick(()=>{
            this.isOn = false;
            this.onSort(1);
          })

      }
      .margin(10)
      .border({width:1,color:'#e4e5e6'})
      .height(32)
      .width(92)
      .borderRadius(16)
      .backgroundColor('#F7F8FA')
    }.backgroundColor(Color.White)
    .justifyContent(FlexAlign.Center)
    .width('100%')
    .height(60)
  }
}


import {CommentData} from '../model/CommentData'
@Component
export struct InfoItem{
  @State stage_praise:boolean = false;
  @ObjectLink itemObj :CommentData
  build() {
    Column(){
      Row(){
        //头像、昵称、等级
        Image(this.itemObj.avater)
          .width(30)
          .aspectRatio(1)
          .borderRadius(15)

        Text(this.itemObj.name)
            .fontSize(16)
            .fontColor(Color.Gray)
          .margin(10)

        Image(this.itemObj.levelIcon)
          .width(30)
          .aspectRatio(1)

      }.width('100%')
      .justifyContent(FlexAlign.Start)
      .margin(10)
      //评论内容
      Row(){
        Text(this.itemObj.commentText)
          .fontSize(16)
          .fontColor(Color.Black)
      }.width('80%')

      //日期、点赞互动
      Row(){
        Text(this.itemObj.timeString)
          .fontSize(12)
          .fontColor(Color.Gray)
          .margin(10)

        Blank()

        if(this.itemObj.isLike) {
          Image($r('app.media.selected'))
            .width(14)
            .aspectRatio(1)
            .onClick(()=>{
              this.itemObj.likeNum-=1
              this.itemObj.isLike = false;
            })
        }
        else{
          Image($r('app.media.unselect'))
            .width(14)
            .aspectRatio(1)
            .onClick(()=>{
              this.itemObj.likeNum+=1
              this.itemObj.isLike = true;
            })
        }

        Text(this.itemObj.likeNum.toString())
          .fontColor(this.itemObj.isLike ? Color.Red : Color.Gray)
          .margin(5)
          .fontSize(14)
          .onClick(()=>{
            this.itemObj.isLike = !this.itemObj.isLike;
          })


      }.width('95%')

    }.width('100%')
  }
}


@Component

export struct InfoBottom{
  @State txt:string = '123'
  onSubmitComment = (content:string) =>{}


  build() {
    Row(){
      Row() {
        Text('\ue607')
          .fontFamily('myfont')
          .fontSize(22)
          .aspectRatio(1)
          .fontColor(Color.Gray)
          .margin({left:10})

        TextInput({placeholder:'写评论...', text:$$this.txt})
          .layoutWeight(1)
          .height(40)
          .backgroundColor(Color.Transparent)
          .borderRadius(20)
          .onSubmit(()=>{
            //这里不能直接添加,需要调用父组件传递过来的方法
            this.onSubmitComment(this.txt)
          })

      }.backgroundColor('#f5f6f5')
      .height(30)
      .layoutWeight(1)
      .borderRadius(15)
      .margin(20)

      Text('\ue616')
        .fontFamily('myfont')
        .fontSize(26)
        .fontColor(Color.Gray)
        .margin(20)
        .onClick(()=>{
          AlertDialog.show({message:'暂无emoji可用'})
        })

     /* Text('\ue666')
        .fontFamily('myfont')
        .fontSize(26)
        .fontColor(Color.Gray)
        .margin(20)*/

    }
    .width('100%')
    .height(60)
    .backgroundColor(Color.White)
  }
}


//准备评论的数据类
@Observed export class CommentData{
  avater:Resource//头像
  name:string //昵称
  level:number //用户等级
  likeNum:number //点赞数量
  commentText:string //评论内容
  isLike:boolean //是否喜欢
  levelIcon:Resource //level等级
  timeString:string //发布时间-基于时间戳处理后,展示给用户看的属性
  time:number //时间戳

  constructor(avater:Resource,name:string,level:number,likeNum:number,commentText:string,isLike:boolean,time:number) {
    this.avater = avater
    this.name = name
    this.level = level
    this.likeNum = likeNum
    this.commentText = commentText
    this.isLike = isLike
    this.levelIcon = this.convertLevel(this.level)
    this.timeString = this.convertTime(time)
    this.time = time
  }

  convertTime(timestamp:number){
    const currentTimestamp = new Date().getTime();
    const timeDifference = (currentTimestamp - timestamp)/1000 //转换为秒

    if(timeDifference<0||timeDifference==0){
      return '刚刚'
    }
    else if(timeDifference<60){
      return `${Math.floor(timeDifference)}秒前`
    }
    else if(timeDifference<3600){
      return `${Math.floor(timeDifference)}分钟前`
    }
    else if(timeDifference<86400){
      return `${Math.floor(timeDifference)}小时前`
    }
    else if(timeDifference<604800){
      return `${Math.floor(timeDifference)}天前`
    }
    else if(timeDifference<2592000){
      return `${Math.floor(timeDifference)}周前`
    }
    else if(timeDifference<31536000){
      return `${Math.floor(timeDifference)}个月前`
    }
    else{
      return `${Math.floor(timeDifference/31536000)}年前`
    }
  }

  convertLevel(level:number){
  const iconLevel = [
    $r('app.media.LV1'),
    $r('app.media.LV2'),
    $r('app.media.LV3'),
    $r('app.media.LV4'),
    $r('app.media.LV5')
  ]
    return iconLevel[level-1]
  }

}

//封装一个方法,创建假数据
export const createListRange = ():CommentData[]=>{
  let result :CommentData[] = new Array()
  result = [

    new CommentData($r("app.media.avater1"),"皖皖",Math.floor(Math.random()*6),Math.floor(Math.random()*111),'岳云鹏说的一句话很对:“不要故意不告诉老人回家而给他们一个惊喜,要提前告诉他们,提前一个月告诉,老人们就多提前高兴一个月',false,1645820201123),
    new CommentData($r("app.media.avater2"),"miraculous",Math.floor(Math.random()*6),Math.floor(Math.random()*111),'我高考了在2023年',false,1645820201123),
    new CommentData($r("app.media.avater3"),"雪山飞孤",Math.floor(Math.random()*6),Math.floor(Math.random()*111),'23年一年干完的事真的非常仓促',false,1645820201123),
    new CommentData($r("app.media.avater4"),"阿婷",Math.floor(Math.random()*6),Math.floor(Math.random()*111),'如果爱忘了泪不想落下,那些幸福啊,让她替我到达,相爱过如果是爱的够久分开越疼把,想念你的脸颊你的发我不害怕',false,1745820201123),
    new CommentData($r("app.media.avater5"),"小狼仔",Math.floor(Math.random()*6),Math.floor(Math.random()*111),'学习使我快乐',false,1645820201123),
    new CommentData($r("app.media.avater6"),"小卢",Math.floor(Math.random()*6),Math.floor(Math.random()*111),'遗忘遗忘快遗忘~',false,1845820201123),
    new CommentData($r("app.media.avater7"),"miraculous11",Math.floor(Math.random()*6),Math.floor(Math.random()*111),'我学着一个人一整天都不失落',false,1245820201123),

  ]


  return result
}
  • 13
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值