HarmonyOS开发实战( Beta5.0)画笔调色板案例实践

news2024/11/18 3:45:54

鸿蒙HarmonyOS开发往期必看:

HarmonyOS NEXT应用开发性能实践总结

最新版!“非常详细的” 鸿蒙HarmonyOS Next应用开发学习路线!(从零基础入门到精通)


介绍

本示例实现了一个网格渐变的画笔调色板,能够根据给定的 HSL 类型颜色和色阶数,按亮度生成渐变色,用户可以通过调色板选择颜色并在画布上绘制路径。

效果图预览

使用说明

  1. 页面底部展示当前画笔颜色和预设的常用颜色,点击预设的常用颜色可以修改画笔颜色。
  2. 点击画笔颜色,显示网格渐变的调色板,选择调色板上的颜色可以修改画笔颜色。
  3. 在图片上触摸并拖动手指,可以绘制路径,路径颜色为当前选中的画笔颜色。

实现思路

  1. 调色板(HslPalette)渐变方案和布局。

    • 根据给定的hslHues(HSL颜色列表)和levels(色阶数量)生成按亮度渐变的颜色,并根据给定的的渐变亮度起止点(gradientStartPointgradientEndPoint),使不同颜色的同一色阶亮度相同。源码参考PaletteMainPage.ets
      private hslHues: HslType[] = []; // HSL 类型颜色的源数组
      private levels: number = 0; // 渐变色阶数
      private gradientStartPoint: number = 0; // 渐变开始点的亮度值
      private gradientEndPoint: number = 0; // 渐变结束点的亮度值
    
      // TODO:知识点:根据 HSL 色相数组和色阶数生成按亮度渐变的 HEX 格式颜色
      computeHSLGradient(hues: HslType[], levels: number): string[] {
        if (levels <= 0) {
          return [];
        }
        const colors: string[] = [];
        for (let i = 0; i < levels; i++) {
          hues.forEach(hsl => {
            // 根据给定的渐变亮度起止点和所处色阶计算渐变亮度
            const fadedL =
              this.gradientStartPoint + Math.round(i * (this.gradientEndPoint - this.gradientStartPoint) / levels); // 逐渐变淡
            // 将 HSL 转换为 HEX 格式
            const hex = hslToHex(hsl.hue, hsl.saturation, fadedL);
            // 添加到颜色数组
            colors.push(hex);
          });
        }
        return colors;
      }
    
    • 由于ArkUI组件不能直接使用HSL类型的颜色,所以获取到渐变亮度后需要通过 hslToHex 函数将 HSL 颜色转换为 HEX 颜色再存储在 colors 数组中。源码参考ColorTypeConverter.ets
    /**
     * 将 HSL 颜色模型转换为 HEX 颜色模型
     *
     * @param {number} hue - 色相 (Hue),范围为 0 到 360
     * @param {number} saturation - 饱和度 (Saturation),范围为 0 到 100
     * @param {number} lightness - 亮度 (Lightness),范围为 0 到 100
     * @returns {string} - 返回 HEX 颜色值,格式为 '#RRGGBB'
     */
    export function hslToHex(hue: number, saturation: number, lightness: number): string {
      // 将 HSL 转换为 RGB
      const rgb: RgbType = hslToRgb(hue, saturation, lightness);
      // 返回 HEX 颜色值
      return rgbToHex(rgb.red, rgb.green, rgb.blue);
    }
    
    /**
     * 将 HSL 颜色值转换为 RGB 颜色格式。
     *
     * @param {number} hue - 色相,范围为 0-360。
     * @param {number} saturation - 饱和度,范围为 0-100,表示颜色的强度。
     * @param {number} lightness - 亮度,范围为 0-100,表示颜色的明暗程度。
     * @returns {rgbType} - 返回一个包含 RGB 值的对象,格式为 { red, green, blue },每个值的范围为 0-255。
     */
    function hslToRgb(hue: number, saturation: number, lightness: number): RgbType {
      let red: number, green: number, blue: number;
    
      // 将饱和度和亮度从百分比转换为小数
      saturation /= 100;
      lightness /= 100;
    
      if (saturation === 0) {
        // 无饱和度,返回灰色
        red = Math.round(lightness * 255); // 灰色的 Red 值
        green = Math.round(lightness * 255); // 灰色的 Green 值
        blue = Math.round(lightness * 255); // 灰色的 Blue 值
      } else {
        // 辅助函数:根据 HSL 值计算 RGB 值,处理不同的色相区间
        const convertHueToRgb = (baseValue: number, brightnessMultiplier: number, hueFraction: number): number => {
          // 确保 hueFraction 在 0 到 1 之间
          if (hueFraction < 0) {
            hueFraction += 1;
          }
          if (hueFraction > 1) {
            hueFraction -= 1;
          }
          // 第一个区间
          if (hueFraction < 1 / 6) {
            return baseValue + (brightnessMultiplier - baseValue) * 6 * hueFraction;
          }
          // 第二个区间
          if (hueFraction < 1 / 2) {
            return brightnessMultiplier;
          }
          // 第三个区间
          if (hueFraction < 2 / 3) {
            return baseValue + (brightnessMultiplier - baseValue) * (2 / 3 - hueFraction) * 6;
          }
          // 第四个区间
          return baseValue;
        };
    
        // 根据亮度计算中间值 brightnessMultiplier 和 baseValue
        const brightnessMultiplier = lightness < 0.5 ? lightness * (1 + saturation) : lightness + saturation - lightness * saturation;
        const baseValue = 2 * lightness - brightnessMultiplier;
    
        // 计算 RGB 值
        red = Math.round(convertHueToRgb(baseValue, brightnessMultiplier, hue / 360 + 1 / 3) * 255);
        green = Math.round(convertHueToRgb(baseValue, brightnessMultiplier, hue / 360) * 255);
        blue = Math.round(convertHueToRgb(baseValue, brightnessMultiplier, hue / 360 - 1 / 3) * 255);
      }
      return {
        red: red,
        green: green,
        blue: blue
      }
    }
    
    /**
     * 将 RGB 颜色值转换为十六进制格式。
     *
     * @param {number} red - 红色分量,范围为 0-255。
     * @param {number} green - 绿色分量,范围为 0-255。
     * @param {number} blue - 蓝色分量,范围为 0-255。
     * @returns {string} - 返回表示 RGB 颜色的十六进制字符串,格式为 "#RRGGBB"。
     */
    function rgbToHex(red: number, green: number, blue: number): string {
      return '#' + ((1 << 24) + (red << 16) + (green << 8) + blue).toString(16).slice(1);
    };
    
    • 使用Gird组件遍历colors颜色数组生成网格型的渐变色块,可以通过点击色块修改 @Link 类型的状态变量 selectedColor 与父组件同步选中颜色。Grid的行、列模板根据给定的hslHues(HSL颜色列表)和levels(色阶数量)计算生成。源码参考PaletteMainPage.ets
      @State columnsTemplate: string = ''; // Gird 组件的columnsTemplate
      @State rowsTemplate: string = ''; // Gird 组件的rowsTemplate
      @State colors: string[] = []; // 栅格布局使用的 HEX 颜色数组
      @Link selectedColor: string; // 当前选中的颜色
    
      // 根据色相数和色阶数初始化Gird的columnsTemplate和rowsTemplate
      initGridTemplate() {
        if (this.hslHues.length === 0) {
          this.columnsTemplate = '';
          this.rowsTemplate = '';
          return;
        }
        let rowsTemplate = '';
        // 初始化列模板
        this.columnsTemplate = this.hslHues.map(hsl => Constants.GRID_TEMPLATE_UINT).join(' ');
        // 初始化行模板
        for (let i = 0; i < this.levels; i++) {
          if (i === 0) {
            rowsTemplate = Constants.GRID_TEMPLATE_UINT;
          } else {
            rowsTemplate = `${rowsTemplate} ${Constants.GRID_TEMPLATE_UINT}`;
          }
        }
        this.rowsTemplate = rowsTemplate;
      }
      
      build() {
        Grid() {
          /**
           * TODO: 性能知识点:此处列表项确定且数量较少,使用了ForEach,在列表项多的情况下,推荐使用LazyForeEach
           * 文档参考链接:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-rendering-control-lazyforeach-0000001820879609
           */
          ForEach(this.colors, (color: string) => {
            GridItem() {
            }
            .border({
              width: this.selectedColor === color ? $r('app.integer.palette_color_block_border_width_selected') :
              $r('app.integer.palette_color_block_border_width'),
              color: $r('app.color.ohos_id_color_background')
            })
            .backgroundColor(color)
            .width($r('app.string.palette_full_size'))
            .height($r('app.string.palette_full_size'))
            .onClick(() => {
              // 点击切换选中颜色
              this.selectedColor = color;
            })
          }, (color: string) => color)
        }
        .columnsTemplate(this.columnsTemplate)
        .rowsTemplate(this.rowsTemplate)
        .width($r('app.string.palette_full_size'))
        .height($r('app.string.palette_full_size'))
      }
    
  2. 根据HEX类型的颜色数组hexHues生成常用颜色网格,点击色块切换选中颜色。源码参考PaletteMainPage.ets

  // 预设的常用颜色
  Grid() {
    /**
     * TODO: 性能知识点:此处列表项确定且数量较少,使用了ForEach,在列表项多的情况下,推荐使用LazyForeEach
     * 文档参考链接:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-rendering-control-lazyforeach-0000001820879609
     */
    ForEach(this.hexHues, (color: string) => {
      GridItem() {
      }
      .border({
        width: this.selectedColor === color ? $r('app.integer.palette_color_block_border_width_selected') :
        $r('app.integer.palette_color_block_border_width'),
        color: $r('app.color.ohos_id_color_background'),
        radius: $r('app.integer.palette_common_color_block_border_radius')
      })
      .backgroundColor(color)
      .width($r('app.string.palette_full_size'))
      .height($r('app.string.palette_full_size'))
      .onClick(() => {
        // 点击切换选中颜色
        this.selectedColor = color;
      })
    }, (color: string) => color)
  }
  .columnsTemplate('1fr 1fr 1fr 1fr 1fr 1fr') // 6列
  .rowsTemplate('1fr 1fr') // 2行
  .columnsGap($r('app.integer.palette_common_color_block_gird_gap'))
  .rowsGap($r('app.integer.palette_common_color_block_gird_gap'))
  .padding({
    left: $r('app.string.ohos_id_card_padding_start'),
    right: $r('app.string.ohos_id_card_padding_start')
  })
  .height($r('app.string.palette_full_size'))
  .width($r('app.integer.palette_common_color_block_gird_width'))
  1. 父组件中定义状态变量selectedColor保存当前选中的画笔颜色,并通过Row组件的背景色进行展示,点击该组件可以切换调色板组件 HslPalette 的显隐。源码参考PaletteMainPage.ets
  @State selectedColor: string = ''; // 当前选中的画笔颜色
  // 展示当前选中的画笔颜色
  Row()
    .backgroundColor(this.selectedColor)
    .width($r('app.integer.palette_pen_color_circle_size'))
    .height($r('app.integer.palette_pen_color_circle_size'))
    .borderRadius($r('app.integer.palette_pen_color_circle_border_radius'))
    .border({
      width: $r('app.integer.palette_pen_color_circle_border_width'),
      color: $r('app.color.palette_pen_color_circle_border_color')
    })
    .margin({ right: $r('app.integer.palette_pen_color_circle_margin_right') })
    .onClick(() => {
      // 点击切换调色板的显隐
      this.isShowPalette = !this.isShowPalette;
    })
  // 调色板区域,使用 isShowPalette 控制显隐
  if (this.isShowPalette) {
    Row() {
      HslPalette({
        hslHues: this.hslHues,
        levels: this.levels,
        gradientStartPoint: Constants.GRADIENT_START_POINT,
        gradientEndPoint: Constants.GRADIENT_END_POINT,
        selectedColor: this.selectedColor
      })
    }
    // ...
  }
  1. 使用自绘制渲染节点MyRenderNode设置画笔颜色,初始化画笔和画布,并将手指移动的path路径绘制到画布上,通过MyNodeController将节点挂载到自定义节点容器组件NodeContainer上进行展示。同时,在NodeContaineronTouch回调函数中,处理手指按下和移动事件,以动态更新绘制的轨迹。

    • 定义RenderNode的子类MyRenderNode,实例初始化时设置画笔颜色penColor,并通过path路径对象存储手指移动轨迹。MyRenderNode实例在进行绘制时会调用draw方法,初始化画笔pen并将保存的path路径绘制到canvas画布上。源码参考RenderNodeModel.ets
    /**
     * MyRenderNode类,初始化画笔和绘制路径
     */
    export class MyRenderNode extends RenderNode {
      path: drawing.Path = new drawing.Path(); // 新建路径对象,用于绘制手指移动轨迹
      penColor: common2D.Color = {
        alpha: 0xFF,
        red: 0x00,
        green: 0x00,
        blue: 0x00
      }; // 画笔颜色,默认为黑色
    
      // 创建节点时设置画笔颜色
      constructor(penColor: common2D.Color) {
        super();
        this.penColor = penColor;
      }
    
      // RenderNode进行绘制时会调用draw方法
      draw(context: DrawContext): void {
        const canvas = context.canvas;
        // 创建一个画笔Pen对象,Pen对象用于形状的边框线绘制
        const pen = new drawing.Pen();
        // 设置画笔开启反走样,可以使得图形的边缘在显示时更平滑
        pen.setAntiAlias(true);
        pen.setColor(this.penColor);
        // 开启画笔的抖动绘制效果。抖动绘制可以使得绘制出的颜色更加真实。
        pen.setDither(true);
        // 设置画笔的线宽为10px
        pen.setStrokeWidth(Constants.PEN_STROKE_WIDTH);
        // 将Pen画笔设置到canvas中
        canvas.attachPen(pen);
        // 绘制path
        canvas.drawPath(this.path);
      }
    }
    
    • 定义NodeController的子类MyNodeController,实例化后可以通过将自绘制渲染节点MyRenderNode挂载到对应节点容器NodeContainer上实现自定义绘制。源码参考RenderNodeModel.ets
    /**
     * NodeController的子类MyNodeController
     */
    export class MyNodeController extends NodeController {
      private rootNode: FrameNode | null = null; // 根节点
      rootRenderNode: RenderNode | null = null; // 从NodeController根节点获取的RenderNode,用于添加和删除新创建的MyRenderNode实例
    
      // MyNodeController实例绑定的NodeContainer创建时触发,创建根节点rootNode并将其挂载至NodeContainer
      makeNode(uiContext: UIContext): FrameNode {
        this.rootNode = new FrameNode(uiContext);
        if (this.rootNode !== null) {
          this.rootRenderNode = this.rootNode.getRenderNode();
        }
        return this.rootNode;
      }
    
      // 绑定的NodeContainer布局时触发,获取NodeContainer的宽高
      aboutToResize(size: Size): void {
        if (this.rootRenderNode !== null) {
          // NodeContainer布局完成后设置rootRenderNode的背景透明
          this.rootRenderNode.backgroundColor = 0X00000000;
          // rootRenderNode的位置从组件NodeContainer的左上角(0,0)坐标开始,大小为NodeContainer的宽高
          this.rootRenderNode.frame = {
            x: 0,
            y: 0,
            width: size.width,
            height: size.height
          };
        }
      }
    
      // 添加节点
      addNode(node: RenderNode): void {
        if (this.rootNode === null) {
          return;
        }
        if (this.rootRenderNode !== null) {
          this.rootRenderNode.appendChild(node);
        }
      }
    
      // 清空节点
      clearNodes(): void {
        if (this.rootNode === null) {
          return;
        }
        if (this.rootRenderNode !== null) {
          this.rootRenderNode.clearChildren();
        }
      }
    }
    
    • 创建自定义节点容器组件NodeContainer,接收MyNodeController的实例,组件的宽高为图片加载完成后实际内容区域的宽高,并通过相对容器布局的alignRules使NodeContainer与图片内容区域重叠,控制绘制区域。源码参考PaletteMainPage.ets
      @Builder
      drawingArea() {
        Image($r('app.media.palette_picture'))
          .width($r('app.string.palette_full_size'))
          .objectFit(ImageFit.Contain)
          .alignRules({
            top: { anchor: Constants.TOP_BUTTON_LINE_ID, align: VerticalAlign.Bottom },
            middle: { anchor: Constants.CONTAINER_ID, align: HorizontalAlign.Center },
            bottom: { anchor: Constants.BOTTOM_PEN_SHAPE_ID, align: VerticalAlign.Top }
          })
          .onComplete((event) => {
            if (event !== undefined) {
              // NodeContainer的宽高设置为图片成功加载后实际绘制的尺寸
              this.nodeContainerWidth = px2vp(event.contentWidth);
              this.nodeContainerHeight = px2vp(event.contentHeight);
            }
          })
        NodeContainer(this.myNodeController)
          .width(this.nodeContainerWidth)
          .height(this.nodeContainerHeight)
          .alignRules({
            top: { anchor: Constants.TOP_BUTTON_LINE_ID, align: VerticalAlign.Bottom },
            middle: { anchor: Constants.CONTAINER_ID, align: HorizontalAlign.Center },
            bottom: { anchor: Constants.BOTTOM_PEN_SHAPE_ID, align: VerticalAlign.Top }
          })
          .onTouch((event: TouchEvent) => {
            this.onTouchEvent(event);
          })
      }
    
    • NodeContainer组件的onTouch回调函数中,手指按下时基于当前选中颜色selectedColor创建新的MyRenderNode节点,并挂载到rootRenderNode,手指移动更新节点中的path对象,并将节点重新渲染,绘制对应颜色的移动轨迹。源码参考PaletteMainPage.ets
      /**
       * touch事件触发后绘制手指移动轨迹
       */
      onTouchEvent(event: TouchEvent): void {
        // 获取手指触摸位置的坐标点
        const positionX: number = vp2px(event.touches[0].x);
        const positionY: number = vp2px(event.touches[0].y);
        switch (event.type) {
          case TouchType.Down: {
            this.isShowPalette = false; // 隐藏调色板
            // TODO:知识点:使用hexToRgb转换函数将当前选中的HEX类型颜色转为RGB格式,创建penColor对象,通过new MyRenderNode(penColor)修改节点中的画笔颜色
            const rgb: RgbType | null = hexToRgb(this.selectedColor);
            if (rgb === null) {
              return;
            }
            const penColor: common2D.Color = {
              alpha: 0xFF,
              red: rgb.red,
              green: rgb.green,
              blue: rgb.blue
            };
            // 每次手指按下,创建一个 MyRenderNode 对象,用于记录和绘制手指移动的轨迹,传入penColor设置画笔颜色
            const newNode = new MyRenderNode(penColor);
            // 定义newNode的大小和位置,位置从组件NodeContainer的左上角(0,0)坐标开始,大小为NodeContainer的宽高
            newNode.frame = {
              x: 0,
              y: 0,
              width: this.nodeContainerWidth,
              height: this.nodeContainerHeight
            };
            this.currentNode = newNode;
            // 移动新节点中的路径path到手指按下的坐标点
            this.currentNode.path.moveTo(positionX, positionY);
            if (this.myNodeController.rootRenderNode !== null) {
              // appendChild在renderNode最后一个子节点后添加新的子节点
              this.myNodeController.addNode(this.currentNode);
            }
            break;
          }
          case TouchType.Move: {
            if (this.currentNode !== null) {
              // 手指移动,绘制移动轨迹
              this.currentNode.path.lineTo(positionX, positionY);
              // 节点的path更新后需要调用invalidate()方法触发重新渲染
              this.currentNode.invalidate();
            }
            break;
          }
          case TouchType.Up: {
            // 手指抬起,释放this.currentNode
            this.currentNode = null;
          }
          default: {
            break;
          }
        }
      }
    

