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

Android?貝塞爾曲線繪制一個(gè)波浪球

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

前言

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

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

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

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

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

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

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

一、繪制 backgroundColor 文本

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

這里就來(lái)繼承 CustomPainter 類,在 paint 方法中先來(lái)繪制顏色為 backgroundColor 的文本。flutter 的 canvas 對(duì)象沒有提供直接 drawText 的 API,所以其繪制文本的步驟相對(duì)原生的自定義 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)建出一個(gè)不超出 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ù)一個(gè)固定的比例值來(lái)求值,以 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 繪制出來(lái),實(shí)際上不需要
    final paint = Paint()
      ..isAntiAlias = true
      ..style = PaintingStyle.fill
      ..strokeWidth = 3
      ..color = waveColor;
    canvas.drawPath(wavePath, paint);
  }

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

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

wavePath 閉合后,此時(shí)半圓的顏色就會(huì)鋪滿了

四、取交集

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

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

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

六、添加動(dòng)畫

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

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

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 來(lái)設(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 貝塞爾曲線繪制一個(gè)波浪球的詳細(xì)內(nèi)容,更多關(guān)于Android貝塞爾曲線的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論