鸿蒙OS开发实例:【瀑布流式图片浏览】

 介绍

瀑布流式展示图片文字,在当前产品设计中已非常常见,本篇将介绍关于WaterFlow的图片浏览场景,顺便集成Video控件,以提高实践的趣味性

准备

  1. 请参照[官方指导],创建一个Demo工程,选择Stage模型
  2. 熟读HarmonyOS 官方指导“https://gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md”

搜狗高速浏览器截图20240326151547.png

效果

竖屏

image.png

横屏

数据源

鸿蒙OS开发更多内容↓点击HarmonyOS与OpenHarmony技术
鸿蒙技术文档开发知识更新库gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md在这。

功能介绍

  1. 瀑布流式图片展示
  2. 横竖屏图片/视频展示

核心代码

布局

整体结构为:瀑布流 + 加载进度条

每条数据结构: 图片 + 文字 【由于没有设定图片宽高比,因此通过文字长度来自然生成瀑布流效果】

由于有点数据量,按照官方指导,采用LazyForEach懒加载方式

Stack() {
  WaterFlow({ scroller: this.scroller }) {
    LazyForEach(dataSource, item => {
      FlowItem() {
        Column({ space: 10 }) {
          Image(item.coverUrl).objectFit(ImageFit.Cover)
            .width('100%')
            .height(this.imageHeight)
          Text(item.title)
            .fontSize(px2fp(50))
            .fontColor(Color.Black)
            .width('100%')
        }.onClick(() => {
          router.pushUrl({ url: 'custompages/waterflow/Detail', params: item })
        })
      }
    }, item => item)
  }
  .columnsTemplate(this.columnsTemplate)
  .columnsGap(5)
  .rowsGap(5)
  .onReachStart(() => {
    console.info("onReachStart")
  })
  .onReachEnd(() => {
    console.info("onReachEnd")

    if (!this.running) {
      if ((this.pageNo + 1) * 15 < this.total) {
        this.pageNo++
        this.running = true

        setTimeout(() => {
          this.requestData()
        }, 2000)
      }
    }

  })
  .width('100%')
  .height('100%')
  .layoutDirection(FlexDirection.Column)

  if (this.running) {
     this.loadDataFooter()
  }

}

横竖屏感知

横竖屏感知整体有两个场景:1. 当前页面发生变化 2.初次进入页面
这里介绍几种监听方式:

当前页面监听

import mediaquery from '@ohos.mediaquery';

//这里你也可以使用"orientation: portrait" 参数
listener = mediaquery.matchMediaSync('(orientation: landscape)');
this.listener.on('change', 回调方法)

外部传参

通过UIAbility, 一直传到Page文件

事件传递

采用EeventHub机制,在UIAbility把横竖屏切换事件发出来,Page文件注册监听事件

this.context.eventHub.on('onConfigurationUpdate', (data) => {
  console.log(JSON.stringify(data))
  let config = data as Configuration
  this.screenDirection = config.direction
  this.configureParamsByScreenDirection()
});

API数据请求

这里需要设置Android 或者 iOS 特征UA

requestData() {
  let url = `https://api.apiopen.top/api/getHaoKanVideo?page=${this.pageNo}&size=15`
  let httpRequest = http.createHttp()
  httpRequest.request(
    url,
    {
      header: {
        "User-Agent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G955U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Mobile Safari/537.36"
      }
    }).then((value: http.HttpResponse) => {

    if (value.responseCode == 200) {

      let searchResult: SearchResult = JSON.parse(value.result as string)

      if (searchResult) {
        this.total = searchResult.result.total

        searchResult.result.list.forEach(ItemModel => {
          dataSource.addData(ItemModel)
        })
      }
    } else {
      console.error(JSON.stringify(value))
    }

  }).catch(e => {

    Logger.d(JSON.stringify(e))

    promptAction.showToast({
      message: '网络异常: ' + JSON.stringify(e),
      duration: 2000
    })

  }).finally(() => {
    this.running = false
  })

}

横竖屏布局调整

因为要适应横竖屏,所以需要在原有布局的基础上做一点改造, 让瀑布流的列参数改造为@State 变量 , 让图片高度的参数改造为@State 变量

