【HarmonyOS开发】ArkUI实现下拉刷新/上拉加载

 列表下拉刷新、上拉加载更多,不管在web时代还是鸿蒙应用都是一个非常常用的功能,基于ArkUI中TS扩展的声明式开发范式实现一个下拉刷新,上拉加载。

上拉加载、下拉刷新

如果数据量过大,可以使用LazyForEach代替ForEach

高阶组件-上拉加载,下拉刷新icon-default.png?t=N7T8https://gitee.com/bingtengaoyu/harmonyos-advanced-componen/tree/master/UpDownRefresh 

1、涉及的知识点

  • 列表容器(List)
  • 触摸事件(onTouch)
  • 位置设置(offset)
  • 显示动画(animateTo)

2、效果图

3、实现思路

根据触摸事件onTouch()处理下拉和上拉,通过记录手指按下的y坐标和move移动的距离判断属于刷拉还是下滑,从而展示不同的内容。

4、关键代码

4.1 生成下拉刷新/上拉加载DOM

  @Builder UpDownRefreshBuilder(text: string, state: string) {
    Row() {
      Image($r('app.media.refreshing'))
        .width(32)
        .height(32)
      Text(text).fontSize(16)
    }
    .justifyContent(FlexAlign.Center)
    .position({
      y: state === 'down' ? 20 : -20
    })
    .zIndex(999)
    .width('94%')
    .height('8%')
  }

4.2 onTouch处理事件

  private currentOffsetY: number = 0;
  private timer: number = 0;
  @State refreshStatus: boolean = false;
  @State upRefreshStatus: boolean = false;

  aboutToDisappear() {
    this.timer = null
  }

  putDownRefresh(event?: TouchEvent): void {
    if (event === undefined) {
      return;
    }
    switch (event.type) {
      case TouchType.Down:
        this.currentOffsetY = event.touches[0].y;
        break;
      case TouchType.Move:
        if(this.scroller.currentOffset().yOffset < 50) {
          this.refreshStatus = event.touches[0].y - this.currentOffsetY > 50;
        }
        this.upRefreshStatus = event.touches[0].y - this.currentOffsetY < -50;
        break;
      case TouchType.Cancel:
        break;
      case TouchType.Up:
        // Only simulation effect, no data request
        this.timer = setTimeout(() => {
          if (this.upRefreshStatus) {
            this.scroller.scrollTo({           // 调用scrollTo滚动到具体位置
              xOffset: 0,                    // 竖直方向滚动,该值不起作用
              yOffset: 680,    // 滚动到底部
              animation: {                     // 滚动动画
                duration: 1500,
                curve: Curve.EaseOut
              }
            })
          }
          this.refreshStatus = false;
          this.upRefreshStatus = false;
        }, 1500);
        break;
    }
  }

5、完整代码

兵腾傲宇/harmonyos-healthy-live - Gitee.com

import router from '@ohos.router'
import curves from '@ohos.curves'
import { BreakpointSystem, BreakPointType } from '../common/BreakpointSystem'
import { FoodInfo, Category } from '../model/DataModels'
import { getFoods, getFoodCategories, getSortedFoodData } from '../model/DataUtil'
import { Records } from './components/DietRecord'
import { PersonalCenter } from './PersonalCenter'

interface FoodId {
  foodId: FoodInfo;
}

@Component
struct FoodListItem {
  private foodItem?: FoodInfo

  build() {
    Navigator({ target: 'pages/FoodDetail' }) {
      Row() {
        Image(this.foodItem!.image!)
          .objectFit(ImageFit.Contain)
          .autoResize(false)
          .height(40)
          .width(40)
          .backgroundColor('#FFf1f3f5')
          .margin({ right: 16 })
          .borderRadius(6)
          .sharedTransition(this.foodItem!.letter, {
            duration: 400,
            curve: curves.cubicBezier(0.2, 0.2, 0.1, 1.0),
            delay: 100
          })
        Text(this.foodItem?.name)
          .fontSize(14)
        Blank()
        Text($r('app.string.calorie_with_kcal_unit', this.foodItem?.calories.toString()))
          .fontSize(14)
      }
      .height(64)
      .width('100%')
    }
    .params({ foodId: this.foodItem } as FoodId)
    .margin({ right: 24, left: 32 })
  }
}

