Flutter — использование кривых Безье для добавления эффекта корзины покупок

Flutter

🥦 Предисловие - О кривых Безье

贝塞尔曲线, Это слово всем знакомо, особенно во фронтенде, я его ни разу не использовал, кажется, слышал.

Также я продолжу рассказывать о знании кривых Безье:

Кривая Безье — это такая кривая, которая строится на основе координат произвольных точек в четырех позициях.плавная кривая. Исторически сложилось так, что люди, изучавшие кривые Безье, первоначально следовали известным кривым.параметрическое уравнениеЧтобы определить идею четырех точек для проектирования этого метода рисования векторной кривой. Интересной особенностью кривой Безье является ее «эффект резиновой ленты», то есть, поскольку точка регулярно перемещается, кривая будет производить то же преобразование, что и растяжение резиновой ленты, оказывая визуальное воздействие. В 1962 году французский математикPierre BézierПервый, кто изучил метод построения кривой с помощью этого вектора и дал подробную формулу расчета, поэтому кривая, построенная по этой формуле, названа кривой Безье по его фамилии. - Энциклопедия Байду

Его появление положило начало компьютерной векторной графике, так что же с ним делать?

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

🍋 Эффект и формула расчета кривой Безье

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

кривая Безье первого порядка

Кривая Безье второго порядка

Кривая Безье третьего порядка

Остальные высокоуровневые, поэтому в подробности вдаваться не буду.Вывод формулы таков:

🥝 Осознайте эффект добавления товаров в корзину

После рассмотрения принципа кривой Безье давайте посмотрим на эффект, который будет достигнут сегодня:

до реализации

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

Кривая Безье второго порядкаТребуемые параметры:

  1. начальная точка p0
  2. контрольная точка p1
  3. конечная точка p2

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

Как должна отображаться «Маленькая красная точка»? Мы продолжим.

приступить к реализации

Давайте начнем осознавать эффект вышеприведенной картинки, с чего начать?

1. Давайте сначала получим начальную и конечную точки

Страница очень простая, код такой:

Column(
  children: <Widget>[
    Expanded(
      child: ListView.builder(
        itemBuilder: (BuildContext context, int index) {
          return Row(
          	// 隐藏无用代码
          );
        },
        itemCount: 100,
      ),
    ),
    Container(
      height: 1,
      color: Colors.grey.withOpacity(0.5),
    ),
    Container(
      height: 60,
      color: Colors.white,
      child: Row(
        children: <Widget>[
          Padding(
            padding: EdgeInsets.only(left: 20),
            child: Icon(
              Icons.shop_two,
              key: _key,
            ),
          )
        ],
      ),
    )
  ],
)

ОдинColumn, вышеListView, за которым следует значок корзины покупок.

Отправная точка — мыListViewЗнак + каждого элемента в нем заканчивается «значком корзины» в левом нижнем углу.

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

@override
void initState() {
  super.initState();
  WidgetsBinding.instance.addPostFrameCallback((c) {
    // 获取「购物车」的位置
    _endOffset = (_key.currentContext.findRenderObject() as RenderBox)
      .localToGlobal(Offset.zero);
  });
}

Что насчет отправной точки?

потому что отправная точкаListViewПосередине еще прокрутится.В это время многие дети могут сказать:«Было бы неплохо, если бы каждому значку был присвоен GlobalKey!»

Дети, вы уверены, что не убиваете себя?

GlobalKey использует статическую константу Map для хранения соответствующего элемента. Вы можете найти виджет, состояние и элемент, содержащие GlobalKey, через GlobalKey. Примечание. Глобальные ключи очень дороги, и их следует использовать с осторожностью.

-Vadaski - Flutter | Объясните простыми словами Ключ

Если в это время будет 10 000 списков продуктов, не будет ли он вот-вот взорваться?

Давайте вернемся к коду, чтобы получить местоположение «корзины».GlobalKeyполучитьcontext,использоватьcontextчтобы получить местоположение, так почему бы нам просто не использовать группуcontextкомпонент?

код показывает, как показано ниже:

Builder(
  builder: (context) {
    return IconButton(
      icon: Icon(Icons.add_circle_outline),
      onPressed: () {
        // 通过 Builder 组件来获取 context
        RenderBox box = context.findRenderObject();
        var offset = box.localToGlobal(Offset.zero);
      },
    );
  },
)

Использовать напрямуюBuilderчтобы получить местоположение компонента.

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

2. Установите контрольные точки кривой Безье второго порядка

Это относительно просто, мы можем посмотреть на эту картинку:

(Нарисовано вручную, смиритесь с этим)

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

var x1 = widget.startPosition.dx - 250;
var y1 = widget.startPosition.dy - 100;

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

3. Получите положение красной точки в каждом кадре

Давайте сначала возьмем эту картинку и формулу, где у нас есть значения P0 (начальная точка), P1 (контрольная точка) и P2 (конечная точка), и есть t, мы используем Flutter’sTweenПросто получите его и, наконец, вставьте формулу:

@override
void initState() {
  super.initState();
  _controller =
    AnimationController(duration: Duration(milliseconds: 800), vsync: this);
  _animation = Tween(begin: 0.0, end: 1.0).animate(_controller);

  // 二阶贝塞尔曲线用值
  var x0 = widget.startPosition.dx;
  var y0 = widget.startPosition.dy;

  var x1 = widget.startPosition.dx - 250;
  var y1 = widget.startPosition.dy - 100;

  var x2 = widget.endPosition.dx;
  var y2 = widget.endPosition.dy;

  _animation.addListener(() {
    // t 动态变化的值
    var t = _animation.value;
    if (mounted)
      setState(() {
        left = pow(1 - t, 2) * x0 + 2 * t * (1 - t) * x1 + pow(t, 2) * x2;
        top = pow(1 - t, 2) * y0 + 2 * t * (1 - t) * y1 + pow(t, 2) * y2;
      });
  });

  // 初始化小圆点的位置
  left = widget.startPosition.dx;
  top = widget.startPosition.dy;

  // 显示小圆点的时候动画就开始
  _controller.forward();
}

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

4. Покажите маленькую красную точку

Как отобразить маленькую красную точку? Что делать при нажатии.

Первое, что пришло мне на ум, былоIndexStackОберните его, установите положение маленькой красной точки при нажатии, а затем отобразите его.

Тогда я вдруг подумалOverlay😂.

ListViewсерединаButtonКод выглядит следующим образом:

IconButton(
  icon: Icon(Icons.add_circle_outline),
  onPressed: () {
    // 点击的时候获取当前 widget 的位置,传入 overlayEntry
    var _overlayEntry = OverlayEntry(builder: (_) {
      RenderBox box = context.findRenderObject();
      var offset = box.localToGlobal(Offset.zero);
      return RedDotPage(
        startPosition: offset,
        endPosition: _endOffset,
      );
    });
    // 显示Overlay
    Overlay.of(context).insert(_overlayEntry);
    // 等待动画结束
    Future.delayed(Duration(milliseconds: 800), () {
      _overlayEntry.remove();
      _overlayEntry = null;
    });
  },
)

вRedDotPageЭто маленькая страница с красной точкой, которую мы определили, перейдите в начальную точку для него, пустьOverlayОтобразите его, запустите анимацию кривой Безье одновременно с ее отображением, дождитесь окончания анимации, чтобы удалить этоOverlayEntryНичего страшного.

🍍 Резюме

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

Код отправлен наGithub — добавить в корзину демо.

Если есть какие-либо недостатки, я надеюсь, что каждый сможет их выдвинуть и учиться вместе! 🤝