高性能知识点

  1. onTouch是系统高频回调函数,避免在函数中进行冗余或耗时操作,例如应该减少或避免在函数打印日志,会有较大的性能损耗。

工程结构&模块类型

palette                                       // har类型
|---/src/main/ets/model                        
|   |---ColorModel.ets                        // 数据模型层-HSL和RGB对象数据模型 
|   |---RenderNodeModel.ets                   // 数据模型层-节点数据模型
|---/src/main/ets/pages                        
|   |---PaletteMainPage.ets                   // 视图层-主页面
|---/src/main/ets/data                        
|   |---ColorsData.ets                        // 预设的常用颜色和用于生成渐变的HSL类型颜色数据
|---/src/main/ets/constants                        
|   |---Constants.ets                         // 常量数据
|---/src/main/ets/utils                        
|   |---ColorTypeConverter.ets                // HSL、RGB和HEX颜色类型转换工具函数

模块依赖

  1. 本实例依赖common模块中的资源文件。
  2. 本示例依赖动态路由模块来实现页面的动态加载。

最后

小编在之前的鸿蒙系统扫盲中,有很多朋友给我留言,有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)路线图、学习视频、文档用来跟着学习是非常有必要的。 

如果你是一名有经验的资深Android移动开发、Java开发、前端开发、对鸿蒙感兴趣以及转行人员

