flutter仿微信底部图标渐变功能的实现代码

先给大家展示下效果图,感觉不错请参考实例代码。

实现思路

在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中路径错误,所以图片不显示)

时间: 2020-04-15

Android仿微信右滑返回功能的实例代码

先上效果图,如下: 先分析一下功能的主要技术点,右滑即手势判断,当滑到一直距离时才执行返回,并且手指按下的位置是在屏幕的最左边(这个也是有一定范围的),  这些可以实现onTouchEvent来实现. 接着就是返回时,有滑动效果,很显然这个是Acitivty切换动画实现的.好啦,分析完了就开干.下面上代码: @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case Mot

Flutter实现仿微信底部菜单栏功能

import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget{ @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( body: MyHomePage(), ), ); } } class MyHomePage extends Sta

Android 仿微信底部渐变Tab效果

先来看一下效果图 除了第三个的发现Tab有所差别外,其他的基本还原了微信的底部Tab渐变效果 每个Tab都是一个自定义View,根据ImageView的tint属性来实现颜色渐变效果,tint属性的使用可以看我的上一篇文章 我将自定义View命名为ShadeView,包含四个自定义属性 意思分别为图标.背景色.底部文本.底部文本大小 <declare-styleable name="ShadeView"> <attr name="icon" for

android 中viewpager+fragment仿微信底部TAG完美渐变

viewpager+fragment仿微信底部TAG完美渐变,在图片渐变的同时字的颜色也在变,注意,是渐变哦! 效果图: activity_main.xml <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:zhy="http://schemas.android.com/apk/res/com.Sing.weixin" xmlns:tools="

Android仿微信底部菜单栏功能显示未读消息数量

底部菜单栏很重要,我看了一下很多应用软件都是用了底部菜单栏,这里使用了tabhost做了一种通用的(就是可以像微信那样显示未读消息数量的,虽然之前也做过但是layout下的xml写的太臃肿,这里去掉了很多不必要的层,个人看起来还是不错的,所以贴出来方便以后使用). 先看一下做出来之后的效果: 以后使用的时候就可以换成自己项目的图片和字体了,主框架不用变哈哈, 首先是要布局layout下xml文件 main.xml: <?xml version="1.0" encoding=&qu

Android仿微信底部按钮滑动变色

Android仿微信底部按钮滑动变色,这里只针对使用Fragment为Tab页的滑动操作,进行简单的变色讲解. 首先说下OnPageChangeListener这个监听 //这个监听有三个方法 public abstract void onPageScrollStateChanged (int state) public abstract void onPageScrolled (int position, float positionOffset, int positionOffsetPixe

Android仿微信底部菜单栏效果

前言 在市面上,大多数的APP都需要通过底部菜单栏来将程序的功能进行分类整理,通常都是分为3-5个大模块,从而正确有效地引导用户去使用我们的APP.实现底部菜单栏的方法也有很多种. 1.仿微信底部菜单栏(ViewPager+ImagerView+TextView) ......(其他方式后续会补充) 效果预览 首先来个开胃菜,看看实现效果: 先贴出项目所需的资源文件,这些可随个人自由更改颜色和文字 colors.xml <color name="bg_line_light_gray&quo

vue router仿天猫底部导航栏功能

首先把天猫的导航贴出来,里面包括精选.品牌.会员.购物车.我五个导航及对应的图标. 分析: 1.图标的获取 进入阿里巴巴矢量图标库,网址  http://www.iconfont.cn. 点击官方图标库,选择天猫图标库,选中放入购物车. 点击添加至项目,点击创建新项目按钮,创建tianmao项目,点击确定. 此时会有查看在线链接和下载至本地两种方式,我选择第一种,因为后期如果要添加小图标的话,只需要重新生成在线链接,然后更新link即可 复制链接到index.html的link标签内,具体为 <

uni app仿微信顶部导航条功能

最近一直在学习uni-app开发,由于uniapp是基于vue.js技术开发的,只要你熟悉vue,基本上很快就能上手了. 在开发中发现uni-app原生导航栏也能实现一些顶部自定义按钮+搜索框,只需在page.json里面做一些配置即可.设置app-plus,配置编译到App平台的特定样式.dcloud平台对app-plus做了详细说明:app-plus配置,需注意 目前暂支持H5.App端,不支持小程序. 在page.json里配置app-plus即可 { "path": "