9afce6f6创建于 2025年5月7日历史提交
/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import {
  ImageKnife,
  ImageKnifeComponent,
  ImageKnifeData,
  ImageKnifeGlobal,
  ImageKnifeOption,
  NONE,
  RequestOption
} from '@ohos/imageknife';
import { BusinessError } from '@kit.BasicServicesKit';
import { Size } from '@kit.ArkUI';
import { image } from '@kit.ImageKit';
import { PlatformInfo, PlatformTypeEnum } from 'utils';
import {
  DEFAULT_IMG_URL,
  DEFAULT_REFER,
  IMAGE_THEFT_BUTTON_CLICK_SCALE,
  IMAGE_THEFT_COLUMN_SPACE,
  IMAGE_THEFT_IMAGEKNIFE_COMPONENT_HEIGHT,
  IMAGE_THEFT_IMAGEKNIFE_COMPONENT_WIDTH
} from '../constants/Constants';

/**
 * 功能描述: 使用第三方库imageknife,通过在请求头中添加Referer来实现防盗链图片的功能。
 *
 * 推荐场景: 网络图片资源需要防盗链的场景
 *
 * 核心组件:
 * 1. imageknife
 *
 * 实现步骤:
 * 1. 新建imageKnifeOption变量,作为ImageKnifeComponent的入参配置。
 * 2. 新建RequestOption对象实例,并在里面配置请求头,缓存规则,以及请求成功和回调等配置。
 * 3. 在imageknife实例调用call函数,传入步骤2里面的实例。
 */
@Component
export struct ImageTheftComponent {
  @State imgUrl: string = DEFAULT_IMG_URL; // 图片image的url
  @State refer: string = DEFAULT_REFER; // 图片image的refer
  @State imageKnifeOption: ImageKnifeOption = {
    // 图片组件ImageKnifeComponent的配置项
    loadSrc: '',
    isCacheable: false,
  };
  @StorageLink('avoidAreaBottomToModule') avoidAreaBottomToModule: number = 0;
  emptyPixel: image.PixelMap | null = null; // 存储重置用的空白图片
  focusController = this.getUIContext().getFocusController(); // 组件焦点控制器

  build() {
    Column() {
      // 场景描述组件
      FunctionDescription({
        content: $r("app.string.image_theft_content")
      })
      // 输入框和按钮
      Column({ space: IMAGE_THEFT_COLUMN_SPACE }) {
        Row() {
          Text($r("app.string.image_theft_image_url"))
            .width($r("app.integer.image_theft_text_width"))
            .height($r("app.integer.image_theft_text_height"))
          TextArea({ text: $$this.imgUrl })
            .width($r("app.string.image_theft_textarea_width"))
            .enableKeyboardOnFocus(false)
            .enterKeyType(EnterKeyType.Done)
        }
        .alignItems(VerticalAlign.Top)

        Row() {
          Text($r("app.string.image_theft_image_refer"))
            .width($r("app.integer.image_theft_text_width"))
            .height($r("app.integer.image_theft_text_height"))
          TextArea({ text: $$this.refer })
            .width($r("app.string.image_theft_textarea_width"))
            .enableKeyboardOnFocus(false)
            .enterKeyType(EnterKeyType.Done)
        }
        .alignItems(VerticalAlign.Top)

        Row({ space: IMAGE_THEFT_COLUMN_SPACE }) {
          Button($r("app.string.image_theft_button_request"))
            .width($r("app.integer.image_theft_button_width"))
            .clickEffect({ level: ClickEffectLevel.HEAVY, scale: IMAGE_THEFT_BUTTON_CLICK_SCALE })
            .onClick(() => {
              this.sendRequest(this.imgUrl, this.refer);
              // 清除输入框焦点
              if (PlatformInfo.getPlatform() === PlatformTypeEnum.HARMONYOS) {
                this.focusController.clearFocus();
              }
            })
          Button($r("app.string.image_theft_button_reset"))
            .width($r("app.integer.image_theft_button_width"))
            .clickEffect({ level: ClickEffectLevel.HEAVY, scale: IMAGE_THEFT_BUTTON_CLICK_SCALE })
            .onClick(() => {
              // 清除输入框焦点
              if (PlatformInfo.getPlatform() === PlatformTypeEnum.HARMONYOS) {
                this.focusController.clearFocus();
              }
              this.imgUrl = DEFAULT_IMG_URL;
              this.refer = DEFAULT_REFER;
              if (this.emptyPixel) { // 如果已经有空白Pixel,则将其赋值,否则生成空白Pixel
                this.imageKnifeOption.loadSrc = this.emptyPixel;
              } else {
                const color: ArrayBuffer = new ArrayBuffer(4); // 4为需要创建的像素buffer大小,取值为:height * width * 4
                const opts: image.InitializationOptions = { size: { height: 1, width: 1 } }; // 为了展示空白,需要生成1*1像素的图片
                image.createPixelMap(color, opts).then(result => {
                  this.imageKnifeOption.loadSrc = result;
                });
              }
            })
        }
      }
      .margin({
        top: $r('app.integer.image_theft_column_margin_top'),
        bottom: $r('app.integer.image_theft_column_margin_bottom')
      })
      .width($r('app.string.image_theft_column_margin_width'))

      // 图片展示组件
      ImageKnifeComponent({
        imageKnifeOption: this.imageKnifeOption
      })
        .width(IMAGE_THEFT_IMAGEKNIFE_COMPONENT_HEIGHT)
        .border({ width: $r("app.integer.image_theft_imageknife_component_border_width"), color: Color.Gray })
        .borderRadius($r('app.integer.image_theft_imageknife_component_border_radius'))
        .clip(true)
        .id('myImage')
        .layoutWeight(1)
    }
    .padding({
      top: $r('app.integer.image_theft_outer_column_padding'),
      bottom: px2vp(this.avoidAreaBottomToModule),
      left: $r('app.integer.image_theft_outer_column_padding'),
      right: $r('app.integer.image_theft_outer_column_padding')
    })
  }