鸿蒙 NEXT 全栈开发学习笔记  希望这一份鸿蒙学习文档能够给大家带来帮助~


鸿蒙(HarmonyOS NEXT)最新学习路线

该路线图包含基础技能、就业必备技能、多媒体技术、六大电商APP、进阶高级技能、实战就业级设备开发,不仅补充了华为官网未涉及的解决方案

路线图适合人群:

IT开发人员:想要拓展职业边界
零基础小白:鸿蒙爱好者,希望从0到1学习,增加一项技能。
技术提升/进阶跳槽:发展瓶颈期,提升职场竞争力,快速掌握鸿蒙技术

2.视频学习教程+学习PDF文档

HarmonyOS Next 最新全套视频教程

  纯血版鸿蒙全套学习文档(面试、文档、全套视频等)       

​​

总结

参与鸿蒙开发,你要先认清适合你的方向,如果是想从事鸿蒙应用开发方向的话,可以参考本文的学习路径,简单来说就是:为了确保高效学习,建议规划清晰的学习路线

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

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

相关文章

Vector - VT System - 板卡_VT板卡使用介绍_01

总体介绍 在常规的车载网络测试中&#xff0c;除了我们常用的使用VN系列设备进行总线协议测试&#xff0c;大多数公司都会将协议强相关的功能测试放在了功能侧&#xff0c;但是实际上这块对于车载网络测试工程师来说也是需要去了解的&#xff0c;毕竟只有懂协议的人才能更好的测…

