欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android?貝塞爾曲線繪制一個波浪球

 更新時間:2022年05月17日 17:24:09   作者:業(yè)志陳  
當(dāng)?flutter?的現(xiàn)有組件無法滿足產(chǎn)品要求的UI效果時,我們就需要通過自繪組件的方式來進(jìn)行實(shí)現(xiàn)了。本文章就來介紹如何用貝塞爾曲線實(shí)現(xiàn)一個帶文本的波浪球,需要的可以參考一下

前言

當(dāng) flutter 的現(xiàn)有組件無法滿足產(chǎn)品要求的 UI 效果時,我們就需要通過自繪組件的方式來進(jìn)行實(shí)現(xiàn)了。本篇文章就來介紹如何用 flutter 自定義實(shí)現(xiàn)一個帶文本的波浪球,效果如下所示:

先來總結(jié)下 WaveLoadingWidget 的特點(diǎn),這樣才能歸納出實(shí)現(xiàn)該效果所需要的步驟:

  • widget 的主體是一個不規(guī)則的半圓形,頂部曲線以類似于波浪的形式從左往右上下起伏運(yùn)行
  • 波浪球可以自定義顏色,此處以 waveColor 命名
  • 波浪球的起伏線將嵌入的文本分為上下兩種顏色,上半部分顏色以 backgroundColor 命名,下半部分顏色以 foregroundColor 命名,文本的整體顏色一直在根據(jù)波浪的運(yùn)行而動態(tài)變化中

雖然文本的整體顏色是在不斷變化的,但只要能夠繪制出其中一幀的圖形,其動態(tài)效果就能通過不斷改變波浪曲線的位置參數(shù)來實(shí)現(xiàn),所以這里先把該 widget 當(dāng)成靜態(tài)的,先實(shí)現(xiàn)其靜態(tài)效果即可

將繪制步驟拆解為以下幾步:

  • 繪制顏色為 backgroundColor 的文本,將其繪制在 canvas 的最底層
  • 根據(jù) widget 的寬高信息構(gòu)建一個不超出范圍的最大圓形路徑 circlePath
  • 以 circlePath 的水平中間線作為波浪的基準(zhǔn)起伏線,在起伏線的上邊和下邊分別用貝塞爾曲線繪制一段連續(xù)的波浪 path,將 path 的首尾兩端以矩形的方式連接在一起,構(gòu)成 wavePath,wavePath 的底部會與 circlePath 的最底部相交
  • 取 circlePath 和 wavePath 的交集 combinePath,用 waveColor 填充, 此時就得到了半圓形的球形波浪了
  • 利用 canvas.clipPath(combinePath) 方法裁切畫布,再繪制顏色為 foregroundColor 的文本,此時繪制的 foregroundColor 文本只會顯示 combinePath 范圍內(nèi)的部分,也即只會顯示下半部分,使得兩次不同時間繪制的文本重疊在了一起,從而得到了有不同顏色范圍的文本
  • 利用 AnimationController 不斷改變 wavePath 的起始點(diǎn)的 X 坐標(biāo),同時重新刷新 UI,從而得到波浪不斷從左往右起伏運(yùn)行的動態(tài)效果

現(xiàn)在就來一步步實(shí)現(xiàn)以上的繪制步驟吧

一、繪制 backgroundColor 文本

flutter 通過 CustomPainter 為開發(fā)者提供了自繪 UI 的入口,其內(nèi)部的 void paint(Canvas canvas, Size size) 方法提供了畫布 canvas 對象以及包含 widget 寬高信息的 size 對象

這里就來繼承 CustomPainter 類,在 paint 方法中先來繪制顏色為 backgroundColor 的文本。flutter 的 canvas 對象沒有提供直接 drawText 的 API,所以其繪制文本的步驟相對原生的自定義 View 要稍微麻煩一點(diǎn)

class _WaveLoadingPainter extends CustomPainter {
  final String text;

  final double fontSize;

