Flutter内阴影

前言

在前几天的业务需求中,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,如果您觉得文章对您有价值,希望您能给我的文章点个❤️,有问题需要联系我的话:我在这里 如果您觉得文章还差了那么点东西,也请通过关注督促我写出更好的文章~万一哪天我进步了呢?😝

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/2400.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Linux内网穿透】使用SFTP工具快速实现内网穿透

文章目录内网穿透简介1. 查看地址2.局域网测试连接3.创建tcp隧道3.1. 安装cpolar4.远程访问5.固定TCP地址内网穿透简介 是一种通过公网将内网服务暴露出来的技术&#xff0c;可以使得内网服务可以被外网访问。以下是内网穿透的一些应用&#xff1a; 远程控制&#xff1a;通过内…

【头歌实验】课外作业一:开通ECS及使用Linux命令

文章目录一、完成下列实验并截图二、简要回答“课堂考核”内容三、在头歌、华为云或阿里云官网上&#xff0c;找出自己的课外学习资源&#xff0c;制定小组的课程学习计划、专业学习计划。四、习题1.10一、完成下列实验并截图 1、实验《ECS云服务器新手上路》 https://develo…

【LeetCode】1022. 从根到叶的二进制数之和、563. 二叉树的坡度

作者&#xff1a;小卢 专栏&#xff1a;《Leetcode》 喜欢的话&#xff1a;世间因为少年的挺身而出&#xff0c;而更加瑰丽。 ——《人民日报》 1022. 从根到叶的二进制数之和 1022. 从根到叶的二进制数之和 题目描述&#xff1a; 给出一…

OpenCV入门(十八)快速学会OpenCV 17 直线检测

OpenCV入门&#xff08;十八&#xff09;快速学会OpenCV 17 直线检测1.霍夫直线变换概述2.霍夫变换原理3.操作实例3.1 HoughLines函数3.2 HoughLinesP函数作者&#xff1a;Xiou 1.霍夫直线变换概述 霍夫变换是一种在图像中寻找直线、圆形以及其他简单形状的方法。霍夫变换采用…

HTML5庆祝生日蛋糕烟花特效

HTML5庆祝生日蛋糕烟花特效 <!DOCTYPE html> <html> <head><meta charset"UTF-8"><title>HTML5 Birthday Cake Fireworks</title><style>canvas {position: absolute;top: 0;left: 0;z-index: -1;}</style> </h…

css + js 超好看的消息提示

先看图 css 使用了layui&#xff0c;直接在官网下载引入即可 实现的功能 自定义消息弹出位置自定义消息类型自定义消息关闭时间消息弹出关闭动画 <style>.message {width: 300px;/* background-color: rgba(0, 0, 0, 0.2); */background-color: rgba(255, 255, 255…

Linux - 进程控制(创建和终止)

1.进程创建fork函数初识 在linux中fork函数时非常重要的函数&#xff0c;它从已存在进程中创建一个新进程。新进程为子进程&#xff0c;而原进程为父进程。返回值&#xff1a;子进程返回0&#xff0c;父进程返回子进程id&#xff0c;出错返回-1getpid()获取子进程id&#xff0c…

【Linux】进程优先级 环境变量

进程优先级 环境变量 一、进程优先级1、基本概念2、查看以及修改系统进程的优先级3、一些其他的关于进程优先级的指令和函数调用4、与进程优先级有关的一些进程性质二、环境变量1、基本概念2、和环境变量相关的命令3、Linux中的常见环境变量介绍4、环境变量的组织方式以及在C代…

人脸识别经典网络-MTCNN(含Python源码实现)

人脸检测-mtcnn 本文参加新星计划人工智能赛道&#xff1a;https://bbs.csdn.net/topics/613989052 文章目录人脸检测-mtcnn1. 人脸检测1.1 人脸检测概述1.2 人脸检测的难点1.3 人脸检测的应用场景2. mtcnn2.1 mtcnn概述2.2 mtcnn的网络结构2.3 图像金字塔2.4 P-Net2.5 R-Net2…

蓝桥杯刷题冲刺 | 倒计时20天

作者&#xff1a;指针不指南吗 专栏&#xff1a;蓝桥杯倒计时冲刺 &#x1f43e;马上就要蓝桥杯了&#xff0c;最后的这几天尤为重要&#xff0c;不可懈怠哦&#x1f43e; 文章目录1.铁路与公路2.数字反转3.奖学金4.求阶乘1.铁路与公路 题目 链接&#xff1a; 4074. 铁路与公路…