@Component
struct ListModeFoods {
  private foodItems: Array<FoodInfo | string> = getSortedFoodData()
  private currentOffsetY: number = 0;
  private timer: number = 0;
  @State refreshStatus: boolean = false;
  @State upRefreshStatus: boolean = false;

  aboutToDisappear() {
    this.timer = null
  }

  putDownRefresh(event?: TouchEvent): void {
    if (event === undefined) {
      return;
    }
    switch (event.type) {
      case TouchType.Down:
        this.currentOffsetY = event.touches[0].y;
        break;
      case TouchType.Move:
        if(this.scroller.currentOffset().yOffset < 50) {
          this.refreshStatus = event.touches[0].y - this.currentOffsetY > 50;
        }
        this.upRefreshStatus = event.touches[0].y - this.currentOffsetY < -50;
        break;
      case TouchType.Cancel:
        break;
      case TouchType.Up:
        // Only simulation effect, no data request
        this.timer = setTimeout(() => {
          if (this.upRefreshStatus) {
            this.scroller.scrollTo({           // 调用scrollTo滚动到具体位置
              xOffset: 0,                    // 竖直方向滚动,该值不起作用
              yOffset: 680,    // 滚动到底部
              animation: {                     // 滚动动画
                duration: 1500,
                curve: Curve.EaseOut
              }
            })
          }
          this.refreshStatus = false;
          this.upRefreshStatus = false;
        }, 1500);
        break;
    }
  }

  @Builder DownRefreshBuilder(text: string, state: string) {
    Row() {
      Image($r('app.media.refreshing'))
        .width(32)
        .height(32)
      Text(text).fontSize(16)
    }
    .justifyContent(FlexAlign.Center)
    .position({
      y: state === 'down' ? 20 : -20
    })
    .zIndex(999)
    .width('94%')
    .height('8%')
  }

  private scroller: Scroller = new Scroller(); // 创建一个滚动控制器

  build() {
    Column() {
      Text($r("app.string.title_food_list"))
        .width('100%')
        .height(56)
        .padding({ left: 20 })
        .backgroundColor('#FF1f3f5')
        .fontSize(20)

      Scroll(this.scroller) {
        if(this.refreshStatus) {
          this.DownRefreshBuilder('正在刷新', 'down')
        }
        List() {
          ForEach(this.foodItems, (item: FoodInfo) => {
            ListItem() {
              if (item.letter !== undefined) {
                FoodListItem({ foodItem: item })
              } else {
                if (typeof (item) === 'string') {
                  Text(item)
                    .fontSize(14)
                    .height(48)
                    .margin({ left: 24 })
                    .width('100%')
                }
              }
            }
          })
          if(this.upRefreshStatus) {
            ListItem(){
              this.DownRefreshBuilder('正在加载', 'up')
            }
          }
        }
        .layoutWeight(1)
      }
      .scrollBar(BarState.Off)
      .edgeEffect(EdgeEffect.Spring)
      .width('100%')
      .height('90%')
      .onTouch((event?: TouchEvent) => {
        this.putDownRefresh(event);
      })
    }
  }
}

@Component
struct FoodGridItem {
  private foodItem?: FoodInfo

  build() {
    Column() {
      Image(this.foodItem!.image!)
        .objectFit(ImageFit.Contain)
        .backgroundColor('#f1f3f5')
        .width('100%')
        .height(152)
        .sharedTransition(this.foodItem!.letter, {
          duration: 400,
          curve: curves.cubicBezier(0.2, 0.2, 0.1, 1.0),
          delay: 100
        })
      Row() {
        Text(this.foodItem?.name)
          .fontSize(14)
        Blank()
        Text($r('app.string.calorie_with_kcal_unit', this.foodItem?.calories.toString()))
          .fontSize(14)
          .fontColor(0x99000000)
      }
      .padding({ left: 12, right: 12 })
      .width('100%')
      .height(32)
      .backgroundColor('#E5E5E5')
    }
    .height(184)
    .clip(new Rect({ width: '100%', height: '100%', radius: 12 }))
    .onClick(() => {
      router.pushUrl({ url: 'pages/FoodDetail', params: { foodId: this.foodItem } })
    })
  }
}