WaterFlow({ scroller: this.scroller }) {
  LazyForEach(dataSource, item => {
    FlowItem() {
      Column({ space: 10 }) {
        Image(item.coverUrl).objectFit(ImageFit.Cover)
          .width('100%')
          .height(this.imageHeight)
        Text(item.title)
          .fontSize(px2fp(50))
          .fontColor(Color.Black)
          .width('100%')
      }.onClick(() => {
        router.pushUrl({ url: 'custompages/waterflow/Detail', params: item })
      })
    }
  }, item => item)
}
.columnsTemplate(this.columnsTemplate)

瀑布流完整代码

API返回的数据结构

import { ItemModel } from './ItemModel'

export default class SearchResult{
  public code: number
  public message: string
  public result: childResult
}

class childResult {
  public total: number
  public list: ItemModel[]
};

Item Model

export class ItemModel{
  public id: number
  public tilte: string
  public userName: string
  public userPic: string
  public coverUrl: string
  public playUrl: string
  public duration: string
}

WaterFlow数据源接口

import List from '@ohos.util.List';
import { ItemModel } from './ItemModel';

export class PicData implements IDataSource {

  private data: List<ItemModel> = new List<ItemModel>()

  addData(item: ItemModel){
    this.data.add(item)
  }

  unregisterDataChangeListener(listener: DataChangeListener): void {

  }

  registerDataChangeListener(listener: DataChangeListener): void {

  }

  getData(index: number): ItemModel {
     return this.data.get(index)
  }

  totalCount(): number {
    return this.data.length
  }


}

布局

import http from '@ohos.net.http';
import { CommonConstants } from '../../common/CommonConstants';
import Logger from '../../common/Logger';
import { PicData } from './PicData';
import SearchResult from './Result';
import promptAction from '@ohos.promptAction'
import router from '@ohos.router';
import common from '@ohos.app.ability.common';
import { Configuration } from '@ohos.app.ability.Configuration';
import mediaquery from '@ohos.mediaquery';

let dataSource = new PicData()

/**
 * 问题: 横竖屏切换,间距会发生偶发性变化
 * 解决方案:延迟300毫秒改变参数
 *
 */
@Entry
@Component
struct GridLayoutIndex {
  private context = getContext(this) as common.UIAbilityContext;
  @State pageNo: number = 0
  total: number = 0
  @State running: boolean = true
  @State screenDirection: number = this.context.config.direction
  @State columnsTemplate: string = '1fr 1fr'
  @State imageHeight: string = '20%'
  scroller: Scroller = new Scroller()

  // 当设备横屏时条件成立
  listener = mediaquery.matchMediaSync('(orientation: landscape)');

  onPortrait(mediaQueryResult) {
    if (mediaQueryResult.matches) {
      //横屏
      this.screenDirection = 1
    } else {
      //竖屏
      this.screenDirection = 0
    }

    setTimeout(()=>{
      this.configureParamsByScreenDirection()
    }, 300)
  }

  onBackPress(){
    this.context.eventHub.off('onConfigurationUpdate')
  }

  aboutToAppear() {
    console.log('已进入瀑布流页面')

    console.log('当前屏幕方向:' + this.context.config.direction)

    if (AppStorage.Get('screenDirection') != 'undefined') {
      this.screenDirection = AppStorage.Get(CommonConstants.ScreenDirection)
    }

    this.configureParamsByScreenDirection()

    this.eventHubFunc()

    let portraitFunc = this.onPortrait.bind(this)
    this.listener.on('change', portraitFunc)

    this.requestData()
  }

  @Builder loadDataFooter() {
    LoadingProgress()
      .width(px2vp(150))
      .height(px2vp(150))
      .color(Color.Orange)
  }