  final double animatedValue;

  final Color backgroundColor;

  final Color foregroundColor;

  final Color waveColor;

  _WaveLoadingPainter({
    required this.text,
    required this.fontSize,
    required this.animatedValue,
    required this.backgroundColor,
    required this.foregroundColor,
    required this.waveColor,
  });

  @override
  void paint(Canvas canvas, Size size) {
    final side = min(size.width, size.height);
    _drawText(canvas: canvas, side: side, color: backgroundColor);
  }

  void _drawText(
      {required Canvas canvas, required double side, required Color color}) {
    ParagraphBuilder paragraphBuilder = ParagraphBuilder(ParagraphStyle(
      textAlign: TextAlign.center,
      fontStyle: FontStyle.normal,
      fontSize: fontSize,
    ));
    paragraphBuilder.pushStyle(ui.TextStyle(color: color));
    paragraphBuilder.addText(text);
    ParagraphConstraints pc = ParagraphConstraints(width: fontSize);
    Paragraph paragraph = paragraphBuilder.build()..layout(pc);
    canvas.drawParagraph(
      paragraph,
      Offset((side - paragraph.width) / 2.0, (side - paragraph.height) / 2.0),
    );
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return animatedValue != (oldDelegate as _WaveLoadingPainter).animatedValue;
  }
}

二、構(gòu)建 circlePath

取 widget 的寬度和高度的最小值作為圓的直徑大小,以此構(gòu)建出一個不超出 widget 范圍的最大圓形路徑 circlePath

  @override
  void paint(Canvas canvas, Size size) {
    final side = min(size.width, size.height);
    _drawText(canvas: canvas, side: side, color: backgroundColor);

    final circlePath = Path();
    circlePath.addArc(Rect.fromLTWH(0, 0, side, side), 0, 2 * pi);
  }

三、繪制波浪線

波浪的寬度和高度就根據(jù)一個固定的比例值來求值,以 circlePath 的中間分隔線作為水平線,在水平線的上下根據(jù)貝塞爾曲線繪制出連續(xù)的波浪線

  @override
  void paint(Canvas canvas, Size size) {
    final side = min(size.width, size.height);
    _drawText(canvas: canvas, side: side, color: backgroundColor);

    final circlePath = Path();
    circlePath.addArc(Rect.fromLTWH(0, 0, side, side), 0, 2 * pi);

    final waveWidth = side * 0.8;
    final waveHeight = side / 6;
    final wavePath = Path();
    final radius = side / 2.0;
    wavePath.moveTo(-waveWidth, radius);
    for (double i = -waveWidth; i < side; i += waveWidth) {
      wavePath.relativeQuadraticBezierTo(
          waveWidth / 4, -waveHeight, waveWidth / 2, 0);
      wavePath.relativeQuadraticBezierTo(
          waveWidth / 4, waveHeight, waveWidth / 2, 0);
    }
    //為了方便讀者理解,這里把 wavePath 繪制出來,實(shí)際上不需要
    final paint = Paint()
      ..isAntiAlias = true
      ..style = PaintingStyle.fill
      ..strokeWidth = 3
      ..color = waveColor;
    canvas.drawPath(wavePath, paint);
  }

此時繪制的曲線還處于非閉合狀態(tài),需要將 wavePath 的首尾兩端連接起來,這樣后面才可以和 circlePath 取交集

wavePath.relativeLineTo(0, radius);
wavePath.lineTo(-waveWidth, side);
wavePath.close();
//為了方便讀者理解,這里把 wavePath 繪制出來,實(shí)際上不需要
final paint = Paint()
  ..isAntiAlias = true
  ..style = PaintingStyle.fill
  ..strokeWidth = 3
  ..color = waveColor;
canvas.drawPath(wavePath, paint);

wavePath 閉合后,此時半圓的顏色就會鋪滿了

四、取交集

取 circlePath 和 wavePath 的交集,就得到一個半圓形波浪球了