@Component
struct FoodGrid {
  @StorageProp('currentBreakpoint') currentBreakpoint: string = 'sm'
  private foodItems?: FoodInfo[]

  build() {
    Grid() {
      ForEach(this.foodItems!, (item: FoodInfo) => {
        GridItem() {
          FoodGridItem({ foodItem: item })
        }
      })
    }
    .columnsTemplate(new BreakPointType({
      sm: '1fr 1fr',
      md: '1fr 1fr 1fr',
      lg: '1fr 1fr 1fr 1fr'
    }).getValue(this.currentBreakpoint) as string)
    .columnsGap(8)
    .rowsGap(8)
    .padding({ left: 16, right: 16 })
  }
}

@Component
struct CategoryModeFoods {
  @State currentTabIndex: number = 0
  private foodItems: FoodInfo[] = getFoods()
  private foodCategories: Category[] = getFoodCategories()

  /* *
   * 头部分类导航栏
   *
   * */
  @Builder
  tabBarItemBuilder(value: Resource, index: number) {
    Text(value)
      .fontColor(this.currentTabIndex === index ? Color.Blue : 'rgba(0,0,0,0.6)')
      .fontSize(this.currentTabIndex === index ? 20 : 18)
      .fontWeight(this.currentTabIndex === index ? FontWeight.Bold : FontWeight.Normal)
      .margin({ top: 2 })
      .height(56)
  }

  build() {
    Tabs() {
      TabContent() {
        FoodGrid({ foodItems: this.foodItems })
      }.tabBar(this.tabBarItemBuilder($r('app.string.category_all'), 0))

      ForEach(this.foodCategories, (foodCategory: Category, index?: number) => {
        TabContent() {
          FoodGrid({ foodItems: this.foodItems.filter(item => (item.categoryId === foodCategory.id)) })
        }.tabBar(this.tabBarItemBuilder(foodCategory.name!,
          index! + 1))
      })
    }
    .animationDuration(0)
    .barWidth('80%')
    .onChange((index) => {
      this.currentTabIndex = index
    })
  }
}

@Component
struct FoodsDisplay {
  @State isCategoryMode: boolean = true
  @State isMoreIconOnClick: boolean = false
  @State isMoreIconOnHover: boolean = false
  @State isMoreIconOnFocus: boolean = false

  getMoreIconBgColor() {
    if (this.isMoreIconOnClick) {
      return $r('sys.color.ohos_id_color_click_effect')
    } else if (this.isMoreIconOnHover) {
      return $r('sys.color.ohos_id_color_hover')
    } else {
      return this.isCategoryMode ? Color.White : '#F1F3F5' || Color.Transparent
    }
  }

  build() {
    Stack({ alignContent: Alignment.TopEnd }) {
      if (this.isCategoryMode) {
        CategoryModeFoods()
      } else {
        ListModeFoods()
      }
      Row() {
        Image($r("app.media.ic_switch"))
          .height(24)
          .width(24)
          .margin({ left: 24, right: 24 })
          .focusable(true)
      }
      .height(56)
      .backgroundColor(this.getMoreIconBgColor())
      .stateStyles({
        focused: {
          .border({
            radius: $r('sys.float.ohos_id_corner_radius_clicked'),
            color: $r('sys.color.ohos_id_color_focused_outline'),
            style: BorderStyle.Solid
          })
        },
        normal: {
          .border({
            radius: $r('sys.float.ohos_id_corner_radius_clicked'),
            width: 0
          })
        }
      })
      .onFocus(() => this.isMoreIconOnFocus = true)
      .onBlur(() => this.isMoreIconOnFocus = false)
      .onHover((isOn) => this.isMoreIconOnHover = isOn)
      .onClick(() => {
        this.isCategoryMode = !this.isCategoryMode
      })
    }
  }
}

@Entry
@Component
struct Home {
  @State currentTabIndex: number = 0
  @StorageProp('currentBreakpoint') currentBreakpoint: string = 'sm'
  private breakpointSystem: BreakpointSystem = new BreakpointSystem()

