使用Swift构建iOS聊天气泡的实战指南

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:在iOS开发中,聊天气泡功能是社交应用的重要组成部分,通过使用Swift编程语言和iOS UI框架实现。本文将介绍使用AutoLayout与Size Classes来适配不同屏幕,利用UIView和CALayer绘制气泡图形,使用UIBezierPath定义气泡形状,以及实现气泡的填充、描边、圆角和剪切效果。同时,本文还会讲解如何利用NSAttributedString处理富文本,并使用UIStackView管理气泡布局。另外,还包括了如何添加动画效果,以及使用MVVM架构设计聊天功能的建议。本指南为开发者提供了构建iOS聊天气泡所需的核心技术和实践方法。

1. AutoLayout与Size Classes的应用

在现代移动应用开发中,尤其是在iOS平台上,灵活且适应不同屏幕尺寸的设计至关重要。开发者经常使用AutoLayout和Size Classes来解决布局问题,以便在不同设备上提供一致的用户体验。本章将详细介绍AutoLayout与Size Classes的基础知识,并深入探讨其在实际开发中的应用。

1.1 AutoLayout基础

AutoLayout是iOS开发中用来定义视图间关系的机制,它允许开发者通过一套规则来定义界面元素的布局,这些规则可以是视图间的距离、边距等。与传统的frame布局相比,AutoLayout更加灵活,能够适应屏幕尺寸和方向的变化。

// 示例代码:创建一个宽度为100点,高度为50点的视图,并设置水平居中于父视图
let myView = UIView()
myView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
    myView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
    myView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
    myView.widthAnchor.constraint(equalToConstant: 100),
    myView.heightAnchor.constraint(equalToConstant: 50)
])

1.2 Size Classes的概念与作用

Size Classes是iOS 8引入的一个概念,用于描述屏幕的尺寸类别,包括水平和垂直方向。它将屏幕尺寸划分为三类:普通(Regular)、紧凑(Compact)和任意(Any)。开发者可以根据不同的Size Class来设计布局,使得应用能够更好地适配不同的设备和屏幕尺寸。

理解Size Classes的核心是能够根据不同的类别定制布局。例如,iPhone在竖屏状态下水平方向是紧凑(Compact),垂直方向是普通(Regular),而在iPad上,则可能需要考虑更复杂的布局组合。

// 示例代码:根据不同的Size Class调整视图尺寸
if UIDevice.current.userInterfaceIdiom == .phone {
    // iPhone
    if process(UIDevice.current.sizeClass.width, == .compact) {
        // iPhone竖屏
    } else if process(UIDevice.current.sizeClass.width, == .regular) {
        // iPhone横屏
    }
} else if UIDevice.current.userInterfaceIdiom == .pad {
    // iPad
    // 根据需要处理iPad的横屏和竖屏
}

通过本章的学习,开发者应该能够熟练运用AutoLayout和Size Classes来构建适应不同设备的界面布局。在后续章节中,我们将进一步探讨如何使用UIView和CALayer进行更复杂的绘图,以及如何利用UIBezierPath和形状技术来创建更加丰富多彩的用户界面。

2. UIView和CALayer在绘图中的运用

在深入探讨UIView和CALayer在iOS应用开发中的绘图技术之前,我们首先要理解它们各自的角色和区别。UIView作为Core Animation框架的核心组件,承担了视图的布局和用户交互任务。而CALayer作为UIView的底层图层,负责处理视觉效果和动画。在选择它们时,通常基于所要实现的视觉效果复杂度和对性能的要求,进行取舍和权衡。

2.1 UIView与CALayer的基础知识

2.1.1 UIView与CALayer的对比和选择

UIView和CALayer两者在功能上虽有重叠,但各有专长。UIView适用于处理布局、响应触摸事件等涉及用户交互的场景。而CALayer则专注于图形渲染,拥有更多绘图相关的属性和方法,更擅长于处理复杂的动画效果。在实际开发中,两者常常协同工作,通过在UIView上添加CALayer层,增强界面的视觉表现。

选择使用UIView还是CALayer,关键在于应用的需求。如果需要视图参与事件处理或是需要复杂的用户交互,那么UIView是更合适的选择。如果仅仅需要画布来渲染图形和动画,使用CALayer会更高效。

