flutter仿微信底部图标渐变功能的实现代码
作者:今天你摸鱼了吗 发布时间:2023-08-18 14:31:39
先给大家展示下效果图,感觉不错请参考实例代码。
实现思路
在flutter中,如果想实现上面的页面切换效果,必然会想到pageView。pageView的controller可以监听到pageView的滚动事件,也可以获取pageView滚动的位置,所以我们在滚动事件中根据位置去改变对应的图标颜色就可以实现了。
改变图标颜色
图标是从微信中提取出来的,都是webp格式的图片。要改变图片颜色可以使用ImageIcon这个组件。
ImageIcon会把一张图片变成单色图片,所以只要图片没有多色的要求,就可以用这个组件。
既然能改变颜色了,我们也需要知道pageView滚动的时候究竟要改什么颜色。从一个页面滚动到另一个页面的过程中,颜色都是线性渐变的,要获取这个过程中的颜色可以使用flutter的Color类提供的lerp方法,作用是获取两种颜色之间的线性差值
里面有3个参数,a和b都是颜色,t是夹在0到1之间的,当t为0时返回a,当t为1时返回b
也就是在滚动事件中,计算出 t ,根据 t 改变图标颜色就可以实现上面的效果了。
pageController.addListener(() {
int currentPage = pageController.page.toInt();
//当前页面的page是double类型的, 把它减去当前页面的int类型就可以得出当前页面到下一个页面的偏移量了
double t = pageController.page - currentPage;
//根据上一次的页面位置获得方向
if (lastPage <= pageController.page) {
//向右滑动时currentPage是当前页
//从当前页过渡到下一页
streamController.sink.add(StreamModel(
timeline: t, index: currentPage, gotoIndex: currentPage + 1));
} else {
//向左滑动时currentPage是上一页
//从当前页过渡到上一页
streamController.sink.add(StreamModel(
timeline: t, index: currentPage + 1, gotoIndex: currentPage));
}
lastPage = pageController.page;
});
上面代码中currentPage的值举个例子:当前page是1,要滑动到2,那么它的值是1.11...1.21...这样一直到2,所以在这个过程中currentPage是当前页。如果当前page是4,要滑动到3的时候,它的值是3.99...3.81...这样一直到3,在这个过程中currentPage就是上一页了。
t 的计算就更简单了,1.11-1=0.11,3.99-3=0.99 .....
管理图标颜色
因为我是用了自带的底部导航BottomNavigationBar,在pageController的滚动事件中改变图标颜色太麻烦了,所以用了Stream来管理图标的状态。使用Stream创建一个多订阅的管道,让所有图标都订阅它,然后在滑动事件中把需要的数据都发送给所有图标。
需要的数据:
class StreamModel {
const StreamModel({this.timeline, this.index, this.gotoIndex});
final double timeline;
final int index;
final int gotoIndex;
}
图标组件
构造方法设置一个index,方便判断图标是哪个。
使用StreamBuilder包住要改变颜色的组件,并且绑定从构造函数设置的StreamController。
在StreamBuilder中根据pageView滚动事件传进来的参数控制图标颜色。
class BottomNavIcon extends StatelessWidget {
final StreamController<StreamModel> streamController;
final int index;
final String img;
final String title;
final double fontSize;
Color _color;
Color _activeColor;
final bool isActive;
BottomNavIcon(this.title, this.img, this.index,
{@required this.streamController,
this.isActive = false,
this.fontSize = 18.0,
Color color = Colors.grey,
Color activeColor = Colors.blue}) {
_color = isActive ? activeColor : color;
_activeColor = isActive ? color : activeColor;
}
@override
Widget build(BuildContext context) {
return StreamBuilder(
stream: streamController.stream,
builder: (BuildContext context, AsyncSnapshot snapshot) {
final StreamModel data = snapshot.data;
double t = 0.0;
if (data != null) {
//开始的index
if (data.index == index) {
t = data.index > data.gotoIndex
? data.timeline
: 1.0 - data.timeline;
print("this${data.index}:${t}");
}
//结束的index
if (data.gotoIndex == index) {
t = data.index > data.gotoIndex
? 1.0 - data.timeline //开始的index大于结束的index方向向左
: data.timeline; //小于方向向右
//过渡到的图标颜色的插值超过0.6时, 个人感觉当前颜色和结束的哪个颜色相差太多,
//所以超过0.6时恢复默认颜色
t = t >= 0.6 ? 1 : t;
print("goto${data.gotoIndex}:${t}");
}
}
if (t > 0.0 && t < 1.0) {
//color.lerp 获取两种颜色之间的线性插值
return Column(
children: <Widget>[
ImageIcon(AssetImage(this.img),
color: Color.lerp(_color, _activeColor, t)),
Text(title,
style: TextStyle(
fontSize: fontSize,
color: Color.lerp(_color, _activeColor, t))),
],
);
}
return Column(
children: <Widget>[
ImageIcon(AssetImage(this.img),
color:
Color.fromRGBO(_color.red, _color.green, _color.blue, 1)),
Text(title,
style: TextStyle(
fontSize: fontSize,
color: Color.fromRGBO(
_color.red, _color.green, _color.blue, 1))),
],
);
});
}
}
图标的颜色都是当前的(index == data.index)渐渐变浅,要滚动到(index==data.gotoIndex)的图标颜色渐深
创建多订阅的管道(Stream)
final StreamController<StreamModel> streamController =
StreamController.broadcast();
加载图标
for (int i = 0; i < pages.length; i++) {
TabBarModel model = pages[i];
bars.add(
BottomNavigationBarItem(
icon: BottomNavIcon(
model.title,
'assets/images/tabbar_' + model.icon + '_c.webp',
i,
streamController: streamController,
),
activeIcon: BottomNavIcon(
model.title,
'assets/images/tabbar_' + model.icon + '_s.webp',
i,
streamController: streamController,
isActive: true,
),
title: Center(),
),
);
}
上面代码的title为Center的原因是已经在图标组件中创建了一个显示标题的组件,方便一起设置颜色。这里就不需要了,但是它的title不允许为null,所以随便给它一个高宽都是0的组件
结语
其实这个效果和微信的不是一模一样,微信的应该是选中图标叠加到默认图标上面。默认图标颜色线性渐变,选中图标透明度渐变。flutter实现这个用自带的BottomNavigationBar估计不行,可能需要自定义一个底部导航。
第一次写技术文章,感觉有点乱,所以贴下完整的代码地址:
gist: gist.github.com/327100395/9 …
dartPad: dartpad.dev/9dee2497a99…(图片读的是本地的,在dartPad中路径错误,所以图片不显示)
来源:https://juejin.im/post/5e947a7551882573ac3cf142