Python with 关键字语法糖

参考文章: Python with 关键字 | 菜鸟教程 (runoob.com)https://www.runoob.com/python3/python-with.html Python 中的 with 语句用于异常处理&#xff0c;封装了 try…except…finally 编码范式&#xff0c;提高了易用性。 with 语句使代码更清晰、更具可读性&#xff0c; 它…

Fake Location模拟定位,刷跑 “运动世界校园”

前言:"科技改变生活&#xff0c;如果本文章对你有帮助&#xff0c;别忘记留下你的点赞&#xff0c;以下我对环境特变刁钻的运动世界校园为实例&#xff0c;也是成功安全正常上传数据&#xff0c;如果遇到问题&#xff0c;请留言评论区&#xff0c;所有链接我会放在文章头部…

157-安全开发-Python 自动化挖掘项目SRC 目标FOFA 资产Web 爬虫解析库

案例一&#xff1a;Python-WEB 爬虫库&数据解析库 这里开发的内容不做过多描述&#xff0c;贴上自己写的代码 爬取数据 要爬取p标签&#xff0c;利用Beautyfulsoup模块 import requests,time from bs4 import BeautifulSoup#url"https://src.sjtu.edu.cn/rank/firm…

99AutoML 自动化机器学习实践--NNI 自动化机器学习工具包

