掌握UICollectionView的瀑布流布局:iOS实战指南

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

简介:UICollectionView是iOS中用于展示复杂布局的控件。本教程详细说明如何实现瀑布流布局,包括创建自定义UICollectionViewFlowLayout子类、处理列数和间距、动态调整单元格高度和宽度,并优化性能与用户体验。通过学习本教程,开发者将能熟练掌握UICollectionView的瀑布流效果实现。 技术专有名词:ZCCollectionView

1. UICollectionView与瀑布流布局简介

UICollectionView是iOS开发中用于展示大量数据和图片的重要控件之一,它能够通过灵活的布局来展示不同形式的内容集合。其中,瀑布流布局作为一种流行的布局方式,因其独特的排列和展示效果,被广泛应用于商品展示、图片墙等场景。

瀑布流布局模仿了自然景观中瀑布的流动特点,即各列高度不一,错落有致,从而为用户提供更加动态和丰富的视觉体验。与传统的网格布局相比,瀑布流布局可以更好地展示具有不同高度元素的集合,使得界面更加美观和实用。

本章将首先对UICollectionView及瀑布流布局进行基本介绍,然后在后续章节中深入探讨UICollectionViewFlowLayout的自定义实现、单元格尺寸计算、以及如何控制单元格间距和列数等相关技术细节。通过本章,读者可以对UICollectionView瀑布流布局有一个初步的了解,并为进一步的学习和实践奠定基础。

2. UICollectionViewFlowLayout的自定义实现

2.1 自定义布局的理论基础

2.1.1 布局属性的继承与覆盖

UICollectionViewFlowLayout作为UICollectionView的默认布局,遵循了一种继承与覆盖的布局策略。它允许开发者通过子类化的方式,继承并覆盖其布局属性,以实现自定义的布局效果。继承默认布局类的子类可以重新定义布局中的各项参数,比如item的尺寸、间距以及滚动方向等。

从技术实现上看,自定义布局时,可以重写如 itemSize minimumInteritemSpacing minimumLineSpacing 等方法,为不同的item定制具体的布局属性。比如,如果你想为头部的几个item设置更大的尺寸,可以重写 sizeForItemAt 方法,根据item的位置返回相应的尺寸。

override func sizeForItemAt indexPath: IndexPath) -> CGSize {
    if indexPath.item == 0 {
        return CGSize(width: self.collectionView.bounds.width, height: 100)
    }
    return super.sizeForItemAt(indexPath)
}
2.1.2 布局方法的扩展与重写

自定义布局的灵活性不仅限于重写现有方法,还可以扩展新的方法来实现更复杂的布局需求。例如,UICollectionViewFlowLayout是支持水平滚动的,如果需要垂直滚动,则可以重写 initialLayoutAttributesForAppearingItem(at:) 方法,并通过这个方法返回一个自定义的布局属性。

override func initialLayoutAttributesForAppearingItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
    let attributes = super.initialLayoutAttributesForAppearingItem(at: indexPath)
    attributes?.frame.origin.y = indexPath.item * attributes?.frame.height ?? 0
    return attributes
}

通过重写这个方法,我们可以在item出现的时候进行垂直方向的滚动定位,实现垂直瀑布流的效果。

2.2 实现自定义布局的关键步骤

2.2.1 初始化布局对象和配置属性

初始化自定义UICollectionViewFlowLayout的布局对象时,通常需要在 viewDidLoad 方法中进行配置。

lazy var flowLayout: CustomFlowLayout = {
    let layout = CustomFlowLayout()
    layout.scrollDirection = .vertical
    // 其他属性可以根据需要进行配置
    return layout
}()

初始化布局对象后,我们配置了布局的滚动方向为垂直,这样我们就有了瀑布流布局的基础。

2.2.2 实现布局的尺寸计算方法

接下来要实现的是布局的尺寸计算方法,例如 sizeForItemAt ,这个方法用来确定每个item的尺寸。