  /**
   * 发送请求
   * @param url 图片url
   * @param ref 图片来源
   */
  sendRequest(url: string, ref: string): void {
    const imageKnife: ImageKnife | undefined = ImageKnifeGlobal.getInstance().getImageKnife();
    if (imageKnife !== undefined) {
      imageKnife.removeAllMemoryCache() // 清理全部缓存
      imageKnife.removeAllFileCache() // 清理全部缓存
      // TODO: 知识点:自定义RequestOption来获取image图片。
      const request = new RequestOption();
      // TODO: 知识点:使用addHeader添加请求头。
      request.addHeader('Referer', ref);
      request.skipMemoryCache(true)
        .diskCacheStrategy(new NONE())// 取消磁盘缓存
        .errorholder($r("app.media.image_theft_failed"), {
          asyncSuccess: (data: ImageKnifeData) => {
            if (data.isPixelMap()) {
              this.imageKnifeOption.loadSrc = data.drawPixelMap?.imagePixelMap;
            }
          }
        })
        .load(url)// TODO: 知识点:添加请求url。
        .addListener({
          // TODO: 知识点:添加请求回调监听器。
          callback: (err: BusinessError | string, data: ImageKnifeData) => {
            if (data.isPixelMap()) {
              if (data.drawPixelMap) {
                // TODO: 知识点:在这里获取到请求返回的图片,将图片赋值给imageKnifeOption.loadSrc。
                this.imageKnifeOption.loadSrc = data.drawPixelMap.imagePixelMap;
              } else {
                this.imageKnifeOption.loadSrc = $r("app.media.image_theft_failed");
              }
            }
            return true;
          }
        })
      const compSize: Size = {
        width: IMAGE_THEFT_IMAGEKNIFE_COMPONENT_WIDTH,
        height: IMAGE_THEFT_IMAGEKNIFE_COMPONENT_HEIGHT
      };
      // (必传)这里setImageViewSize函数必传初始组件大小,因为涉及到图片变换效果都需要适配图像源和组件大小。
      // TODO: 知识点:如果是自适应组件等不确定初始图片宽高的情况,可以通过getInspectorByKey获取,参考https://developer.huawei.com/consumer/cn/doc/harmonyos-faqs/faqs-arkui-kit-0000001769732210#section10880155113412。
      request.setImageViewSize(compSize);
      // 磁盘Lru缓存数量设置成0张,0字节
      imageKnife.setLruCacheSize(0, 0);
      // 最后使用ImageKnife的call函数调用request即可
      imageKnife.call(request);
    }
  }
}

/**
 * 模块功能描述组件
 * @param title 标题
 * @param context 内容
 */
@Component
struct FunctionDescription {
  private title: ResourceStr = '';
  private content: ResourceStr = '';

  build() {
    Column() {
      Row() {
        Text(this.title)
          .fontSize($r('app.string.image_theft_text_size_headline'))
          .fontWeight(FontWeight.Medium)
          .textOverflow({ overflow: TextOverflow.Ellipsis })
          .maxLines(1)
      }
      .margin({ bottom: $r('app.string.image_theft_elements_margin_vertical_m') })

      Row() {
        Text(this.content)
          .wordBreak(WordBreak.BREAK_ALL)
      }
      .width('100%')
    }
    .width('100%')
    .backgroundColor($r('app.color.image_theft_color_sub_background'))
    .borderRadius($r('app.string.image_theft_corner_radius_default_m'))
    .padding($r('app.string.image_theft_card_padding_start'))
  }
}