鸿蒙NEXT开发:ArkTS组件-通用属性(拖拽控制)

 往期鸿蒙5.0全套实战文章必看:(文中附带全栈鸿蒙5.0学习资料)


拖拽控制

设置组件是否可以响应拖拽事件。

说明

从API Version 10开始支持。后续版本如有新增内容,则采用上角标单独标记该内容的起始版本。

ArkUI框架对以下组件实现了默认的拖拽能力,支持对数据的拖出或拖入响应。开发者也可以通过实现通用拖拽事件来自定义拖拽响应。

Text、TextInput、TextArea、Hyperlink、Image、RichEditor和Web组件的draggable属性默认为true,默认支持拖出能力。其他组件需要开发者将draggable属性设置为true,并在onDragStart等接口中实现数据传输相关内容,才能正确处理拖拽。

说明

Text组件需配合copyOption一起使用,设置copyOptions为CopyOptions.InApp或者CopyOptions.LocalDevice。

allowDrop

allowDrop(value: Array<UniformDataType> | null)

设置该组件上允许落入的数据类型。

元服务API: 从API version 11开始,该接口支持在元服务中使用。

系统能力: SystemCapability.ArkUI.ArkUI.Full

参数:

参数名类型必填说明
valueArray<UniformDataType> | null12+

设置该组件上允许落入的数据类型。从API version 12开始,允许设置成null使该组件不接受所有的数据类型。

默认值:空

draggable

draggable(value: boolean)

设置该组件是否允许进行拖拽。

元服务API: 从API version 11开始,该接口支持在元服务中使用。

系统能力: SystemCapability.ArkUI.ArkUI.Full

参数:

参数名类型必填说明
valueboolean

设置该组件是否允许进行拖拽。

默认值:false

dragPreview11+

dragPreview(value: CustomBuilder | DragItemInfo | string)

设置组件拖拽过程中的预览图。

元服务API: 从API version 12开始,该接口支持在元服务中使用。

系统能力: SystemCapability.ArkUI.ArkUI.Full

参数:

参数名类型必填说明
valueCustomBuilder | DragItemInfo | string12+

设置组件拖拽过程中的预览图,仅在onDragStart拖拽方式中有效。

当组件支持拖拽并同时设置bindContextMenu的预览图时,则长按浮起的预览图以bindContextMenu设置的预览图为准。开发者在onDragStart中返回的背板图优先级低于dragPreview设置的预览图,当设置了dragPreview预览图时,拖拽过程中的背板图使用dragPreview预览图。由于CustomBuilder需要离线渲染之后才能使用,因此存在一定的性能开销和时延,推荐优先使用 DragItemInfo中的PixelMap方式。

当传入类型为string的id时,则将id对应组件的截图作为预览图。如果id对应的组件无法查找到,或者id对应的组件Visibility属性设置成none/hidden,则对组件自身进行截图作为拖拽预览图。目前截图不含有亮度、阴影、模糊和旋转等视觉效果。

默认值:空

dragPreviewOptions11+

dragPreviewOptions(value: DragPreviewOptions, options?: DragInteractionOptions)

设置拖拽过程中背板图处理模式及数量角标的显示。不支持onItemDragStart拖拽方式。

元服务API: 从API version 12开始,该接口支持在元服务中使用。

系统能力: SystemCapability.ArkUI.ArkUI.Full

参数:

参数名类型必填说明
valueDragPreviewOptions11+

设置拖拽过程中背板图处理模式及数量角标的显示。

默认值:空

options12+DragInteractionOptions12+

设置拖拽过程中背板图浮起的交互模式。

默认值:空

DragPreviewOptions11+

元服务API: 从API version 12开始,该接口支持在元服务中使用。

名称类型必填描述
modeDragPreviewMode | Array<DragPreviewMode>12+

表示拖拽过程中背板图处理模式。

默认值:DragPreviewMode.AUTO

当组件同时设置DragPreviewMode.AUTO和其它枚举值时,以DragPreviewMode.AUTO为准,其它枚举值设置无效。

numberBadge12+boolean | number

控制数量角标是否显示,或强制设置显示的数量。当设置数量角标时取值范围为[0,231-1],超过取值范围时会按默认状态处理。当设置为浮点数时,只显示整数部分。

说明:

在多选拖拽场景,需通过该接口设置拖拽对象的数量。

默认值:true

modifier12+ImageModifier

用于配置拖拽背板图的样式Modifier对象,可使用图片组件所支持的属性和样式来配置背板图样式(参考示例6),当前支持透明度,阴影,背景模糊度,圆角。文本拖拽只支持默认效果,不支持通过modifier进行自定义。

1.透明度

通过opacity设置透明度,不透明度的取值范围为0-1。设置0或不设置时采用默认值0.95,设置1或异常值时不透明。

2.阴影

通过shadow设置阴影。

3.背景模糊度

通过backgroundEffectbackgroundBlurStyle设置背景模糊度,如果两者同时设置,以backgroundEffect为准。

4.圆角

通过borderborderRadius设置圆角,当同时在mode和modifier中设置圆角,mode设置的圆角显示优先级低于modifier设置。

默认值:空,无法修改属性

DragPreviewMode11+枚举说明

元服务API: 从API version 12开始,该接口支持在元服务中使用。

名称枚举值描述
AUTO1系统根据拖拽场景自动改变跟手点位置,根据规则自动对拖拽背板图进行缩放变换等。
DISABLE_SCALE2禁用系统对拖拽背板图的缩放行为。
ENABLE_DEFAULT_SHADOW12+3启用非文本类组件默认阴影效果。
ENABLE_DEFAULT_RADIUS12+4启用非文本类组件统一圆角效果,默认值12vp。当应用自身设置的圆角值大于默认值或modifier设置的圆角时,则显示应用自定义圆角效果。

DragInteractionOptions12+

元服务API: 从API version 12开始,该接口支持在元服务中使用。

名称类型必填描述
isMultiSelectionEnabledboolean

表示拖拽过程中背板图是否支持多选聚拢效果。该参数只在GridList组件中的GridItem组件和ListItem组件生效。

当一个item组件设置为多选拖拽时,该组件的子组件不可拖拽。聚拢组件预览图设置的优先级为dragPreview中的string,dragPreview中的PixelMap,组件自截图,不支持dragPreview中的Builder形式。

不支持组件绑定bindContextMenu中参数存在isShown的模式。

默认值:false

defaultAnimationBeforeLiftingboolean

表示是否启用长按浮起阶段组件自身的默认点按效果(缩小)。

默认值:false

示例

示例1(允许拖拽和落入)

该示例通过配置allowDrop和draggable分别设置组件是否可落入和拖拽。

// xxx.ets
import { unifiedDataChannel, uniformTypeDescriptor } from '@kit.ArkData';

@Entry
@Component
struct ImageExample {
  @State uri: string = ""
  @State AblockArr: string[] = []
  @State BblockArr: string[] = []
  @State AVisible: Visibility = Visibility.Visible
  @State dragSuccess :Boolean = false

