Site icon AppTractor

Работаем с сетчатыми градиентами в iOS 18

В iOS 18 появилась возможность создавать сетчатые градиенты. Сетчатые градиенты — это действительно хороший способ создания очень крутых эффектов в пользовательском интерфейсе. В этой статье мы рассмотрим, что такое сетчатые градиенты, как их можно использовать и как их можно анимировать, чтобы они выглядели действительно круто.

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

Давайте начнем?

Создание сетчатого градиента

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

На этом рисунке мы видим градиент, использующий девять цветов для создания интересного эффекта, когда мы переходим от фиолетового цвета в левом верхнем углу к другому цвету в середине, а затем к другому цвету в правом верхнем и левом нижнем углу, что позволяет нам иметь различные цвета внутри градиента, и все они переходят друг в друга, как и предполагает название mesh gradient.

Мы можем создавать такие градиенты в SwiftUI с помощью нового объекта.

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

Например, мы можем создать сетчатый градиент, которая использует четыре цвета вместо девяти. Ширина такой сетки будет равна двум, а высота — двум. В результате у сетки будет две строки с двумя столбцами в каждой. Мы также можем сделать градиент шириной 2 и высотой 3, что означает, что у нас будет два столбца на три строки, то есть всего шесть цветов.

При таком градиенте каждый цвет будет располагаться по краям сетки.

Размещение наших цветов контролируется путем передачи их инициализатору MeshGradient.

Прежде чем объяснять дальше, думаю, стоит взглянуть на код, необходимый для создания градиента сетки.

MeshGradient(
    width: 2,
    height: 2,
    points: [
        .init(x: 0, y: 0), .init(x: 1, y: 0),
        .init(x: 0, y: 1), .init(x: 1, y: 1),
    ] ,
    colors: [
        .red, .orange,
        .purple, .blue
    ]
)

В этом коде вы можете увидеть, что для создания градиента мы задаем ширину и высоту, о которых мы только что говорили, а затем список точек. Этот список точек указывает градиенту, где именно внутри сетки находится каждый цвет.

Итак, первый цвет в данном случае находится в левом верхнем углу (0, 0), а затем мы переходим к следующей точке, которая будет находиться в правом верхнем углу (1, 0). Интересно отметить, что мы задаем эти позиции по столбцам и строкам. То есть мы начинаем с верхнего левого, затем верхнего правого, а следующим становится нижний левый.

Позиции, которые мы передаем, всегда имеют значения от нуля до единицы, и они представляют собой относительное положение в сетке. Неважно, насколько большим или маленьким в реальности будет ваш градиент, он будет точно знать, где все должно быть, основываясь на наших относительных позициях.

Четвертый и последний аргумент, который вы также можете увидеть в фрагменте кода, — это colors. Цвета — это список всех цветов, которые мы хотим использовать. Так что если у нас ширина 2 и высота 2, то мы указываем 4 цвета. Порядок цветов такой же, как и в нашем позиционировании. В данном случае это красный, оранжевый, пурпурный, синий:

Даже если мы поместим первый цвет, например, в правое нижнее положение, система все равно будет рассчитывать градиенты так, как если бы красный располагался (примерно) в левом верхнем углу. Не стесняйтесь немного поиграться с этим, чтобы понять, что я имею в виду.

Помимо целых значений, таких как 0 и 1, мы также можем указывать десятичные значения, например 0.5. Результат не всегда эстетически приятен, потому что если мы, например, присвоим нашему первому цвету (красному) значение x, равное 0.5, результат будет выглядеть примерно так.

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

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

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

Это подводит меня к анимации сетчатого градиента. Давайте рассмотрим это дальше.

Анимация сетчатого градиента

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

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

struct ContentView: View {
    @State var positions: [SIMD2<Float>] = [
        .init(x: 0, y: 0), .init(x: 0.2, y: 0), .init(x: 1, y: 0),
        .init(x: 0, y: 0.7), .init(x: 0.1, y: 0.5), .init(x: 1, y: 0.2),
        .init(x: 0, y: 1), .init(x: 0.9, y: 1), .init(x: 1, y: 1)
    ]

    let timer = Timer.publish(every: 1/6, on: .current, in: .common).autoconnect()

    var body: some View {
        MeshGradient(
            width: 3,
            height: 3,
            points: positions,
            colors: [
                .purple, .red, .yellow,
                .blue, .green, .orange,
                .indigo, .teal, .cyan
            ]
        )
        .frame(width: 300, height: 200)
        .onReceive(timer, perform: { _ in
            positions[1] = randomizePosition(
                currentPosition: positions[1],
                xRange: (min: 0.2, max: 0.9),
                yRange: (min: 0, max: 0)
            )

            positions[3] = randomizePosition(
                currentPosition: positions[3],
                xRange: (min: 0, max: 0),
                yRange: (min: 0.2, max: 0.8)
            )

            positions[4] = randomizePosition(
                currentPosition: positions[4],
                xRange: (min: 0.3, max: 0.8),
                yRange: (min: 0.3, max: 0.8)
            )

            positions[5] = randomizePosition(
                currentPosition: positions[5],
                xRange: (min: 1, max: 1),
                yRange: (min: 0.1, max: 0.9)
            )

            positions[7] = randomizePosition(
                currentPosition: positions[7],
                xRange: (min: 0.1, max: 0.9),
                yRange: (min: 1, max: 1)
            )
        })
    }

    func randomizePosition(
        currentPosition: SIMD2<Float>,
        xRange: (min: Float, max: Float),
        yRange: (min: Float, max: Float)
    ) -> SIMD2<Float> {
        var updateDistance: Float = 0.01

        let newX = if Bool.random() {
            min(currentPosition.x + updateDistance, xRange.max)
        } else {
            max(currentPosition.x - updateDistance, xRange.min)
        }

        let newY = if Bool.random() {
            min(currentPosition.y + updateDistance, yRange.max)
        } else {
            max(currentPosition.y - updateDistance, yRange.min)
        }

        return .init(x: newX, y: newY)
    }
}

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

И эффект, и код — это довольно простые примеры того, что мы можем сделать. Существует множество других способов добиться аналогичных, таких же или лучших результатов. Поэтому я надеюсь, что это лишь отправная точка для вас, чтобы вы знали, что можно сделать, и чтобы вдохновить вас на то, как начать анимировать градиенты.

Больше мне нечего сказать о градиентах сетки и их анимации.

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

Теперь, когда вы увидели, как анимировать сетчатый градиент, давайте поговорим немного о том, где и когда имеет смысл его использовать.

Где и когда использовать сетчатые градиенты

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

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

MeshGradient(
    width: 3,
    height: 3,
    points: positions,
    colors: [
        .purple, .red, .yellow,
        .blue, .green, .orange,
        .indigo, .teal, .cyan
    ]
)
.frame(width: 300, height: 200)
.overlay(.ultraThinMaterial)

Если вы сделаете это, градиент будет более приглушенным, как показано на рисунке ниже.

Если применить эффект таким образом, то градиент сетки станет гораздо более тонким, и это действительно добавит удобства вашему пользовательскому интерфейсу при использовании в качестве фона.

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

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

В заключение

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

Я думаю, что MeshGradient сможет создать несколько действительно крутых эффектов для вашего пользовательского интерфейса. И я с нетерпением жду, когда приложения внедрят его, потому что мне бы хотелось увидеть, как они используют этот новый API в iOS 18.

Источник

Exit mobile version