override func sizeForItemAt indexPath: IndexPath) -> CGSize {
    // 这里可以基于item的内容动态计算尺寸
    let contentSize = items[indexPath.item].content.size
    // 假设contentSize是已经计算好的内容尺寸
    return CGSize(width: self.collectionView.bounds.width, height: contentSize.height)
}
2.2.3 实现布局的更新和重载机制

布局的更新和重载机制对动态内容变化尤为重要。我们可以通过监听数据源的变更,并在数据变化时,调用 invalidateLayout() 方法来让布局失效并重新计算。

func updateData(newData: [Item]) {
    self.items = newData
    collectionView?.collectionViewLayout.invalidateLayout()
    collectionView?.reloadData()
}

通过调用 invalidateLayout() ,系统会自动调用 prepare() 方法重新准备布局,然后通过调用 layoutAttributesForElements(in:) layoutAttributesForItem(at:) 方法来重新计算布局属性。

2.3 自定义布局的应用实例

2.3.1 瀑布流布局的布局特点

瀑布流布局的特点在于它的行高是不固定的,每行的item数量也是变化的。这允许布局能够自然地适应不同大小和比例的图片。在实现上,我们通过重写布局方法来动态计算每行的高度以及行的宽度,使布局能够适应内容的大小。

2.3.2 瀑布流布局的代码实现与调试

在具体实现时,我们需要对UICollectionViewFlowLayout进行扩展,添加计算方法来确定每行的item数量和高度。

func computeLineAttributes() -> [UICollectionViewLayoutAttributes] {
    let lineAttrs = [UICollectionViewLayoutAttributes]()
    // 此处省略具体的计算实现,涉及行高度、item宽度的计算
    return lineAttrs
}

在计算过程中,要考虑到不同设备和不同屏幕尺寸的适配,这意味着要对多种屏幕进行测试并调整布局参数。

这样,我们就完成了UICollectionViewFlowLayout的自定义实现的详细讲解,接下来可以进一步深入探讨如何通过代码实现和调试,使布局更加完善和符合实际项目的需求。

3. 计算单元格高度与estimatedItemSize

单元格尺寸计算的理论依据

在为UICollectionView实现一个布局时,单元格尺寸的计算是至关重要的一步。在设计布局时,我们需要考虑多个因素,包括单元格内部内容的布局和大小、UICollectionView的宽度和高度、以及滚动方向的改变对尺寸的影响。

UICollectionView 的尺寸约束模型

UICollectionView 作为iOS中的一个灵活的布局组件,它遵循自动布局(Auto Layout)来管理和适应不同的布局需求。在设计单元格时,需要确保内部元素和单元格本身遵循正确的约束。这些约束定义了单元格内容与单元格边界之间的关系,也定义了内容如何适应不同的屏幕尺寸和方向。

在讨论约束模型时,有几个关键概念需要理解:

  • 内容模式(Content Hugging) UICollectionView 中的单元格会根据内容模式来决定是否愿意被压缩或扩展。内容模式较高的视图会尽量扩展以填充多余空间,而较低的内容模式则会尝试保持其内容的大小不变。
  • 优先级(Content Compression Resistance) :此属性决定在系统压缩单元格以适应布局时的优先级。较高的压缩抵抗优先级意味着单元格内容更不容易被压缩。

estimatedItemSize 的作用与影响

在自定义UICollectionView布局时, estimatedItemSize 属性是计算单元格尺寸的核心。它为 UICollectionView 提供了预先估计的单元格大小,这个估计值可以用来优化布局过程。

  • 布局优化 UICollectionView 可以使用 estimatedItemSize 来进行优化,比如预先分配内存和减少布局计算的次数。在滚动时,这一估计尺寸用于快速渲染即将到来的单元格,确保滚动流畅性。
  • 动态尺寸 :在动态内容场景中, estimatedItemSize 可以提供一个初始尺寸,而实际的单元格尺寸可以在数据加载之后通过 UICollectionViewDelegateFlowLayout 中的方法动态调整。

