通过Flutter打造炫酷时尚的 Neumorphism 设计!

简介: 当你打算为你的程序添加一些新鲜的设计元素时,Neumorphism绝对是一个值得尝试的选择。这种设计风格为你的UI元素增添了更多的纹理和深度感,使用户界面看起来更加现代化和真实。

前言

在前几天的业务需求中,UI给出的页面中有新拟态的按钮,就是带内部阴影的按钮,如果是利用cssbox-shadow的属性,那么实现起来很简单,但是奈何Flutter中的ContainerBoxShadow不具备inset内部阴影的功能,那么本文就来解决这个问题,在解决的过程中,我发现了Neumorphism.io,这可是一个神奇的网站,能满足各种圆角矩形icon图表立体化效果要求,同时给出了css代码。那么咱用Flutter简单模仿一个,并给出Flutter Container按钮对应的代码。

源码地址:https://github.com/taxze6/flutter_neumorphism

效果图:适配了手机端与网页

实现Flutter内部阴影

在Flutter中,可以使用以下几种方式来实现阴影:

  • BoxShadow

BoxShadow(

 color: Colors.grey.withOpacity(0.5),

 spreadRadius: 5,

 blurRadius: 7,

 offset: Offset(0, 3),

),

  • 使用Material组件的elevation属性来添加阴影

Material(

 elevation: 5.0,

 child: Text('Material Shadow'),

),

  • 使用PhysicalModel组件来添加阴影

PhysicalModel(

 color: Colors.white,

 elevation: 5.0,

 shadowColor: Colors.grey.withOpacity(0.5),

 borderRadius: BorderRadius.circular(10),

 child: const Text('PhysicalModel Shadow'),

),

  • 其他方法

总的来说,Flutter实现阴影的方法有很多。但是,在这么多实现的方法中,却没有能实现这样内部阴影的方法。

而在前几天的需求中,就遇到了需要这样的按钮UI,第一时间想到的是,通过Stack来实现多层阴影,从而达到内部阴影的效果,下面是简单的例子,实现出来的效果还可以,但是不够优雅。

Stack(

 alignment: AlignmentDirectional.center,

 children: [

   Container(

     width: 63,

     height: 63,

     decoration: const BoxDecoration(

       color: Color(0xFF949494),

       shape: BoxShape.circle,

       boxShadow: [

         BoxShadow(

           color: Color(0xFFE8E8E8),

           offset: Offset(8, 8),

           blurRadius: 10,

           spreadRadius: 1,

         ),

       ],

     ),

   ),

   ClipRRect(

     borderRadius: BorderRadius.circular(63),

     child: Container(

       width: 63,

       height: 63,

       decoration: const BoxDecoration(

         shape: BoxShape.circle,

         boxShadow: [

           BoxShadow(

             color: Color(0xFFF7F7F7),

             offset: Offset(3, 3),

             blurRadius: 3,

             spreadRadius: 1,

           ),

         ],

       ),

     ),

   ),

 ],

)

这时我想到了BoxShadow实现外阴影的功能,我想它既然能实现外部的阴影,那么把它的源码拉出来,模仿它外阴影的实现逻辑去绘制内阴影是否可行呢?我觉得可以,那么理论方案已经出现,开始实践。

将box_shadow与box_decoration的源码拷贝

先看box_decoration,看绘制的方法,这个paint方法主要就是绘制BoxDecoration中的各种装饰,例如背景颜色。而_paintShadows方法就是本文关注的重点,

//绘制阴影效果的函数

 void _paintShadows(Canvas canvas, Rect rect, TextDirection? textDirection) {

   // 检查是否需要绘制阴影

   if (_decoration.boxShadow == null) {

     // 如果不需要,直接返回

     return;

   }


   // 遍历阴影效果列表中的每个阴影配置

   for (final BoxShadow boxShadow in _decoration.boxShadow!) {

     // 根据阴影配置创建 Paint 对象

     final Paint paint = boxShadow.toPaint();

     // 根据阴影配置计算出阴影绘制区域

     final Rect bounds =

         rect.shift(boxShadow.offset).inflate(boxShadow.spreadRadius);

     //绘制阴影

     _paintBox(canvas, bounds, paint, textDirection);

   }

 }

根据外阴影的绘制逻辑,我们要做的就是在BoxShadow添加一个是否是绘制内阴影的属性,用于判断,因为如果需要内阴影就不再绘制外阴影了。

for (final painting.BoxShadow boxShadow in _decoration.boxShadow!) {

//添加判断

 if (boxShadow is! BoxShadow || !boxShadow.inset) {

   continue;

 }

...

}

既然判断了,如果需要绘制内阴影,就跳过外阴影的绘制逻辑,那么我们就需要自己添加内阴影的绘制逻辑。