  build() {
    Column() {
      Text('Image拖拽')
        .fontSize('30dp')
      Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceAround }) {
        Image($r('app.media.icon'))
          .width(100)
          .height(100)
          .border({ width: 1 })
          .visibility(this.AVisible)
          .draggable(true)
          .onDragEnd((event: DragEvent) => {
            let ret = event.getResult();
            if(ret == 0) {
              console.log("enter ret == 0")
              this.AVisible = Visibility.Hidden;
            } else {
              console.log("enter ret != 0")
              this.AVisible = Visibility.Visible;
            }
          })
      }
      .margin({ bottom: 20 })
      Row() {
        Column(){
          Text('不允许释放区域')
            .fontSize('15dp')
            .height('10%')
          List(){
            ForEach(this.AblockArr, (item:string, index) => {
              ListItem() {
                Image(item)
                  .width(100)
                  .height(100)
                  .border({width: 1})
              }
              .margin({ left: 30 , top : 30})
            }, (item:string) => item)
          }
          .height('90%')
          .width('100%')
          .allowDrop([uniformTypeDescriptor.UniformDataType.TEXT])
          .onDrop((event?: DragEvent, extraParams?: string) => {
            this.uri = JSON.parse(extraParams as string).extraInfo;
            this.AblockArr.splice(JSON.parse(extraParams as string).insertIndex, 0, this.uri);
            console.log("ondrop not udmf data");
          })
          .border({width: 1})
        }
        .height("50%")
        .width("45%")
        .border({ width: 1 })
        .margin({ left: 12 })
        Column(){
          Text('可释放区域')
            .fontSize('15dp')
            .height('10%')
          List(){
            ForEach(this.BblockArr, (item:string, index) => {
              ListItem() {
                Image(item)
                  .width(100)
                  .height(100)
                  .border({width: 1})
              }
              .margin({ left: 30 , top : 30})
            }, (item:string) => item)
          }
          .border({width: 1})
          .height('90%')
          .width('100%')
          .allowDrop([uniformTypeDescriptor.UniformDataType.IMAGE])
          .onDrop((event?: DragEvent, extraParams?: string) => {
            console.log("enter onDrop")
            let dragData:UnifiedData = (event as DragEvent).getData() as UnifiedData;
            if(dragData != undefined) {
              let arr:Array<unifiedDataChannel.UnifiedRecord> = dragData.getRecords();
              if(arr.length > 0) {
                let image = arr[0] as unifiedDataChannel.Image;
                this.uri = image.imageUri;
                this.BblockArr.splice(JSON.parse(extraParams as string).insertIndex, 0, this.uri);
              } else {
                console.log(`dragData arr is null`)
              }
            } else {
              console.log(`dragData  is undefined`)
            }
            console.log("ondrop udmf data");
            this.dragSuccess = true
          })
        }
        .height("50%")
        .width("45%")
        .border({ width: 1 })
        .margin({ left: 12 })
      }
    }.width('100%')
  }
}

示例2(设置预览图)

该示例通过配置dragPreview设置拖拽过程的预览图。

// xxx.ets
@Entry
@Component
struct DragPreviewDemo{
  @Builder dragPreviewBuilder() {
    Column() {
      Text("dragPreview")
        .width(150)
        .height(50)
        .fontSize(20)
        .borderRadius(10)
        .textAlign(TextAlign.Center)
        .fontColor(Color.Black)
        .backgroundColor(Color.Pink)
    }
  }

  @Builder MenuBuilder() {
    Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
      Text("menu item 1")
        .fontSize(15)
        .width(100)
        .height(40)
        .textAlign(TextAlign.Center)
        .fontColor(Color.Black)
        .backgroundColor(Color.Pink)
      Divider()
        .height(5)
      Text("menu item 2")
        .fontSize(15)
        .width(100)
        .height(40)
        .textAlign(TextAlign.Center)
        .fontColor(Color.Black)
        .backgroundColor(Color.Pink)
    }
    .width(100)
  }

  build() {
    Row() {
      Column() {
        Image('/resource/image.jpeg')
          .width("30%")
          .draggable(true)
          .bindContextMenu(this.MenuBuilder, ResponseType.LongPress)
          .onDragStart(() => {
            console.log("Image onDragStart")
          })
          .dragPreview(this.dragPreviewBuilder)
      }
      .width("100%")
    }
    .height("100%")
  }
}

示例3(设置背板图样式)

该示例通过配置dragPreviewOptions为ENABLE_DEFAULT_SHADOW和ENABLE_DEFAULT_RADIUS设置默认阴影和统一圆角效果。

// xxx.ets
@Entry
@Component
struct dragPreviewOptionsDemo{
  build() {
    Row() {
      Column() {
        Image('/resource/image.jpeg')
          .margin({ top: 10 })
          .width("100%")
          .draggable(true)
          .dragPreviewOptions({ mode: DragPreviewMode.AUTO })
        Image('/resource/image.jpeg')
          .margin({ top: 10 })
          .width("80%")
          .border({ radius: { topLeft: 1, topRight: 2, bottomLeft: 4, bottomRight: 8 } })
          .draggable(true)
          .dragPreviewOptions({ mode: [ DragPreviewMode.ENABLE_DEFAULT_SHADOW, DragPreviewMode.ENABLE_DEFAULT_RADIUS ] })
      }
      .width("100%")
      .height("100%")
    }
  }
}

示例4(设置多选拖拽)

该示例通过配置isMultiSelectionEnabled实现Grid组件的多选拖拽效果。