  /* *
   * 主页和记录的tabs
   * */
  @Builder
  bottomBarItemBuilder(name: Resource, icon: Resource, index: number) {
    Flex({
      direction: new BreakPointType({
        sm: FlexDirection.Column,
        md: FlexDirection.Row,
        lg: FlexDirection.Column
      }).getValue(this.currentBreakpoint),
      justifyContent: FlexAlign.Center,
      alignItems: ItemAlign.Center
    }) {
      Image(icon)
        .height(24)
        .width(24)
        .fillColor(this.getTabBarColor(index))
      Text(name)
        .margin(new BreakPointType<Padding>({
          sm: { top: 4 },
          md: { left: 8 },
          lg: { top: 4 }
        }).getValue(this.currentBreakpoint) as Padding)
        .fontSize(11)
        .fontColor(this.getTabBarColor(index))
    }
  }

  aboutToAppear() {
    this.breakpointSystem.register()
  }

  aboutToDisappear() {
    this.breakpointSystem.unregister()
  }

  build() {
    Tabs({
      barPosition: new BreakPointType({
        sm: BarPosition.End,
        md: BarPosition.End,
        lg: BarPosition.Start
      }).getValue(this.currentBreakpoint)
    }) {
      TabContent() {
        FoodsDisplay()
      }.tabBar(this.bottomBarItemBuilder($r("app.string.tab_bar_home"), $r("app.media.ic_bottom_home"), 0))

      TabContent() {
        Records()
      }.tabBar(this.bottomBarItemBuilder($r("app.string.tab_bar_record"), $r("app.media.ic_bottom_record"), 1))

      TabContent() {
        PersonalCenter()
      }.tabBar(this.bottomBarItemBuilder($r("app.string.tab_bar_me"), $r("app.media.ic_public_me"), 2))
    }
    .vertical(new BreakPointType({ sm: false, md: false, lg: true }).getValue(this.currentBreakpoint) as boolean)
    .barWidth(new BreakPointType({ sm: '100%', md: '100%', lg: '56vp' }).getValue(this.currentBreakpoint) as string)
    .barHeight(new BreakPointType({ sm: '56vp', md: '56vp', lg: '60%' }).getValue(this.currentBreakpoint) as string)
    .animationDuration(300)
    .onChange((index) => {
      this.currentTabIndex = index
    })
  }

  private getTabBarColor(index: number) {
    return this.currentTabIndex == index ? $r('app.color.tab_bar_select_color') : $r('app.color.tab_bar_normal_color')
  }
}

6、另外一个思路实现上拉加载,下拉刷新

根据List中的回调方法onScrollIndex()监听当前列表首尾索引,根据触摸事件onTouch()处理下拉和上拉。

const TopHeight = 200;

@Entry
@Component
struct Index {
  @State list: Array<number> = []
  // 列表y坐标偏移量
  @State offsetY: number = 0
  // 按下的y坐标
  private downY = 0
  // 上一次移动的y坐标
  private lastMoveY = 0
  // 当前列表首部的索引
  private startIndex = 0
  // 当前列表尾部的索引
  private endIndex = 0

  // 下拉刷新的布局高度
  private pullRefreshHeight = 70
  // 下拉刷新文字:下拉刷新、松开刷新、正在刷新、刷新成功
  @State pullRefreshText: string= '下拉刷新'
  // 下拉刷新图标:与文字对应
  @State pullRefreshImage: Resource = $r("app.media.ic_pull_refresh_down")
  // 是否可以刷新:未达到刷新条件,收缩回去
  private isCanRefresh = false
  // 是否正在刷新:刷新中不进入触摸逻辑
  private isRefreshing: boolean = false
  // 是否已经进入了下拉刷新操作
  private isPullRefreshOperation = false

  // 上拉加载的布局默认高度
  private loadMoreHeight = 70
  // 上拉加载的布局是否显示
  @State isVisibleLoadMore: boolean = false
  // 是否可以加载更多
  private isCanLoadMore = false
  // 是否加载中:加载中不进入触摸逻辑
  private isLoading: boolean = false