estimatedItemSize 的设置策略

在实现自定义布局时, estimatedItemSize 的设置策略需要考虑到实际内容的变化范围和性能要求。

如何根据内容动态计算尺寸

根据内容动态计算尺寸时,开发者需要实现 UICollectionViewDelegateFlowLayout 协议中的 sizeForItemAt 方法。这个方法允许开发者根据每个单元格中的内容来定义其尺寸。

示例代码:

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    // 假设我们有一个数组来存储每个单元格的数据
    let itemCount = itemsArray.count
    let item = itemsArray[indexPath.item]
    // 根据内容动态计算高度
    var itemHeight: CGFloat = 100 // 默认高度
    if let text = item["text"] as? String {
        // 使用CoreText来计算文本的高度
        let textRect = text.boundingRect(with: CGSize(width: collectionView.bounds.width - 40, height: CGFloat.greatestFiniteMagnitude),
                                          options: .usesLineFragmentOrigin,
                                          attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16)],
                                          context: nil)
        itemHeight = ceil(textRect.height)
    }
    return CGSize(width: collectionView.bounds.width, height: itemHeight)
}

在上述代码中, itemsArray 是一个包含字典的数组,每个字典代表一个单元格的内容。根据字典中的文本内容,使用CoreText来计算文本的尺寸,并返回计算结果。

estimatedItemSize 的最佳实践

在确定 estimatedItemSize 的最佳值时,需要在布局的性能和精确度之间找到平衡点。理想情况下,这个值应该接近实际单元格大小,但不需要完全精确。

  • 估算值 :如果所有单元格的大小相近, estimatedItemSize 可以设置为这个预期的尺寸。例如,一个带有图片和标题的单元格,其高度可能大致在200像素左右。
  • 动态调整 :在内容尺寸动态变化的情况下,可以将 estimatedItemSize 设置为一个适当的默认值,并在 sizeForItemAt 中根据实际内容进行调整。

实现单元格高度动态调整的逻辑

动态调整单元格高度是自定义UICollectionView布局的一个重要方面,特别是在处理内容依赖于用户输入或数据的场景。

响应单元格内容变化的机制

为了响应单元格内容的变化,需要有机制来监听这些变化,并触发布局更新。

  • 内容变化监听 :可以通过观察数据源数组的更改,或者通过监听特定数据模型对象的变化来检测内容的变化。
  • 布局更新触发 :一旦检测到内容变化,应该调用 invalidateLayout() 方法来请求布局更新,这将触发 UICollectionView 重新调用 sizeForItemAt 方法。
class ContentModel: ObservableObject {
    @Published var text: String
    init(text: String) {
        self.text = text
    }
    func updateText(newText: String) {
        self.text = newText
        // 更新通知
        self.objectWillChange.send()
    }
}

// 使用时,在数据更新后调用
contentModel.updateText(newText: "更新的文本")

上述代码展示了如何通过 @Published 属性包装一个文本字符串,以便在文本更新时通知视图进行更新。

动态尺寸变化下的布局更新

当单元格尺寸由于内容变化而需要调整时,整个布局可能需要更新以适应新的尺寸。

  • 滚动位置保持 :在尺寸变化时,用户滚动位置的保持是一个需要考虑的问题。一个好的实践是在更新布局后能够尽可能地保持用户滚动位置不变。
  • 布局性能考虑 :在调整尺寸时,需要确保布局更新的性能开销尽可能小,避免因为尺寸调整导致滚动卡顿。
extension UICollectionView {
    func reloadItem(at indexPath: IndexPath) {
        let frame = self.rect(for: indexPath)
        self.reloadItems([indexPath])
        if let index = self.indexPath(for: frame.origin) {
            self.scrollToItem(at: index, at: .left, animated: false)
        }
    }
    func updateItemSize(at indexPath: IndexPath) {
        self.invalidateLayout()
        self.reloadItem(at: indexPath)
    }
}

这个扩展方法 updateItemSize 允许我们更新指定单元格的尺寸,首先使布局无效,然后重新加载单元格。这里还考虑到了保持滚动位置,通过重用 rect(for:) 方法来重新定位滚动位置。

4. 控制单元格间距与列数

在第四章中,我们将深入探讨如何在UICollectionView中精确地控制单元格的间距以及列数的设置。布局的精细控制对于设计美观且功能强大的UI至关重要,特别是在需要动态调整以适应不同屏幕尺寸和内容的情况下。本章节将提供理论依据、实现策略以及相关应用案例。

单元格间距与列数的布局属性

控制间距的属性与方法

间距控制是布局设计中不可或缺的一部分,它直接影响到视觉效果和用户交互体验。在UICollectionView中,开发者可以通过设置layout对象的属性来控制单元格之间的间距以及列与列之间的间距。

间距相关属性

间距主要通过以下属性来控制:

  • minimumLineSpacing :相邻行或列之间的最小间距。
  • minimumInteritemSpacing :同一行或列中相邻项之间的最小间距。
  • sectionInset :定义每个section边缘与其内部第一个和最后一个元素之间的间距。

这些属性可以在UICollectionViewFlowLayout中被设置,从而实现间距的全局调整。

示例代码
let layout = UICollectionViewFlowLayout()
layout.minimumLineSpacing = 10
layout.minimumInteritemSpacing = 5
layout.sectionInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
collectionView.collectionViewLayout = layout

通过调整这些属性,开发者能够创建出既美观又符合功能需求的布局。

列数与行数的计算与设置

列数和行数的设置对于布局的总体结构有着决定性的影响。在某些情况下,开发者可能希望根据内容的多少来动态调整列数和行数,以实现最佳的视觉效果和用户界面布局。

列数与行数的计算

要动态计算列数和行数,开发者需要根据以下条件来编写逻辑:

  • collectionView.frame :UICollectionView的尺寸。
  • itemSize :单元格的尺寸。
  • minimumLineSpacing minimumInteritemSpacing :用于计算行间和列间的额外空间。
示例代码
let itemWidth = collectionView.frame.width / 2 - layout.minimumInteritemSpacing
let itemHeight = itemWidth // 假设宽高比为1:1
let numberOfItemsPerRow = Int(ceil(CGFloat(collectionView.frame.width) / (itemWidth + layout.minimumInteritemSpacing)))
let numberOfItemsPerColumn = Int(ceil(CGFloat(collectionView.frame.height) / (itemHeight + layout.minimumLineSpacing)))

在实际应用中,开发者需要综合考虑间距和单元格尺寸,以计算出合理的行数和列数。

实现可变间距与列数的布局

响应不同屏幕尺寸的布局适配

为了确保UICollectionView布局在不同屏幕尺寸的设备上均能正常工作,开发者需要在设计布局时考虑屏幕尺寸的变化。

屏幕尺寸适配策略

适配不同屏幕尺寸的策略通常包括以下步骤:

  1. 获取当前设备的屏幕尺寸。
  2. 根据屏幕尺寸计算合适的间距和列数。
  3. 更新UICollectionView的布局设置。
示例代码
func configureCollectionViewLayout() {
    let screenWidth = UIScreen.main.bounds.width
    let screenHeight = UIScreen.main.bounds.height
    // 根据屏幕尺寸设置布局参数
    let layout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
    if screenWidth < 400 {
        layout.minimumInteritemSpacing = 5
        layout.minimumLineSpacing = 5
    } else {
        layout.minimumInteritemSpacing = 10
        layout.minimumLineSpacing = 10
    }
    // 根据屏幕高度决定行数
    let itemHeight = screenWidth / 2 - layout.minimumInteritemSpacing
    let numberOfItemsPerRow = Int(ceil(screenWidth / (itemHeight + layout.minimumLineSpacing)))
    let numberOfRows = Int(ceil(CGFloat(collectionView.numberOfItems(inSection: 0)) / CGFloat(numberOfItemsPerRow)))
    // 动态调整高度
    let sectionInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
    let itemSize = CGSize(width: screenWidth / CGFloat(numberOfItemsPerRow) - layout.minimumInteritemSpacing, height: itemHeight)
    layout.itemSize = itemSize
    layout.sectionInset = sectionInset
    layout.estimatedItemSize = itemSize
    collectionView.collectionViewLayout = layout
}