@Entry
@Component
struct Example {
  @State numbers: number[] = [0, 1, 2, 3, 4 , 5, 6, 7, 8]
  build() {
    Column({ space: 5}) {
      Grid() {
        ForEach(this.numbers, (item: number) => {
          GridItem() {
            Column()
              .backgroundColor(Color.Blue)
              .width('100%')
              .height('100%')
          }
          .width(90)
          .height(90)
          .selectable(true)
          .selected(true)
          .dragPreviewOptions({}, {isMultiSelectionEnabled:true})
          .onDragStart(()=>{

          })
    }, (item: string) => item)
      }
      .columnsTemplate('1fr 1fr 1fr')
      .rowsTemplate('1fr 1fr 1fr')
      .height(300)
    }
    .width('100%')
  }
}

示例5(设置默认点按效果)

该示例通过配置defaultAnimationBeforeLifting实现Grid组件的默认点按效果。

@Entry
@Component
struct Example {
  @State numbers: number[] = [0, 1, 2, 3, 4 , 5, 6, 7, 8]
  build() {
    Column({ space: 5}) {
      Grid() {
        ForEach(this.numbers, (item: number) => {
          GridItem() {
            Column()
              .backgroundColor(Color.Blue)
              .width('100%')
              .height('100%')
          }
          .width(90)
          .height(90)
          .selectable(true)
          .selected(true)
          .dragPreviewOptions({}, {isMultiSelectionEnabled:true, defaultAnimationBeforeLifting:true})
          .onDragStart(()=>{

          })
    }, (item: string) => item)
      }
      .columnsTemplate('1fr 1fr 1fr')
      .rowsTemplate('1fr 1fr 1fr')
      .height(300)
    }
    .width('100%')
  }
}

示例6(自定义背板图样式)

该示例通过配置ImageModifier实现Image组件的自定义背板图样式。

// xxx.ets
import { ImageModifier } from '@kit.ArkUI'

@Entry
@Component
struct dragPreviewOptionsDemo{
  @State myModifier: ImageAttribute = new ImageModifier().opacity(0.5)
  @State vis: boolean = true
  @State changeValue: string = ''
  @State submitValue: string = ''
  @State positionInfo: CaretOffset = { index: 0, x: 0, y: 0 }
  controller: SearchController = new SearchController()
  @State OpacityIndex: number = 0
  @State OpacityList:(number | undefined | null)[]=[
    0.3,0.5,0.7,1,-50,0,10,undefined,null
  ]
  build() {
    Row() {
      Column() {
        Text(this.OpacityList[this.OpacityIndex] + "")
        Button("Opacity")
          .onClick(()=> {
            this.OpacityIndex++
            if(this.OpacityIndex > this.OpacityList.length - 1){
              this.OpacityIndex = 0
            }
          })
        Image($r('app.media.image'))
          .margin({ top: 10 })
          .width("100%")
          .draggable(true)
          .dragPreviewOptions({modifier: this.myModifier.opacity(this.OpacityList[this.OpacityIndex]) as ImageModifier})
      }
      .width("50%")
      .height("50%")
    }
  }
}

示例7(图片拖拽设置)

该示例展示了不同图片(在线图片资源、本地图片资源和PixelMap)在拖拽时组件的设置。

使用网络图片时,需要申请权限ohos.permission.INTERNET。具体申请方式请参考声明权限

// xxx.ets
import { uniformTypeDescriptor, unifiedDataChannel } from '@kit.ArkData';
import { image } from '@kit.ImageKit';
import { request } from '@kit.BasicServicesKit';
import { common } from '@kit.AbilityKit';
import { fileIo } from '@kit.CoreFileKit';
import { buffer } from '@kit.ArkTS';
import { BusinessError } from '@kit.BasicServicesKit';

@Entry
@Component
struct ImageDrag {
  @State targetImage1: string | PixelMap | null = null;
  @State targetImage2: string | PixelMap | null = null;
  @State targetImage3: string | PixelMap | null = null;
  context = getContext(this) as common.UIAbilityContext;
  filesDir = this.context.filesDir;