  // 自定义下拉刷新布局
  @Builder CustomPullRefreshLayout(){
    Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
      Image(this.pullRefreshImage)
        .width(18)
        .height(18)

      Text(this.pullRefreshText)
        .margin({ left: 7, bottom: 1 })
        .fontSize(17)
    }
    .width('100%')
    .height(this.pullRefreshHeight)
    // 布局跟着列表偏移量移动
    .offset({ x: 0, y: `${vp2px(-this.pullRefreshHeight) + this.offsetY}px` })
  }

  // 自定义加载更多布局
  @Builder CustomLoadMoreLayout(){
    Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
      Image($r("app.media.ic_loading"))
        .width(18)
        .height(18)

      Text('加载更多中...')
        .margin({ left: 7, bottom: 1 })
        .fontSize(17)
    }
    .width('100%')
    .height(this.loadMoreHeight)
    .backgroundColor('#f4f4f4')
    .visibility(this.isVisibleLoadMore ? Visibility.Visible : Visibility.None)
  }

  // 刷新测试数据
  private refreshData(){
    this.list = []
    for (var i = 0; i < 15; i++) {
      this.list.push(i)
    }
  }

  // 加载更多测试数据
  private loadMoreData(){
    let initValue = this.list[this.list.length-1] + 1
    for (var i = initValue; i < initValue + 10; i++) {
      this.list.push(i)
    }
  }

  build() {
    Column() {
      // 下拉刷新布局
      this.CustomPullRefreshLayout()

      // 列表布局
      List() {
        ForEach(this.list, item => {
          ListItem() {
            Column() {
              Text(`Item ${item}`)
                .padding(15)
                .fontSize(18)
            }
          }
        }, item => item.toString())

        // 加载更多布局
        ListItem(){
          this.CustomLoadMoreLayout()
        }
      }
      .backgroundColor(Color.White) // 背景
      .divider({ color: '#e2e2e2', strokeWidth: 1 }) // 分割线
      .edgeEffect(EdgeEffect.None) // 去掉回弹效果
      .offset({ x: 0, y: `${this.offsetY - TopHeight}px` }) // touch事件计算的偏移量单位是px,记得加上单位
      .onScrollIndex((start, end) => { // 监听当前列表首位索引
        console.info(`${start}=start============end=${end}`)
        this.startIndex = start
        this.endIndex = end
      })

    }
    .width('100%')
    .height('100%')
    .backgroundColor('#f4f4f4')
    .onTouch((event) => this.listTouchEvent(event))// 父容器设置touch事件,当列表无数据也可以下拉刷新
    .onAppear(() => {
      this.refreshData()
    })
  }

  // 触摸事件
  listTouchEvent(event: TouchEvent){
    switch (event.type) {
      case TouchType.Down: // 手指按下
      // 记录按下的y坐标
        this.downY = event.touches[0].y
        this.lastMoveY = event.touches[0].y
        break
      case TouchType.Move: // 手指移动
      // 下拉刷新中 或 加载更多中,不进入处理逻辑
        if(this.isRefreshing || this.isLoading){
          console.info('========Move刷新中,返回=========')
          return
        }
      // 判断手势
        let isDownPull = event.touches[0].y - this.lastMoveY > 0
      // 下拉手势 或 已经进入了下拉刷新操作
        if ((isDownPull || this.isPullRefreshOperation) && !this.isCanLoadMore) {
          this.touchMovePullRefresh(event)
        } else {
          this.touchMoveLoadMore(event)
        }
        this.lastMoveY = event.touches[0].y
        break
      case TouchType.Up: // 手指抬起
      case TouchType.Cancel: // 触摸意外中断:来电界面
      // 刷新中 或 加载更多中,不进入处理逻辑
        if(this.isRefreshing || this.isLoading){
          console.info('========Up刷新中,返回=========')
          return
        }
        if (this.isPullRefreshOperation) {
          this.touchUpPullRefresh()
        } else {
          this.touchUpLoadMore()
        }
        break
    }
  }

  //============================================下拉刷新==================================================
  // 手指移动,处理下拉刷新
  touchMovePullRefresh(event:TouchEvent){
    // 当首部索引位于0
    if (this.startIndex == 0) {
      this.isPullRefreshOperation = true
      // 下拉刷新布局高度
      var height = vp2px(this.pullRefreshHeight)
      // 滑动的偏移量
      this.offsetY = event.touches[0].y - this.downY

      // 偏移量大于下拉刷新布局高度,达到刷新条件
      if (this.offsetY >= height) {
        // 状态1:松开刷新
        this.pullRefreshState(1)
        // 偏移量的值缓慢增加
        this.offsetY = height + this.offsetY * 0.15
      } else {
        // 状态0:下拉刷新
        this.pullRefreshState(0)
      }

      if (this.offsetY < 0) {
        this.offsetY = 0
        this.isPullRefreshOperation = false
      }
    }
  }

  // 手指抬起,处理下拉刷新
  touchUpPullRefresh(){
    // 是否可以刷新
    if (this.isCanRefresh) {
      console.info('======执行下拉刷新========')
      // 偏移量为下拉刷新布局高度
      this.offsetY = vp2px(this.pullRefreshHeight)
      // 状态2:正在刷新
      this.pullRefreshState(2)

      // 模拟耗时操作
      setTimeout(() => {
        this.refreshData()
        this.closeRefresh()
      }, 2000)

    } else {
      console.info('======关闭下拉刷新!未达到条件========')
      // 关闭刷新
      this.closeRefresh()
    }
  }

  // 下拉刷新状态
  // 0下拉刷新、1松开刷新、2正在刷新、3刷新成功
  pullRefreshState(state:number){
    switch (state) {
      case 0:
      // 初始状态
        this.pullRefreshText = '下拉刷新'
        this.pullRefreshImage = $r("app.media.ic_pull_refresh_down")
        this.isCanRefresh = false
        this.isRefreshing = false
        break;
      case 1:
        this.pullRefreshText = '松开刷新'
        this.pullRefreshImage = $r("app.media.ic_pull_refresh_up")
        this.isCanRefresh = true
        this.isRefreshing = false
        break;
      case 2:
        this.offsetY = vp2px(this.pullRefreshHeight)
        this.pullRefreshText = '正在刷新'
        this.pullRefreshImage = $r("app.media.ic_loading")
        this.isCanRefresh = true
        this.isRefreshing = true
        break;
      case 3:
        this.pullRefreshText = '刷新成功'
        this.pullRefreshImage = $r("app.media.ic_refresh_succeed")
        this.isCanRefresh = true
        this.isRefreshing = true
        break;
    }
  }

  // 关闭刷新
  closeRefresh() {
    // 如果允许刷新,延迟进入,为了显示刷新中
    setTimeout(() => {
      var delay = 50
      if (this.isCanRefresh) {
        // 状态3:刷新成功
        this.pullRefreshState(3)
        // 为了显示刷新成功,延迟执行收缩动画
        delay = 500
      }
      animateTo({
        duration: 150, // 动画时长
        delay: delay, // 延迟时长
        onFinish: () => {
          // 状态0:下拉刷新
          this.pullRefreshState(0)
          this.isPullRefreshOperation = false
        }
      }, () => {
        this.offsetY = 0
      })
    }, this.isCanRefresh ? 500 : 0)
  }

  //============================================加载更多==================================================
  // 手指移动,处理加载更多
  touchMoveLoadMore(event:TouchEvent) {
    // 因为加载更多是在列表后面新增一个item,当一屏能够展示全部列表,endIndex 为 length+1
    if (this.endIndex == this.list.length - 1 || this.endIndex == this.list.length) {
      // 滑动的偏移量
      this.offsetY = event.touches[0].y - this.downY
      if (Math.abs(this.offsetY) > vp2px(this.loadMoreHeight)/2) {
        // 可以刷新了
        this.isCanLoadMore = true
        // 显示加载更多布局
        this.isVisibleLoadMore = true
        // 偏移量缓慢增加
        this.offsetY = - vp2px(this.loadMoreHeight) + this.offsetY * 0.1
      }
    }
  }

  // 手指抬起,处理加载更多
  touchUpLoadMore() {
    animateTo({
      duration: 200, // 动画时长
    }, () => {
      // 偏移量设置为0
      this.offsetY = 0
    })
    if (this.isCanLoadMore) {
      console.info('======执行加载更多========')
      // 加载中...
      this.isLoading = true
      // 模拟耗时操作
      setTimeout(() => {
        this.closeLoadMore()
        this.loadMoreData()
      }, 2000)
    } else {
      console.info('======关闭加载更多!未达到条件========')
      this.closeLoadMore()
    }
  }

  // 关闭加载更多
  closeLoadMore() {
    this.isCanLoadMore = false
    this.isLoading = false
    this.isVisibleLoadMore = false
  }

}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Proteus/8086】swjtu西南交大微机与接口技术实验:计时器

