HarmonyOS Next 系列之列表下拉刷新和触底加载更多数据实现(十一)

news2024/11/23 11:36:04

系列文章目录

HarmonyOS Next 系列之省市区弹窗选择器实现(一)
HarmonyOS Next 系列之验证码输入组件实现(二)
HarmonyOS Next 系列之底部标签栏TabBar实现(三)
HarmonyOS Next 系列之HTTP请求封装和Token持久化存储(四)
HarmonyOS Next 系列之从手机选择图片或拍照上传功能实现(五)
HarmonyOS Next 系列之可移动悬浮按钮实现(六)
HarmonyOS Next 系列之沉浸式状态实现的多种方式(七)
HarmonyOS Next系列之Echarts图表组件(折线图、柱状图、饼图等)实现(八)
HarmonyOS Next系列之地图组件(Map Kit)使用(九)
HarmonyOS Next系列之半圆环进度条实现(十)
HarmonyOS Next 系列之列表下拉刷新和触底加载更多数据实现(十一)


文章目录

  • 系列文章目录
  • 前言
  • 一、下拉刷新
    • 1、实现解析
    • 2、Refresh简单回顾
    • 3、代码实现
      • (1) 默认样式
      • (2) 自定义样式
  • 二、上拉触底加载更多
    • 1、实现解析
    • 2、触底交互布局设计
    • 3、List和Scroll区别
      • 总结
    • 4、代码实现
      • LoadingMoreView.ets
      • ListPage.ets


前言

HarmonyOS Next(基于API12)实现下拉刷新和上拉触底加载更多功能。 下拉刷新和触底加载作为实战项目列表页中最常见的2种操作,本文将通过示例讲解这2种功能实现,以及需要注意的埋坑点。

下拉刷新示例:
请添加图片描述

触底加载示例:
请添加图片描述


一、下拉刷新

1、实现解析

官方已经提供了Refresh(下拉刷新组件),只要通过监听下拉状态,根据状态变化改变界面样式或文字提示并在
下拉处于加载状态下去请求接口刷新数据即可。组件默认样式是个简易版本,也可以通过传入builder自定义更加炫酷的界面。

2、Refresh简单回顾

入参:

只有一个value: RefreshOptions

RefreshOptions对象类型常用属性字段:

refreshing:是否显示下拉刷新组件,支持$$双向绑定,boolean类型
builder:自定义样式布局,CustomBuilder类型
promptText:设置刷新区域底部显示的自定义文本,设置了builder此字段无效,ResourceStr类型

常用属性:

refreshOffset:设置触发刷新的下拉偏移量,下拉超过该值触发刷新状态,类型number,单位vp

常用事件:

onStateChange:当前刷新状态变更时,触发回调,onStateChange(callback: (state: RefreshStatus) => void)

状态RefreshStatus枚举值:

名称描述
Inactive0默认未下拉状态。
Drag1下拉中,下拉距离小于刷新距离。
OverDrag2下拉中,下拉距离超过刷新距离。
Refresh3下拉结束,回弹至刷新距离,进入刷新状态。
Done4刷新结束,返回初始状态(顶部)。

其中我们主要关心Drag、OverDrag、Refresh三种状态即可

3、代码实现

(1) 默认样式

PullRefresh.ets:

/**
 * 下拉刷新-默认样式
 */
@Entry
@Component
struct PullRefresh {
  @State isRefreshing: boolean = false //是否正在刷新
  @State promptText: string = '' //下拉提示文字
  @State list: number[] = [] //列表数据

  aboutToAppear(): void {
    this.getList()
  }

  getList() {
    //模拟接口获取列表数据
    setTimeout(() => {
      for (let i = 0; i < 20; i++) {
        this.list.push(i)
      }
      this.isRefreshing = false
    }, 1000)
  }

  build() {
    Refresh({ refreshing: $$this.isRefreshing, promptText: this.promptText }) {
      List({ space: 20 }) {
        ForEach(this.list, (item: number) => {
          ListItem() {
            Text(item.toString())
          }.width('100%').height(90).backgroundColor('#fff').borderRadius(10)
        }, (item: number) => item.toString())
      }.height('100%').width('100%').padding(20).backgroundColor('#f2f2f2')
    }
    .onStateChange(async (state) => {
      switch (state) {
      //下拉中,下拉距离小于刷新距离
        case RefreshStatus.Drag:
          this.promptText = '下拉可以刷新'
          break;
      //下拉中,下拉距离超过刷新距离
        case RefreshStatus.OverDrag:
          this.promptText = '释放立即刷新'
          break;
      //刷新状态
        case RefreshStatus.Refresh:
          this.promptText = "正在刷新..."
          this.getList()
          break;
      }
    })
  }
}