  public async createPixelMap(pixelMap: unifiedDataChannel.SystemDefinedPixelMap): Promise<image.PixelMap | null> {
    let mWidth: number = (pixelMap.details?.width ?? -1) as number;
    let mHeight: number = (pixelMap.details?.width ?? -1) as number;
    let mPixelFormat: image.PixelMapFormat =
      (pixelMap.details?.['pixel-format'] ?? image.PixelMapFormat.UNKNOWN) as image.PixelMapFormat;
    let mItemPixelMapData: Uint8Array = pixelMap.rawData;
    const opts: image.InitializationOptions = {
      editable: false, pixelFormat: mPixelFormat, size: {
        height: mHeight,
        width: mWidth
      }
    };
    const buffer: ArrayBuffer = mItemPixelMapData.buffer.slice(mItemPixelMapData.byteOffset,
      mItemPixelMapData.byteLength + mItemPixelMapData.byteOffset);
    try {
      let pixelMap: image.PixelMap = await image.createPixelMap(buffer, opts);
      return pixelMap;
    } catch (err) {
      console.error('dragtest--> getPixelMap', err);
      return null;
    }
  }

  build() {
    Column() {
      Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.Center }) {
        // 在线图片资源拖出
        Column() {
          Text('Online Image').fontSize(14)
          Image('https://www.example.com/xxx.png') // 请填写一个具体的网络图片地址
            .objectFit(ImageFit.Contain).draggable(true)
            .onDragStart(() => {})
            .width(100).height(100)
        }
        .border({ width: 2, color: Color.Gray, radius: 5, style: BorderStyle.Dotted })
        .alignItems(HorizontalAlign.Center).justifyContent(FlexAlign.Center)

        // 本地图片资源拖出
        Column() {
          Text('Local Image').fontSize(14)
          Image($r('app.media.example'))
            .objectFit(ImageFit.Contain).draggable(true)
            .onDragStart(() => {})
            .width(100).height(100)
        }
        .border({ width: 2, color: Color.Gray, radius: 5, style: BorderStyle.Dotted })
        .alignItems(HorizontalAlign.Center).justifyContent(FlexAlign.Center)

        // PixelMap拖出
        Column() {
          Text('PixelMap').fontSize(14)
          Image(this.context.resourceManager.getDrawableDescriptor($r('app.media.example').id).getPixelMap())
            .objectFit(ImageFit.Contain).draggable(true)
            .onDragStart(() => {})
            .width(100).height(100)
        }
        .border({ width: 2, color: Color.Gray, radius: 5, style: BorderStyle.Dotted })
        .alignItems(HorizontalAlign.Center).justifyContent(FlexAlign.Center)
      }

      // 落入数据类型为Image
      Text('Data type is Image').fontSize(14).margin({ top: 10 })
      Column() {
        Image(this.targetImage1)
          .objectFit(ImageFit.Contain)
          .width('70%').height('70%')
          .allowDrop([uniformTypeDescriptor.UniformDataType.IMAGE])
          .onDrop((event: DragEvent, extraParams: string) => {
            // 通过extraParams获取图片
            let arr: Record<string, object> = JSON.parse(extraParams) as Record<string, object>;
            let uri = arr['extraInfo'];
            if (typeof uri == 'string') {
              this.targetImage1 = uri;

              try {
                request.downloadFile(this.context, {
                  url: uri,
                  filePath: this.filesDir + '/example.png'
                }).then((downloadTask: request.DownloadTask) => {
                  let file = fileIo.openSync(this.filesDir + '/example.png', fileIo.OpenMode.READ_WRITE);
                  let arrayBuffer = new ArrayBuffer(1024);
                  let readLen = fileIo.readSync(file.fd, arrayBuffer);
                  let buf = buffer.from(arrayBuffer, 0, readLen);
                  console.info(`The content of file: ${buf.toString()}`);
                  fileIo.closeSync(file);
                })
              } catch (error) {}
            }
          })
      }
      .width('70%').height('25%')
      .border({ width: 2, color: Color.Gray, radius: 5, style: BorderStyle.Dotted })
      .alignItems(HorizontalAlign.Center).justifyContent(FlexAlign.Center)