实验内容: 计时器基本功能: 1)CPU必须用8086 2)计时器最小计时单位为秒。 3)以00:00格式显示计时,前2位表示分钟,后2位表示秒。 4)计时器是正计时方式 5)有暂停、继续计时功能 6&#xff09;有复位计时功能 7&#xff09;每次按下暂停键&#xff0c;能显示计时间隔时间 参考…

智能优化算法应用:基于类电磁机制算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于类电磁机制算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于类电磁机制算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.类电磁机制算法4.实验参数设定5.算法…

【Linux学习】进程间通信

目录 十四:进程间通信 14.1 进程间通信的介绍 进程间通信的概念: 为什么需要进程间通信: 常见的进程间通信方式: 14.2 进程间通信的原理 十五:进程间通信--管道 15.1 管道的介绍 管道的概念: 管道的分类: 15.2 匿名管道 匿名管道的原理: pipe函数创建匿名管道: 匿名管道实现父…

Spring Security:从入门到精通,全方位指南

Spring Security 是 Spring 家族中的一个安全管理框架。相比与另外一个安全框架Shiro&#xff0c;它提供了更丰富的功能&#xff0c;社区资源也比Shiro丰富。 一般来说中大型的项目都是使用SpringSecurity 来做安全框架。小项目有Shiro的比较多&#xff0c;因为相比与SpringSe…