运行效果:
请添加图片描述

(2) 自定义样式

样式设计:
(1)下拉距离小于刷新距离左边图标显示向下箭头,右边文字显示下拉可以刷新
(2)下拉距离超过刷新距离左边图标显示向上箭头,右边文字释放立即刷新
(3)刷新状态左边图标显示加载转圈动画,右边文字显示正在刷新…
所有状态右边底部都显示上次更新时间

PullRefresh.ets:

/**
 * 下拉刷新-自定义样式
 */
@Entry
@Component
struct PullRefresh{
  @State isRefreshing: boolean = false //是否正在刷新
  @State promptText: string = '' //下拉提示文字
  @State refreshStatus:number=0 // 刷新状态
  @State list:number[]=[]//列表数据
  @State lastTime:string=this.getDateTime()//上次刷新时间

  aboutToAppear(): void {
    this.getList()
  }

  //获取列表数据
  getList(){
    //模拟接口获取列表数据
    setTimeout(()=>{
      for(let i=0;i<20;i++){
         this.list.push(i)
      }
      //关闭下拉刷新
      this.isRefreshing=false
    },1000)
  }

  //获取当前日期时间MM-dd HH:ss
  getDateTime(){
    let date: Date = new Date()
    let month = (date.getMonth() + 1).toString().padStart(2, '0')
    let day = date.getDate().toString().padStart(2, '0')
    let hour = date.getHours().toString().padStart(2, '0')
    let minus = date.getMinutes().toString().padStart(2, '0')
    return `${month}-${day} ${hour}:${minus}`
  }

  //自定义刷新区域内容
  @Builder customRefreshComponent(){
     Row({space:15}){
       //下拉刷新
       if(this.refreshStatus===RefreshStatus.Drag){
         Image($r('app.media.arrow_down')).width(20)
       }
       //释放刷新
       else if(this.refreshStatus===RefreshStatus.OverDrag){
         Image($r('app.media.arrow_up')).width(20)
       }
       //刷新中
       else if(this.refreshStatus===RefreshStatus.Refresh){
         Image($r('app.media.loading')).width(20)
           .transition(TransitionEffect.rotate({ angle: -360 })
             .animation({ iterations: -1, curve: Curve.Linear, duration: 2000 }))
       }
       Column({space:3}){
         Text(this.promptText).fontSize(14).fontColor('#666').lineHeight(21)
         Text(`上次更新 ${this.lastTime}`).fontSize(11).fontColor('#808080')
       }
     }.width('100%').constraintSize({minHeight:50}).justifyContent(FlexAlign.Center)
  }


  build() {
    Refresh({ refreshing: $$this.isRefreshing, builder:this.customRefreshComponent() }) {
        List({space:20}){
           ForEach(this.list,(item:number)=>{
             ListItem(){
               Text(item.toString())
             }.width('100%').height(90).backgroundColor('#fff').borderRadius(10)
           },(item:number)=>item.toString())
        }.height('100%').width('100%').padding(20).backgroundColor('#f2f2f2')
    }
    .refreshOffset(100)//触发刷新的下拉偏移量,单位vp
    //下拉刷新状态监听
    .onStateChange(async (state) => {
      this.refreshStatus=state
      switch (state) {
        //下拉中,下拉距离小于刷新距离
        case RefreshStatus.Drag:
          this.promptText = '下拉可以刷新'
          break;
          //下拉中,下拉距离超过刷新距离
        case RefreshStatus.OverDrag:
          this.promptText = '释放立即刷新'
          break;
          //刷新状态
        case RefreshStatus.Refresh:
          this.promptText = "正在刷新..."
          this.getList()
          break;
          //刷新结束
        case RefreshStatus.Done:
          //保存更新时间
          this.lastTime=this.getDateTime()
          break;
      }
    })
  }
}

应用到的三张图标:

请添加图片描述
请添加图片描述
请添加图片描述

运行效果:

请添加图片描述