  build() {
    Stack() {
      WaterFlow({ scroller: this.scroller }) {
        LazyForEach(dataSource, item => {
          FlowItem() {
            Column({ space: 10 }) {
              Image(item.coverUrl).objectFit(ImageFit.Cover)
                .width('100%')
                .height(this.imageHeight)
              Text(item.title)
                .fontSize(px2fp(50))
                .fontColor(Color.Black)
                .width('100%')
            }.onClick(() => {
              router.pushUrl({ url: 'custompages/waterflow/Detail', params: item })
            })
          }
        }, item => item)
      }
      .columnsTemplate(this.columnsTemplate)
      .columnsGap(5)
      .rowsGap(5)
      .onReachStart(() => {
        console.info("onReachStart")
      })
      .onReachEnd(() => {
        console.info("onReachEnd")

        if (!this.running) {
          if ((this.pageNo + 1) * 15 < this.total) {
            this.pageNo++
            this.running = true

            setTimeout(() => {
              this.requestData()
            }, 2000)
          }
        }

      })
      .width('100%')
      .height('100%')
      .layoutDirection(FlexDirection.Column)

      if (this.running) {
         this.loadDataFooter()
      }

    }

  }

  requestData() {
    let url = `https://api.apiopen.top/api/getHaoKanVideo?page=${this.pageNo}&size=15`
    let httpRequest = http.createHttp()
    httpRequest.request(
      url,
      {
        header: {
          "User-Agent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G955U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Mobile Safari/537.36"
        }
      }).then((value: http.HttpResponse) => {

      if (value.responseCode == 200) {

        let searchResult: SearchResult = JSON.parse(value.result as string)

        if (searchResult) {
          this.total = searchResult.result.total

          searchResult.result.list.forEach(ItemModel => {
            dataSource.addData(ItemModel)
          })
        }
      } else {
        console.error(JSON.stringify(value))
      }

    }).catch(e => {

      Logger.d(JSON.stringify(e))

      promptAction.showToast({
        message: '网络异常: ' + JSON.stringify(e),
        duration: 2000
      })

    }).finally(() => {
      this.running = false
    })

  }

  eventHubFunc() {
    this.context.eventHub.on('onConfigurationUpdate', (data) => {
      console.log(JSON.stringify(data))
      // let config = data as Configuration
      // this.screenDirection = config.direction
      // this.configureParamsByScreenDirection()
    });
  }

  configureParamsByScreenDirection(){
    if (this.screenDirection == 0) {
      this.columnsTemplate = '1fr 1fr'
      this.imageHeight = '20%'
    } else {
      this.columnsTemplate = '1fr 1fr 1fr 1fr'
      this.imageHeight = '50%'
    }
  }

}

图片详情页

import { CommonConstants } from '../../common/CommonConstants';
import router from '@ohos.router';
import { ItemModel } from './ItemModel';
import common from '@ohos.app.ability.common';
import { Configuration } from '@ohos.app.ability.Configuration';

@Entry
@Component
struct DetailIndex{
  private context = getContext(this) as common.UIAbilityContext;

  extParams: ItemModel
  @State previewUri: Resource = $r('app.media.splash')
  @State curRate: PlaybackSpeed = PlaybackSpeed.Speed_Forward_1_00_X
  @State isAutoPlay: boolean = false
  @State showControls: boolean = true
  controller: VideoController = new VideoController()

  @State screenDirection: number = 0
  @State videoWidth: string = '100%'
  @State videoHeight: string = '70%'

  @State tipWidth: string = '100%'
  @State tipHeight: string = '30%'

  @State componentDirection: number = FlexDirection.Column
  @State tipDirection: number = FlexDirection.Column

  aboutToAppear() {
    console.log('准备加载数据')
    if(AppStorage.Get('screenDirection') != 'undefined'){
      this.screenDirection = AppStorage.Get(CommonConstants.ScreenDirection)
    }

    this.configureParamsByScreenDirection()

    this.extParams = router.getParams() as ItemModel
    this.eventHubFunc()
  }

  onBackPress(){
    this.context.eventHub.off('onConfigurationUpdate')
  }

