Flutter 84: 图解自定义 ACEWave 波浪 Widget (二)

简介: 0 基础学习 Flutter,第八十四步:尝试优化自定义波浪效果 (二)!

      小菜继续完善前两天自定义 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 案例源码


      至此,小菜自定义的波浪效果基本完成,其中对于性能和异常的处理还为完善;如有错误,请多多指导!

来源: 阿策小和尚

目录
相关文章
|
2天前
|
容器
Flutter Widget 解析
Flutter Widget 解析
|
2天前
|
存储 容器
Flutter 有状态Widget 和 无状态Widget
Flutter 有状态Widget 和 无状态Widget
|
1月前
深入理解Flutter鸿蒙next版本 中的Widget继承:使用extends获取数据与父类约束
本文详细介绍了Flutter中如何通过继承其他Widget来创建自定义组件。首先解释了Widget继承的基本概念,包括StatelessWidget和StatefulWidget的区别。接着通过具体示例展示了如何继承StatelessWidget和StatefulWidget,并在子类中访问父类的build方法和状态。最后,结合多个自定义Widget展示了如何在实际应用中灵活使用继承和组合来构建复杂的UI。
76 8
|
1月前
|
容器
flutter&鸿蒙next 使用 InheritedWidget 实现跨 Widget 传递状态
在 Flutter 中,状态管理至关重要。本文详细介绍了如何使用 InheritedWidget 实现跨 Widget 的状态传递。InheritedWidget 允许数据在 Widget 树中向下传递,适用于多层嵌套的场景。通过一个简单的计数器示例,展示了如何创建和使用 InheritedWidget,包括其基础概念、工作原理及代码实现。虽然 InheritedWidget 较底层,但它是许多高级状态管理解决方案的基础。
103 2
|
1月前
|
UED 开发者 容器
Flutter&鸿蒙next 的 Sliver 实现自定义滚动效果
Flutter 提供了强大的滚动组件,如 ListView 和 GridView,但当需要更复杂的滚动效果时,Sliver 组件是一个强大的工具。本文介绍了如何使用 Sliver 实现自定义滚动效果,包括 SliverAppBar、SliverList 等常用组件的使用方法,以及通过 CustomScrollView 组合多个 Sliver 组件实现复杂布局的示例。通过具体代码示例,展示了如何实现带有可伸缩 AppBar 和可滚动列表的页面。
110 1
|
1月前
Flutter 自定义组件继承与调用的高级使用方式
本文深入探讨了 Flutter 中自定义组件的高级使用方式,包括创建基本自定义组件、继承现有组件、使用 Mixins 和组合模式等。通过这些方法,您可以构建灵活、可重用且易于维护的 UI 组件,从而提升开发效率和代码质量。
131 1
|
1月前
|
前端开发 开发者
深入探索 Flutter 鸿蒙版的画笔使用与高级自定义动画
本文深入探讨了 Flutter 中的绘图功能,重点介绍了 CustomPainter 和 Canvas 的使用方法。通过示例代码,详细讲解了如何绘制自定义图形、设置 Paint 对象的属性以及实现高级自定义动画。内容涵盖基本绘图、动画基础、渐变动画和路径动画,帮助读者掌握 Flutter 绘图与动画的核心技巧。
80 1
|
1月前
|
Dart UED 开发者
Flutter&鸿蒙next中的按钮封装:自定义样式与交互
在Flutter应用开发中,按钮是用户界面的重要组成部分。Flutter提供了多种内置按钮组件,但有时这些样式无法满足特定设计需求。因此,封装一个自定义按钮组件变得尤为重要。自定义按钮组件可以确保应用中所有按钮的一致性、可维护性和可扩展性,同时提供更高的灵活性,支持自定义颜色、形状和点击事件。本文介绍了如何创建一个名为CustomButton的自定义按钮组件,并详细说明了其样式、形状、颜色和点击事件的处理方法。
86 1
|
1月前
|
Dart 搜索推荐 API
Flutter & 鸿蒙next版本:自定义对话框与表单验证的动态反馈与错误处理
在现代移动应用开发中,用户体验至关重要。本文探讨了如何在 Flutter 与鸿蒙操作系统(HarmonyOS)中创建自定义对话框,并结合表单验证实现动态反馈与错误处理,提升用户体验。通过自定义对话框和表单验证,开发者可以提供更加丰富和友好的交互体验,同时利用鸿蒙next版本拓展应用的受众范围。
84 1
|
2月前
|
容器
flutter:第一个flutter&Widget的使用 (二)
本文介绍了Flutter框架下的基本组件及其用法,包括简单的 Stateless Widget 如文本和按钮,以及更复杂的 StatefulWidget 示例。详细解释了如何使用 `context` 获取祖先小部件的信息,并展示了 `MaterialApp` 的属性及用途。此外,还探讨了 `StatefulWidget` 与 `StatelessWidget` 的区别,以及 `AppBar` 的常见属性配置方法。适合Flutter初学者参考学习。