二、上拉触底加载更多

1、实现解析

列表触底加载更多数据,可以选择List组件也可以选择Scroll组件来实现,两个组件都有一个触底回调方法onReachEnd,两者使用上不同请看下文分析。通过触底回调接口请求下一页数据,把新数据追加到原数据上就实现更多数据展示,直到下一页数据为空或者判断当前列表渲染的数据个数已达到总个数,如果是表示已经没有更多数据,之后不再触发加载数据行为。为了更好的交互体验,需要为触底过程绘制不同交互样式比如不同文字提醒,让用户看到触底过程所处状态,最后需要注意触底操作可能由于用户多次操作短时间内高频率触发,需要做节流处理,当上一次数据请求还未完成不能进行下一次触底加载。

2、触底交互布局设计

整个过程可分为3种状态:

初始状态:还未进行下一次触底前或进行接口数据请求前状态,文字显示“—— 下拉加载更多 ——”
加载状态:进行接口数据请求时候状态, 图标转圈动画和文字显示"正在加载"
无数据状态:接口请求完后下一页没任何数据时候状态,文字显示"—— 已到底了 ——"

请添加图片描述

在这里插入图片描述
请添加图片描述

3、List和Scroll区别

List示例:


@Entry
@Component
struct Index {
  @State list: number[] = [] //列表数据

   aboutToAppear():void {
    this.init()
  }

  //初始化
   init(){
    setTimeout(()=>{
      //模拟10条数据
      for(let i=0;i<10;i++){
        this.list.push(i)
      }
    },500)
  }


  build() {
      List({ space: 20 }) {
        ForEach(this.list, (item: number) => {
          ListItem() {
            Text(item.toString())
          }.width('100%').height(90).backgroundColor('#fff').borderRadius(10)
        }, (item: number) => item.toString())


      }
      .width('100%')
      .padding(20)
      .backgroundColor('#f2f2f2')
      .onReachEnd(()=>{
        console.log('触底')
      })


  }

}

运行:
在这里插入图片描述
发现在未进行任何操作情况下,首次渲染会触发一次触底事件

Scroll示例:

@Entry
@Component
struct Index {
  @State list: number[] = [] //列表数据

  aboutToAppear(): void {
    this.init()
  }

  //初始化
  init() {
    setTimeout(() => {
      //模拟10条数据
      for (let i = 0; i < 10; i++) {
        this.list.push(i)
      }
    }, 500)
  }

  build() {
    Scroll() {
      List({ space: 20 }) {
        ForEach(this.list, (item: number) => {
          ListItem() {
            Text(item.toString())
          }.width('100%').height(90).backgroundColor('#fff').borderRadius(10)
        }, (item: number) => item.toString())


      }
      .width('100%')
      .padding(20)
      .backgroundColor('#f2f2f2')
    }.height('100%')
    .onReachEnd(() => {
      console.log('触底')
    })

  }
}

运行:
在这里插入图片描述
首次渲染不会触发触底事件

总结

List和Scroll触底事件主要区别在于List首次渲染会执行一次触底事件而Scroll不会。另一点两者使用区别上Scroll必须设置高度才有滚动条而List不需要。

避免List首次触发触底解决办法:

可以定义一个boolean变量标识表示是否可以执行触底加载逻辑,默认值false,当列表首次数据请求完成后再把这个标识打开即可避免首次执行,也就相当于延迟打开这个触底开关。

示例:

@Entry
@Component
struct Index {
  @State list: number[] = [] //列表数据
  private canLoadingMore: boolean = false //是否可以加载更多

  aboutToAppear(): void {
    this.init()
  }

  //初始化
  init() {
    setTimeout(() => {
      //模拟10条数据
      for (let i = 0; i < 10; i++) {
        this.list.push(i)
      }
      //首次请求完数据打开
      this.canLoadingMore=true
    }, 500)
  }

  build() {
    List({ space: 20 }) {
      ForEach(this.list, (item: number) => {
        ListItem() {
          Text(item.toString())
        }.width('100%').height(90).backgroundColor('#fff').borderRadius(10)
      }, (item: number) => item.toString())

    }
    .width('100%')
    .padding(20)
    .backgroundColor('#f2f2f2')
    .onReachEnd(() => {
       if(this.canLoadingMore){
         //触底逻辑
         console.log('触底')
       }
    })
  }
}