NNI 自动化机器学习工具包 NNI 是 Neural Network Intelligence 的缩写&#xff0c;可以译作&#xff1a;智能神经网络。名字听起来陌生&#xff0c;但 NNI 实际上就是一个自动化机器学习工具包。它通过多种调优的算法来搜索最好的神经网络结构和超参数&#xff0c;并支持单机、…

【Fastapi】使用Pandas作为大数据分析处理工具

【Fastapi】使用Pandas作为大数据分析处理工具 gitee https://gitee.com/zz1521145346/fastapi_frame.git github https://github.com/zz001357/fastapi_frame.git 准备工作 能联接的sql软件&#xff08;如&#xff0c;mysql&#xff09; 安装pandas &#xff08;pip in…

vue3 使用swiper制作带缩略图的轮播图

效果图 实现代码 <template><div class"wrap"><!-- 主轮播图 --><swiper :style"{--swiper-navigation-color: #fff,--swiper-pagination-color: #fff,}" :modules"modules" :navigation"true" :thumbs"{ …

深圳建站公司-如何做网站

深圳建站公司&#xff1a;如何制作一个成功的网站 在信息化快速发展的今天&#xff0c;企业和个人越来越重视网络形象&#xff0c;网站成为了展示品牌、推广产品和服务的重要平台。深圳作为科技创新和经济发展的前沿城市&#xff0c;涌现出许多专业的建站公司&#xff0c;能够为…

