Как создать анимацию протирания круга с перьями в iOS?

Мой вопрос требует небольшого пояснения. Если вы хотите перейти к вопросу, найдите вопрос, выделенный жирным шрифтом: ближе к концу.

Довольно легко создать анимацию протирания круга в iOS или Mac OS с помощью базовой анимации и масок.

Один из способов - установить CAShapeLayer в качестве маски в представлении (обычно представление изображения), установить круг в слое формы и анимировать круг от 0 размера до достаточно большого, чтобы покрыть все изображение (r = sqrt(height^2 + width^2)

Другой способ - использовать радиальный CAGradientLayer в качестве маски, настроить центр градиента как непрозрачный цвет и иметь внезапный переход к очистке сразу за углами изображения. Затем вы анимируете значения позиций слоя градиента, чтобы переместить чистый цвет в центр изображения.

Оба этих подхода создают вытеснение круга с острыми краями, в результате чего изображение сжимается до точки и исчезает.

Этот эффект выглядит так:

введите описание изображения здесь

Однако классическая кинематографическая анимация вытеснения круга имеет скошенный край, а не резкую границу. Внешний край круга полностью прозрачный. По мере того, как вы приближаетесь к центру, изображение становится все более и более плотным на довольно коротком расстоянии, а затем изображение становится полностью непрозрачным оттуда в центр. По мере того как круг сжимается, толщина перехода от прозрачного к непрозрачному остается постоянной, а круг воняет до точки и исчезает. (Маска представляет собой непрозрачный круг посередине и имеет слегка размытый край, а радиус круга уменьшается до 0, а толщина размытого края остается постоянной.)

Вот пример протирания круга старой школы. Технически это Iris Out, где переход к черному, но идея та же:

https://youtu.be/IqDhAW3TDR8?t=90

Я понятия не имею, как добиться кругового вытеснения со скошенным краем, используя слой-фигуру в качестве маски. Слои-фигуры всегда имеют острые края. Я мог бы подойти довольно близко, используя слой-фигуру, где круг обведен с непрозрачностью 50%, а середина круга заполнена цветом с непрозрачностью 100%, но это не приведет к плавному переходу от прозрачного к непрозрачному, который Я после.

Другой подход, который я пробовал, - использовать радиальный градиент с тремя цветами. Я установил внутренние цвета как непрозрачные, а внешний - как чистый. Я установил положение внешнего цвета как ›1 (скажем, 1.2), среднее положение - 1, а внутреннее положение - 0. Массив местоположений содержит [1.2, 1.0, 0], что делает всю часть маски, покрывающей изображение, непрозрачной, с плавный переход к прозрачному, который происходит за краями изображения.

Затем я делаю анимацию массива местоположений в 2 этапа. На первом этапе я анимирую массив местоположений в [0, 0, 0.2]. Это приводит к тому, что полоса растушевки приближается к углам и останавливается на 0,2 от центра изображения.

На 2-м шаге я анимирую массив местоположений от [0, 0, 0.2] до [0,0,0]. Это приводит к тому, что внутренний, от прозрачного до непрозрачного, центр сжимается до точки и исчезает.

Эффект нормальный, и если я запускаю его с узкой полосой размытия и быстрой продолжительностью, он выглядит прилично, но не идеально.

Вот как это выглядит с диапазоном размытия 0,2 и продолжительностью (я думаю) 0,25 секунды:

введите описание изображения здесь

Однако если я сделаю паузу между этапами анимации, вы увидите недостатки в эффекте:

введите описание изображения здесь

Вопрос: есть ли способ анимировать круг с небольшим радиусом размытия (5 точек или около того) от полного размера вида до 0 с помощью Core Animation?

Тот же вопрос относится и к другим типам анимации протирания: замочная скважина, звезда, протирание сбоку, прямоугольник и многие другие. Для них трюк с радиальным градиентом, который я использую для вытеснения с перьями, вообще не сработает. (Вы создаете эти специализированные анимации протирания с помощью CAShapeLayer, который представляет собой форму, которую хотите анимировать, а затем регулируете ее размер от большого до маленького.

Было бы легко создать статический размытый круг (или другую форму), сначала нарисовав круг, а затем применив к нему фильтр размытия основного изображения, но в iOS нет способа анимировать это, а фильтр размытия CI работает слишком медленно. iOS, даже если бы вы могли.

Вот ссылка на проект, который я использовал для создания анимации в этом посте с использованием описываемого мною трехцветного радиального градиента: https://github.com/DuncanMC/GradientView.git.

Редактировать:

Основываясь на комментарии Джеймса, я попытался использовать слой-фигуру с тенью в качестве маски. Эффект находится на правильном пути, но все еще есть резкий переход от непрозрачной части изображения к большей части прозрачной части, которая маскируется теневым слоем. Даже с непрозрачностью тени 1.0 тень по-прежнему остается прозрачной. Вот как это выглядит:

введите описание изображения здесь

Частично прозрачность цвета линии круга помогает некоторым и дает переход между непрозрачной частью изображения и большей частью прозрачной тенью. Вот изображение выше, но обводка слоя-фигуры с шириной линии 2 точки и непрозрачностью 0,6:

введите описание изображения здесь

Редактировать # 2: РЕШЕНО!

Идея Джеймса об использовании shadowPath - это решение. Вам не нужно ничего в вашем слое маски, кроме shadowPath. Если вы это сделаете, вы можете использовать свойство shadowRadius для управления степенью размытия и плавно анимировать траекторию тени от круга, который полностью охватывает вид до точки исчезновения, за один шаг.

Я обновил свой проект github по адресу https://github.com/DuncanMC/GradientView.git, чтобы включить для сравнения анимацию радиального градиента и анимацию shadowPath.

Вот как выглядит готовый эффект, сначала с радиусом размытия 5 пикселей:

Вытеснение круга на основе ShadowPath с 5-пиксельным размытием

А затем с радиусом размытия 30 пикселей:

введите описание изображения здесь


person Duncan C    schedule 06.09.2020    source источник
comment
Что ж, я собирался предложить анимировать виньетирование CIFilter. Но затем я наткнулся на ваше возражение о том, что есть «недостатки», которые я не вижу в том, что вы уже делаете. Боюсь, что читатель останется без четкого положительного представления о том, как будет выглядеть цель.   -  person matt    schedule 07.09.2020
comment
Я попытаюсь найти пример перехода с протиранием кинематографического круга, чтобы добавить к моему вопросу в качестве примера того, что мне нужно. Статья Wiki о переходах через стирание (en.wikipedia.org/wiki/Wipe_(transition) показывает несколько примеров переходов с стиранием, которые мне нужны, но не включает стирание круга, как назло.   -  person Duncan C    schedule 07.09.2020
comment
При настройке процента размытия, равном 1%, анимация выглядит почти так же, как слегка растушеванный круг. По мере того, как я увеличиваю процент размытия, он выглядит все менее и менее естественным. Я только что выпустил новую версию, в которой есть элементы управления для установки процента размытия, продолжительности анимации и, при желании, добавления задержки между первой и второй половинами анимации. Не стесняйтесь скачать его и поиграть с ним.   -  person Duncan C    schedule 07.09.2020
comment
Я не совсем уверен, что вы ищете, но как насчет CAShapelayer с анимированным shadowPath?   -  person James P    schedule 07.09.2020
comment
Это действительно хорошая идея. Я должен это попробовать. Я бы хотел смещение тени 0, чтобы создать ореол вокруг формы.   -  person Duncan C    schedule 07.09.2020
comment
Не заполняйте слой формы и не обводите его (контур тени заполняется сам по себе), и я думаю, что это может помочь с переходом.   -  person James P    schedule 07.09.2020
comment
Я не использую слой-фигуру. Я просто устанавливаю свойства тени на слое формы. Я могу попробовать использовать shadowPath, как вы говорите.   -  person Duncan C    schedule 08.09.2020
comment
@JamesP, ваша идея анимировать свойство shadowLayer слоя маски действительно является решением. Он работает прекрасно и намного чище, чем пытаться возиться с радиальным градиентом и двухэтапной анимацией. Можете ли вы опубликовать свою идею в качестве ответа, чтобы я мог ее принять?   -  person Duncan C    schedule 08.09.2020


Ответы (1)


Вы можете использовать shadowPath на CAShapeLayer, чтобы создать круг с размытым контуром, который будет использоваться в качестве маски.

Создайте слой-фигуру без заливки или обводки (мы хотим видеть только тень) и добавьте его в качестве маски слоя к вашему виду.

let shapeLayer = CAShapeLayer()
shapeLayer.shadowColor = UIColor.black.cgColor
shapeLayer.shadowOffset = CGSize(width: 0, height: 0)
shapeLayer.shadowOpacity = 1
shapeLayer.shadowRadius = 5
self.maskLayer = shapeLayer
self.layer.mask = shapeLayer

И оживите его с помощью CABasicAnimation.

func show(_ show: Bool) {
    
    let center = CGPoint(x: self.bounds.width/2, y: self.bounds.height/2)
    var newPath: CGPath
    if show {
        let radius = sqrt(self.bounds.height*self.bounds.height + self.bounds.width*self.bounds.width)/2 //assumes view is square
        newPath = UIBezierPath(arcCenter: center, radius: radius, startAngle: 0, endAngle: CGFloat.pi * 2, clockwise: true).cgPath
    } else {
        newPath = UIBezierPath(arcCenter: center, radius: 0.01, startAngle: 0, endAngle: CGFloat.pi * 2, clockwise: true).cgPath
    }
    
    let shadowAnimation = CABasicAnimation(keyPath: "shadowPath")
    shadowAnimation.fromValue = self.maskLayer.shadowPath
    shadowAnimation.toValue = newPath
    shadowAnimation.duration = totalDuration

    self.maskLayer.shadowPath = newPath
    self.maskLayer.add(shadowAnimation, forKey: "shadowAnimation")

}

Поскольку вы можете установить любой путь для тени, он также должен работать с другими формами (звезда, прямоугольник и т. Д.).

person James P    schedule 09.09.2020
comment
Это именно то, что я искал. Интересно, генерируют ли незакрашенные формы тени? Если так, это позволит вам рисовать размытые линии и кривые, что я пытался придумать уже очень давно. - person Duncan C; 09.09.2020
comment
@DuncanC Кажется, shadowPath закроется и заполнит незакрытый путь, поэтому вы не можете просто установить его в строку. Но если вы скопируете контур линии с помощью copy(strokingWithWidth:lineCap:lineJoin:miterLimit:transform:), он создаст контур с заливкой, который выглядит так же, как контур с обводкой. Вы просто не сможете анимировать strokeEnd / Start. - person James P; 09.09.2020
comment
Прохладный. Я не был знаком с этой функцией. Должна быть возможность анимировать растушеванную / размытую линию с помощью copy (strokingWithWidth: lineCap: lineJoin: miterLimit: transform :), чтобы сопоставить путь линии с контуром с заливкой, установив его в shadowPath слоя, а затем анимируя изменения пути . - person Duncan C; 09.09.2020
comment
Я думал о написании моей собственной функции для преобразования заштрихованного пути в заполненный. Это потребовало бы рисования параллельных сегментов линии на любой границе обведенного контура. Я почти уверен, что у меня был метод, который сработал бы, но его было бы довольно сложно и утомительно кодировать. Функция copy (strokingWithWidth: lineCap: lineJoin: miterLimit: transform:) значительно упрощает задачу на много. - person Duncan C; 10.09.2020