2.1.2 UIView与CALayer的基本属性和方法

UIView和CALayer都拥有位置、大小、变换等基本属性。不同的是,CALayer提供了更多专门用于图形和动画的属性,如阴影、边框、圆角等,而UIView则提供了更多的布局和事件处理相关的方法。从代码结构上看,UIView继承自UIResponder,可以处理事件,而CALayer继承自NSObject,专注于绘图。

此外,两者在方法上也有差异,例如UIView有 addSubview: 方法来添加子视图,而CALayer使用 addSublayer: 方法。在实际应用中,根据需要在UIView上添加CALayer层来增强视图的视觉效果。

2.2 UIView和CALayer的高级运用

2.2.1 UIView和CALayer的动画效果实现

UIView和CALayer都能够实现动画效果,但实现方式略有不同。UIView的动画是通过在一定时间内连续改变视图属性的方式来实现的,使用起来比较直观、简单。例如,改变视图的背景色:

UIView.animate(withDuration: 1.0) {
    self.view.backgroundColor = .blue
}

而CALayer则提供了更为丰富的属性和方法,它允许我们对图层的每一个细节进行动画处理。例如,对图层的边框进行渐变动画:

let gradientLayer = CAGradientLayer()
gradientLayer.colors = [UIColor.red.cgColor, UIColor.blue.cgColor]
gradientLayer.frame = self.layer.bounds

self.layer.addSublayer(gradientLayer)

CABasicAnimation.animateKeypath("colors") {
    animation in
    animation.fromValue = [UIColor.red.cgColor]
    animation.toValue = [UIColor.blue.cgColor]
    animation.duration = 3.0
}

2.2.2 UIView和CALayer的交互处理

虽然UIView在处理用户交互方面是主角,但CALayer同样可以在某些情况下进行交互处理。通常,CALayer的交互是通过它的代理方法 actionForLayer(_:for:) 来实现的,该方法会返回一个CATransition对象,用于定义图层的转换效果。在某些特别的应用场景中,可以自定义CALayer的交互,例如实现图层点击事件:

class CustomLayer: CALayer {
    override class func actionForLayer(layer: CALayer, for event: UIEvent) -> CAAction? {
        let touchLocation = event.allTouches?.first?.location(in: layer)
        let containsPoint = layer.bounds.contains(touchLocation!)
        return containsPoint ? self.eyeCatcherAction() : nil
    }

    class func eyeCatcherAction() -> CAAction {
        return CATransition()
    }
}

在上面的代码示例中,我们创建了一个 CustomLayer 类,并重写了 actionForLayer(_:for:) 方法,当触摸事件发生在自定义图层内时,将触发一个自定义的动画效果。

UIView与CALayer的综合运用,可以构建出视觉效果丰富且性能优越的应用界面。随着我们深入探索和实践,会发现更多的优化和创新的空间,让我们的应用程序在视觉和交互上达到一个新的高度。

3. UIBezierPath的路径绘制技术

3.1 UIBezierPath的基本知识

3.1.1 UIBezierPath的创建和属性设置

UIBezierPath是iOS开发中用于绘制矢量图形的重要工具,它是一个轻量级的Core Graphics上下文,提供了一系列方法来创建线条、矩形、圆形和复杂的自定义形状。使用UIBezierPath时,首先需要创建一个实例,然后通过不同的方法来绘制路径。

创建UIBezierPath实例的代码示例如下:

let bezierPath = UIBezierPath()

在创建了路径后,可以通过 move(to:) addLine(to:) addArc(withCenter:radius:startAngle:endAngle:clockwise:) 等方法来构建路径的形状。例如,绘制一个简单的正方形的代码如下:

let squarePath = UIBezierPath()
squarePath.move(to: CGPoint(x: 10, y: 10))
squarePath.addLine(to: CGPoint(x: 10, y: 100))
squarePath.addLine(to: CGPoint(x: 100, y: 100))
squarePath.addLine(to: CGPoint(x: 100, y: 10))
squarePath.addLine(to: CGPoint(x: 10, y: 10))

UIBezierPath还提供了一系列属性用于设置线条的样式,如颜色、线宽等。设置颜色可以使用 strokeColor 属性:

squarePath.strokeColor = UIColor.red

设置线宽可以使用 lineWidth 属性:

squarePath.lineWidth = 2.0

3.1.2 UIBezierPath的路径绘制方法

绘制完成后,UIBezierPath提供了几种方法来渲染路径到视图上。最常用的包括填充( fill() )和描边( stroke() )。填充会将路径内的区域用颜色填充,而描边则只是在路径边缘绘制颜色。

在UIView的 draw(_:) 方法中使用UIBezierPath的绘制方法:

override func draw(_ rect: CGRect) {
    let path = UIBezierPath(rect: rect)
    path.fill()
}

这段代码会在UIView的绘制方法中使用当前的填充颜色填充整个视图。另外,也可以通过CAShapeLayer来使用UIBezierPath,这在视图不直接参与绘制时特别有用。

3.2 UIBezierPath的高级运用

3.2.1 UIBezierPath的路径变形和动画实现

UIBezierPath不仅能够绘制静态的图形,还可以通过变形和动画来制作动态的视觉效果。路径的变形可以通过 CGAffineTransform 进行,这允许对路径进行缩放、旋转和倾斜等变换。

例如,对路径进行顺时针旋转90度的代码如下:

let transform = CGAffineTransform(rotationAngle: CGFloat.pi / 2)
let transformedPath = squarePath.cgPath applying: transform

动画的实现可以借助于UIKit中的动画API。通过改变路径的属性并使用动画块来制作动画效果。例如,制作一个路径长度从0到全长的动画:

UIView.animate(withDuration: 2.0, animations: {
    squarePath.lineWidth = 10.0
}) { finished in
    // 动画完成后的回调
}

3.2.2 UIBezierPath在自定义控件中的应用

自定义控件经常需要绘制复杂的图形,UIBezierPath为自定义绘制提供了强大的支持。比如在自定义的UIView中,可以在 draw(_:) 方法中使用UIBezierPath来绘制复杂的形状和图案。

例如,创建一个自定义的圆形进度指示器:

override func draw(_ rect: CGRect) {
    let center = CGPoint(x: rect.midX, y: rect.midY)
    let radius = min(rect.width, rect.height) / 2
    let progressPath = UIBezierPath()
    progressPath.addArc(withCenter: center, radius: radius, startAngle: CGFloat(-.pi / 2), endAngle: CGFloat(.pi * 1.5), clockwise: true)
    progressPath.lineWidth = radius / 4
    progressPath.stroke()
}

以上代码段展示了如何使用UIBezierPath来绘制一个圆环,该圆环可以用来展示进度。

接下来,可以使用 CAShapeLayer 来进一步优化和处理动画效果,使圆环能够以动态形式展示进度变化。

总结

UIBezierPath为iOS开发者提供了强大的路径绘制能力,使得自定义图形和动画的创建变得简单而高效。通过学习UIBezierPath的创建、属性设置、路径绘制、路径变形和动画实现、以及在自定义控件中的运用,开发者可以创建出美观且功能丰富的界面元素。

4. 气泡的填充和描边技术

4.1 气泡的填充技术

4.1.1 气泡的填充方法和效果

气泡在聊天应用中是常见的UI元素,用于显示文字消息。气泡的填充技术主要是指在气泡形状的视图中填充颜色、图片或渐变色等。在iOS开发中,可以通过 CAShapeLayer 来定义气泡的形状,并通过设置图层的 fillColor 属性来改变填充颜色。

举例来说,可以创建一个 CAShapeLayer ,定义一个椭圆形的路径作为气泡的基础形状,然后通过设置 fillColor 属性来填充颜色:

let shapeLayer = CAShapeLayer()
shapeLayer.path = UIBezierPath(ovalIn: bubbleView.bounds).cgPath
shapeLayer.fillColor = UIColor.blue.cgColor
bubbleView.layer.addSublayer(shapeLayer)

这段代码首先创建了一个 CAShapeLayer 实例,然后定义了气泡视图的边界为一个椭圆形的路径,并将其赋值给 shapeLayer.path 。之后设置 fillColor 为蓝色,并将这个图层添加到气泡视图的层级中。这样就完成了一个简单的气泡填充。

4.1.2 气泡填充效果的优化和调试

气泡的填充效果在不同的场景下有不同的要求,例如在夜间模式下可能需要深色的填充颜色。优化和调试时,需要考虑以下几个方面:

  • 性能优化 :避免在动画或频繁更新的视图中使用复杂填充,例如使用渐变填充,因为这可能会导致性能问题。
  • 颜色选择 :根据聊天内容的类型和背景,选择合适的填充颜色。例如,重要消息可以使用醒目的颜色,而普通消息可以使用较为柔和的颜色。
  • 用户体验 :考虑到用户体验,气泡的填充效果应提供良好的视觉反馈,比如当用户触摸到气泡时,可以通过改变填充颜色或添加边框来提供反馈。
  • 适配性 :确保气泡在不同屏幕尺寸和分辨率的设备上都能保持良好的显示效果。

优化和调试过程中,可以利用Xcode的调试工具,如View Hierarchy和Color Inspector来查看图层的属性和视图的层次结构。同时,也可以编写测试用例,使用自动化测试框架来持续检查气泡的填充效果是否符合预期。

4.2 气泡的描边技术

4.2.1 气泡的描边方法和效果

描边是指给图形边缘添加线条的过程。在iOS中,可以通过设置 CAShapeLayer strokeColor lineWidth 属性来实现气泡的描边效果。描边可以给气泡带来更好的视觉层次感,让气泡在界面上显得更加突出。

以下是一个简单的代码示例,展示如何给气泡添加描边:

let shapeLayer = CAShapeLayer()
shapeLayer.path = UIBezierPath(ovalIn: bubbleView.bounds).cgPath
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = UIColor.black.cgColor
shapeLayer.lineWidth = 2.0
bubbleView.layer.addSublayer(shapeLayer)

在这段代码中,我们首先创建了一个 CAShapeLayer ,设置了气泡形状的路径和清空了填充颜色,使其呈现透明效果。接着设置了描边颜色为黑色,并设定了线条宽度为2.0。最后,将这个图层添加到气泡视图中。这样气泡就具有了一个清晰的黑色边缘。

4.2.2 气泡描边效果的优化和调试

在实际开发中,对于气泡的描边效果,开发者需要对以下几个方面进行优化和调试:

  • 边缘清晰度 :调整 lineWidth 确保描边在不同设备上都足够清晰。对于视网膜显示屏,可能需要使用更细的线条宽度以保持视觉的锐利度。
  • 与背景的对比度 :确保描边颜色与背景颜色之间有足够的对比度,以避免用户在界面上难以区分气泡。
  • 交互反馈 :在用户交互时(如点击或长按),可以通过改变描边颜色或宽度来提供视觉反馈。
  • 性能考虑 :在具有复杂动画或大量气泡的应用中,描边可以消耗大量的GPU资源。适当使用描边可以避免性能问题,特别是在低端设备上。

通过Xcode的Instruments工具,开发者可以监测图层渲染性能,找出描边可能造成的性能瓶颈。此外,可以利用Core Animation的 CADisplayLink 来同步动画与屏幕刷新率,以进一步优化描边效果的呈现。

在调试过程中,开发者可以使用Xcode提供的Core Animation调试工具来查看图层的渲染性能,例如通过帧分析器来发现潜在的性能问题。此外,开发者需要定期进行用户测试,收集反馈,调整描边的颜色和宽度以获得最佳的用户视觉体验。

5. 圆角和剪切mask的实现方法

5.1 圆角的实现方法

5.1.1 圆角的设置和效果

在 iOS 开发中,圆角是一种常见且非常实用的视觉效果,它可以美化界面元素,提供更友好的用户体验。UIView 提供了简洁的接口来为视图的边界设置圆角。通过修改视图的 cornerRadius masksToBounds 属性可以轻松实现这一效果。

以下是一段示例代码,演示如何为一个 UIView 设置圆角:

view.layer.cornerRadius = 10.0
view.layer.masksToBounds = true