基于内容自适应的列数调整策略

在某些情况下,开发者可能希望根据内容的长度自动调整列数,以实现最佳的展示效果。

基于内容的列数调整

为了实现基于内容自适应的列数调整,开发者可以采取以下策略:

  1. 预估内容长度,根据内容长度动态调整每行显示的列数。
  2. 使用估计的itemSize来优化滚动性能。
示例代码
func adjustColumnsBasedOnContent() {
    let screenWidth = collectionView.frame.width
    let itemSize = CGSize(width: screenWidth / 2, height: screenWidth / 2)
    let layout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
    // 根据内容动态计算itemSize
    let estimator: UICollectionViewLayoutAttributesEstimation closure = { (forItemAt: IndexPath) -> UICollectionViewLayoutAttributes in
        let height = calculateContentHeight(for: itemSize.width)
        return UICollectionViewLayoutAttributes(forCellWith: forItemAt)
            .apply { $0.size.height = height }
    }
    layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
    layout.estimatedItemSize = estimator

    // 根据itemSize调整间距
    let numberOfItemsPerRow = Int(ceil(screenWidth / (itemSize.width + layout.minimumInteritemSpacing)))
    let numberOfItemsPerColumn = Int(ceil(CGFloat(collectionView.numberOfItems(inSection: 0)) / CGFloat(numberOfItemsPerRow)))
    let sectionInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
    layout.itemSize = itemSize
    layout.sectionInset = sectionInset
    collectionView.collectionViewLayout = layout
}

开发者应当注意,动态调整列数和间距可能会影响性能,因此应当在实际项目中进行适当的优化。

单元格间距与列数的应用案例

案例分析:多列瀑布流布局的实现

多列瀑布流布局常用于图片展示,例如社交网络中的图片墙。在这一布局中,间距的控制和列数的设置显得尤为重要,它不仅需要保持内容的整洁性,还要兼顾用户体验。

实现步骤
  1. 设计UICollectionView的flow layout。
  2. 确定每行显示多少列,以及间距和列宽的合理值。
  3. 根据内容动态调整列数,确保内容的展示效果。
示例代码
class MultiColumnWaterfallLayout: UICollectionViewFlowLayout {
    override init() {
        super.init()
        self.minimumInteritemSpacing = 5
        self.minimumLineSpacing = 10
    }
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        let superAttrs = super.layoutAttributesForElements(in: rect)
        // 这里可以添加自定义间距调整代码
        return superAttrs
    }
    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        let attrs = super.layoutAttributesForItem(at: indexPath)
        // 这里可以添加根据内容动态调整item尺寸的代码
        return attrs
    }
}

实际项目中的应用与优化

在实际项目中,开发者需要根据具体的应用场景和需求来调整间距和列数。同时,要关注性能的优化,尤其是在处理大量数据和动态布局时。

优化策略

以下是一些常见的优化策略:

  • 缓存计算结果以避免重复计算。
  • 合理使用预估尺寸以优化滚动性能。
  • 对于大数据集,使用懒加载和分页加载来减少内存使用。
示例代码
// 示例中展示了如何在数据更新时重新计算布局
func updateLayoutWithNewData() {
    collectionView.reloadData()
    (collectionView.collectionViewLayout as! UICollectionViewFlowLayout).invalidateLayout()
}

本章节通过理论阐述、实现策略、实际应用和性能优化,向读者展示了如何控制UICollectionView中的单元格间距与列数,旨在帮助开发者在实际项目中高效而准确地实现复杂布局。