final paint = Paint()
  ..isAntiAlias = true
  ..style = PaintingStyle.fill
  ..strokeWidth = 3
  ..color = waveColor;
final combinePath = Path.combine(PathOperation.intersect, circlePath, wavePath);
canvas.drawPath(combinePath, paint);

五、繪制 foregroundColor 文本

文本的顏色是分為上下兩部分的,上半部分顏色為 backgroundColor,下半部分為 foregroundColor。在第一步的時候已經(jīng)繪制了顏色為 backgroundColor 的文本了,foregroundColor 文本不需要顯示上半部分,所以在繪制 foregroundColor 文本之前需要先把繪制區(qū)域限定在 combinePath 內(nèi),使得兩次不同時間繪制的文本重疊在了一起,從而得到有不同顏色范圍的文本

canvas.clipPath(combinePath);
_drawText(canvas: canvas, side: side, color: foregroundColor);

六、添加動畫

現(xiàn)在已經(jīng)繪制好靜態(tài)時的效果了,可以考慮如何使 widget 動起來了

要實(shí)現(xiàn)動態(tài)效果也很簡單,只要不斷改變貝塞爾曲線的起始點(diǎn)坐標(biāo),使之不斷從左往右移動,就可以營造出波浪從左往右前進(jìn)的效果了。_WaveLoadingPainter 根據(jù)外部傳入的動畫值 animatedValue 來設(shè)置 wavePath 的起始坐標(biāo)點(diǎn)即可,生成 animatedValue 的邏輯和其它繪制參數(shù)均由 _WaveLoadingState 來提供

class _WaveLoadingState extends State<WaveLoading>
    with SingleTickerProviderStateMixin {
  String get _text => widget.text;

  double get _fontSize => widget.fontSize;

  Color get _backgroundColor => widget.backgroundColor;

  Color get _foregroundColor => widget.foregroundColor;

  Color get _waveColor => widget.waveColor;

  late AnimationController _controller;

  late Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
        duration: const Duration(milliseconds: 700), vsync: this);
    _animation = Tween(
      begin: 0.0,
      end: 1.0,
    ).animate(_controller)
      ..addListener(() {
        setState(() => {});
      });
    _controller.repeat();
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return RepaintBoundary(
      child: CustomPaint(
        painter: _WaveLoadingPainter(
          text: _text,
          fontSize: _fontSize,
          animatedValue: _animation.value,
          backgroundColor: _backgroundColor,
          foregroundColor: _foregroundColor,
          waveColor: _waveColor,
        ),
      ),
    );
  }
}

_WaveLoadingPainter 根據(jù) animatedValue 來設(shè)置 wavePath 的起始坐標(biāo)點(diǎn)

wavePath.moveTo((animatedValue - 1) * waveWidth, radius);

七、使用

最后將 _WaveLoadingState 包裹到 StatefulWidget 中,在 StatefulWidget 中開放可以自定義配置的參數(shù)就可以了

class WaveLoading extends StatefulWidget {
  final String text;

  final double fontSize;

  final Color backgroundColor;

  final Color foregroundColor;

  final Color waveColor;

  WaveLoading({
    Key? key,
    required this.text,
    required this.fontSize,
    required this.backgroundColor,
    required this.foregroundColor,
    required this.waveColor,
  }) : super(key: key) {
    assert(text.isNotEmpty && fontSize > 0);
  }

  @override
  State<StatefulWidget> createState() {
    return _WaveLoadingState();
  }
}

使用方式:

SizedBox(
	width: 300,
	height: 300,
	child: WaveLoading(
  		text: "開",
  		fontSize: 210,
  		backgroundColor: Colors.lightBlue,
  		foregroundColor: Colors.white,
  		waveColor: Colors.lightBlue,
)

源代碼看這里:WaveLoadingWidget

以上就是Android 貝塞爾曲線繪制一個波浪球的詳細(xì)內(nèi)容,更多關(guān)于Android貝塞爾曲線的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論