Last active
April 9, 2020 10:07
-
-
Save 327100395/9dee2497a99e560f52c6ecd541242c7b to your computer and use it in GitHub Desktop.
仿微信app底部导航图标渐变
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:async'; | |
import 'package:flutter/material.dart'; | |
void main() => runApp(MyApp()); | |
class MyApp extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
theme: ThemeData(primaryColor: Colors.blue), | |
home: MyHomePage(title: 'test wechat msg'), | |
); | |
} | |
} | |
final StreamController<StreamModel> streamController = | |
StreamController.broadcast(); | |
class MyHomePage extends StatefulWidget { | |
MyHomePage({Key key, this.title}) : super(key: key); | |
final String title; | |
@override | |
_MyHomePageState createState() => _MyHomePageState(); | |
} | |
class _MyHomePageState extends State<MyHomePage> { | |
final List<TabBarModel> pages = [ | |
TabBarModel( | |
title: '微信', | |
icon: 'chat', | |
page: Page('微信'), | |
), | |
TabBarModel( | |
title: '通讯录', | |
icon: 'contacts', | |
page: Page('通讯录'), | |
), | |
TabBarModel( | |
title: '发现', | |
icon: 'discover', | |
page: Page('发现'), | |
), | |
TabBarModel( | |
title: '我', | |
icon: 'me', | |
page: Page('me'), | |
), | |
]; | |
List bars = new List<BottomNavigationBarItem>(); | |
int currentIndex = 0; | |
PageController pageController; | |
double lastPage = 0.0; | |
@override | |
void initState() { | |
// TODO: implement initState | |
super.initState(); | |
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(), | |
), | |
); | |
} | |
pageController = PageController(initialPage: currentIndex); | |
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; | |
}); | |
} | |
@override | |
Widget build(BuildContext context) { | |
final BottomNavigationBar bottomNavigationBar = new BottomNavigationBar( | |
items: bars, | |
type: BottomNavigationBarType.fixed, | |
currentIndex: currentIndex, | |
onTap: (int index) { | |
setState(() => currentIndex = index); | |
pageController.jumpToPage(currentIndex); | |
}, | |
unselectedFontSize: 18.0, | |
selectedFontSize: 18.0, | |
); | |
return Scaffold( | |
appBar: AppBar( | |
title: Text(widget.title), | |
), | |
body: PageView.builder( | |
controller: pageController, | |
itemCount: pages.length, | |
itemBuilder: (BuildContext context, int index) => pages[index].page, | |
onPageChanged: (index) => setState(() => currentIndex = index), | |
), | |
bottomNavigationBar: new Container( | |
decoration: BoxDecoration( | |
border: Border(top: BorderSide(color: Colors.grey, width: 0.2))), | |
child: bottomNavigationBar, | |
), | |
); | |
} | |
} | |
class TabBarModel { | |
const TabBarModel({this.title, this.page, this.icon}); | |
final String title; | |
final String icon; | |
final Widget page; | |
} | |
class Page extends StatelessWidget { | |
final String title; | |
Page(this.title); | |
@override | |
Widget build(BuildContext context) { | |
return Container( | |
color: Colors.white30, | |
child: Center( | |
child: Text(title), | |
), | |
); | |
} | |
} | |
class StreamModel { | |
const StreamModel({this.timeline, this.index, this.gotoIndex}); | |
final double timeline; | |
final int index; | |
final int gotoIndex; | |
} | |
// ignore: must_be_immutable | |
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))), | |
], | |
); | |
}); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment