<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:atom="http://www.w3.org/2005/Atom"
>
<channel>
<title><![CDATA[凌零博客]]></title> 
<atom:link href="https://llsix.com/rss.php" rel="self" type="application/rss+xml" />
<description><![CDATA[一个分享技术、记录生活的个人技术博客]]></description>
<link>https://llsix.com/</link>
<language>zh-cn</language>

<item>
    <title>鸿蒙开发提效秘籍：这款取色板让多端界面色彩 0 偏差</title>
    <link>https://llsix.com/HarmonyOS/149.html</link>
    <description><![CDATA[<p><span style="font-size: 18px;"><strong>一、概述</strong></span></p>
<p>今天分享一个简单的颜色选择器</p>
<p><span style="font-size: 18px;"><strong>二、颜色转工具函数</strong></span></p>
<p>// color-utils.ets</p>
<pre class="language-javascript"><code>class ColorUtils {
  hexToRgb(hex: string): [number, number, number] {
    const r = parseInt(hex.slice(1, 3), 16);
    const g = parseInt(hex.slice(3, 5), 16);
    const b = parseInt(hex.slice(5, 7), 16);
    return [r, g, b];
  }
  hexToHsv(hex: string): [number, number, number] {
    hex = hex.replace('#', '');
    const r = parseInt(hex.slice(0, 2), 16);
    const g = parseInt(hex.slice(2, 4), 16);
    const b = parseInt(hex.slice(4, 6), 16);
    const max = Math.max(r, g, b);
    const min = Math.min(r, g, b);
    const value = max / 255;
    let saturation = (max !== 0) ? ((max - min) / max) : 0;
    let hue = 0;
    if (saturation === 0) {
      hue = 0;
    } else {
      switch (max) {
        case r:
          hue = ((g - b) / (max - min)) + (g &lt; b ? 6 : 0);
          break;
        case g:
          hue = ((b - r) / (max - min)) + 2;
          break;
        case b:
          hue = ((r - g) / (max - min)) + 4;
          break;
      }
      hue /= 6;
      hue = hue &gt;= 0 ? hue : hue + 1;
    }
    return [hue * 360, saturation, value];
  }
  hsvToHex(h: number, s: number, v: number): string {
    let r: number = 0, g: number = 0, b: number = 0;
    let i = Math.floor(h / 60);
    let f = h / 60 - i;
    let p = v * (1 - s);
    let q = v * (1 - f * s);
    let t = v * (1 - (1 - f) * s);
    switch (i % 6) {
      case 0:
        r = v;
        g = t;
        b = p;
        break;
      case 1:
        r = q;
        g = v;
        b = p;
        break;
      case 2:
        r = p;
        g = v;
        b = t;
        break;
      case 3:
        r = p;
        g = q;
        b = v;
        break;
      case 4:
        r = t;
        g = p;
        b = v;
        break;
      case 5:
        r = v;
        g = p;
        b = q;
        break;
    }
    r = Math.round(r * 255);
    g = Math.round(g * 255);
    b = Math.round(b * 255);
    return `#${this.toHex(r)}${this.toHex(g)}${this.toHex(b)}`;
  }
  toHex(n: number) {
    let hex = n.toString(16);
    return hex.length === 1 ? '0' + hex : hex;
  }
}
export default new ColorUtils()</code></pre>
<p><span style="font-size: 18px;"><strong>三、实现取色面板</strong></span></p>
<p>// ColorPickDialog .ets</p>
<pre class="language-javascript"><code>import { Size } from '@kit.ArkUI'
// 导入使用
import ColorUtils from './color-utils'
class Point {
  x: number = 0
  y: number = 0
  constructor(x: number, y: number)
  constructor();
  constructor(x: number = 0, y: number = 0) {
    this.x = x;
    this.y = y;
  }
}
@CustomDialog
export struct ColorPickDialog {
  controller: CustomDialogController
  private settings: RenderingContextSettings = new RenderingContextSettings(true)
  private hueContext: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)
  private satValContext: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)
  /**
   * hue panel point
   */
  private hueTrackerPointSize: Size = { width: 20, height: 20 }
  @State private hueTrackerPoint: Point =
    new Point(0 - this.hueTrackerPointSize.height * 0.5, 0 - this.hueTrackerPointSize.width * 0.5)
  /**
   * satVal panel point
   */
  private satValTrackerPointSize: Size = { width: 24, height: 24 }
  @State private satValTrackerPoint: Point =
    new Point(0 - this.satValTrackerPointSize.width * 0.5, 0 - this.satValTrackerPointSize.height * 0.5)
  /**
   * 默认颜色
   */
  @Link color: string
  @State private hue: number = 360;
  @State private sat: number = 0;
  @State private val: number = 0;
  aboutToAppear(): void {
    // hex to hsv
    const hsv = ColorUtils.hexToHsv(this.color)
    this.hue = hsv[0]
    this.sat = hsv[1]
    this.val = hsv[2]
  }
  build() {
    Column() {
      this.TitleBar()
      this.SatValPanel()
      Row() {
        this.HuePanel()
      }.margin(15)
      Stack() {
        TextInput({ text: this.color })
          .fontColor('#1d2129')
          .fontSize(16)
          .backgroundColor(Color.Transparent)
          .onEditChange((isEditing) =&gt; {
            if (!isEditing) {
              const hsv = ColorUtils.hexToHsv(this.color)
              this.hue = hsv[0]
              this.sat = hsv[1]
              this.val = hsv[2]
              // invalidate canvas
              this.invalidateHuePanel()
              this.invalidateSatValPanel()
            }
          })
          .onChange((value) =&gt; {
            this.color = value
          })
      }
      .width(120)
      .height(35)
      .backgroundColor('#e5e6eb')
      .margin({ bottom: 10 })
    }
  }
  @Builder
  private TitleBar() {
    RelativeContainer() {
      Row() {
        Text('鸿蒙取色器')
          .fontWeight(FontWeight.Bold)
          .fontSize(16)
          .layoutWeight(1)
          .textAlign(TextAlign.Center)
      }
      .justifyContent(FlexAlign.Center)
      .alignRules( {
        'center': {'anchor': '__container__', 'align': VerticalAlign.Center },
        'left': { 'anchor': '__container__', 'align': HorizontalAlign.Start }
      })
      Row() {
        SymbolGlyph($r('sys.symbol.xmark'))
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
          .fontColor([$r('sys.color.font_primary')])
          .onClick(() =&gt; {
            this.controller.close();
          })
      }
      .justifyContent(FlexAlign.Center)
      .alignRules(
        {
          'right': { 'anchor': '__container__', 'align': HorizontalAlign.End },
          'center': {'anchor': '__container__', 'align': VerticalAlign.Center }
        }
      )
      .margin({right: 10})
    }
    .width('100%')
    .height(40)
  }
  @Builder
  private SatValPanel() {
    Stack() {
      Canvas(this.satValContext)
        .width('90%')
        .height(200)
        .margin({ top: 20 })
        .onReady(() =&gt; {
          this.drawSatValPanel(true)
        })
        .onTouch((event) =&gt; {
          let x = event.touches[0].x
          let y = event.touches[0].y
          if (x &gt;= this.satValContext.width) {
            x = this.satValContext.width
          }
          if (x &lt; 0) {
            x = 0
          }
          if (y &gt;= this.satValContext.height) {
            y = this.satValContext.height
          }
          if (y &lt; 0) {
            y = 0
          }
          this.satValTrackerPoint =
            new Point(x - this.satValTrackerPointSize.width * 0.5, y - this.satValTrackerPointSize.height * 0.5)
          this.color = this.getColor()
          const p = this.pointToSatVal(x, y)
          this.sat = p[0]
          this.val = p[1]
        })
      Shape() {
        Circle()
          .size(this.satValTrackerPointSize)
          .fill(Color.Transparent)
          .borderRadius(this.satValTrackerPointSize.width / 2)
          .border({ color: Color.White, width: 4 })
          .fill(Color.Transparent);
      }
      .margin({ top: 20 })
      .enabled(false)
      .focusOnTouch(false)
      .position({ x: this.satValTrackerPoint.x, y: this.satValTrackerPoint.y })
    }
  }
  private drawSatValPanel(isUpdateTrackerPoint: boolean = false) {
    this.satValContext.clearRect(0, 0, this.satValContext.width, this.satValContext.height)
    this.satValContext.fillStyle = ColorUtils.hsvToHex(this.hue, 1, 1);
    this.satValContext.fillRect(0, 0, this.satValContext.width, this.satValContext.height);
    const whiteGradient = this.satValContext.createLinearGradient(0, 0, this.satValContext.width, 0);
    whiteGradient.addColorStop(0, "#fff");
    whiteGradient.addColorStop(1, "transparent");
    this.satValContext.fillStyle = whiteGradient;
    this.satValContext.fillRect(0, 0, this.satValContext.width, this.satValContext.height);
    const blackGradient = this.satValContext.createLinearGradient(0, 0, 0, this.satValContext.height);
    blackGradient.addColorStop(0, "transparent");
    blackGradient.addColorStop(1, "#000");
    this.satValContext.fillStyle = blackGradient;
    this.satValContext.fillRect(0, 0, this.satValContext.width, this.satValContext.height);
    if (isUpdateTrackerPoint) {
      const p = this.setValToPoint(this.sat, this.val)
      this.satValTrackerPoint =
        new Point(p.x - this.satValTrackerPointSize.width / 2, p.y - this.satValTrackerPointSize.height / 2)
    }
  }
  private invalidateSatValPanel() {
    this.satValContext.clearRect(0, 0, this.satValContext.width, this.satValContext.height)
    this.drawSatValPanel(true)
  }
  private setValToPoint(sat: number, val: number): Point {
    const width = this.satValContext.width
    const height = this.satValContext.height
    const p = new Point()
    p.x = sat * width + 0
    p.y = (1 - val) * height + 0
    return p
  }
  private pointToSatVal(x: number, y: number): [number, number] {
    const width = this.satValContext.width
    const height = this.satValContext.height
    if (x &lt; 0) {
      x = 0
    } else if (x &gt; width) {
      x = width
    } else {
      x = x - 0
    }
    if (y &lt; 0) {
      y = 0
    } else if (y &gt; height) {
      y = height
    } else {
      y = y - 0
    }
    return [1 / width * x, 1 - (1 / height * y)]
  }
  @Builder
  private HuePanel() {
    Stack() {
      Canvas(this.hueContext)
        .width('100%')
        .height(20)
        .onReady(() =&gt; {
          this.drawHuePanel()
          this.drawSatValPanel()
        })
        .onTouch((event) =&gt; {
          let x = event.touches[0].x
          let y = event.touches[0].y
          let xMaxBoundary = this.hueContext.width
          let xMinBoundary = 0
          if (x &gt; xMaxBoundary) {
            x = xMaxBoundary
          }
          if (x &lt; xMinBoundary) {
            x = xMinBoundary
          }
          this.hueTrackerPoint = new Point(x - this.hueTrackerPointSize.width * 0.5, y)
          this.hue = this.pointToHue(x)
          this.invalidateHuePanel()
          this.color = this.getColor()
        })
      Shape() {
        Circle()
          .size(this.hueTrackerPointSize)
          .fill(Color.Transparent)
          .borderRadius(this.hueTrackerPointSize.width / 2)
          .border({ color: Color.White, width: 4 })
          .fill(Color.Transparent);
      }
      .enabled(false)
      .focusOnTouch(false)
      .position({ x: this.hueTrackerPoint.x, y: 0 })
    }
  }
  private drawHuePanel() {
    const grad = this.hueContext.createLinearGradient(0, 0, this.hueContext.width, this.hueContext.height);
    let hue = new Array&lt;string&gt;(361)
    let count = 0
    for (let i = hue.length - 1; i &gt;= 0; i--, count++) {
      hue[count] = ColorUtils.hsvToHex(i, 1, 1)
      grad.addColorStop(1 - i / 360, hue[count])
    }
    this.hueContext.fillStyle = grad
    this.hueContext.fillRect(0, 0, this.hueContext.width, this.hueContext.height)
    const p = this.hueToPoint(this.hue)
    this.hueTrackerPoint = new Point(p.x - this.hueTrackerPointSize.width / 2, 0)
  }
  private invalidateHuePanel() {
    this.hueContext.clearRect(0, 0, this.hueContext.width, this.hueContext.height)
    this.drawHuePanel()
    this.drawSatValPanel()
  }
  /**
   * Get coordinate points based on hue
   * @param hue
   * @returns
   */
  private hueToPoint(hue: number): Point {
    const width = this.hueContext.width
    const p = new Point()
    p.x = (width - (hue * width / 360) + 0)
    p.y = 0
    return p
  }
  /**
   * Calculate hue value based on x coordinate
   * @param x axis
   * @returns
   */
  private pointToHue(x: number) {
    if (x &lt; 0) {
      x = 0
    } else if (x &gt; this.hueContext.width) {
      x = this.hueContext.width
    } else {
      x = x - 0
    }
    let hue = 360 - (x * 360 / this.hueContext.width)
    if (hue &lt; 0) {
      hue = 0
    } else if (hue &gt; 360) {
      hue = 360
    }
    return hue
  }
  private getColor(): string {
    return ColorUtils.hsvToHex(this.hue, this.sat, this.val)
  }
}</code></pre>
<p><span style="font-size: 18px;"><strong>四、在其他页面使用</strong></span></p>
<pre class="language-javascript"><code>// 导入使用
import { ColorPickDialog } from '../components/colorPicker/index';
@Entry
@Component
struct Index {
  @State color: string = '#ff0000';
  colorPickDialogController: CustomDialogController | null = new CustomDialogController({
    builder: ColorPickDialog({color: this.color}),
    alignment: DialogAlignment.Center,
    width: '80%',
    cornerRadius: 15,
    backgroundColor: $r('sys.color.background_primary')
  })
  build() {
    Column({space: 10}) {
      Text(this.color)
      Shape() {
        Rect()
          .width(100)
          .height(100)
          .fill(this.color)
      }
      Button('选择颜色')
        .onClick(() =&gt; {
          this.colorPickDialogController?.open()
        })
    }
    .height('100%')
    .width('100%')
  }
  aboutToDisappear(): void {
    this.colorPickDialogController = null
  }
}</code></pre>]]></description>
    <pubDate>Tue, 11 Mar 2025 11:13:17 +0800</pubDate>
    <dc:creator>凌零の小窝</dc:creator>
    <guid>https://llsix.com/HarmonyOS/149.html</guid>