在这段代码中, cornerRadius 属性定义了圆角的半径,单位为点。 masksToBounds 属性设置为 true 表示子视图将被限制在父视图的边界内,从而实现圆角效果。

5.1.2 圆角效果的优化和调试

圆角效果虽然简单,但是在不同尺寸的屏幕和不同方向的设备上可能会遇到问题。开发者需要注意到,视图的尺寸和圆角的大小会影响视觉效果,因此在多设备适配上需要进行调整和优化。

为了确保圆角在所有设备上都能正确显示,你可以使用 Autolayout 来适应不同屏幕尺寸,同时编写适配代码来处理不同设备方向变化时的圆角调整。

这里是一个使用 Autolayout 设置圆角的示例代码:

view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
    view.leadingAnchor.constraint(equalTo: parentView.leadingAnchor),
    view.trailingAnchor.constraint(equalTo: parentView.trailingAnchor),
    view.topAnchor.constraint(equalTo: parentView.topAnchor),
    view.bottomAnchor.constraint(equalTo: parentView.bottomAnchor),
])

// 圆角设置代码保持不变...

在调试过程中,通过 Interface Builder 设计界面或者直接在视图控制器中调整圆角设置,可以帮助开发者快速观察到效果并进行调整。

5.2 剪切mask的实现方法

5.2.1 剪切mask的设置和效果

剪切 mask 是一种更高级的视觉效果,它能够通过一个遮罩(mask layer)来剪切掉视图的一部分,从而实现复杂的形状或者对视图的边界进行精确控制。在 iOS 中,CALayer 提供了 mask 属性,允许开发者通过这个属性来实现剪切效果。

以下是一段使用 CALayer 来实现剪切 mask 的示例代码:

let maskLayer = CAShapeLayer()
maskLayer.path = UIBezierPath(rect: yourView.bounds).cgPath
yourView.layer.mask = maskLayer

在这段代码中,首先创建了一个 CAShapeLayer 对象作为遮罩层。然后使用 UIBezierPath 来定义遮罩层的形状。 rect 方法创建了一个矩形路径来表示遮罩层的边界。最后,将遮罩层设置给目标视图的 mask 属性。

5.2.2 剪切mask效果的优化和调试

使用剪切 mask 可以实现非常复杂的视觉效果,但是它的实现通常比简单的圆角更为复杂,并且性能消耗也更大。开发者需要在性能和视觉效果之间做出权衡。

在调试和优化时,可以通过分析和监控性能指标来评估剪切 mask 对性能的影响。如果发现性能瓶颈,可以尝试减少 mask 的复杂度或者寻找其他替代方案。

优化建议:

  • 减少遮罩层的路径复杂度,例如使用简单的几何形状而不是复杂的贝塞尔路径。
  • 尽量避免频繁地修改遮罩层的属性,这样可以减少视图重绘的次数。
  • 在不需要显示视图时,可以隐藏视图的 mask 来提高性能。

最终,优化剪切 mask 的最佳方式还是要根据实际应用场景和性能测试结果来决定。

以上章节详细介绍了在 iOS 开发中,如何通过代码和界面布局工具实现圆角和剪切 mask 的效果,并提供了相应的实现代码及性能优化建议。通过这些技术的应用,开发者可以为 iOS 应用创造出更多独特和精美的视觉效果。

6. MVVM架构在聊天功能中的应用

6.1 MVVM架构的基础知识

6.1.1 MVVM架构的定义和特点

MVVM(Model-View-ViewModel)架构模式是现代软件开发中经常采用的一种模式,尤其在iOS开发中,它为开发者提供了一种清晰的层次结构。它基于MVC(Model-View-Controller)架构改进而来,通过将数据逻辑(Model)、界面显示(View)和用户交互逻辑(ViewModel)解耦,实现了更好的模块化和代码重用。

MVVM架构的关键特点包括:

  • 双向数据绑定 :ViewModel与View之间通过数据绑定机制同步,视图层的任何变化都会自动更新到ViewModel,反之亦然。
  • 职责分离 :模型层负责数据逻辑,视图层负责界面展示,而ViewModel则作为两者之间的桥梁,处理用户交互逻辑和界面状态。
  • 便于单元测试 :由于逻辑分离,可以更容易地对ViewModel进行单元测试,而不需要依赖于UI。