4、代码实现

LoadingMoreView.ets

触底显示的交互组件

@Extend(Text)
function textStyle() {
  .fontSize(13).fontColor('#999').lineHeight(20)
}

@Component
export default struct LoadingMoreView {
  @Link visible: boolean//组件是否显示
  @Prop status: number = 0 //触底状态 0:初始状态,1:加载状态 2:已到底了

  build() {
    Row() {
      //初始态
      if (this.status === 0) {
        Text('—— 下拉加载更多 ——').textStyle()
      }
      //加载中
      else if (this.status === 1) {
        Row({ space: 5 }) {
          Image($r('app.media.loading'))
            .width(20)
            .transition(TransitionEffect.rotate({ angle: -360 })
              .animation({ duration: 2000, curve: Curve.Linear, iterations: -1 }))
          Text('正在加载').textStyle()
        }
      }
      //已到底
      else if (this.status === 2) {
        Text('—— 已到底了 ——').textStyle()
      }
    }
    .width('100%')
    .justifyContent(FlexAlign.Center)
    .padding(5)
    .visibility(this.visible ? Visibility.Visible : Visibility.None)
  }
}

说明:

组件定义了visible参数,支持双向绑定设置组件是否显示。
定义了status表示不同状态,不同状态分别显示不同布局,三种枚举状态分别为 0:初始状态,1:加载状态 2:已到底了

ListPage.ets

列表页,基于List实现

/**
 * 触底加载更多
 */
import  LoadingMoreView from '../components/LoadingMoreView' //触底组件
@Entry
@Component
struct ListPage {
  @State list: number[] = [] //列表数据
  private pageSize: number = 10 //分页每页个数
  private pageNo: number = 1 //分页当前页数
  @State  reachStatus:number=0//触底状态 0:初始状态,1:加载状态 2:已到底了
  private isLoadingMore:boolean=false //是否正在通过接口请求加载数据
  private  initCompleted:boolean=false //初始化是否完成
  @State loadingMoreVisible:boolean=false //加载更多组件是否显示

   aboutToAppear():void {
    this.init()
  }

  //初始化
  async init(){
    try {
      this.list = await this.getList(1)
    } catch (e) {
    } finally {
      this.initCompleted=true
    }
  }

  //接口获取列表数据,入参pageNo:分页页数,返回请求后的数据
  getList(pageNo: number): Promise<Array<number>> {
    return new Promise((resolve, reject) => {
      //模拟接口数据,总共30条数据,每次返回10条
      setTimeout(() => {
         if(pageNo<4){
               let newData:number[]=[]
              //每次返回10条
              for(let i=(pageNo-1)*this.pageSize;i<pageNo*this.pageSize;i++){
                 newData.push(i)
              }
              resolve(newData)
         }
         else {
           resolve([])
         }
      }, 1000)
    })
  }

  //触底加载更多数据
  async handleLoadingMore(){
    //防止多次请求,触底请求完才能进行下一次,节流
    if (this.isLoadingMore) {
      return
    }
    this.isLoadingMore = true
    //设置组件处于加载状态
    this.reachStatus=1
    //请求下一页数据
    let pageNo = this.pageNo + 1
    try {
      let data = await this.getList(pageNo)
      //有新数据
      if (data && data.length > 0) {
        //延迟500毫秒,防止接口响应过快使得肉眼能看到加载转圈动画
        setTimeout(() => {
          //新数据追加到list
          this.list = [...this.list , ...data]
          this.pageNo += 1
          this.isLoadingMore = false
          //设置组件为初始状态
          this.reachStatus=0
        }, 500)
      }
      //到底了
      else {
        this.isLoadingMore = false
        //设置组件为到底状态
        this.reachStatus=2
      }
    } catch (e) {
      this.isLoadingMore = false
    }
  }

  build() {
    List({ space: 20 }) {
      ForEach(this.list, (item: number) => {
        ListItem() {
          Text(item.toString())
        }.width('100%').height(90).backgroundColor('#fff').borderRadius(10)
      }, (item: number) => item.toString())

      LoadingMoreView({visible:this.loadingMoreVisible, status:this.reachStatus})

    }.height('100%').width('100%').padding(20).backgroundColor('#f2f2f2')
     .onReachEnd(()=>{
          //初始化完成由于请求接口有个时间延迟防止首次渲染会执行一次触底
         if(this.initCompleted&&this.reachStatus!==2){
           //首次触底后才显示加载组件
            this.loadingMoreVisible=true
           //加载数据处理
            this.handleLoadingMore()
         }
     })
  }

}