猜你喜欢
- 本文实例讲述了Android TextView显示Html类解析的网页和图片及自定义标签。分享给大家供大家参考,具体如下:Android系统
- 引言:关于java IO流的操作是非常常见的,基本上每个项目都会用到,每次遇到都是去网上找一找就行了,屡试不爽。上次突然一个同事问了我jav
- 字节数组的关键在于它为存储在该部分内存中的每个8位值提供索引(快速),精确的原始访问,并且您可以对这些字节进行操作以控制每个位。 坏处是计算
- 本文实例讲述了C#实现的MD5加密功能与用法。分享给大家供大家参考,具体如下:1、创建MD5Str.cs加密处理类public class
- 一、依赖传递1. 直接依赖与间接依赖pom.xml 声明了的依赖是直接依赖,依赖中又包含的依赖就是间接依赖(直接依赖的直接依赖),间接依赖虽
- 由于工作中 使用 MultipartFile 与现有的一些上传文件组件冲突所以使用其他的接收上传文件的方法.首先我把 M
- 最近因为项目的国际化的需要,需要对整个项目的100来个插件做国际化,这是一件痛苦的事情,因为纯体力劳动。为了省点工作量,想着能不能写个程序批
- 本文通过老王和小王买车,引出设计模式中的结构型设计之桥接模式,接着说明设计型模式的概念和代码实现,为了加深理解,会说明适配器设计模式在JDB
- 一、国际化准备资源文件,资源文件的命名格式如下:baseName_language_country.propertiesbaseName_l
- 有时候可能需要将手机上的一些操作投影出来,比如一些App Demo的展示等。其实,有专门的硬件设备能干这件事儿,但没必要专门为展示个Demo
- 前言关于android的volley封装之前写过一篇文章,见链接(https://www.jb51.net/article/155875.h
- 自定义TextView控件TimeTextView代码:import android.content.Context;import andr
- 首先来看看Map集合获取元素的三种常见方法keySet()、values()、entrySet()1. values():返回map集合的所
- 本来准备在ListView的每个Item的布局上设置一个隐藏的Button,当滑动的时候显示。但是因为每次只要存在一个Button,发现每个
- 在Java SE中,对IO流与集合的操作在应用中比较重要。接下来,我以一个小型项目的形式,演示IO流、集合等知识点在实践中的运用。该项目名称
- Rss 是一种描述和同步网站内容的格式,是目前使用最广泛的XML应用。RSS 搭建了信息迅速传播的一个技术平台,使得每个
- WPF在样式定义和UI动画上面相对于以前的技术有了不少的提升,下面给出WPF技术实现钟表的效果:1、Visual Studio新建一个WPF
- 1 synchronized场景回顾目标:synchronized回顾(锁分类–>多线程)概念synchroniz
- 本文实例讲述了C#自定义针对URL地址的处理类。分享给大家供大家参考。具体分析如下:这个C#类是专门针对URL网址处理的类,可以对URL地址
- Java基于对象流实现银行系统的具体代码,供大家参考,具体内容如下系统特点:数据持久化到文件中,系统启动后,加载文件中数据到集合中,相当于做