污水处理厂可视化:让环保与科技共舞

随着科技的飞速发展&#xff0c;我们的生活环境变得越来越美好。然而&#xff0c;随着城市化进程的加快&#xff0c;污水处理问题也日益凸显。如何有效、高效地处理污水&#xff0c;成为了一个亟待解决的问题。而“污水处理厂可视化”技术的出现&#xff0c;为这个问题提供了一…

Python---TCP 客户端程序开发

1. 开发 TCP 客户端程序开发步骤回顾 创建客户端套接字对象和服务端套接字建立连接发送数据接收数据关闭客户端套接字 2. socket 类的介绍 导入 socket 模块 import socket 创建客户端 socket 对象 socket.socket(AddressFamily, Type) 参数说明: AddressFamily 表示IP地…

Debug

0、Debug的步骤 Debug(调试)程序步骤如下&#xff1a; 1、添加断点 2、启动调试 3、单步执行 4、观察变量和执行流程&#xff0c;找到并解决问题 1、添加断点 在源代码文件中&#xff0c;在想要设置断点的代码行的前面的标记行处&#xff0c;单击鼠标左键就可以设置断点&…

创建型模式 | 原型模式

一、原型模式 1、原理 原型模式&#xff0c;用原型实例指定创建对象的种类&#xff0c;并且通过拷贝这些原型创建新的对象。原型模式其实就是从一个对象再创建另外一个可定制的对象&#xff0c;而且不需要知道任何创建的细节。原型像是一个模板&#xff0c;可以基于它复制好多…

Matlab论文插图绘制模板第131期—函数等高线图

在之前的文章中&#xff0c;分享了Matlab函数折线图的绘制模板&#xff1a; 函数三维折线图&#xff1a; 函数网格曲面图&#xff1a; 函数曲面图&#xff1a; 进一步&#xff0c;再来分享一下函数等高线图。 先来看一下成品效果&#xff1a; 特别提示&#xff1a;本期内容『数…

Python连接数据库

