Flutter实现笑嘻嘻的动态表情的示例代码
作者:岛上码农 发布时间:2023-02-13 21:39:51
前言
身在孤岛有很多无奈,比如说程序员属于比较偏门的职业。尤其是早些年,在行业里跳过几次槽后,可能你就已经认识整个圈子的人了。然后,再跳槽很可能就再次“偶遇”前同事了,用大潘的口头语来说就是:“好尴尬呀”。因此, 问起职业,往往只能是回答是搞计算机的。结果可能更尴尬,问的人可能笑嘻嘻地瞅着你,像看怪物一样看着你,接着突然冒出一句灵魂拷问:“我家电脑坏了,你能修不?”不过也不奇怪,那个时候在岛上重装一个 Windows XP 系统都需要100大洋。唉,当初后悔没在中关村的鼎好多学习攒机技术……
image.png
这个印象太深刻,本篇我们就用动画复现一下这种表情,效果如下图所示。
笑脸动画.gif
AnimatedContainer 介绍
在实现之前,先介绍一个新组件 —— AnimatedContainer
。看这个名字就知道和 Container
有关,实际上AnimatedContainer
是 Flutter 中的一个动画容器,Container
有的属性基本上它都有,我们看一下二者的构造方法的区别。
AnimatedContainer({
Key? key,
this.alignment,
this.padding,
Color? color,
Decoration? decoration,
this.foregroundDecoration,
double? width,
double? height,
BoxConstraints? constraints,
this.margin,
this.transform,
this.transformAlignment,
this.child,
this.clipBehavior = Clip.none,
Curve curve = Curves.linear,
required Duration duration,
VoidCallback? onEnd,
});
Container({
Key? key,
this.alignment,
this.padding,
this.color,
this.decoration,
this.foregroundDecoration,
double? width,
double? height,
BoxConstraints? constraints,
this.margin,
this.transform,
this.transformAlignment,
this.child,
this.clipBehavior = Clip.none,
});
可以看到,实际上 AnimatedContainer
和 Container
只差了3个属性,而这三个属性就是控制动画的参数:
curve
:动画曲线,默认是线性;duration
:动效时长参数;onEnd
:动效结束后的回调方法。
AnimatedContainer
的特性是所有涉及外观的属性都会生成一个过渡动效,当这些外观属性发生改变的时候就会使用生成的的动效来完成过渡,从而展现出动画效果。像我们要实现的笑嘻嘻的表情其实就是利用 AnimatedContainer
实现的。
组件结构
我们的笑嘻嘻动效,底部是一个圆形脑袋,上面有两颗眼睛和一个嘴巴,其中眼睛和嘴巴有移动动效,而眼睛的眼珠还有方向的动效。这些动效都可以使用AnimatedContainer
来实现。大的页面结构如下:
细节实现
脑袋这个很容易,直接用原型裁剪,设置尺寸和底色即可:
// 脑袋
ClipOval(
child: Container(
width: 120,
height: 120,
color: Colors.blue,
),
),
眼睛左眼和右眼有点不一样,眼球实际就是AnimatedContainer
使用 borderRadius
裁剪为圆形,而眼珠是AnimatedContainer
的子组件 —— 黑色的圆圈。具体实现向左或向右看使用一个变量 seeLeft
控制,而向左向右的转换过渡效果都由 AnimatedContainer
控制。
seeLeft = true
,向左看:眼珠对齐的方式是bottomLeft
,左眼纵向方向上稍微往下移一点;右眼往左移动一定的位置,这样就会有向左看的效果了;seeLeft = false
,向右看:眼珠对齐的方式是bottomRight
,右眼纵向方向上稍微往下移一点;左眼往右移动一定的位置,这样就会有向右看的效果了;
实现代码如下:
// 左眼
Positioned(
top: marginTop,
left: marginLR,
child: AnimatedContainer(
alignment:
seeLeft ? Alignment.bottomLeft : Alignment.bottomRight,
padding: EdgeInsets.all(eyePadding),
transform: Matrix4.identity()
..translate(
seeLeft ? 0.0 : sideOffset, seeLeft ? eyeOffset : 0.0, 0),
duration: Duration(seconds: 1),
curve: Curves.fastOutSlowIn,
width: eyeSize,
height: eyeSize,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(eyeSize / 2),
),
child: ClipOval(
child: Container(
color: Colors.black,
width: eyeBallSize,
height: eyeBallSize,
),
),
),
),
// 右眼
Positioned(
top: marginTop,
right: marginLR,
child: AnimatedContainer(
alignment:
seeLeft ? Alignment.bottomLeft : Alignment.bottomRight,
padding: EdgeInsets.all(eyePadding),
transform: Matrix4.identity()
..translate(seeLeft ? -sideOffset : 0.0,
seeLeft ? 0.0 : eyeOffset, 0),
duration: Duration(seconds: 1),
curve: Curves.fastOutSlowIn,
width: eyeSize,
height: eyeSize,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(eyeSize / 2),
),
child: ClipOval(
child: Container(
color: Colors.black,
width: eyeBallSize,
height: eyeBallSize,
),
),
),
),
这里的眼珠对齐使用的就是AnimatedContainer
的 alignment
参数控制,而眼球的位置使用 Matrix4
的平移实现:
Matrix4.identity()
..translate(seeLeft ? -sideOffset : 0.0, seeLeft ? 0.0 : eyeOffset, 0),
笑脸的实现使用ClipPath
,绘制两条弧线就可以了,然后平移的幅度和眼珠保持一致,就可以感觉是转头的效果了,AnimatedContainer
部分的代码如下:
// 笑嘻嘻的嘴巴
Positioned(
bottom: 10,
height: 40,
left: 0,
child: AnimatedContainer(
alignment:
seeLeft ? Alignment.bottomLeft : Alignment.bottomRight,
padding: EdgeInsets.all(4.0),
transform: Matrix4.identity()
..translate(seeLeft ? 25.0 : 35.0, 0, 0),
duration: Duration(seconds: 1),
curve: Curves.fastOutSlowIn,
child: ClipPath(
clipper: SmileClipPath(),
child: Container(
width: 60,
height: 40,
color: Colors.yellow,
),
),
),
),
笑嘻嘻的嘴巴的裁剪类 SmileClipPath
代码如下:
class SmileClipPath extends CustomClipper<Path> {
@override
Path getClip(Size size) {
return Path()
..moveTo(0, 0)
..arcToPoint(
Offset(size.width, 0),
radius: Radius.circular(size.width * 0.55),
clockwise: false,
)
..arcToPoint(
Offset(0, 0),
radius: Radius.circular(size.width),
clockwise: true,
);
}
@override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
return false;
}
}
最后,控制状态变量 seeLeft
的变化通过一个按钮点击触发就好了。
floatingActionButton: FloatingActionButton(
child: Icon(Icons.play_arrow, color: Colors.white),
onPressed: () {
setState(() {
seeLeft = !seeLeft;
});
},
),
最终运行效果如下,完整代码已提交至:动画相关代码。
笑脸动画.gif
来源:https://mp.weixin.qq.com/s/Mk2WdCmealI9sRGR0CfHUA