  build() {
      Flex({direction: this.componentDirection}){
        Video({
          src: this.extParams.playUrl,
          previewUri: this.extParams.coverUrl,
          currentProgressRate: this.curRate,
          controller: this.controller,
        }).width(this.videoWidth).height(this.videoHeight)
          .autoPlay(this.isAutoPlay)
          .objectFit(ImageFit.Contain)
          .controls(this.showControls)
          .onStart(() => {
            console.info('onStart')
          })
          .onPause(() => {
            console.info('onPause')
          })
          .onFinish(() => {
            console.info('onFinish')
          })
          .onError(() => {
            console.info('onError')
          })
          .onPrepared((e) => {
            console.info('onPrepared is ' + e.duration)
          })
          .onSeeking((e) => {
            console.info('onSeeking is ' + e.time)
          })
          .onSeeked((e) => {
            console.info('onSeeked is ' + e.time)
          })
          .onUpdate((e) => {
            console.info('onUpdate is ' + e.time)
          })

        Flex({direction: this.tipDirection, justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center, alignContent: FlexAlign.Center}){
          Row() {
            Button('src').onClick(() => {
              // this.videoSrc = $rawfile('video2.mp4') // 切换视频源
            }).margin(5)
            Button('previewUri').onClick(() => {
              // this.previewUri = $r('app.media.poster2') // 切换视频预览海报
            }).margin(5)
            Button('controls').onClick(() => {
              this.showControls = !this.showControls // 切换是否显示视频控制栏
            }).margin(5)
          }

          Row() {
            Button('start').onClick(() => {
              this.controller.start() // 开始播放
            }).margin(5)
            Button('pause').onClick(() => {
              this.controller.pause() // 暂停播放
            }).margin(5)
            Button('stop').onClick(() => {
              this.controller.stop() // 结束播放
            }).margin(5)
            Button('setTime').onClick(() => {
              this.controller.setCurrentTime(10, SeekMode.Accurate) // 精准跳转到视频的10s位置
            }).margin(5)
          }
          Row() {
            Button('rate 0.75').onClick(() => {
              this.curRate = PlaybackSpeed.Speed_Forward_0_75_X // 0.75倍速播放
            }).margin(5)
            Button('rate 1').onClick(() => {
              this.curRate = PlaybackSpeed.Speed_Forward_1_00_X // 原倍速播放
            }).margin(5)
            Button('rate 2').onClick(() => {
              this.curRate = PlaybackSpeed.Speed_Forward_2_00_X // 2倍速播放
            }).margin(5)
          }
        }
        .width(this.tipWidth).height(this.tipHeight)
      }

  }

  eventHubFunc() {
    this.context.eventHub.on('onConfigurationUpdate', (data) => {
       console.log(JSON.stringify(data))
       let config = data as Configuration
       this.screenDirection = config.direction

       this.configureParamsByScreenDirection()

    });
  }


  configureParamsByScreenDirection(){
    if(this.screenDirection == 0){
      this.videoWidth = '100%'
      this.videoHeight = '70%'
      this.tipWidth = '100%'
      this.tipHeight = '30%'
      this.componentDirection = FlexDirection.Column

    } else {
      this.videoWidth = '60%'
      this.videoHeight = '100%'
      this.tipWidth = '40%'
      this.tipHeight = '100%'
      this.componentDirection = FlexDirection.Row

    }

  }

}

鸿蒙开发岗位需要掌握那些核心要领?

目前还有很多小伙伴不知道要学习哪些鸿蒙技术?不知道重点掌握哪些?为了避免学习时频繁踩坑,最终浪费大量时间的。

自己学习时必须要有一份实用的鸿蒙(Harmony NEXT)资料非常有必要。 这里我推荐,根据鸿蒙开发官网梳理与华为内部人员的分享总结出的开发文档。内容包含了:【ArkTS、ArkUI、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、Harmony南向开发、鸿蒙项目实战】等技术知识点。

废话就不多说了,接下来好好看下这份资料。

如果你是一名Android、Java、前端等等开发人员,想要转入鸿蒙方向发展。可以直接领取这份资料辅助你的学习。鸿蒙OpenHarmony知识←前往。下面是鸿蒙开发的学习路线图。

针对鸿蒙成长路线打造的鸿蒙学习文档。鸿蒙(OpenHarmony )学习手册(共计1236页)与鸿蒙(OpenHarmony )开发入门教学视频,帮助大家在技术的道路上更进一步。

其中内容包含:

《鸿蒙开发基础》鸿蒙OpenHarmony知识←前往

  1. ArkTS语言
  2. 安装DevEco Studio
  3. 运用你的第一个ArkTS应用
  4. ArkUI声明式UI开发
  5. .……