6.1.2 MVVM架构在iOS开发中的应用

在iOS开发中,MVVM架构特别适用于需要频繁更新用户界面的数据驱动型应用,如聊天应用。它可以优化代码的组织结构,让开发团队更集中于业务逻辑的实现,同时保持视图的简洁和可维护性。

为了在iOS应用中使用MVVM架构,开发者通常会利用数据绑定库(如ReactiveCocoa、RxSwift等),通过声明式编程的方式实现数据与视图的绑定。此外,KVO(键值观察)机制也是实现MVVM架构的一种手段,尽管它比专门的数据绑定库更底层。

6.2 MVVM架构的高级运用

6.2.1 MVVM架构在聊天功能中的实现

在聊天应用中,MVVM架构的应用通常会涉及消息展示、发送消息、接收消息和用户状态更新等场景。以发送消息为例,其核心逻辑可以封装在ViewModel中,而消息内容的展示和用户交互则由View负责。

假设我们有一个发送消息的功能,我们可以按照以下步骤实现:

  1. 定义Model :消息模型包含消息内容、发送时间、发送人等属性。
  2. 创建ViewModel :ViewModel负责处理用户输入的消息,管理消息发送的逻辑,并响应Model的更新。
  3. 配置View :通过数据绑定或KVO,View监听ViewModel中的消息状态,并实时更新UI。

这里是一个简单的ViewModel实现示例代码:

class ChatViewModel {
    var messageText: String = ""
    let messageDidSend = PassthroughSubject<Message, Never>()
    func sendMessage() {
        let message = Message(content: messageText, sender: "Me")
        messageDidSend.send(message)
        messageText = ""
    }
}

在上面的代码中, messageDidSend 是一个输出信号,每当消息被发送时,它都会被触发。View层可以通过监听这个信号来更新UI,实现消息的显示。

6.2.2 MVVM架构在聊天功能中的优化和调试

在实现和运行MVVM架构的聊天功能时,优化和调试是不可或缺的步骤。一个有效的方法是使用单元测试来确保ViewModel的逻辑正确性。此外,利用Xcode的调试工具和日志记录可以帮助开发者追踪数据流动和状态变化。

在调试时,关注以下方面:

  • 响应性和性能 :确保ViewModel对Model变化的响应是及时的,同时视图层的更新没有造成性能问题。
  • 数据绑定的准确性 :数据绑定是否能够准确反映Model和View的状态。
  • 异常处理 :当网络请求失败或数据输入有误时,ViewModel是否能够提供恰当的反馈。

通过这些优化措施,我们可以确保聊天应用不仅功能完备,而且具有良好的用户体验和稳定性。

graph TD
    A[用户输入消息] -->|数据绑定| B(ViewModel接收消息)
    B -->|处理逻辑| C(消息是否符合发送标准?)
    C -- 是 -->|调用API发送消息| D(服务器)
    C -- 否 -->|显示错误信息| E(View显示错误)
    D -- 消息发送成功 -->|通知ViewModel| F(ViewModel更新状态)
    F -->|数据绑定| G(View更新显示)
    E -- 用户修改消息 --> A
    G --> H[用户看到新消息]

以上流程图展示了从用户输入消息到消息显示在界面上的整个流程,包括了消息是否被发送和接收的各种情况。

以上就是MVVM架构在聊天功能中的应用、实现和优化调试的详细解释。在实际开发过程中,遵循这些原则和实践,可以显著提高开发效率和应用质量。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:在iOS开发中,聊天气泡功能是社交应用的重要组成部分,通过使用Swift编程语言和iOS UI框架实现。本文将介绍使用AutoLayout与Size Classes来适配不同屏幕,利用UIView和CALayer绘制气泡图形,使用UIBezierPath定义气泡形状,以及实现气泡的填充、描边、圆角和剪切效果。同时,本文还会讲解如何利用NSAttributedString处理富文本,并使用UIStackView管理气泡布局。另外,还包括了如何添加动画效果,以及使用MVVM架构设计聊天功能的建议。本指南为开发者提供了构建iOS聊天气泡所需的核心技术和实践方法。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值