      Column() {
        Image(this.targetImage2)
          .objectFit(ImageFit.Contain)
          .width('70%').height('70%')
          .allowDrop([uniformTypeDescriptor.UniformDataType.IMAGE])
          .onDrop((event: DragEvent, extraParams: string) => {
            // 通过uniformTypeDescriptor获取图片
            let data: UnifiedData = event.getData();
            let records: Array<unifiedDataChannel.UnifiedRecord> = data.getRecords();
            if (records[0].getType() ===uniformTypeDescriptor.UniformDataType.IMAGE) {
              let image: unifiedDataChannel.Image = records[0] as unifiedDataChannel.Image;
              this.targetImage2 = image.imageUri;
            }
          })
      }
      .width('70%').height('25%')
      .border({ width: 2, color: Color.Gray, radius: 5, style: BorderStyle.Dotted })
      .alignItems(HorizontalAlign.Center).justifyContent(FlexAlign.Center)

      // 落入数据类型为PixelMap
      Text('Data type is PixelMap').fontSize(14).margin({ top: 10 })
      Column() {
        Image(this.targetImage3)
          .objectFit(ImageFit.Contain)
          .width('70%').height('70%')
          .allowDrop([uniformTypeDescriptor.UniformDataType.OPENHARMONY_PIXEL_MAP])
          .onDrop(async (event: DragEvent, extraParams: string) => {
            // 通过uniformTypeDescriptor获取图片
            let data: UnifiedData = event.getData();
            let records: Array<unifiedDataChannel.UnifiedRecord> = data.getRecords();
            if (records[0].getType() ===uniformTypeDescriptor.UniformDataType.OPENHARMONY_PIXEL_MAP) {
              let record: unifiedDataChannel.SystemDefinedPixelMap = records[0] as unifiedDataChannel.SystemDefinedPixelMap;
              this.targetImage3 = await this.createPixelMap(record);

              // 落盘到本地
              const imagePackerApi = image.createImagePacker();
              let packOpts : image.PackingOption = { format: "image/jpeg", quality:98 };
              const path : string = this.context.cacheDir + "/pixel_map.jpg";
              let file = fileIo.openSync(path, fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE);
              imagePackerApi.packToFile(this.targetImage3, file.fd, packOpts).then(() => {
                // 直接打包进文件
              }).catch((error : BusinessError) => {
                console.error('Failed to pack the image. And the error is: ' + error);
              })
            }
          })
      }
      .width('70%').height('25%')
      .border({ width: 2, color: Color.Gray, radius: 5, style: BorderStyle.Dotted })
      .alignItems(HorizontalAlign.Center).justifyContent(FlexAlign.Center)

    }.width('100%').height('100%')
  }
}

### 鸿蒙原生应用开发 ArkTS 快速入门教程 #### 了解 ArkTS 编程语言 ArkTS 是一种专门为鸿蒙操作系统设计的编程语言,旨在简化应用程序开发过程并提高效率。该语言融合了许多现代编程特性,使得开发者能够更轻松地构建高性能的应用程序[^2]。 #### 设置开发环境 为了开始使用 ArkTS 进行开发,首先需要安装合适的 IDE 和工具链。推荐使用的集成开发环境是 DevEco Studio,它提供了完整的项目创建向导和支持 ArkTS 的插件。通过访问官方网站下载最新版本,并按照说明完成设置。 #### 创建第一个 ArkTS 应用程序 一旦开发环境准备好之后就可以着手建立首个基于 ArkTS 构建的小型 demo app 来熟悉其语法结构: 1. 打开 DevEco Studio 并新建一个 HarmonyOS 工程; 2. 在工程配置阶段选择支持 TypeScript 或 JavaScript 类型的语言选项; 3. 添加必要的依赖库来增强功能实现; 下面是一个简单的 "Hello World!" 实现例子: ```typescript // HelloWorld.ts 文件内容如下所示 import { Text, Column } from '@ohos/arkui'; export default function App() { return ( <Column> <Text>Hello World!</Text> </Column> ); } ``` 这段代码定义了一个名为 `App` 的组件,在其中包含了用于显示文本消息 `<Text>` 组件以及布局容器 `<Column>` 。当运行此应用程序时将会看到屏幕上呈现 “Hello World!” 字样[^5]。 #### 学习更多高级特性和最佳实践 随着对基础概念掌握程度加深后还可以进一步探索诸如状态管理、路由导航等方面的知识点。此外,《鸿蒙之光HarmonyOS NEXT原生应用开发入门》这本书籍和技术文档也是很好的参考资料之一,书中不仅介绍了理论知识还涵盖了大量实际案例分析帮助读者更好地理解如何运用这些技巧到具体场景当中去[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值