《鸿蒙开发进阶》鸿蒙OpenHarmony知识←前往

  1. Stage模型入门
  2. 网络管理
  3. 数据管理
  4. 电话服务
  5. 分布式应用开发
  6. 通知与窗口管理
  7. 多媒体技术
  8. 安全技能
  9. 任务管理
  10. WebGL
  11. 国际化开发
  12. 应用测试
  13. DFX面向未来设计
  14. 鸿蒙系统移植和裁剪定制
  15. ……

《鸿蒙开发实战》鸿蒙OpenHarmony知识←前往

  1. ArkTS实践
  2. UIAbility应用
  3. 网络案例
  4. ……

最后

鸿蒙是完全具备无与伦比的机遇和潜力的;预计到年底将有 5,000 款的应用完成原生鸿蒙开发,这么多的应用需要开发,也就意味着需要有更多的鸿蒙人才。鸿蒙开发工程师也将会迎来爆发式的增长,学习鸿蒙势在必行!

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

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

相关文章

解决前后端通信跨域问题

因为浏览器具有同源策略的效应。 同源策略是一个重要的网络安全机制&#xff0c;用于Web浏览器中&#xff0c;以防止一个网页文档或脚本来自一个源&#xff08;域、协议和端口&#xff09;&#xff0c;获取另一个源的数据。同源策略的目的是保护用户的隐私和安全&#xff0c;防…

PostgreSQL到Doris的迁移技巧:实时数据同步新选择!

PostgreSQL可以说是目前比较抢手的关系型数据库了&#xff0c;除了兼具多样功能和强大性能之外&#xff0c;还具备非常优秀的可扩展性&#xff0c;更重要的是它还开源&#xff0c;能火不是没有理由的。 虽然PostgreSQL很强大&#xff0c;但是它也有短板&#xff0c;相对于专业…

【Java数据结构】关于栈的操作出栈,压栈,中缀表达式,后缀表达式,逆波兰表达式详解

&#x1f525;个人主页&#xff1a;努力学编程’ &#x1f525;内容管理&#xff1a;java数据结构 上一篇文章我们讲过了java数据结构的链表&#xff0c;对于链表我们使用了它的一些基本操作&#xff0c;完成了扑克牌小游戏的操作&#xff0c;如果你感兴趣的话&#xff0c;点…

数组类模板(进阶版)

目录 介绍&#xff1a; 分析&#xff1a; 实现&#xff1a; .hpp框架创建 .hpp函数内容 有参构造 拷贝构造&#xff1a; 重载 插入数据 删除数据 通过下标访问 获取数组大小 获取数组容量 析构函数 .cpp框架 int类型数据测试 char类型测试 总代码 .hpp代码 …

是德科技keysight N9000B 信号分析仪

181/2461/8938产品概述&#xff1a; 工程的内涵就是将各种创意有机地联系起来&#xff0c;并解决遇到的问题。 CXA 信号分析仪具有出色的实际性能&#xff0c;它是一款出类拔萃、经济高效的基本信号表征工具。 它的功能十分强大&#xff0c;为一般用途和教育行业的用户执行测试…

wireshark 使用

wireshark介绍 wireshak可以抓取经过主机网卡的所有数据包&#xff08;包括虚拟机使用的虚拟网卡的数据包&#xff09;。 环境安装 安装wireshark: https://blog.csdn.net/Eoning/article/details/132141665 安装网络助手工具&#xff1a;https://soft.3dmgame.com/down/213…

【LIMS】CMA与CNAS:中国认证体系中的两大支柱

目录 一、CMA&#xff1a;[中国计量认证](http://cma-cma.org.cn/)什么是CMA&#xff1f;CMA的作用 二、CNAS&#xff1a;[中国合格评定国家认可委员会](https://www.cnas.org.cn/)什么是CNAS&#xff1f;CNAS的作用 三、CMA与CNAS的关系相互促进共同目标 结语系列文章版本记录…

TCP网络协议栈和Posix网络部分API总结

文章目录 Posix网络部分API综述TCP协议栈通信过程TCP三次握手和四次挥手&#xff08;看下图&#xff09;三次握手常见问题&#xff1f;为什么是三次握手而不是两次&#xff1f;三次握手和哪些函数有关&#xff1f;TCP的生命周期是从什么时候开始的&#xff1f; 四次挥手通信状态…

git基本操作二(小白快速上手)