文章目录 一、安装mysql二、SQLyog可视化操作三、python实现数据库单表类封装1. config 文件——config.py2. 封装类&#xff08;model&#xff09;——model.py3. 测试文件——test.py 一、安装mysql 官网安装&#xff0c;或者Windows64位直接在我的资源里面上传了mysql&…

Python: 函数参数是值传递还是引用传递

是引用传递。 Python的设计哲学是一切皆对象&#xff0c;不仅体现在内置数据类型、数据结构是对象&#xff0c;还包括Python编译运行需要的一些设施&#xff0c;比如stackframe、traceback等等。所以&#xff0c;为了更方便的传递数据&#xff0c;cpython内部全部采用指针传递…

如何开发专属花店展示平台小程序?

如今&#xff0c;微信小程序已经成为了花店行业拓展客户资源的重要工具。通过开发一个专属花店小程序&#xff0c;你可以为自己的花店带来更多的曝光和客户资源。那么&#xff0c;如何开发一个专属花店小程序呢&#xff1f;接下来&#xff0c;我们将一步步为你详细讲解。 首先&…

Valentina Studio Pro for Mac:高效数据库管理工具

作为一款强大而高效的数据库管理工具&#xff0c;Valentina Studio Pro for Mac在Mac平台上的表现无疑是令人印象深刻的。无论您是初学者还是专业数据库管理员&#xff0c;Valentina Studio Pro都能够满足您的需要&#xff0c;并提供一流的工具和功能来简化数据库管理的过程。 …

电机(按用途分类)介绍

文章目录 一、前言二、按用途分类1.步进电机1.1 介绍1.2 工作原理1.3 单极性和双极性1.4 驱动方式1.4.1 全步1.4.2 半步1.4.3 微步 1.5 分类1.6 应用 2.伺服电机2.1 介绍2.2 工作原理2.3 分类2.3.1 直流伺服电机2.3.1.1 特性2.3.1.2 有刷和无刷 2.3.2 交流伺服电机2.3.2.1 特性…

PPT怎么做成二维码?扫二维码就能获得文档

在工作中很多的小伙伴可能经常会用到PPT这一种类型的文件&#xff0c;比如做年度总结、项目汇报、产品介绍等等类型的内容。那么需要将做的PPT课件分享给其他人时&#xff0c;将文件制作二维码图片后分享的方法会更加的简单方便&#xff0c;其他人只需要扫描二维码就可以查看或…

Redis基础篇-002 初识Redis

1、认识NoSQL 1.1 概念 NoSQL是一个非关系型数据库。 常见的NoSQL有&#xff1a;Redis、MongoDB 1.2 NoSQL与SQL的区别 类别SQLNoSQL数据结构结构化非结构化数据关联关联非关联查询方式SQL非SQL事务特性ACIDBASE存储方式磁盘内存扩展性垂直水平使用场景1&#xff09;数据结…

论文润色机构哪个好 PaperBERT

大家好&#xff0c;今天来聊聊论文润色机构哪个好&#xff0c;希望能给大家提供一点参考。 以下是针对论文重复率高的情况&#xff0c;提供一些修改建议和技巧&#xff0c;可以借助此类工具&#xff1a; 标题&#xff1a;论文润色机构哪个好――专业、高效、可靠的学术支持 一…

什么是大小端?

今天说一下什么是大小端模式&#xff1f; 大小端模式指的是什么&#xff1f;通常我们在存储器当中存储数据的字节顺序&#xff0c;注意这里强调的是“字节的顺序”。因为在计算机系统中&#xff0c;不管是单片机DSP或者是X86&#xff0c;我们说一个地址对应的存储空间大小呢就是…

一款超级给力的弱网测试神器—Qnet(上)

&#x1f4e2;专注于分享软件测试干货内容&#xff0c;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;交流讨论&#xff1a;欢迎加入我们一起学习&#xff01;&#x1f4e2;资源分享&#xff1a;耗时200小时精选的「软件测试」资…

yum install net-tools 命令报错,无法安装成功

编辑网卡文件 插入数据&#xff0c;输入&#xff1a; i 保存编辑&#xff1a;输入 Esc 然后:wq