Swift中headerView内的动画制作

3
我目前正在尝试为用户的经验条添加动画。
如果我将动画放在collectionViewController中,动画会被成功加载(一次)。但是,如果我将动画放在headerView中(因为我想在个人资料图片上添加条形图),则该条形图会被多次启动。

enter image description here

这是我的代码(headerViewCell):

let shapeLayerXp = CAShapeLayer()

override func layoutSubviews() {
      super.layoutSubviews()
      showUserXp()
      self.animateXp(toValue: 1)
}

func showUserXp() {
      let center = profileImage.center
      let circularPath = UIBezierPath(arcCenter: center, radius: 40, startAngle: -CGFloat.pi / 2, endAngle: 2 * CGFloat.pi, clockwise: true)
      shapeLayerXp.path = circularPath.cgPath

      let color = UIColor(red: 122 / 255, green: 205 / 255, blue: 186 / 255, alpha: 1)

      shapeLayerXp.strokeColor = color.cgColor
      shapeLayerXp.lineWidth = 4
      shapeLayerXp.fillColor = UIColor.clear.cgColor
      shapeLayerXp.lineCap = kCALineCapRound

      shapeLayerXp.strokeEnd = 0

      self.contentView.layer.addSublayer(shapeLayerXp)


}

func animateXp(toValue: Int) {
      let basicAnimation = CABasicAnimation(keyPath: "strokeEnd")
      basicAnimation.toValue = toValue
      basicAnimation.duration = 2

      basicAnimation.fillMode = kCAFillModeForwards
      basicAnimation.isRemovedOnCompletion = false

      shapeLayerXp.add(basicAnimation, forKey: "urSoBasic")
}
是这样启动的:
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {

      let headerViewCell = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "Header", for: indexPath) as! UserHeaderView

// ....

return cell

}

你正在layoutSubViews()中调用showUserXp()animateXp(),因此每次系统决定布局标题单元格时都会再次发生动画。显然,当集合视图滚动时会发生这种情况,但也可能在其他时间发生,例如设备旋转、标题单元格重用等。你想要实现什么目的?它只应该在控制器首次显示时进行动画,然后只在某些其他时间进行动画吗? - Upholder Of Truth
我想在视图控制器启动时(仅一次)对该条形进行动画处理。 - KevinB
这个控制器只有一个表头单元格吗?该单元格是如何定义的(使用Storyboard、Xib文件还是手动创建)? - Upholder Of Truth
我编辑了我的第一篇帖子,以展示单元格是如何定义的。 - KevinB
1个回答

1

假设控制器中只有一个标题单元格,除非数据发生更改(例如不同的用户),那么您可以在视图控制器上设置属性来指示是否已显示动画。

可以这样实现:

var animatedHeader = false // Starts false because we want an animation the first time.

当第一次获取表头单元格时,您可以像这样决定是否触发动画:

override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {

      let headerViewCell = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "Header", for: indexPath) as! UserHeaderView

      if !self.animatedHeader {
          cell.showUserXp()
          cell.animateXp()

          self.animatedHeader = true
      }

      return cell
}

showUserXp()animateXp()方法当然需要是公共的。

使用此方法,标题单元格仅在第一次出队并因此显示时才会进行动画处理。

如果您确实想再次执行动画处理,则只需重置animateHeader属性并重新加载集合视图(或仅重新加载标题)。

如果有多个标题,则需要分别跟踪每个标题。

编辑:由于showXPanimateXP函数的定义方式,这确实需要(意外地)使用相同的单元格。 如果我自己做这件事,我可能会使用更像这样的方法:

func showUserXp(animated: Bool) {
    let center = profileImage.center
    let circularPath = UIBezierPath(arcCenter: center, radius: 40, startAngle: -CGFloat.pi / 2, endAngle: 2 * CGFloat.pi, clockwise: true)
    shapeLayerXp.path = circularPath.cgPath

    let color = UIColor(red: 122 / 255, green: 205 / 255, blue: 186 / 255, alpha: 1)

    shapeLayerXp.strokeColor = color.cgColor
    shapeLayerXp.lineWidth = 4
    shapeLayerXp.fillColor = UIColor.clear.cgColor
    shapeLayerXp.lineCap = kCALineCapRound

    shapeLayerXp.strokeEnd = 0

    self.contentView.layer.addSublayer(shapeLayerXp)

    if animated {
        let basicAnimation = CABasicAnimation(keyPath: "strokeEnd")
        basicAnimation.toValue = toValue
        basicAnimation.duration = 2

        basicAnimation.fillMode = kCAFillModeForwards
        basicAnimation.isRemovedOnCompletion = false

        shapeLayerXp.add(basicAnimation, forKey: "urSoBasic")
    } else {
        shapeLayerXp.strokeEnd = 1
    }
}

然后您可以像这样使用它:
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {

    let headerViewCell = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "Header", for: indexPath) as! UserHeaderView

    if self.animatedHeader {
        cell.showUserXp(animated: false)
    } else {
        cell.showUserXp(animated: true)

        self.animatedHeader = true
    }

    return cell
}

现在,您可以使用动画显示标题单元格,也可以不使用动画,使用动画的方式由animatedHeader属性控制。这不再依赖于特定单元格被出队。


在这种情况下,我宁愿在VC中持有对headerViewCell的指针。collectionView没有义务在每次调用时返回相同的实例。这依赖于实现细节。 - Daij-Djan
使用我的方法来扩展很容易。例如,假设您现在有一个包含500个项目的列表,只需保留一个控制它们是否已呈现给用户(因此是否已动画显示标题)的50个布尔值数组即可。如果您保留对标题单元格的引用,则现在需要保留对500个标题单元格的引用,并且现在绕过了出队整个思想,这是为了性能和内存原因而进行的。 - Upholder Of Truth
你的方法有缺陷,我担心。它只在集合视图出列正确的单元格时才起作用。因此,你依赖于集合视图不重用视图。[已删除我的帖子并投了反对票] - Daij-Djan
1
你部分正确,因为单元格以未显示的用户XP开头(我没有完全检查)。 我只是真的使用给定的示例来显示如何仅动画一次。 如果我真的这样做,我会使用具有动画属性的函数来设置XP值。 然后第一次看到它时,我会显示它动画,然后每次都不会动画。 这样,您就不必关心使用哪个单元格。 我将更新我的答案以更清楚地显示此内容。 - Upholder Of Truth

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接