食品分类2检测系统源码分享

食品分类2检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vi…

【Leetcode:257. 二叉树的所有路径 + 二叉树 + 递归 】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

多语言文本检测系统源码分享

多语言文本检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer V…

中国水土保持能力防治数据集(1992-2019)

该数据集包括1992年至2019年中国每年的水土保持能力及其影响因子。这些数据是基于改进的RUSLE模型开发的&#xff0c;其中包含植被覆盖和管理(C)因子和降雨侵蚀率(R)因子作为重要的输入因子&#xff0c;针对不同区域进行了优化。 其中该数据集一共包含了9个数据它们分别是&…

【遍历二叉树】---先,中,后,层序遍历 及 先序建立整树

0.二叉树结点的链式存储结构 #include<stdio.h> #include<stdlib.h>typedef char TElemType;//树中元素基本类型为char类型#define bool int #define true 1 #define false 0//二叉树结点链式存储结构&#xff08;二叉链表&#xff09; typedef struct BiNode {TE…

java项目之基于springboot的贸易行业crm系统(源码+文档)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的基于springboot的贸易行业crm系统。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 基于sp…

GNSS多路径误差提取CMC和MPC

基本概念 伪距和载波相位观测值的多径误差并不相同&#xff0c;多径误差一般1-5米&#xff0c;最高可达10-20米。PPP利用伪距辅助模糊度固定&#xff0c;伪距质量不高多路径误差太大&#xff0c;会导致模糊度固定错。载波相位的多径误差小于四分之一波长。由于载波相位的多径误…