猜你喜欢
- java代理有jdk * 、cglib代理,这里只说下jdk * ,jdk * 主要使用的是java反射机制(既java.lang.r
- 日期显示和选择类库,可以用来选择一段连续的和多个不连续的日期,具体的UI完全抽象出来了,可以高度自定义(GITHUB地址)支持的功能:1、选
- 点击此处:官网下载 根据自己的系统 ,下载相应的JDK版本。1. JDK1.8安装1.双击下载的安装包(.exe文件),进行安装。2.点击“
- 1. 概述官方JavaDocsApi: java.awt.Component,java.awt.Containernull,绝对布局。绝对布
- 目录1,基本介绍2,HttpURLConnection实现3.HttpClient实现4.Spring的RestTemplate1,基本介绍
- 1. Maven简介相对于传统的项目,Maven 下管理和构建的项目真的非常好用和简单,所以这里也强调下,尽量使用此类工具进行项目构建, 它
- 组件在容器(比如Jframe)中的位置和大小是由布局管理器来决定的。所有的容器都会使用一个布局管理器,通过它来自动进行组件的布局管理。种类j
- java 中遍历取值异常(Hashtable Enumerator)解决办法用迭代器取值时抛出的异常:java.util.NoSuchEle
- using System;using System.Collections.Generic;using System.Linq;using
- 文件复制和文件上传最近在看文件和IO流相关的东西,写了一些代码,发现这个有很多很有趣的地方。特别是对 File 和 IO 流的使用之后,我对
- # 前言在我们实际项目开发过程中,我们经常需要将不同的两个对象实例进行属性复制,从而基于源对象的属性信息进行后续操作,而不改变源对象的属性信
- 我们在开发需求的时候,难免会接入一下第三方的H5页面,有些H5页面是具有上传照片的功能,Android 中的 WebView是不能直接打开文
- 最近在开发的过程中,遇到了一个需要截取屏幕保存为图片的需求,具体为截取webview的视图保存图片。方法1:首先想到的思路是利用SDK提供的
- ArrayList的构造方法(前置知识)可快速过一些基本成员变量:// 默认初始大小private static final int DEF
- 以下代码为一个工具类package com.imooc.reflect;import java.lang.reflect.Method;pu
- 前言Activity是Android中一个很重要的概念,堪称四大组件之首,关于Activity有很多内容,比如生命周期和启动Flags,这二
- //程序下载升级 zhouxiang@JavascriptInterfacepublic void UpdateCAECP(final St
- 前台form 表单:设置method=post,enctype=multipart/form-data。struts2在原有的上传解析器继承
- 介绍Objects Comparer是用于对象比较的工具,c#常见的数据结构都是可以用这个三方库进行对比,比较复杂的对象也是可以比较的。简而
- /// <summary> /// 计算本周起始日期(礼拜一的日期) /// </summary&