5. UICollectionViewDataSource与Delegate的实现

在本章节中,我们将深入探讨如何通过UICollectionViewDataSource和UICollectionViewDelegateFlowLayout实现各种数据源管理和布局管理功能,以及如何处理滚动事件,以增强用户交互体验。

5.1 UICollectionViewDataSource 的理论与实践

5.1.1 数据源协议方法详解

UICollectionView的数据源协议 UICollectionViewDataSource 负责提供cell数量和内容。核心方法包括:

  • numberOfItems(in:) :返回某个特定section的item数量。
  • collectionView(_:cellForItemAt:) :提供对应索引路径的cell。

这两个方法是实现数据源最基本的两个接口。为使cell更具互动性,我们还可以实现以下代理方法:

  • collectionView(_:willDisplay:forItemAt:) :在cell即将显示前调用,可以在此处进行一些布局调整。
  • collectionView(_:didEndDisplaying:forItemAt:) :在cell完全消失后调用,可以用于管理对象的内存释放。

5.1.2 动态数据源的管理与更新

随着应用需求的变化,数据源可能需要动态更新。我们可以通过以下方法在运行时修改数据源:

  • insertItems(at:with:) :插入一个或多个新的item。
  • deleteItems(at:with:) :删除一个或多个item。
  • reloadItems(at:) :重新加载一个或多个item。

这些方法提供了一种高效更新数据和视图的方式,确保在数据变化时能够快速更新界面,同时减少不必要的资源消耗。

5.2 处理不同列高度变化的逻辑

5.2.1 多种高度单元格的布局管理

UICollectionView能够处理不同高度的单元格。实现这一功能的关键在于 UICollectionViewDelegateFlowLayout heightForItemAt 方法。在这个方法中,根据每个item的具体内容返回不同的高度值:

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, heightForItemAt indexPath: IndexPath) -> CGFloat {
    // 根据indexPath或者其他条件返回不同的高度
    return CGFloat(80 + indexPath.row % 2 * 20)
}

这个高度计算逻辑可以根据实际的布局需求进行调整,实现复杂多变的布局效果。

5.2.2 高度变化时的滚动与布局同步

当单元格高度发生变化时,需要确保滚动位置和布局保持同步。这通常涉及以下几个步骤:

  1. 更新数据源,引起布局更新。
  2. heightForItemAt 中返回新的高度值。
  3. 通过调用 collectionView.layoutIfNeeded() 来强制立即进行布局更新。

5.3 UICollectionViewDelegateFlowLayout 的实现与滚动事件处理

5.3.1 实现自定义的布局属性

除了单元格高度之外, UICollectionViewDelegateFlowLayout 还可以自定义其他多个布局相关的属性,如:

  • insetForSection(at:) :设置section边缘的内边距。
  • minimumLineSpacingForSection(at:) :设置section中行之间的最小间距。
  • minimumInteritemSpacingForSection(at:) :设置section中单元格之间的最小间距。

例如,要为第一个section设置不同的内边距,可以这样实现:

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
    if section == 0 {
        return UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
    }
    return .zero
}

5.3.2 滚动事件的监听与响应

UICollectionView提供了滚动事件的监听机制。通过实现 UIScrollViewDelegate scrollViewDidScroll(_:) 方法,可以在用户滚动时获取当前的偏移量:

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    print("当前垂直偏移量:\(scrollView.contentOffset.y)")
}

这可以帮助开发者根据滚动状态进行特定的处理,例如懒加载图片,或是实现下拉刷新和上拉加载更多等交互效果。

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

简介:UICollectionView是iOS中用于展示复杂布局的控件。本教程详细说明如何实现瀑布流布局,包括创建自定义UICollectionViewFlowLayout子类、处理列数和间距、动态调整单元格高度和宽度,并优化性能与用户体验。通过学习本教程,开发者将能熟练掌握UICollectionView的瀑布流效果实现。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值