void _paintInnerShadows(

 Canvas canvas,

 Rect rect,

 TextDirection? textDirection,

) {

 // 检查是否有需要绘制的阴影,如果没有则直接返回

 if (_decoration.boxShadow == null) {

   return;

 }

 // 遍历所有的BoxShadow

 for (final painting.BoxShadow boxShadow in _decoration.boxShadow!) {

   // 如果BoxShadow不是BoxShadow类型,或者不是内阴影,跳过本次循环

   if (boxShadow is! BoxShadow || !boxShadow.inset) {

     continue;

   }


   // 获取BoxShadow的颜色

   final color = boxShadow.color;


   // 计算圆角

   final borderRadiusGeometry = _decoration.borderRadius ??

       (_decoration.shape == BoxShape.circle

           ? BorderRadius.circular(rect.longestSide)

           : BorderRadius.zero);

   // 解决文本方向

   final borderRadius = borderRadiusGeometry.resolve(textDirection);


   // 使用RRect剪切画布

   final clipRRect = borderRadius.toRRect(rect);


   // 计算内部矩形

   final innerRect = rect.deflate(boxShadow.spreadRadius);


   // 如果内部矩形为空,则绘制整个矩形

   if (innerRect.isEmpty) {

     final paint = Paint()..color = color;

     canvas.drawRRect(clipRRect, paint);

   }

   // 否则,绘制内阴影

   else {

     // 计算内部矩形的RRect

     var innerRRect = borderRadius.toRRect(innerRect);

     // 保存画布状态

     canvas.save();

     // 在剪切区域内绘制内阴影

     canvas.clipRRect(clipRRect);

     // 计算包含内阴影和剪切区域的矩形

     final outerRect = _areaCastingShadowInHole(rect, boxShadow);

     // 绘制内阴影

     canvas.drawDRRect(

       RRect.fromRectAndRadius(outerRect, Radius.zero),

       innerRRect.shift(boxShadow.offset),

       Paint()

         ..color = color

         ..colorFilter = ColorFilter.mode(color, BlendMode.srcIn)

         ..maskFilter =

             MaskFilter.blur(BlurStyle.normal, boxShadow.blurSigma),

     );

     // 恢复画布状态

     canvas.restore();

   }

 }

}

其中_areaCastingShadowInHole方法就是用来计算box中阴影的区域:

///holeRect:表示阴影的位置和大小

///shadow:表示阴影的颜色、大小、位置

Rect _areaCastingShadowInHole(Rect holeRect, BoxShadow shadow) {

 var bounds = holeRect;

 //将bounds沿着所有方向膨胀shadow.blurRadius的距离

 //确保生成的阴影图像元素不会被截断

 bounds = bounds.inflate(shadow.blurRadius);


 //BoxShadow.spreadRadius用于控制阴影扩展的距离

 //如果值小于0,则阴影会从矩形边界开始,向内收缩。

 if (shadow.spreadRadius < 0) {

   bounds = bounds.inflate(-shadow.spreadRadius);

 }


 //Rect.shift 方法用于将矩形的位置偏移指定的距离

 final offsetBounds = bounds.shift(shadow.offset);


 return _unionRects(bounds, offsetBounds);

}

返回的_unionRects作用主要是先检查boundsoffsetBounds两个矩形是否有空矩形,如果有,则直接返回非空矩形。否则,它计算出包含这两个矩形的最小矩形,并返回该矩形。

Rect _unionRects(Rect a, Rect b) {

 if (a.isEmpty) {

   return b;

 }


 if (b.isEmpty) {

   return a;

 }


 final left = math.min(a.left, b.left);

 final top = math.min(a.top, b.top);

 final right = math.max(a.right, b.right);

 final bottom = math.max(a.bottom, b.bottom);


 return Rect.fromLTRB(left, top, right, bottom);

}

然后就能在paint方法中参与绘制的过程:

更多的细节可以参考源码,注释都很全。

讲完了如何实现Flutter的内部阴影,本文也没有其他重要的知识点了,不过有一些有趣的东西:

在实现Flutter_Neumorphism中,计算方块上下两个阴影的颜色的过程:

// 定义静态函数 getAdjustColor,接收基础颜色 baseColor 和需要调整的颜色量 amount

static Color getAdjustColor(Color baseColor, int amount) {

 // 将 baseColor 的 red、green、blue 数值存储在一个 Map 对象 colors 中

 Map colors = {

   "red": baseColor.red,

   "green": baseColor.green,

   "blue": baseColor.blue

 };


 // 使用 map 函数对 colors 中的每一个键值对进行处理

 colors = colors.map((key, value) {

   // 如果 value + amount < 0,则将当前数值设为 0

   if (value + amount < 0) return MapEntry(key, 0);

   // 如果 value + amount > 255,则将当前数值设为 255

   if (value + amount > 255) return MapEntry(key, 255);

   // 否则,将当前数值设为 value + amount

   return MapEntry(key, value + amount);

 });


 // 返回根据调整后的 red、green、blue 数值创建的颜色对象

 return Color.fromRGBO(colors["red"], colors["green"], colors["blue"], 1);

}

