1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/CarGuo-GSYFlutterBook

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
Flutter-N21.md 16 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Отправлено 10.03.2025 00:06 5767d61

Небольшие хитрости Flutter для реализации эффекта "глюка" текста в стиле ночных вывесок

На приведённом ниже изображении показана анимация "дрожания" текста, которая напоминает повреждение ночных вывесок. Целью данной статьи является воспроизведение этой анимации с помощью Flutter.

Этот эффект был реализован с использованием CSS на сайте codepen.io. Однако Flutter не имеет такой мощной системы стилей как CSS, поэтому задача заключается в том, чтобы воспроизвести этот эффект в Flutter.

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

Для реализации эффекта "глюк" в Flutter нам потребуется:

  • Текст со светящимся эффектом, как у ночных вывесок
  • Эффект "разрывания" текста
  • Эффект "дрожания" текста

Следуя этому плану, мы создадим аналогичный эффект "глюк" в Flutter.

Светящийся текст

Эта часть довольно проста. В Flutter стиль текста (TextStyle) позволяет использовать тени (shadows). Это позволяет быстро создать эффект "светящегося" текста.

Мы используем два объекта Shadow, чтобы добиться эффекта "светящихся" букв. Основная идея состоит в использовании свойства blurRadius для создания некоторого уровня размытия, которое создаёт эффект свечения. Два разных Shadow обеспечивают различные цветовые глубины и эффекты размытия, что делает текст выглядеть "светящимся".> На следующем изображении показано, как выглядят тени без заполненного цвета текста.

Наконец, как показано ниже, добавление цвета текста через foreground позволяет получить текст, который выглядит как "светящийся".

Конечно, если вам не нужен foreground, можно просто использовать color.

Text(
  widget.text,
  style: TextStyle(
    fontSize: 48,
    fontWeight: FontWeight.bold,
    foreground: Paint()
      ..style = PaintingStyle.fill
      ..strokeWidth = 5
      ..color = Colors.white,
    shadows: [
      Shadow(
        blurRadius: 10,
        color: Colors.white,
        offset: Offset(0, 0),
      ),
      Shadow(
        blurRadius: 20,
        color: Colors.white30,
        offset: Offset(0, 0),
      ),
    ],
  ),
)
```![](http://img.cdn.guoshuyu.cn/20230322_N21/image3.png)

Здесь стоит отметить, что аналогичный подход можно применить к изображению, чтобы добиться эффекта "светового пятна". Ниже приведён пример кода, который использует вложенные `Stack`, два `Image` и `BackdropFilter` с `ImageFilter`, чтобы создать эффект размытости, создающий вид светящегося изображения.

```dart
var child = Image.asset(
  'static/test_logo.png',
  width: 250,
);
return Stack(
  children: [
    child,
    Positioned.fill(
      child: BackdropFilter(
        filter: ImageFilter.blur(
          sigmaX: blurRadius,
          sigmaY: blurRadius,
        ),
        child: Container(color: Colors.transparent),
      ),
    ),
    child,
  ],
)

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

Разрыв текста

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

Хотя используется Polygon, но официальная библиотека Flutter не предоставляет поддержку аналогичной API для полигонов, как это делается в CSS. Но сообщество всегда готово помочь, поэтому мы можем использовать стороннюю библиотеку polygon: ^0.1.0.

Polygon — это просто упаковка методов moveTo и quadraticBezierTo объекта Path через шаги.

Диапазон значений Polygon в Flutter составляет от -1 до 1, то есть положение определяется относительно размера контейнера. Например, -1 указывает на начальную точку, а 1 — на максимальную ширину или высоту. Как показано ниже, здесь используются три точки, которые вместе образуют треугольник.

List<Offset> generatePoints() {
  List<Offset> points = [];
  points.add(Offset(-1, -1));
  points.add(Offset(-1, 0));
  points.add(Offset(0, -1));
  return points;
}