</item>
<item>
    <title>鸿蒙开发：如何正确上架一个元服务应用</title>
    <link>https://llsix.com/HarmonyOS/148.html</link>
    <description><![CDATA[<p>&nbsp;</p>
<p>&nbsp;</p>
<p>元服务，类似于微信的小程序，无需安装，即开即用，即用即走，它是基于鸿蒙系统，而非某个应用，也就是系统层的支持，可以说，这一点是非常的便捷，除此之外，元服务是支持独立上架、分发和运行的，能够独立实现业务闭环，可大幅度提升信息与服务的获取效率；特别是对于个人开发者，元服务，更加的包容，目前它可以签署免责函，一些简单的工具类应用，可以不备案，不需要软著，就可以提交上架，这对于个人开发者而言，释放了很多枷锁，可以让很多的开发者参与到生态之中。</p>
<p><span style="font-size: 18px;"><strong>一、创建一个元服务</strong></span></p>
<p>首先我们要在<a href="https://developer.huawei.com/consumer/cn/agconnect" target="_blank" rel="noopener">华为AGC平台</a>创建元服务应用，找到<strong>证书、APP ID和Profile</strong>选项，选择APP ID,点击新建即可。</p>
<p><a href="/content/uploadfile/202502/d2b51739939127.png" target="_blank" rel="noopener"><img src="/content/uploadfile/202502/thum-d2b51739939127.png" alt="image.png"></a></p>
<p>然后我们就能看到刚刚创建的元服务应用啦！</p>
<p><a href="/content/uploadfile/202502/d2b51739939257.png" target="_blank" rel="noopener"><img src="/content/uploadfile/202502/thum-d2b51739939257.png" alt="image.png"></a></p>
<p>现在有了应用，就可以根据应用创建一个项目了，准备开发了。<strong>新建项目选择Atomic Service并点击Next</strong></p>
<p><a href="/content/uploadfile/202502/d2b51739939432.png" target="_blank" rel="noopener"><img src="/content/uploadfile/202502/thum-d2b51739939432.png" alt="image.png"></a></p>
<p>按照提示登录我们的华为账号，并选择刚才创建的元服务应用。<strong>点击Next创建项目</strong>即可。</p>
<p><a href="/content/uploadfile/202502/d2b51739939660.png" target="_blank" rel="noopener"><img src="/content/uploadfile/202502/thum-d2b51739939660.png" alt="image.png"></a></p>
<p>接下来就是和普通项目一样，写代码</p>
<p>元服务倾向于轻量级的应用，和微信小程序一样，都有包体积的限制，目前整体包体积不超过10MB，单个包不得超过2MB，除此之外，还有着<a class="RichContent-EntityWord css-b7erz1" href="https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/arkui-arkts-V5?catalogVersion=V5" target="_blank" rel="noopener" data-za-not-track-link="true" data-paste-text="true">API</a>上的限制，在实际的开发中需要查看API是否支持再进行使用。</p>
<p>(元服务是没有图标的，但是可以添加卡片！！！)</p>
<p><span style="font-size: 18px;"><strong>二、配置签名密钥信息</strong></span></p>
<p>目前你进行到这步骤，肯定是已经能开发完这个项目了。接下来要进行的就是出包。</p>
<p>1、生成密钥和scr文件</p>
<p>中文：点击构建=&gt;生成私钥和证书请求文件</p>
<p>英文：点击Build=&gt;Generate Key and CSR</p>
<p>点击New后选择密钥文件存储的位置。并设置密码(我个人建议在项目中创建一个文件夹保存生成的签名文件、密码(简单的)、别名(key))</p>
<p><a href="/content/uploadfile/202502/d2b51739940420.png" target="_blank" rel="noopener"><img src="/content/uploadfile/202502/thum-d2b51739940420.png" alt="image.png"></a></p>
<p><img src="/content/uploadfile/202502/d2b51739940254.png" alt="image.png"></p>
<p>点击Next后会进入到生成SCR文件中，我们同样存放在一个文件夹中，然后我们在文件夹中就会看到这样的几个文件</p>
<p><a href="/content/uploadfile/202502/d2b51739940604.png" target="_blank" rel="noopener"><img src="/content/uploadfile/202502/d2b51739940604.png" alt="image.png"></a></p>
<p><strong>2、新增证书(AGC)</strong></p>
<p>我们现在回到华为AGC的平台，找到<strong>证书、APP ID和Profile，选择证书，点击新增</strong></p>
<p><a href="/content/uploadfile/202502/d2b51739940893.png" target="_blank" rel="noopener"><img src="/content/uploadfile/202502/thum-d2b51739940893.png" alt="image.png"></a></p>
<p>点击提交后，我们把刚才生成的证书下载到刚才的证书文件夹中</p>
<p><a href="/content/uploadfile/202502/d2b51739940968.png" target="_blank" rel="noopener"><img src="/content/uploadfile/202502/d2b51739940968.png" alt="image.png"></a></p>
<p><strong>3、新增Profile</strong></p>
<p>选择刚才证书下面的Profile选项，添加Profile证书，这里用到的证书就是我们上一步骤中生成的证书文件，选择即可</p>
<p><a href="/content/uploadfile/202502/d2b51739941179.png" target="_blank" rel="noopener"><img src="/content/uploadfile/202502/thum-d2b51739941179.png" alt="image.png"></a></p>
<p>添加之后我们同样点击，下载刚才生成的证书文件。保存在证书文件夹中</p>
<p><a href="/content/uploadfile/202502/d2b51739941287.png" target="_blank" rel="noopener"><img src="/content/uploadfile/202502/d2b51739941287.png" alt="image.png"></a></p>
<p><strong>4、在项目中出包</strong></p>
<p>根据图片中的步骤按顺序操作后点击应用保存，并按下一张提示选择release(调试)</p>
<p><a href="/content/uploadfile/202502/d2b51739941574.png" target="_blank" rel="noopener"><img src="/content/uploadfile/202502/thum-d2b51739941574.png" alt="image.png"></a></p>
<p><a href="/content/uploadfile/202502/d2b51739941682.png" target="_blank" rel="noopener"><img src="/content/uploadfile/202502/thum-d2b51739941682.png" alt="image.png"></a></p>
<p>中文：构建=&gt;编译Hap(s)=&gt;编译App(s)</p>
<p>英文：Build=&gt;Build Hap(s)=&gt;BuildApp(s)</p>
<p><a href="/content/uploadfile/202502/d2b51739941798.png" target="_blank" rel="noopener"><img src="/content/uploadfile/202502/d2b51739941798.png" alt="image.png"></a></p>
<p>等待生成成功后，就会在项目的根目录新增一个build文件如图：</p>
<p><a href="/content/uploadfile/202502/d2b51739943192.png" target="_blank" rel="noopener"><img src="/content/uploadfile/202502/d2b51739943192.png" alt="image.png"></a></p>
<p><span style="font-size: 18px;"><strong>三、 填写上架信息</strong></span></p>
<p>然后我们点击APP ID，找到刚才的应用点击发布，就会在我的元服务中看到如图</p>
<p>在应用信息中需要填写图标和这个应用的分类标签</p>
<p><a href="/content/uploadfile/202502/d2b51739943502.png" target="_blank" rel="noopener"><img src="/content/uploadfile/202502/thum-d2b51739943502.png" alt="image.png"></a></p>
<p>图标生成我们点击任意一个文件，右键新建生成即可</p>
<p><a href="/content/uploadfile/202502/d2b51739943598.png" target="_blank" rel="noopener"><img src="/content/uploadfile/202502/thum-d2b51739943598.png" alt="image.png"></a></p>
<p><img src="/content/uploadfile/202502/thum-d2b51739943659.png" alt="image.png"></p>
<p>然后我们在软件包管理中上传刚才生成的包文件.app后缀结尾的，可以往上翻有标注的哈</p>
<p>点击协议服务，并生成用户协议与隐私政策</p>
<p>用户协议这里需要填写一个在线地址，可以使用Gitte中md文件作为在线地址(协议模板可以联系我发你)</p>
<p><a href="/content/uploadfile/202502/d2b51739943959.png" target="_blank" rel="noopener"><img src="/content/uploadfile/202502/thum-d2b51739943959.png" alt="image.png"></a></p>
<p><a href="/content/uploadfile/202502/d2b51739943845.png" target="_blank" rel="noopener"><img src="/content/uploadfile/202502/thum-d2b51739943845.png" alt="image.png"></a></p>
<p>然后其他的就按操作流程来就可以</p>
<p><span style="font-size: 18px;">最后：祝君好运！！！</span></p>
<p>&nbsp;</p>]]></description>
    <pubDate>Wed, 19 Feb 2025 10:07:36 +0800</pubDate>
    <dc:creator>凌零の小窝</dc:creator>
    <guid>https://llsix.com/HarmonyOS/148.html</guid>
</item>
<item>
    <title>鸿蒙中调用相机,相册,抖动</title>
    <link>https://llsix.com/HarmonyOS/45.html</link>
    <description><![CDATA[<p><strong>1. 调用相册</strong></p>
<pre class="language-javascript"><code>import { camera, cameraPicker } from '@kit.CameraKit';
import fs from '@ohos.file.fs';
import { util } from '@kit.ArkTS';

class CameraPlugin {
  async pickerCamera() {
    // 1. 打开相机后置摄像头得到拍照结果集
    const pickerProfile: cameraPicker.PickerProfile = {
      cameraPosition: camera.CameraPosition.CAMERA_POSITION_BACK
    };
    const pickerResult: cameraPicker.PickerResult = await cameraPicker.pick(getContext(),
      [cameraPicker.PickerMediaType.PHOTO], pickerProfile);

    // 2. 根据结果集的URI属性同步打开文件
    const file = fs.openSync(pickerResult.resultUri)
    // 3. 同步读取文件的详情信息
    const stat = fs.statSync(file.fd)
    // 4. 定义缓冲区用于保存读取的文件
    const buffer = new ArrayBuffer(stat.size)
    // 5. 开始同步读取内容到缓冲区
    fs.readSync(file.fd, buffer)
    // 6. 读取完毕后关闭文件流
    fs.closeSync(file)

    // 7. 借助util工具方法把读取的文件流转成base64编码的字符串
    const helper = new util.Base64Helper()
    const str = helper.encodeToStringSync(new Uint8Array(buffer))
    console.log('mk-logger', 'pickerCamera', str)
    return str
  }
}

export const cameraPlugin = new CameraPlugin()</code></pre>
<p><strong>2. 调用相册</strong></p>
<pre class="language-javascript"><code>import { picker } from '@kit.CoreFileKit'
import fs from '@ohos.file.fs';
import { util } from '@kit.ArkTS';

class PhotoPlugin {
  async pickerPhoto(){
     // 1. 打开相册选择图片
     const photoSelectOptions = new picker.PhotoSelectOptions()
     photoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE;
     photoSelectOptions.maxSelectNumber = 1;
     const  photoPicker = new picker.PhotoViewPicker();
     const res = await photoPicker.select(photoSelectOptions)
     console.log('mk-logger', 'photoPlugin', JSON.stringify(res))

    // 2. 文件操作
    // 2.1 获取照片的uri地址
    const uri = res.photoUris[0]
    // 2.2 根据uri同步打开文件
    const file = fs.openSync(uri)
    // 2.3 同步获取文件的详细信息
    const stat = fs.statSync(file.fd)
    // 2.4 创建缓冲区存储读取的文件流
    const buffer = new ArrayBuffer(stat.size)
    // 2.5 开始同步读取文件流到缓冲区
    fs.readSync(file.fd, buffer)
    // 2.6 关闭文件流
    fs.closeSync(file)

    // 3. 转成base64编码的字符串
    const helper = new util.Base64Helper()
    const str = helper.encodeToStringSync(new Uint8Array(buffer))
    console.log('mk-logger', 'photoPlugin-str', str)

    return str
   }
}

export const photoPlugin = new PhotoPlugin()</code></pre>
<p><strong>3. 调用抖动</strong></p>
<pre class="language-javascript"><code>import { vibrator } from '@kit.SensorServiceKit'

class SensorPlugin {
  vibrator() {
    vibrator.startVibration({ type: 'time', duration: 50 }, { usage: 'touch' })
  }
}

export const sensorPlugin = new SensorPlugin()</code></pre>
<p>&nbsp;</p>]]></description>
    <pubDate>Mon, 18 Nov 2024 07:41:00 +0800</pubDate>
    <dc:creator>凌零の小窝</dc:creator>
    <guid>https://llsix.com/HarmonyOS/45.html</guid>
</item>
<item>
    <title>鸿蒙中Web组件中调试工具</title>
    <link>https://llsix.com/HarmonyOS/46.html</link>
    <description><![CDATA[<p>使用Eruda 手机调试面板工具,第三方库：<a class="ne-link" href="https://github.com/liriliri/eruda" target="_blank" rel="noopener">https://github.com/liriliri/eruda</a></p>
<p>导入在HTML页面即可</p>
<pre class="language-markup"><code>&lt;!-- 引入调试控制台 --&gt;
  &lt;script src="https://cdn.jsdelivr.net/npm/eruda"&gt;&lt;/script&gt;
  &lt;!-- 初始化 --&gt;
  &lt;script&gt;eruda.init();&lt;/script&gt;</code></pre>]]></description>
    <pubDate>Sun, 17 Nov 2024 08:36:00 +0800</pubDate>
    <dc:creator>凌零の小窝</dc:creator>
    <guid>https://llsix.com/HarmonyOS/46.html</guid>
</item>
<item>
    <title>鸿蒙中使用Web组件</title>
    <link>https://llsix.com/HarmonyOS/44.html</link>
    <description><![CDATA[<p>提供具有网页显示能力的Web组件，<a href="https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/js-apis-webview-V5" target="_blank" rel="noopener">@ohos.web.webview</a>提供web控制能力。<br>访问在线网页时需添加网络权限：ohos.permission.INTERNET</p>
<pre class="language-javascript"><code>import { webview } from '@kit.ArkWeb';

@Entry
@Component
struct WebComponent {
  controller: webview.WebviewController = new webview.WebviewController();

  build() {
    Column() {
      Web({ src: '在线URL地址', controller: this.controller })
    }
  }
}</code></pre>
<p><strong>常用的事件触发：</strong></p>
<pre class="language-javascript"><code>// 网页开始加载时触发该回调，且只在主frame触发，iframe或者frameset的内容加载时不会触发此回调。
.onPageBegin((event) =&gt; {
          if (event) {
            console.log('url:' + event.url);
          }
        })</code></pre>
<pre class="language-javascript"><code>// 网页加载进度变化时触发该回调。
.onProgressChange((event) =&gt; {
          if (event) {
            console.log('newProgress:' + event.newProgress);
          }
        })</code></pre>
<pre class="language-javascript"><code>// 网页加载完成时触发该回调，且只在主frame触发。
.onPageEnd((event) =&gt; {
          if (event) {
            console.log('url:' + event.url);
          }
        })</code></pre>
<pre class="language-javascript"><code>// 加载网页页面完成时触发该回调，用于应用更新其访问的历史链接。
.onRefreshAccessedHistory((event) =&gt; {
          if (event) {
            console.log('url:' + event.url + ' isReload:' + event.isRefreshed);
          }
        })</code></pre>
<pre class="language-javascript"><code>// 网页document标题更改时触发该回调，当H5未设置&lt;title&gt;元素时会返回对应的URL。
.onTitleReceive((event) =&gt; {
          if (event) {
            console.log('title:' + event.title);
          }
        })</code></pre>
<pre class="language-javascript"><code>@Consume
  pageStack: NavPathStack
/**
   * 回到web容器的上一个页面
   */
  webBack() {
// 判断 web 组件的 页面栈容量
    // this.historySize web 组件返回上一页之后，size 不会变
    if (this.historyCurrentIndex &gt; 0) {
      // web 组件有多个页面栈，web 组件返回上一页
      this.controller.backward()
    } else {
      // web 组件就只有 1 页
      this.pageStack.pop()
    }}</code></pre>
<pre class="language-javascript"><code>@Consume
  pageStack: NavPathStack
 /**
   * 回到上一个页面
   */
  webClose() {
    this.pageStack.pop()
  }</code></pre>]]></description>
    <pubDate>Sat, 16 Nov 2024 09:26:00 +0800</pubDate>
    <dc:creator>凌零の小窝</dc:creator>
    <guid>https://llsix.com/HarmonyOS/44.html</guid>
