Вам нужно либо реализовать draw(_:)
, либо использовать CAAnimation
, но не то и другое одновременно.
Как правило, не реализуйте draw(_:)
для классов представлений. Это заставляет систему выполнять всю рендеринг на ЦП и не использует преимущества аппаратного ускорения рендеринга на основе плиток на устройствах iOS. Вместо этого используйте CALayers и CAAnimation, и пусть оборудование сделает за вас тяжелую работу.
Используя CALayers и CAAnimation, вы можете получить такой эффект:
Я бы посоветовал сделать следующее:
Создайте полный круг шестиугольника как CAShapeLayer
. (Код в вашем draw()
методе уже генерирует путь шестиугольника. Вы можете легко адаптировать его, чтобы установить свой путь шестиугольника в CAShapeLayer
.)
Добавьте этот слой-фигуру в качестве подслоя вида.
Создайте конус CAGradientLayer
с начальной точкой центра слоя и конечной точкой верхнего центра.
Добавьте к слою градиента цвета в диапазоне от прозрачных до любых непрозрачных, используя массив locations
, который растушевывает градиент по желанию.
установите градиентный слой в качестве маски на слой с шестиугольником.
Создайте CABasicAnimation, который поворачивает слой градиента вокруг оси Z на 1/4 оборота за раз. Запускайте эту анимацию постоянно, пока не закончите с анимацией.
Код для создания слоя градиента может выглядеть так:
let gradientLayer = CAGradientLayer()
gradientLayer.frame = self.bounds
gradientLayer.type = .conic
gradientLayer.colors = [UIColor.clear.cgColor,
UIColor.clear.cgColor,
UIColor.white.cgColor,
UIColor.white.cgColor]
let center = CGPoint(x: 0.5, y: 0.5)
gradientLayer.locations = [0, 0.3, 0.7, 0.9]
gradientLayer.startPoint = center
gradientLayer.endPoint = CGPoint(x: 0.5, y: 0)
(Вам нужно будет обновить границы слоя градиента, если границы представления-владельца изменятся.)
Код для поворота слоя градиента может выглядеть так:
private func animateGradientRotationStep() {
let rotation = CABasicAnimation(keyPath: "transform.rotation.z")
animationStepsRemaining -= 1
rotation.fromValue = rotationAngle
rotationAngle += CGFloat.pi / 2
rotation.toValue = rotationAngle
rotation.duration = 0.5
rotation.delegate = self
gradientLayer.add(rotation, forKey: nil)
// After a tiny delay, set the layer's transform to the state at the end of the animation
// so it doesnt jump back once the animation is complete.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
// You have to wrap this step in a CATransaction with setDisableActions(true)
// So you don't get an implicit animation
CATransaction.begin()
CATransaction.setDisableActions(true)
self.gradientLayer.transform = CATransform3DMakeRotation(self.rotationAngle, 0, 0, 1)
CATransaction.commit()
}
}
И вам нужно, чтобы ваше представление соответствовало протоколу CAAnimationDelegate
:
extension GradientLayerView: CAAnimationDelegate {
func animationDidStop(_ anim: CAAnimation,
finished flag: Bool) {
if animating && animationStepsRemaining > 0 {
animateGradientRotation()
}
}
}
Обратите внимание, что свойство преобразования слоя неявно анимируется, что означает, что по умолчанию система генерирует анимацию изменения. Мы можем воспользоваться этим фактом и просто внести некоторые изменения в неявную анимацию. Это упрощает функцию анимации:
// This version of the function takes advantage of the fact
// that a layer's transform property is implicitly animated
private func animateGradientRotationStep() {
animationStepsRemaining -= 1
rotationAngle += CGFloat.pi / 2
// MARK: - CATransaction begin
// Use a CATransaction to set the animation duration, timing function, and completion block
CATransaction.begin()
CATransaction.setAnimationDuration(0.5)
CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(name: .linear))
CATransaction.setCompletionBlock {
self.animationDidStop(finished:true)
}
self.gradientLayer.transform = CATransform3DMakeRotation(self.rotationAngle, 0, 0, 1)
CATransaction.commit()
// MARK: CATransaction end -
}
Эта версия требует немного другой функции завершения, поскольку она не использует CAAnimation:
func animationDidStop(finished flag: Bool) {
delegate?.animationStepComplete(animationStepsRemaining)
if animating && animationStepsRemaining > 0 {
animateGradientRotationStep()
}
Я создал небольшой пример приложения, которое создает такую анимацию.
Вы можете загрузить демонстрационное приложение с Github по по этой ссылке .
Одна часть вашего примера анимации, которую я не знаю, как скопировать, - это тот факт, что цвет шестиугольника вначале кажется ярко-белым, а затем переходит в желтый. В моем примере приложения создается анимация, в которой шестиугольник имеет фиксированный цвет и переходит от непрозрачного к прозрачному.
Вот README из проекта:
ПолярныйГрадиентMaskView
В этом проекте показано, как использовать конический градиент для маскировки вида и создания круговой анимации.
Он использует CAGradientLayer
типа .conic
, настроенный как в основном непрозрачный, причем последняя половина становится прозрачной. Он устанавливает слой градиента как маску на слой-фигуру, содержащий желтый шестиугольник.
Слой гадиента выглядит так:
(Отображается синим цветом на сером фоне шахматной доски, чтобы вы могли видеть переход от непрозрачного к прозрачному.)
Непрозрачные (синие) части градиента делают видимым слой-фигуру. Прозрачные части градиента скрывают (маскируют) эти части слоя-фигуры, а частично прозрачные части градиентного слоя делают эти части слоя-фигуры частично прозрачными.
Анимация просто поворачивает слой градиента по оси Z вокруг центра слоя. Он поворачивает слой на 1/4 оборота за раз, и каждый раз, когда этап анимации завершается, он просто создает новую анимацию, которая поворачивает маску еще на 1/4 оборота.
Немного сложно понять, что происходит, когда вы маскируете форму шестиугольника. Я создал вариант, в котором я добавил представление изображения как часть пользовательского представления. Анимация для этого выглядит так:
Окно приложения выглядит так:
person
Duncan C
schedule
03.06.2021
CGMutablePath
имеет очень классную функциюaddArc(tangent1End:tangent2End:radius:)
, которая упрощает добавление закругленных углов под произвольными углами. Использование этого значительно упростило бы ваш код, строящий ваш шестиугольник. Единственный триггер, который вам понадобится, это триггер для вычисления ваших вершин. См. Мой проект github.com/DuncanMC/RoundedCornerPolygon для универсального способа создания произвольных многоугольников с смесь закругленных и нескругленных углов. - person Duncan C   schedule 04.06.2021