Если количество точек увеличивается, то они могут образовать серию нерегулярных форм. В следующем примере случайным образом добавлены 60 точек, и белый Container на экране выглядит хаотично разрезанным.dart List<Offset> generatePoints() { List<Offset> points = [];java точки.add(Offset(-1.00, -0.76)); точки.add(Offset(0.06, -0.76)); точки.add(Offset(0.06, -0.48)); точки.add(Offset(-0.50, -0.48)); точки.add(Offset(-0.50, 0.72)); точки.add(Offset(-0.38, 0.72)); точки.add(Offset(-0.38, -1.00)); точки.add(Offset(0.06, -1.00)); точки.add(Offset(0.06, 0.67)); точки.add(Offset(0.84, 0.67)); точки.add(Offset(0.84, 0.63)); точки.add(Offset(0.39, 0.63)); точки.add(Offset(0.39, -0.42)); точки.add(Offset(0.56, -0.42)); точки.add(Offset(0.56, 0.30)); точки.add(Offset(0.37, 0.30)); точки.add(Offset(0.37, 0.32)); точки.add(Offset(0.54, 0.32)); точки.add(Offset(0.54, -0.09)); точки.add(Offset(0.70, -0.09)); точки.add(Offset(0.70, -0.48)); точки.add(Offset(0.94, -0.48)); точки.add(Offset(0.94, -0.43)); точки.add(Offset(0.67, -0.43)); точки.add(Offset(0.67, -0.31)); точки.add(Offset(0.08, -0.31)); точки.add(Offset(0.08, 0.78)); точки.add(Offset(-0.40, 0.78)); точки.add(Offset(-0.40, 0.15)); точки.add(Offset(0.65, 0.15)); точки.add(Offset(0.65, 0.00)); точки.add(Offset(0.36, 0.00)); точки.add(Offset(0.36, -0.28)); точки.add(Offset(0.24, -0.28)); точки.add(Offset(0.24, -0.80)); точки.add(Offset(-0.76, -0.80)); точки.add(Offset(-0.76, -0.31)); точки.add(Offset(0.19, -0.31)); точки.add(Offset(0.19, 0.13)); точки.add(Offset(0.96, 0.13)); точки.add(Offset(0.96, 0.65)); точки.add(Offset(-0.80, 0.65)); точки.add(Offset(-0.80, 0.06)); точки.add(Offset(0.82, 0.06)); точки.add(Offset(0.82, 0.67)); точки.add(Offset(0.60, 0.67)); точки.add(Offset(0.60, 0.65)); точки.add(Offset(-0.19, 0.65)); return точки; }