运行效果:
请添加图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2046247.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

智碳云/高能耗企业 水-电-气-热-油-空压机等能源数据采集系统【源码】

智碳云/高能耗企业 水-电-气-热-油-空压机等能源数据采集系统【源码】 介绍基于SpringCloud的能源管理系统-能源管理平台源码-能源在线监测平台-双碳平台源码-SpringCloud全家桶-能管管理系统源码-能管系统软件架构

js 深入学习各种继承方法使用场景

前言 问题&#xff1a; JS 如何实现继承呢&#xff1f;主要有几种继承方式及分别适用于哪些场景呢&#xff1f; 我们学习高级语言&#xff0c;就必须学习面向对象&#xff0c;想要成为高手&#xff0c;就必须学习别人不会的&#xff0c;就比如JS中的继承&#xff0c;好多前端人…

ASP.NET在线交流论坛管理系统

ASP.NET在线交流论坛管理系统 说明文档 运行前附加数据库.mdf&#xff08;或sql生成数据库&#xff09; 主要技术&#xff1a; 基于asp.net架构和sql server数据库 用户功能有个人信息管理 帖了信息管理 意见反馈信息管理 点赞管理 收藏管理 后台管理员可以进行用户管理 …

部署及使用seata

目录 1.下载seata1.7.0.zip 2.上传至云服务器&#xff0c;使用unar工具解压 3.配置application.yml&#xff0c;主要配置如下参数 4.为seata执行mysql脚本 5.配置nacos配置中心 *6.启动seata服务器 问题&#xff1a;“cause:can not register RM,err:can not connect to…

国外项目管理软件最佳实践:选型与应用

国内外主流的10款国外项目管理软件对比&#xff1a;PingCode、Worktile、Asana、Trello、Monday.com、ClickUp、Wrike、ProofHub、Zoho Projects、Hive。 在寻找适合的国外项目管理软件时&#xff0c;你是否感到不知从何选择&#xff1f;市场上琳琅满目的选项往往令人眼花缭乱&…

微软披露Office最新零日漏洞,可能导致数据泄露

近日&#xff0c;微软披露了 Office 中一个未修补的零日漏洞&#xff0c;如果被成功利用&#xff0c;可能导致敏感信息在未经授权的情况下泄露给恶意行为者。 该漏洞被追踪为 CVE-2024-38200&#xff08;CVSS 得分&#xff1a;7.5&#xff09;&#xff0c;被描述为一个欺骗漏洞…

关于xilinx的FFTIP的使用和仿真

工具&#xff1a;vivado2018.3&#xff0c;modelsim10.6d 场景&#xff1a;在进行数据进行频谱分析的时候&#xff0c;使用FPGA来完成FFT的计算可以加快数据的计算速度。 下面使用仿真完成DDS产生的数据的FFT以及IFFT。原始数据使用DDSIP产生&#xff0c;通过IP产生的波形数据…

旧手机NAS方案

这里写目录标题 1、参考2、alpine-term-v16.0-release.apk下载安装3、电脑端ssh连接3、安装docker3.1 网络配置3.2 配置APK源 1、参考 【Docker与Termux】闲置旧安卓手机上的NAS无缝部署方案 https://blog.csdn.net/AnRanGeSi/article/details/138717589 【Alpine Term】Andr…

线程与进程(5)

目录 信号量&#xff08;线程的同步 &#xff09; 信号量的分类&#xff1a; 框架&#xff1a; &#xff08;1&#xff09;信号量的定义(semaphore): &#xff08;2&#xff09;信号量的初始化&#xff1a; &#xff08;3&#xff09;信号量的PV 操作 &#xff08;4&#…

Ubuntu Linux安装Go语言

Golang是Google公司在2007年开始开发的一种静态强类型、编译型语言。Go语言被设计成一门简单、高效且可靠的编程语言&#xff0c;旨在解决大规模网络服务和分布式系统开发中的复杂性问题。Go语言结合了动态语言的开发速度和C/C等编译型语言的性能与安全性&#xff0c;提供了强大…