更多的细节请看源码,建议大家运行体验看看~


关于我

Hello,我是Taxze,如果您觉得文章对您有价值,希望您能给我的文章点个❤️,有问题需要联系我的话:我在这里 。如果您觉得文章还差了那么点东西,也请通过关注督促我写出更好的文章~万一哪天我进步了呢?😝

相关文章
|
6月前
|
编解码 前端开发 开发者
【Flutter前端技术开发专栏】Flutter中的响应式设计与自适应布局
【4月更文挑战第30天】Flutter框架助力移动应用实现响应式设计与自适应布局,通过层次化布局系统和`Widget`树管理,结合`BoxConstraints`定义尺寸范围,实现自适应。利用`MediaQuery`获取设备信息,调整布局以适应不同屏幕。`FractionallySizedBox`按比例设定尺寸,`LayoutBuilder`动态计算布局。借助这些工具,开发者能创建跨屏幕尺寸、方向兼容的应用,提升用户体验。
162 0
【Flutter前端技术开发专栏】Flutter中的响应式设计与自适应布局
|
1月前
|
缓存 Dart IDE
鸿蒙Flutter实战:10-常见问题集合
本文介绍了鸿蒙 Flutter 开发的学习路径,包括掌握 Flutter 和鸿蒙基础知识,解决 MatePad 适配、模拟器异常、debug 版本错误等问题,并提供了更换 App 图标和名称的方法及环境变量配置指导。
63 0
|
6月前
|
开发框架 前端开发 数据安全/隐私保护
【Flutter 前端技术开发专栏】Flutter 中的布局与样式设计
【4月更文挑战第30天】本文探讨了Flutter的布局和样式设计,关键点包括:1) 布局基础如Column、Row和Stack用于创建复杂结构;2) Container、Center和Expanded等常用组件的作用;3) Theme和Decoration实现全局样式和组件装饰;4) 实战应用如登录界面和列表页面的构建;5) 响应式布局利用MediaQuery和弹性组件适应不同屏幕;6) 性能优化,避免过度复杂设计。了解并掌握这些,有助于开发者创建高效美观的Flutter应用。
192 0
【Flutter 前端技术开发专栏】Flutter 中的布局与样式设计
|
4月前
|
设计模式 编解码 API
Flutter UI设计模式与实现:深入探索与实践
【7月更文挑战第20天】Flutter以其独特的声明式UI模式和丰富的UI组件库,为移动应用开发提供了强大的支持。通过深入理解Flutter的UI设计模式和实现技巧,开发者可以构建出高性能、可维护性强的UI界面。同时,随着Flutter生态的不断完善和发展,相信未来Flutter将在移动应用开发领域发挥更加重要的作用。
|
6月前
|
前端开发 开发者 UED
【Flutter前端技术开发专栏】Flutter中的手势识别与触摸事件处理
【4月更文挑战第30天】本文探讨了Flutter中的手势识别和触摸事件处理,关键点包括: 1. 使用`GestureRecognizer`类体系实现手势识别,如`TapGestureRecognizer`检测点击,`HorizontalDragGestureRecognizer`和`VerticalDragGestureRecognizer`识别滑动,`ScaleGestureRecognizer`识别捏合和扩张。
160 0
【Flutter前端技术开发专栏】Flutter中的手势识别与触摸事件处理
|
Android开发 iOS开发 UED
闲鱼技术2022年度白皮书-Flutter主题-Flutter富文本编辑器系列文章3——交互篇(上)
闲鱼技术2022年度白皮书-Flutter主题-Flutter富文本编辑器系列文章3——交互篇
202 0
闲鱼技术2022年度白皮书-Flutter主题-Flutter富文本编辑器系列文章3——交互篇(上)
|
JavaScript 测试技术 Android开发
闲鱼技术2022年度白皮书-Flutter主题-Flutter富文本编辑器系列文章3——交互篇(下)
闲鱼技术2022年度白皮书-Flutter主题-Flutter富文本编辑器系列文章3——交互篇
131 0
|
Android开发 iOS开发
闲鱼技术2022年度白皮书-Flutter主题-Flutter富文本编辑器系列文章3——交互篇(中)
闲鱼技术2022年度白皮书-Flutter主题-Flutter富文本编辑器系列文章3——交互篇
149 0
|
Android开发 iOS开发
闲鱼技术2022年度白皮书-Flutter主题-打造Flutter高性能富文本编辑器——渲染篇(中)
闲鱼技术2022年度白皮书-Flutter主题-打造Flutter高性能富文本编辑器——渲染篇
300 0
闲鱼技术2022年度白皮书-Flutter主题-打造Flutter高性能富文本编辑器——渲染篇(下)
闲鱼技术2022年度白皮书-Flutter主题-打造Flutter高性能富文本编辑器——渲染篇
197 0