![](http://img.cdn.guoshuyu.cn/20230322_N21/image6.png)

Если в этот момент белый `Container` заменить на текстовое содержимое, то можно получить такой эффект, как показано ниже. Видите ли вы похожесть на случайное перемещение текста? Далее нам достаточно генерировать каждый раз один путь, чтобы реализовать требуемый эффект "разрыв" текста.
![](http://img.cdn.guoshuyu.cn/20230322_N21/image7.png)

> Для того чтобы каждый раз генерировался новый `Path`, нам достаточно сделать эту реализацию случайной. Как показано ниже, мы используем метод `generatePoint`, чтобы случайным образом генерировать 60 точек каждый раз. Затем эти точки преобразуются в путь с помощью метода `computePath`. Этот путь затем передается в метод `getClip` класса `CustomClipper`.

> Обратите внимание на условие `i % 2`, которое позволяет предыдущему значению x или y оставаться тем же местоположением, обеспечивая непрерывность при соединении.

```dart
class RandomTearingClipper extends CustomClipper<Path> {
  bool tear;

  RandomTearingClipper(this.tear);

  List<Offset> generatePoint() {
    List<Offset> points = [];
    var x = -1.0;
    var y = -1.0;
    for (var i = 0; i < 60; i++) {
      if (i % 2 != 0) {
        x = Random().nextDouble() * (Random().nextBool() ? -1 : 1);
      } else {
        y = Random().nextDouble() * (Random().nextBool() ? -1 : 1);
      }
      points.add(Offset(x, y));
    }
    return points;
  }

  @override
  Path getClip(Size size) {
    var points = generatePoint();
    var polygon = Polygon(points);
    if (tear)
      return polygon.computePath(rect: Offset.zero & size);
    else
      return Path()..addRect(Offset.zero & size);
  }

  @override
  bool shouldReclip(RandomTearingClipper oldClipper) => true;
}

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

timer = Timer.periodic(Duration(milliseconds: 400), (timer) {
  tearFunction();
});
```return ClipPath(
   child: Center(
     child: Text(
       widget.text,
       style: TextStyle(
         fontSize: 48,
         fontWeight: FontWeight.bold,
         foreground: Paint()
           ..style = PaintingStyle.fill
           ..strokeWidth = 1
           ..color = Colors.white,
         shadows: [
           Shadow(
             blurRadius: 10,
             color: Colors.white,
             offset: Offset(0, 0),
           ),
           Shadow(
             blurRadius: 20,
             color: Colors.white30,
             offset: Offset(0, 0),
           ),
         ],
       ),
     ),
   ),
   clipper: RandomTearingClipper(tear),
 );
```![image](http://img.cdn.guoshuyu.cn/20230322_N21/image8.gif)

> На данный момент это выглядит недостаточно реалистично.

# Деформация и мигание

Для достижения желаемого эффекта нам ещё требуется выполнить несколько специальных операций, таких как создание двух фигур с различными цветами и позициями «неоновых текстов», чтобы реализовать эффект «деформации и мигания».

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

```dart
ShaderMask(
  blendMode: BlendMode.srcATop,
  shaderCallback: (bounds) {
    return LinearGradient(
      colors: [Colors.blue, Colors.green, Colors.red],
      stops: [0.0, 0.5, 1.0],
    ).createShader(bounds);
  },
  child:

image

Аналогично мы можем создать текст с эффектом «деформации». На основе белого «неонового» текста добавляем эффекты «курсивного шрифта» и «пятна светлых оттенков», чтобы обеспечить эффект «деформации» при мигании.

image

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

transform:
    Matrix4.translationValues(randomPosition(4), randomPosition(4), 0),

double randomPosition(int position) {
  return Random().nextInt(position).toDouble() * 
         (Random().nextBool() ? -1 : 1);
}
```| ![image](http://img.cdn.guoshuyu.cn/20230322_N21/image11.gif) | ![image](http://img.cdn.guoshuyu.cn/20230322_N21/image12.gif) |
| ------------------------------------------------------------ | ------------------------------------------------------------ |

Итоговый этап  объединение этих эффектов текста с помощью `Stack`. Мы используем таймер для постоянной смены состояний «поломки» и «нормального режима» текста, а также случайного выбора различных состояний «поломки».

```dart
timer = Timer.periodic(Duration(milliseconds: 400), (timer) {
  tearFunction();
});
timer2 = Timer.periodic(Duration(milliseconds: 600), (timer) {
  tearFunction();
});

tearFunction() {
  count++;
  tear = count % 2 == 0;
  if (tear == true) {
    setState(() {});
    Future.delayed(Duration(milliseconds: 150), () {
      setState(() {
        tear = false;
      });
    });
  }
}

Результат отображается на следующем рисунке:

Конечно, здесь реализация не учитывает вопросы производительности, поэтому код довольно грубый. Однако основная цель состоит в демонстрации использования ClipPath и Shadow. Надеюсь, этот пример поможет вам лучше понять применение путь к рисованию и тени в Flutter.

На этом данная небольшая статья завершена. Если у вас есть какие-либо идеи или предложения, оставьте свои комментарии ниже.

Полный код доступен по адресу: https://github.com/CarGuo/gsy_flutter_demo/blob/master/lib/widget/tear_text_demo_page.dart

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/CarGuo-GSYFlutterBook.git
git@api.gitlife.ru:oschina-mirror/CarGuo-GSYFlutterBook.git
oschina-mirror
CarGuo-GSYFlutterBook
CarGuo-GSYFlutterBook
master