【Flutter 專題】84 圖解自定義 ACEWave 波浪 Widget (二)|8月更文挑戰

阿策小和尚發表於2021-08-04

      “這是我參與8月更文挑戰的第4天,活動詳情檢視: 8月更文挑戰” juejin.cn/post/698796…

      小菜繼續完善前兩天自定義 ACEWave 波浪元件,小菜預期的效果是多條波浪,漸變顏色,波浪寬高自定義等;

1. 區分波浪寬度動畫

      小菜上一節測試時波浪寬度小於螢幕寬度,當放大波浪寬度時,迴圈過程中動畫會跳動一下,不順暢;其原因是 Animation 動畫設定有問題;

      小菜調整了平移動畫的 Offset 位置,並設定波浪起始位置偏移量與小波浪時相反;

return Transform.translate(
    offset: Offset(waveWidth * _curvedAnimation.value, 0.0),
    child: Container(); // 波浪
複製程式碼

2. 填充波浪顏色

      再此之前小菜嘗試的均為線條波浪,小菜理想的效果的是有填充色的,於是設定三屏波浪最末點與三屏波浪的最初點,通過 lineTo 連線起來,並設定 Paint 畫筆為填充效果;

Path _wavePath(path, size, plusWidth) {
  for (int i = 0; i < _count; i++) {
    path.moveTo(waveWidth * i - size.width - startOffset, startOffsetY);
    path.quadraticBezierTo(
        _quaterWidth + waveWidth * i - size.width - startOffset, startOffsetY - waveHeight,
        _quaterWidth * 2 + waveWidth * i - size.width - startOffset, startOffsetY);
    path.moveTo(_quaterWidth * 2 + waveWidth * i - size.width - startOffset, startOffsetY);
    path.quadraticBezierTo(
        _quaterWidth * 3 + waveWidth * i - size.width - startOffset, startOffsetY + waveHeight,
        _quaterWidth * 4 + waveWidth * i - size.width - startOffset, startOffsetY);
    path.moveTo(_quaterWidth * 4 + waveWidth * i - size.width - startOffset, startOffsetY);
  }
  path.lineTo(_quaterWidth * 4 + waveWidth * (_count - 1) - size.width - startOffset, 600.0);
  path.lineTo(-size.width - startOffset, 600.0);
  path.lineTo(-size.width - startOffset, startOffsetY);

  for (int i = 0; i < _count; i++) {
    path.moveTo(waveWidth * i - startOffset + plusWidth, startOffsetY);
    path.quadraticBezierTo(
        _quaterWidth + waveWidth * i - startOffset + plusWidth, startOffsetY - waveHeight,
        _quaterWidth * 2 + waveWidth * i - startOffset + plusWidth, startOffsetY);
    path.moveTo(_quaterWidth * 2 + waveWidth * i - startOffset + plusWidth, startOffsetY);
    path.quadraticBezierTo(
        _quaterWidth * 3 + waveWidth * i - startOffset + plusWidth, startOffsetY + waveHeight,
        _quaterWidth * 4 + waveWidth * i - startOffset + plusWidth, startOffsetY);
    path.moveTo(_quaterWidth * 4 + waveWidth * i - startOffset + plusWidth, startOffsetY);
  }
  path.lineTo(_quaterWidth * 4 + waveWidth * (_count - 1) - startOffset + plusWidth, 600.0);
  path.lineTo(-startOffset + plusWidth, 600.0);
  path.lineTo(-startOffset + plusWidth, startOffsetY);

  for (int i = 0; i < _count; i++) {
    path.moveTo(waveWidth * i + size.width - startOffset + plusWidth * 2, startOffsetY);
    path.quadraticBezierTo(
        _quaterWidth + waveWidth * i + size.width - startOffset + plusWidth * 2, startOffsetY - waveHeight,
        _quaterWidth * 2 + waveWidth * i + size.width - startOffset + plusWidth * 2, startOffsetY);
    path.moveTo(
        _quaterWidth * 2 + waveWidth * i + size.width - startOffset + plusWidth * 2, startOffsetY);
    path.quadraticBezierTo(
        _quaterWidth * 3 + waveWidth * i + size.width - startOffset + plusWidth * 2,
        startOffsetY + waveHeight, _quaterWidth * 4 + waveWidth * i + size.width - startOffset + plusWidth * 2, startOffsetY);
    path.moveTo(_quaterWidth * 4 + waveWidth * i + size.width - startOffset + plusWidth * 2, startOffsetY);
  }
  path.lineTo(
      _quaterWidth * 4 + waveWidth * (_count - 1) + size.width - startOffset + plusWidth * 2, 600.0);
  path.lineTo(size.width - startOffset + plusWidth * 2, 600.0);
  path.lineTo(size.width - startOffset + plusWidth * 2, startOffsetY);
  return path;
}
複製程式碼

3. 波浪漸變色

      小菜填充完波浪顏色之後,想進一步實現波浪漸變色,可以通過 Paint 畫筆來設定 shader 漸變效果;其中線性漸變的起始點從波峰開始,至最底部為止;

Paint paint = Paint()
    ..color = Colors.blue ..strokeCap = StrokeCap.round ..strokeWidth = 6
    ..style = PaintingStyle.fill;
var rect = Offset(0.0, startOffsetY) & Size(size.width, size.height)
paint.shader = LinearGradient(
    begin: Alignment.bottomCenter,
    end: Alignment.topCenter,
    colors: [Colors.blue.withOpacity(0.4), Colors.white.withOpacity(0.4) ]).createShader(rect);
複製程式碼

4. 裁剪波浪

      小菜設定的波浪高度預設是填充滿父控制元件的,但若父控制元件高度小於波浪的波峰到波谷高度時,波谷依然繪製出來,此時小菜通過裁剪方式,只展示設定的最高高度即可;此時注意優先設定裁剪範圍,之後再進行波浪的繪製;

canvas.save();

canvas.clipPath(_clipPath(size, _plusWidth));
canvas.drawPath(_wavePath(size, _plusWidth), paint);

canvas.restore();
複製程式碼

5. 設定多條波浪

      小菜想一次性展示多條波浪,於是將各個自定義引數型別及動畫 Animation 放在 List 中,只需在初始化時傳遞多條資料即可;其中包括波浪寬高,一個波浪動時長,初始橫縱偏移量以及漸變色波浪顏色等;

// 波浪整體高度(裁剪後)
final double allHeight;
// 一個週期波浪寬度 List
final List<double> waveWidthList;
// 波峰到中心點高度 List
final List<double> waveHeightList;
// 水平偏移量 List
final List<double> startOffsetXList;
// 波峰距頂點偏移量 List
final List<double> startOffsetYList;
// 時間 List
final List<Duration> durationList;
// 漸變色 List
final List<List<Color>> waveColorList;

ACEWave(this.waveWidthList, this.waveHeightList, this.allHeight,
    {this.durationList,
    this.startOffsetXList,
    this.startOffsetYList,
    this.waveColorList});
複製程式碼
List<double> waveWidth = [600, 800, 300];
List<double> waveHeight = [60, 80, 70];
List<double> startOffsetX = [30, 150, 100];
List<double> startOffsetY = [100, 120, 100];
List<Duration> duration = [
  Duration(milliseconds: 6000),
  Duration(milliseconds: 4000),
  Duration(milliseconds: 5000)
];
List<List<Color>> colorList = [
  [Colors.green.withOpacity(0.2), Colors.white.withOpacity(0.4)],
  [Colors.blue.withOpacity(0.2), Colors.white.withOpacity(0.4)],
  [Colors.blue.withOpacity(0.2), Colors.white.withOpacity(0.4)]
];
return Scaffold(
    appBar: AppBar(title: Text('ACEWave Page')),
    body: Container(
        color: Colors.grey,
        height: (MediaQuery.of(context).size.height),
        child: Container(
            child: ACEWave(
          waveWidth,
          waveHeight,
          300.0,
          startOffsetXList: startOffsetX,
          startOffsetYList: startOffsetY,
          durationList: duration,
          waveColorList: colorList,
        ))));
複製程式碼


      ACEWave 案例原始碼


      至此,小菜自定義的波浪效果基本完成,其中對於效能和異常的處理還為完善;如有錯誤,請多多指導!

來源: 阿策小和尚

相關文章