论文阅读《Point NeRF:Point-based Neural Radiance Fileds》

论文地址&#xff1a;https://arxiv.org/abs/2201.08845 源码地址&#xff1a;https://xharlie.github.io/projects/project_sites/pointnerf 概述 体素神经渲染的方法生成高质量的结果非常耗时&#xff0c;且对不同场景需要重新训练&#xff08;模型不具备泛化能力&#xff09…

多线程 (六) wait和notify

&#x1f389;&#x1f389;&#x1f389;点进来你就是我的人了 博主主页&#xff1a;&#x1f648;&#x1f648;&#x1f648;戳一戳,欢迎大佬指点!人生格言&#xff1a;当你的才华撑不起你的野心的时候,你就应该静下心来学习! 欢迎志同道合的朋友一起加油喔&#x1f9be;&am…

Qt实践项目:仿Everything软件实现一个QtEverything

⭐️我叫忆_恒心&#xff0c;一名喜欢书写博客的在读研究生&#x1f468;‍&#x1f393;。 如果觉得本文能帮到您&#xff0c;麻烦点个赞&#x1f44d;呗&#xff01; 近期会不断在专栏里进行更新讲解博客~~~ 有什么问题的小伙伴 欢迎留言提问欧&#xff0c;喜欢的小伙伴给个三…

九【springboot】

Springboot一 Spring Boot是什么二 SpringBoot的特点1.独立运行的spring项目三 配置开发环境四 配置开发环境五 创建 Spring Boot 项目1.在 IntelliJ IDEA 欢迎页面左侧选择 Project &#xff0c;然后在右侧选择 New Project&#xff0c;如下图2.在新建工程界面左侧&#xff0c…

GPT-4来了!看看她究竟强在哪里!

GPT-4来了&#xff01;OpenAI老板Sam Altman直接开门见山地介绍说&#xff1a;这是我们迄今为止功能最强大的模型&#xff01;GPT-4是一个超大的多模态模型&#xff0c;也就是说&#xff0c;它的输入可以是文字&#xff08;上限2.5万字&#xff09;&#xff0c;还可以是图像。我…

【洛谷刷题】蓝桥杯专题突破-深度优先搜索-dfs(7)

目录 写在前面&#xff1a; 题目&#xff1a;P1596 [USACO10OCT]Lake Counting S - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 题目描述&#xff1a; 输入格式&#xff1a; 输出格式&#xff1a; 输入样例&#xff1a; 输出样例&#xff1a; 解题思路&#xff1a; …

list底层的简单实现(万字长文详解!)

list底层的简单实现 文章目录list底层的简单实现list_node的实现&#xff01;list_node的构造函数list的迭代器&#xff01;——重点&#xff01;list迭代器的成员变量迭代器的构造函数* 重载前置 重载后置 重载前置-- 重载后置-- 重载! 重载 重载-- 重载list的const迭代器——…

提高曝光率:外贸网站如何充分利用谷歌优化赢得客户

自从我从事外贸行业以来&#xff0c;谷歌优化一直是我关注的重点。 作为一个外贸从业者&#xff0c;我深知提高网站在谷歌搜索引擎中的排名对企业的重要性。 那么&#xff0c;如何利用谷歌优化来提高外贸网站的曝光率&#xff0c;从而赢得更多客户呢&#xff1f; 以下是我在…

单例模式,饿汉与懒汉

文章目录什么是单例模式单例模式的两种形式饿汉模式懒汉模式懒汉模式与饿汉模式是否线程安全懒汉模式的优化什么是单例模式 单例模式其实就是一种设计模式&#xff0c;跟象棋的棋谱一样&#xff0c;给出一些固定的套路帮助你更好的完成代码。设计模式有很多种&#xff0c;单例…

Ubuntu-C语言下的应用

文章目录一、Ubuntu下C语言的应用&#xff08;一&#xff09;如何使用gedit创建/打开/保存/关闭文件&#xff08;二&#xff09;gedit中相关参数配置&#xff1a;首选项&#xff08;三&#xff09;ubuntu下C语言的编译器 -- gcc一、Ubuntu下C语言的应用 &#xff08;一&#x…
最新文章