[DL]深度学习_针对图像恢复的高效扩散模型DiffIR

DiffIR: Efficient Diffusion Model for Image Restoration Abstract 扩散模型(DM)通过将图像合成过程建模为去噪网络的顺序应用&#xff0c;实现了SOTA的性能。然而&#xff0c;与图像合成不同的是&#xff0c;图像恢复(IR)对生成符合ground-truth的结果有很强的约束。因此&am…

家穷就去互联网

吉祥知识星球http://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247485367&idx1&sn837891059c360ad60db7e9ac980a3321&chksmc0e47eebf793f7fdb8fcd7eed8ce29160cf79ba303b59858ba3a6660c6dac536774afb2a6330#rd 《网安面试指南》http://mp.weixin.qq.com/s?…

AI编程工具合集(附使用地址)

在AI编程工具领域&#xff0c;随着技术的飞速发展&#xff0c;越来越多的工具正在改变编程的方式。以下是目前排名前十的AI编程工具合集&#xff0c;提供了丰富的功能来提升开发效率&#xff0c;并在多个编程场景中帮助开发者解决问题。 1. GitHub Copilot • 是什么: GitHub C…

打靶笔记--medium_socnet

medium_socnet 靶机地址:https://www.vulnhub.com/entry/boredhackerblog-social-network,454/ 内容简介&#xff1a; 这是本系列的第04次打靶&#xff0c;我选择了一个中等难度的靶机。在这次打靶过程中&#xff0c;我们将使用到以下攻击手段&#xff1a; 主机发现 端口扫…

javaEE WebServlet、SpringWebMVC、SpringBoot实现跨域访问的4种方式及优先级,nginx配置跨域

文章目录 1. 前置知识2. 原理和解决方案总结2.1. 跨域不通过原理流程图2.2. 实现原理&#xff1a;添加以下http响应头2.3. 四种跨域实现方式及优先级&#xff08;从高到低&#xff09; 3. 具体实现代码3.1. 跨域全局配置方式-Filter(全适用)3.2. 跨域全局配置方式-SpringMvc3.3…

鸿蒙内核源码分析(内存规则篇) | 内存管理到底在管什么

先说如果没有内存管理会怎样? 那就是个奴才们能把主子给活活踩死&#xff0c; 想想主奴不分&#xff0c;吃喝拉撒睡都在一起&#xff0c;称兄道弟的想干啥? 没规矩不成方圆嘛&#xff0c;这事业肯定搞不大&#xff0c;单片机时代就是这种情况. 裸机编程&#xff0c;指针可以…

【笔记】MSPM0G3507使用RT-Thread FinSH——MSPM0G3507与RT_Thread(四)

接上篇 KEIL 添加 FinSH 源码 添加自己的函数实现rt_hw_console_getchar 修改为&#xff1a; #include "C:\ti\mspm0_sdk_2_01_00_03\examples\nortos\LP_MSPM0G3507\driverlib\G3507_RTT\ti_msp_dl_config.h"//ti_msp_dl_config.h的绝对地址RT_WEAK char rt_hw_con…

Java知识点一——列表、表格与媒体元素

显示表格边框&#xff1a;<table border"1"></table> 因为初始的表格是没有边框的 collapse相邻的单元格共用同一条边框&#xff08;采用 collapsed-border 表格渲染模型&#xff09;。 separate默认值。每个单元格拥有独立的边框&#xff08;采用 sep…

Haskell爬虫中日志记录:监控HTTP请求与响应

在当今信息爆炸的时代&#xff0c;数据抓取成为了获取信息的重要手段。Haskell&#xff0c;以其强大的类型系统和函数式编程特性&#xff0c;成为了编写高效、可靠爬虫的理想选择。然而&#xff0c;随着爬虫的运行&#xff0c;监控其行为变得尤为重要。本文将探讨如何在Haskell…

ROS2从入门到精通2-4:Rviz2插件制作案例(以工具栏和多点导航插件为例)

目录 0 专栏介绍1 Rviz2插件2 项目配置3 案例一&#xff1a;工具栏插件4 案例二&#xff1a;多点导航插件5 综合演示5.1 添加插件5.2 多点巡航 0 专栏介绍 本专栏旨在通过对ROS2的系统学习&#xff0c;掌握ROS2底层基本分布式原理&#xff0c;并具有机器人建模和应用ROS2进行实…