1、前言 接上篇我们接着来继续讲 2、.gitignore忽略文件 创建一个.gitignore文件&#xff0c;并将其置于项目的根目录下&#xff0c;Git将自动识别并根据该规则忽略相应的文件和目录。 # 忽略所有的 .log 文件 *.log# 但跟踪所有的 build.log 文件 !build.log# 忽略所有的 /lo…

lookup函数

lookup函数 单条件查询 示例 扩展多条件 扩展

文件的顺序读写——顺序读写函数——fgets、fgetc、fputs、 fputc

✨✨ 欢迎大家来到莉莉的博文✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 目录 一、fgetc和fputc函数 1.1 fputc 1.2 fgetc 二、fputs和fgets函数 2.1 fputs函数 2.2 fgets函数 一、fgetc和fputc函数 1.1 fputc 返回类…

结构体类型,结构体变量的创建和初始化 以及结构中存在的内存对齐

一般结构体类型的声明 struct 结构体类型名 { member-list; //成员表列 }variable-list; //变量表列 例如描述⼀个学⽣&#xff1a; struct Stu { char name[20]; //名字 int age; //年龄 char sex[5]; //性别 }&#xff1b; //结构体变量的初始化 int main() { S…

鸿蒙OS开发实例:【工具类封装-页面路由】

import common from ohos.app.ability.common; import router from ohos.router 封装app内的页面之间跳转、app与app之间的跳转工具类 【使用要求】 DevEco Studio 3.1.1 Release api 9 【使用示例】 import MyRouterUtil from ../common/utils/MyRouterUtil MyRouterUtil…

基于重写ribbon负载实现灰度发布

项目结构如下 代码如下&#xff1a; pom&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocat…

使用第三方远程连接工具ssh连接vagrant创建的虚拟机

vagrant默认密码都是vagrant 密码认证默认是关闭的&#xff0c;进入虚拟机&#xff0c;打开密码认证 1、使用命令vi /etc/ssh/sshd_config进入配置&#xff0c;注意要切换到root用户&#xff0c;这个配置root有权限 2、找到PasswordAuthentication默认为no,改为yes 3、重启虚…

ETL中RESTful API 组件的用法

一、ETL是什么 ETL&#xff0c;全称为Extract-Transform-Load&#xff0c;即数据提取&#xff08;Extract&#xff09;、数据转换&#xff08;Transform&#xff09;和数据加载&#xff08;Load&#xff09;。这是数据仓库中数据处理的重要过程。ETL过程中&#xff0c;数据从源…

小小狠招:巧妙使用HANA数据库的jdbc driver

SAP旗下的HANA数据库&#xff0c;实际上是分为两个系列进行发布&#xff0c;一种是基于本地部署的称之为HANA Platform。另一种是面向Cloud平台的&#xff0c;称之为HANA Cloud。 在实际使用当用&#xff0c;因为两者基本上共用同一代码库&#xff0c;除个别地方略有差异以外&…

【更清晰】照片分享,欢迎家庭新成员HPE ProLiant DL580 Gen9

正文共&#xff1a;1234 字 29 图&#xff0c;预估阅读时间&#xff1a;1 分钟 距离上一台服务器HPE ProLiant DL360 Gen9开箱已经过去4年了&#xff0c;回忆满满&#xff08;风雨同舟&#xff0c;感谢HP Proliant DL360 Gen9陪我走过的四年&#xff09;&#xff1b;就在上周&a…

相册清理大师-手机重复照片整理、垃圾清理软件

相册清理大师是一款超级简单实用的照片视频整理工具。通过便捷的操作手势&#xff0c;帮助你极速整理相册中的照片和视频、释放手机存储空间。 【功能简介】 向上滑动&#xff1a;删除不要的照片 向左滑动&#xff1a;切换下一张照片 向右滑动&#xff1a;返回上一张照片 整理分…

拌合楼管理软件开发(十三) 对接耀华XK3190-A9地磅(实战篇)

前言: 实战开整 目前而言对于整个拌合楼管理软件开发,因为公司对这个项目还处于讨论中,包括个人对其中的商业逻辑也存在一些质疑,都是在做一些技术上的储备.很早就写好了串口与地磅对接获取代码,也大概知道真个逻辑,这次刚好跟库区沟通,远程连接到磅房电脑,开始实操一下. 一、地…