</item>
<item>
    <title>鸿蒙调用原生登录实现注册登录</title>
    <link>https://llsix.com/HarmonyOS/142.html</link>
    <description><![CDATA[<div class="lake-content">
<p id="uf1402789" class="ne-p"><span class="ne-text">除了常规的账号密码登录之外，我们还可以借助华为账号实现三方登录，</span><span class="ne-text">Account Kit（华为帐号服务）提供简单、快速、安全的登录和授权功能，</span><strong><span class="ne-text">让用户无需输入帐号、密码和繁琐验证，避免因忘记密码而带来的麻</span></strong><span class="ne-text">烦，为您创建帐号并登录所有HarmonyOS应用，带来更高的注册转化，同时通过授权获得用户头像昵称、手机号码等信息，提升用户黏性。</span></p>
<p class="ne-p"><strong><span class="ne-text">一. 开发准备</span></strong></p>
<p class="ne-p"><strong><span class="ne-text">1.1&nbsp; 申请</span></strong><strong><span class="ne-text">手机号并验证手机号权限</span></strong></p>
<p class="ne-p">&nbsp;</p>
<div class="lake-content">
<p id="uad942955" class="ne-p">登录华为开发者联盟，选择<code class="ne-code">管理中心-&gt;API服务-&gt;授权管理</code> ，选择目标应用的应用名称，服务选择&ldquo;华为帐号服务&rdquo;，选择&ldquo;敏感权限&rdquo;，再选择&ldquo;获取您的手机号&rdquo;或&ldquo;获取并验证您的手机号&rdquo;，点击&ldquo;申请&rdquo; = 》<strong><span class="ne-text"><a href="https://developer.huawei.com/consumer/cn/console/api/scopeManage" target="_blank" rel="noopener">华为开发者联盟</a></span></strong></p>
</div>
<p class="ne-p"><strong><span class="ne-text">1.2 配置应用签名证书(四个)</span></strong></p>
<p class="ne-p"><span class="ne-text">登录</span><a class="ne-link" href="https://developer.huawei.com/consumer/cn/service/josp/agc/index.html" target="_blank" rel="noopener" data-href="https://developer.huawei.com/consumer/cn/service/josp/agc/index.html"><span class="ne-text">AppGallery Connect</span></a><span class="ne-text">，点击&ldquo;我的项目&rdquo;。</span><span class="ne-text">在项目列表中找到您的项目，在项目中点击您的应用/元服务。</span><span class="ne-text">在&ldquo;项目设置 &gt; 常规&rdquo;页面的&ldquo;应用&rdquo;区域，点击&ldquo;SHA256证书/公钥指纹&rdquo;后的&ldquo;添加公钥指纹（HarmonyOS API 9及以上）&rdquo;</span></p>
<p class="ne-p"><strong><span class="ne-text">1.3&nbsp;</span> <span class="ne-text">配置</span><span class="ne-text">Client ID</span></strong></p>
<div class="lake-content">
<p id="u4b15c977" class="ne-p"><span class="ne-text">把ClientId后面的数字复制一下，放到项目的</span><code class="ne-code"><span class="ne-text">module.json5</span></code><span class="ne-text">文件中</span></p>
<pre class="language-javascript"><code>"module": {
  "name": "xxx",
  "type": "entry",
  "description": "xxx",
  "mainElement": "xxx",
  "deviceTypes": [],
  "pages": "xxx",
  "abilities": [],
  "metadata": [ // 配置信息如下
    {
      "name": "client_id",
      "value": "id"
    }
  ]
}</code></pre>
<p><strong>1.4 调用华为提供的登录API拉起原生登录页</strong></p>
<pre class="language-javascript"><code>import { authentication } from '@kit.AccountKit'

PersistentStorage.persistProp&lt;string&gt;('openId', '')

class HuaweiAuthPlugin {
//  登录
  async requestAuth() {
    // 1. 创建一个Account Kit授权请求对象，可通过返回值设置请求参数。
    const huaweiIdProvider = new authentication.HuaweiIDProvider()
    const authCreateRequest = huaweiIdProvider.createAuthorizationWithHuaweiIDRequest()
    // 2. 添加请求参数
    authCreateRequest.scopes = ['phone', 'openid']
    authCreateRequest.permissions = ['serviceauthcode']
    authCreateRequest.forceAuthorization = true
    // 3. 执行授权请求，获取认证码
    const authController = new authentication.AuthenticationController(getContext())
    const authResponse: authentication.AuthorizationWithHuaweiIDResponse =
      await authController.executeRequest(authCreateRequest)
    const serviceauthcode = authResponse.data?.authorizationCode
    AppStorage.setOrCreate&lt;string&gt;('openId', authResponse.data?.openID)
    return serviceauthcode
  }

  // getHuaweiIDState  api12 支持

// 登出账号
  async cancelAuth() {
    try {
      // 1. 创建一个Account Kit授权请求对象，可通过返回值设置请求参数。
      const huaweiIdProvider = new authentication.HuaweiIDProvider()
      const authCancelRequest = huaweiIdProvider.createCancelAuthorizationRequest()
      // 2. 取消授权
      const authController = new authentication.AuthenticationController(getContext())
      await authController.executeRequest(authCancelRequest)
      return true
    } catch (e) {
      console.log('mk-logger', JSON.stringify(e))
      return false
    }
  }
}

export const huaweiAuthPlugin = new HuaweiAuthPlugin()</code></pre>
<p><strong>1.5 提供一个登录组件</strong></p>
<pre class="language-javascript"><code>import { huaweiAuthPlugin } from '@mk/basic'
import { MkDialogLoading } from '@mk/basic'

@Component
export struct HuaweiLoginCom {
  dialog: CustomDialogController = new CustomDialogController({
    builder: MkDialogLoading({ message: '华为登录中' }),
    customStyle: true,
    alignment: DialogAlignment.Center
  })

  build() {
    Image($r('app.media.ic_user_huawei'))
      .width(40)
      .onClick(async () =&gt; {
        const res = await huaweiAuthPlugin.requestAuth()
    AlertDialog.show({
      message: res  // res返回code码
    })
      })
  }
}</code></pre>
<p><strong>&nbsp;返回code码 提交到后台服务器，后台服务器提交到华为服务器，华为验真后返回用户数据给后台，后台返回给我们</strong></p>
</div>
</div>]]></description>
    <pubDate>Thu, 31 Oct 2024 17:57:00 +0800</pubDate>
    <dc:creator>凌零の小窝</dc:creator>
    <guid>https://llsix.com/HarmonyOS/142.html</guid>