抢占AI营销新红利!枢纽云揭秘企业转型背后的成功路径

搜索作为用户获取信息的关键途径&#xff0c;正在经历一场具有划时代意义的变革&#xff0c;不断影响着用户的搜索行为习惯&#xff0c;还为品牌营销以及企业的数字化转型提供了良好契机。 从传统搜索到内容生态&#xff1a;品牌展现的新舞台 传统搜索引擎曾是互联网世界的绝对…

MQTT 协议概述

目录 一、概述二、协议模型1、组成部分2、客户端3、服务器 三、MATT 通信过程1、连接服务器2、订阅主题3、发布消息4、取消订阅5、断开连接 四、MQTT 数据包结构1、MQTT 固定头2、MQTT 可变头3. Payload消息体 五、示例演示 一、概述 MQTT&#xff08;Message Queuing Telemet…

乔拓云模板助力,微信小程序快速上线无需愁备案

想要快速打造并上线自己的微信小程序吗&#xff1f;乔拓云平台是您的不二之选&#xff01;无需担心复杂的备案流程&#xff0c;乔拓云提供免费服务&#xff0c;远程协助您轻松完成微信小程序的备案工作。 只需简单几步&#xff0c;您的小程序就能闪亮登场&#xff1a;首先&…

常见加密算法——哈希算法(MD)

文章目录 发现宝藏1.加密算法简介1.1 加密算法分类1.2 应用场景1.3 哈希算法的特点 2. 哈希算法的分类2.1 加密哈希算法2.2 非加密哈希算法2.3 其他常见哈希算法 3. MD53.1 MD5 简介3.2 MD5 Java 代码示例&#xff08;未加盐&#xff09;3.2 MD5 Python 代码示例&#xff08;未…

DroidBot: A Lightweight UI-Guided Test InputGenerator for Android论文学习

DroidBot就是之前用过的那个自动截图程序。那我很熟悉了&#xff0c;快速读完这篇论文。 brain默认使用深度优先探索&#xff0c;当然用户也可以使用自己的方法。 这玩意支持各种输入&#xff08;点击&#xff0c;滑动&#xff0c;输入文本&#xff09; 可以看到它会分辨当前页…