</item>
<item>
    <title>鸿蒙ForEach和LazyForEach的区别</title>
    <link>https://llsix.com/HarmonyOS/140.html</link>
    <description><![CDATA[<p>在鸿蒙系统开发中，ForEach 和 LazyForEach 是两种用于遍历数据集合的组件，它们的主要区别在于渲染机制和性能优化方面：<br><strong>ForEach:</strong><br>立即渲染：ForEach 会立即渲染所有子组件，无论这些子组件是否在当前视图中可见。<br>适用场景：适用于数据量较小且所有项都需要立即显示的场景。<br>性能影响：当数据量较大时，可能会导致性能问题，因为所有项都会被渲染到内存中。<br><strong>LazyForEach: <a href="https://ohpm.openharmony.cn/#/cn/detail/@ohos%2Fpulltorefresh" target="_blank" rel="noopener">PullToRefresh</a> <a href="https://gitee.com/openharmony-sig/ohos_pull_to_refresh/blob/master/entry/src/main/ets/pages/lazyForEachGuide.ets" target="_blank" rel="noopener">示例代码</a></strong><br>懒加载：LazyForEach 只会渲染当前视图中可见的子组件，当用户滚动时，才会动态加载新的子组件。<br>适用场景：适用于数据量较大或需要滚动显示的场景，可以显著提高性能和用户体验。<br>性能优化：通过懒加载机制，减少了内存占用和初始加载时间，提高了应用的响应速度。<br><strong>总结：</strong><br>如果数据量较小且所有项都需要立即显示，使用 ForEach。<br>如果数据量较大或需要滚动显示，使用 LazyForEach 以优化性能。</p>
<div class="lake-content">
<p><strong><span class="ne-text">1. 实现提供的一个 IDataSource 的接口</span></strong></p>
</div>
<pre class="language-javascript"><code>class BasicDataSource implements IDataSource {
  private listeners: DataChangeListener[] = new Array&lt;DataChangeListener&gt;();

  public totalCount(): number {
    return 0;
  }

  public getData(index: number): Object {
    return index;
  }

  // 为LazyForEach组件向其数据源处添加listener监听
  registerDataChangeListener(listener: DataChangeListener): void {
    if (this.listeners.indexOf(listener) &lt; 0) {
      console.info('add listener');
      this.listeners.push(listener);
    }
  }

  // 为对应的LazyForEach组件在数据源处去除listener监听
  unregisterDataChangeListener(listener: DataChangeListener): void {
    const pos = this.listeners.indexOf(listener);
    if (pos &gt;= 0) {
      console.info('remove listener');
      this.listeners.splice(pos, 1);
    }
  }

  // 通知LazyForEach组件需要重载所有子组件
  notifyDataReload(): void {
    this.listeners.forEach(listener =&gt; {
      listener.onDataReloaded();
    })
  }

  // 通知LazyForEach组件需要在index对应索引处添加子组件
  notifyDataAdd(index: number): void {
    this.listeners.forEach(listener =&gt; {
      listener.onDataAdd(index);
    })
  }

  // 通知LazyForEach组件需要在index对应索引处添加子组件
  notifyDataChange(index: number): void {
    this.listeners.forEach(listener =&gt; {
      listener.onDataChange(index);
    })
  }

  // 通知LazyForEach组件需要在index对应索引处删除该子组件
  notifyDataDelete(index: number): void {
    this.listeners.forEach(listener =&gt; {
      listener.onDataDelete(index);
    })
  }

  notifyDataMove(from: number, to: number): void {
    this.listeners.forEach(listener =&gt; {
      listener.onDataMove(from, to);
    })
  }
}

class MyDataSource extends BasicDataSource {
  private dataArray: string[] = [];

  public totalCount(): number {
    return this.dataArray.length;
  }

  public getData(index: number): Object {
    return this.dataArray[index];
  }

  public addData(index: number, data: string): void {
    this.dataArray.splice(index, 0, data);
    this.notifyDataAdd(index);
  }

  public pushData(data: string): void {
    this.dataArray.push(data);
    this.notifyDataAdd(this.dataArray.length - 1);
  }

  public clear(): void {
    this.dataArray = [];
  }
}</code></pre>
<p>2. 只替换 MyDataSource 中&nbsp;dataArray&nbsp;的类型即可</p>
<p>3. 渲染数组中重新 new 一个实例化数</p>
<pre class="language-javascript"><code>@State list: MyDataSource = new MyDataSource()</code></pre>
<p>4. 把ForEach换成 LazyForEach后 会出现爆红 修改爆红地区即可</p>
<div>
<pre>&nbsp;</pre>
</div>
<div>
<pre>&nbsp;</pre>
</div>]]></description>
    <pubDate>Tue, 29 Oct 2024 22:08:00 +0800</pubDate>
    <dc:creator>凌零の小窝</dc:creator>
    <guid>https://llsix.com/HarmonyOS/140.html</guid>
</item>
<item>
    <title>鸿蒙组件抽取-MkDialogLoading弹窗</title>
    <link>https://llsix.com/HarmonyOS/43.html</link>
    <description><![CDATA[<pre class="language-javascript"><code>@CustomDialog
export struct MkDialogLoading {
  message: string = '加载中'
  controller: CustomDialogController

  build() {
    Column({ space: 10 }) {
      LoadingProgress()
        .width(48)
        .height(48)
        .color('#ffffff')
      if (this.message) {
        Text(this.message)
          .fontSize(14)
          .fontColor('#ffffff')
      }
    }
    .justifyContent(FlexAlign.Center)
    .width(120)
    .height(120)
    .backgroundColor('rgba(0,0,0,0.6)')
    .borderRadius(16)
  }
}

</code></pre>
<pre class="language-javascript"><code>  // 使用自定义弹窗
  dialog = new CustomDialogController({
    builder: MkDialogLoading({ message: '拼命加载中...' }), // 抽取弹窗设置给 控制器
    alignment: DialogAlignment.Center, // 位置
    customStyle: true // 背景透明
  })

this.dialog.open() // 开启自定义弹窗
this.dialog.close() // 关闭自定义弹窗</code></pre>]]></description>
    <pubDate>Fri, 25 Oct 2024 07:17:00 +0800</pubDate>
    <dc:creator>凌零の小窝</dc:creator>
    <guid>https://llsix.com/HarmonyOS/43.html</guid>
</item>
<item>
    <title>鸿蒙实现帧动画效果</title>
    <link>https://llsix.com/HarmonyOS/42.html</link>
    <description><![CDATA[<pre class="language-javascript"><code>@Component
export struct MkLoading {
  // 宽度可以由外部传入
  loadingWidth: number = 80

  build() {
    // 帧动画组件
    ImageAnimator()
      .images([// 图片数组
        { src: $r('app.media.loading_01') },
        { src: $r('app.media.loading_02') },
        { src: $r('app.media.loading_03') },
        { src: $r('app.media.loading_04') }
      ])
      .state(AnimationStatus.Running)// 动画的状态 running 播放
      .duration(500)// 持续时间
      .iterations(-1)// 动画次数 -1无限
      .width(this.loadingWidth)// 宽度
      .aspectRatio(3) // 高度
  }
}</code></pre>]]></description>
    <pubDate>Thu, 24 Oct 2024 07:56:00 +0800</pubDate>
    <dc:creator>凌零の小窝</dc:creator>
    <guid>https://llsix.com/HarmonyOS/42.html</guid>
</item>
<item>
    <title>鸿蒙日志工具第三方库@abner/log和下拉刷新和sku库存组件</title>
    <link>https://llsix.com/HarmonyOS/40.html</link>
    <description><![CDATA[<p>HarmonyOsLog是一个日志打印工具，支持hilog和console两种工具打印，支持各种类型包含JSON格式化打印，非常的方便！<a href="https://ohpm.openharmony.cn/#/cn/detail/@abner%2Flog" target="_blank" rel="noopener">第三方库@abner/log</a></p>
<p>在Terminal窗口中，执行如下命令安装三方包，DevEco Studio会自动在工程的oh-package.json5中自动添加三方包依赖。</p>
<p>1. 下载/安装第三方库包</p>
<pre class="language-javascript"><code>ohpm i @abner/log</code></pre>
<p>2. 全局初始化 工具(仅一次)</p>
<pre class="language-javascript"><code>onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');

    // 全局初始化一次 日志工具
    Log.init({
      tag: "MeiKou_log",
      domain: 0x0000,
      close: false,
      isHilog: true,
      showLogLocation: true,
      logSize: 800
    })
  }</code></pre>
<p>3. 使用 Log 打印输出</p>
<pre class="language-javascript"><code>Log.info({
        name: '凌零博客', url: 'https://llsix.com'
      })</code></pre>
<p><strong>二. PullToRefresh是一款OpenHarmony环境下可用的下拉刷新、上拉加载组件。 支持设置内置动画的各种属性，支持设置自定义动画，支持lazyForEarch的数据作为数据源。&nbsp;<a href="https://ohpm.openharmony.cn/#/cn/detail/@ohos%2Fpulltorefresh" target="_blank" rel="noopener">PullToRefresh</a></strong></p>
<p>1. 下载安装第三方库包</p>
<pre class="language-javascript"><code>ohpm i @ohos/pulltorefresh</code></pre>
<p><strong>三.&nbsp; <span class="ne-text">在电商类项目中，SKU（Stock Keeping Unit，库存量单位）是一个核心功能，它是指商品的最小库存管理单位 <a href="https://ohpm.openharmony.cn/#/cn/detail/@ohmos%2Fsku" target="_blank" rel="noopener">sku库存组件</a></span></strong></p>
<p>&nbsp;</p>
<pre class="language-javascript"><code>ohpm install @ohmos/sku</code></pre>]]></description>
    <pubDate>Wed, 23 Oct 2024 08:29:00 +0800</pubDate>
    <dc:creator>凌零の小窝</dc:creator>
    <guid>https://llsix.com/HarmonyOS/40.html</guid>
</item>
</channel>
</rss>