Flutter开发实战 高仿微信(一)首页

  • Flutter开发实战 高仿微信(一)首页
    • flutter 开发微信项目 (一)
      • 1. 开发HomePage页
      • 2. 用到的知识点讲解
        • 2.1 BottomNavigationBar
        • 2.2 Container
          • 2.2.1. 简介
          • 2.2.2. 组成
          • 2.2.3. Container的属性
          • 2.2.4. Container使用
          • 2.2.5. Container源码分析
        • 2.3 Scaffold

Flutter开发实战 高仿微信(一)首页

flutter 开发微信项目 (一)

源码地址:flutter_wetchat

1. 开发HomePage页

  1. 运行效果:
  2. 功能介绍
  3. 代码讲解
  • KYLRootPage是根页面
class KYLRootPage extends StatefulWidget {@overrideState<StatefulWidget> createState() {// TODO: implement createStatereturn _RootPageState();}}class _RootPageState extends State<KYLRootPage> {int _currentIndex = 0;List<Widget> pages = [Scaffold(appBar: AppBar(title: Text('微信'),),body: Center(child: Text('微信主页'),),),Scaffold(appBar: AppBar(title: Text('通讯录'),),body: Center(child: Text('通讯录列表'),),),Scaffold(appBar: AppBar(title: Text('发现'),),body: Center(child: Text('发现列表'),),),Scaffold(appBar: AppBar(title: Text('我'),),body: Center(child: Text('我的页面'),),)];@overrideWidget build(BuildContext context) {// TODO: implement buildreturn Container(child: Scaffold(bottomNavigationBar: BottomNavigationBar(onTap: (int index) {_currentIndex = index;},type: BottomNavigationBarType.fixed,fixedColor: Colors.green,currentIndex: _currentIndex,items: <BottomNavigationBarItem>[BottomNavigationBarItem(icon: Icon(Icons.chat),title: Text('微信'),),BottomNavigationBarItem(icon: Icon(Icons.bookmark),title: Text('通讯录'),),BottomNavigationBarItem(icon: Icon(Icons.history),title: Text('发现'),),BottomNavigationBarItem(icon: Icon(Icons.person_outline),title: Text('我'),),]),body: pages[_currentIndex],),);}
}
  • main.dart
import 'package:flutter/material.dart';
import 'KYLRootPage.dart';void main() => runApp(MyApp());class MyApp extends StatelessWidget {// This widget is the root of your application.@overrideWidget build(BuildContext context) {return MaterialApp(title: 'Flutter Demo',theme: ThemeData(// This is the theme of your application.//// Try running your application with "flutter run". You'll see the// application has a blue toolbar. Then, without quitting the app, try// changing the primarySwatch below to Colors.green and then invoke// "hot reload" (press "r" in the console where you ran "flutter run",// or simply save your changes to "hot reload" in a Flutter IDE).// Notice that the counter didn't reset back to zero; the application// is not restarted.primarySwatch: Colors.blue,),home: KYLRootPage(),);}
}

2. 用到的知识点讲解

2.1 BottomNavigationBar

相当于是一个自定义的Button,用来放在BottomNavigationBar上,它实现了Material(Android)和Cupertino(iOS)两种风格。

Scaffold是Root Widget- MaterialApp的脚手架。封装了Material Design App会用到的AppBar,Drawer,SnackBar,BottomNavigationBar等。BottomNavigationBarType有fixed 和shifting两种样式,超过3个才会有区别,一般为了体验一致,我们会用fixed type。

BottomNavigationBar是一个StatefulWidget,可以按以下步骤分析这种组件:
1,先看它持有的状态;
2,看下他的生命周期实现;
3,再仔细分析它的build方法.

  • 持有状态
List<AnimationController> _controllers = <AnimationController>[];
List<CurvedAnimation> _animations;// A queue of color splashes currently being animated.
final Queue<_Circle> _circles = Queue<_Circle>();// Last splash circle's color, and the final color of the control after
// animation is complete.
Color _backgroundColor;

前面三个属性都和动画相关,第四个是设背景。
这里有个疑问:BottomNavigationBar为什么没有变量标记当前哪个item选中?

函数式编程一个原则是要函数尽量纯,currentIndex这个属性依赖外边传入,每次变化重新触发Render。如果自己维护,则还需要提供一个回调方法供外部调用,返回最新的currentIndex值。

  • 生命周期方法
// 初始化操作,具体实现再resetState里,对上面的这些状态属性初始化操作
@override
//initState里有个操作比较隐蔽:_controllers[widget.currentIndex].value = 1.0;
void initState() {super.initState();_resetState();
}// 回收资源操作,一般用到动画都需要的
@override
void dispose() {for (AnimationController controller in _controllers)controller.dispose();for (_Circle circle in _circles)circle.dispose();super.dispose();}// 当属性变化时Flutter系统回调该方法。当item数量变化时直接重新初始化;当index变化,做相应动画。
@override
void didUpdateWidget(BottomNavigationBar oldWidget) {super.didUpdateWidget(oldWidget);// No animated segue if the length of the items list changes.if (widget.items.length != oldWidget.items.length) {_resetState();return;}if (widget.currentIndex != oldWidget.currentIndex) {switch (widget.type) {case BottomNavigationBarType.fixed:break;case BottomNavigationBarType.shifting:_pushCircle(widget.currentIndex);break;}_controllers[oldWidget.currentIndex].reverse();_controllers[widget.currentIndex].forward();}if (_backgroundColor != widget.items[widget.currentIndex].backgroundColor)_backgroundColor = widget.items[widget.currentIndex].backgroundColor;}// 下面分析
@override
Widget build(BuildContext context) {}
  • 分析build方法
@overrideWidget build(BuildContext context) {// debug 检查assert(debugCheckHasDirectionality(context));assert(debugCheckHasMaterialLocalizations(context));// Labels apply up to _bottomMargin padding. Remainder is media padding.final double additionalBottomPadding = math.max(MediaQuery.of(context).padding.bottom - _kBottomMargin, 0.0);// 根据BottomNavigationBarType设背景色,shifting才会有Color backgroundColor;switch (widget.type) {case BottomNavigationBarType.fixed:break;case BottomNavigationBarType.shifting:backgroundColor = _backgroundColor;break;}return Semantics( // Semantics用来实现无障碍的container: true,explicitChildNodes: true,child: Stack(children: <Widget>[Positioned.fill(child: Material( // Casts shadow.elevation: 8.0,color: backgroundColor,),),ConstrainedBox(constraints: BoxConstraints(minHeight: kBottomNavigationBarHeight + additionalBottomPadding),child: Stack(children: <Widget>[Positioned.fill(  // 点击时的圆形类波纹动画child: CustomPaint(painter: _RadialPainter(circles: _circles.toList(),textDirection: Directionality.of(context),),),),Material( // Splashes.type: MaterialType.transparency,child: Padding(padding: EdgeInsets.only(bottom: additionalBottomPadding),child: MediaQuery.removePadding(context: context,removeBottom: true, // tiles就是_BottomNavigationTile,里面放BottomNavigationBarItemchild: _createContainer(_createTiles()),)))]))]));}}
  • _BottomNavigationTile看下
Widget _buildIcon() {...// 构建Icon}Widget _buildFixedLabel() {....// 骚操作,用矩阵来给文字作动画,更平滑// The font size should grow here when active, but because of the way// font rendering works, it doesn't grow smoothly if we just animate// the font size, so we use a transform instead.child: Transform(transform: Matrix4.diagonal3(Vector3.all(Tween<double>(begin: _kInactiveFontSize / _kActiveFontSize,end: 1.0,).evaluate(animation),),),alignment: Alignment.bottomCenter,child: item.title,),),),);}Widget _buildShiftingLabel() {return Align(
.....// shifting的label是fade动画,只有当前选中的才会显示labelchild: FadeTransition(alwaysIncludeSemantics: true,opacity: animation,child: DefaultTextStyle.merge(style: const TextStyle(fontSize: _kActiveFontSize,color: Colors.white,),child: item.title,),),),);}@overrideWidget build(BuildContext context) {int size;Widget label;// 生成不同的labelswitch (type) {case BottomNavigationBarType.fixed:size = 1;label = _buildFixedLabel();break;case BottomNavigationBarType.shifting:size = (flex * 1000.0).round();label = _buildShiftingLabel();break;}return Expanded(....children: <Widget>[_buildIcon(),label,],),),Semantics(label: indexLabel,
}

2.2 Container

2.2.1. 简介

Container在Flutter中太常见了。官方给出的简介,是一个结合了绘制(painting)、定位(positioning)以及尺寸(sizing)widget的widget。
可以得出几个信息,它是一个组合的widget,内部有绘制widget、定位widget、尺寸widget。后续看到的不少widget,都是通过一些更基础的widget组合而成的。

2.2.2. 组成
  1. Container的组成如下:

最里层的是child元素;
child元素首先会被padding包着;
然后添加额外的constraints限制;
最后添加margin。

  1. Container的绘制的过程如下:

首先会绘制transform效果;
接着绘制decoration;
然后绘制child;
最后绘制foregroundDecoration。

  1. Container自身尺寸的调节分两种情况:

Container在没有子节点(children)的时候,会试图去变得足够大。除非constraints是unbounded限制,在这种情况下,Container会试图去变得足够小。
带子节点的Container,会根据子节点尺寸调节自身尺寸,但是Container构造器中如果包含了width、height以及constraints,则会按照构造器中的参数来进行尺寸的调节。

2.2.3. Container的属性
  • key:Container唯一标识符,用于查找更新。

  • alignment:控制child的对齐方式,如果container或者container父节点尺寸大于child的尺寸,这个属性设置会起作用,有很多种对齐方式。

  • padding:decoration内部的空白区域,如果有child的话,child位于padding内部。padding与margin的不同之处在于,padding是包含在content内,而margin则是外部边界,设置点击事件的话,padding区域会响应,而margin区域不会响应。

  • color:用来设置container背景色,如果foregroundDecoration设置的话,可能会遮盖color效果。

  • decoration:绘制在child后面的装饰,设置了decoration的话,就不能设置color属性,否则会报错,此时应该在decoration中进行颜色的设置。

  • foregroundDecoration:绘制在child前面的装饰。

  • width:container的宽度,设置为double.infinity可以强制在宽度上撑满,不设置,则根据child和父节点两者一起布局。

  • height:container的高度,设置为double.infinity可以强制在高度上撑满。

  • constraints:添加到child上额外的约束条件。

  • margin:围绕在decoration和child之外的空白区域,不属于内容区域。

  • transform:设置container的变换矩阵,类型为Matrix4。

  • child:container中的内容widget。

实例:

new Container(constraints: new BoxConstraints.expand(height:Theme.of(context).textTheme.display1.fontSize * 1.1 + 200.0,),decoration: new BoxDecoration(border: new Border.all(width: 2.0, color: Colors.red),color: Colors.grey,borderRadius: new BorderRadius.all(new Radius.circular(20.0)),image: new DecorationImage(image: new NetworkImage('http://h.hiphotos.baidu.com/zhidao/wh%3D450%2C600/sign=0d023672312ac65c67506e77cec29e27/9f2f070828381f30dea167bbad014c086e06f06c.jpg'),centerSlice: new Rect.fromLTRB(270.0, 180.0, 1360.0, 730.0),),),padding: const EdgeInsets.all(8.0),alignment: Alignment.center,child: new Text('Hello World',style: Theme.of(context).textTheme.display1.copyWith(color: Colors.black)),transform: new Matrix4.rotationZ(0.3),
)
2.2.4. Container使用

Container算是目前项目中,最经常用到的一个widget。在实际使用过程中,笔者在以下情况会使用到Container,当然并不是绝对的,也可以通过其他widget来实现。

  1. 需要设置间隔(这种情况下,如果只是单纯的间隔,也可以通过Padding来实现);
  2. 需要设置背景色;
  3. 需要设置圆角或者边框的时候(ClipRRect也可以实现圆角效果);
  4. 需要对齐(Align也可以实现);
  5. 需要设置背景图片的时候(也可以使用Stack实现)。
2.2.5. Container源码分析
decoration = decoration ?? (color != null ? new BoxDecoration(color: color) : null),

可以看出,对于颜色的设置,最后都是转换为decoration来进行绘制的。如果同时包含decoration和color两种属性,则会报错。

@overrideWidget build(BuildContext context) {Widget current = child;if (child == null && (constraints == null || !constraints.isTight)) {current = new LimitedBox(maxWidth: 0.0,maxHeight: 0.0,child: new ConstrainedBox(constraints: const BoxConstraints.expand()));}if (alignment != null)current = new Align(alignment: alignment, child: current);final EdgeInsetsGeometry effectivePadding = _paddingIncludingDecoration;if (effectivePadding != null)current = new Padding(padding: effectivePadding, child: current);if (decoration != null)current = new DecoratedBox(decoration: decoration, child: current);if (foregroundDecoration != null) {current = new DecoratedBox(decoration: foregroundDecoration,position: DecorationPosition.foreground,child: current);}if (constraints != null)current = new ConstrainedBox(constraints: constraints, child: current);if (margin != null)current = new Padding(padding: margin, child: current);if (transform != null)current = new Transform(transform: transform, child: current);return current;}

Container的build函数不长,绘制也是一个线性的判断的过程,一层一层的包裹着widget,去实现不同的样式。
最里层的是child,如果为空或者其他约束条件,则最里层包含的为一个LimitedBox,然后依次是Align、Padding、DecoratedBox、前景DecoratedBox、ConstrainedBox、Padding(实现margin效果)、Transform。
Container的源码本身并不复杂,复杂的是它的各种布局表现。我们谨记住一点,如果内部不设置约束,则按照父节点尽可能的扩大,如果内部有约束,则按照内部来。

2.3 Scaffold

Scaffold 实现了基本的 Material 布局。只要是在 Material 中定义了的单个界面显示的布局控件元素,都可以使用 Scaffold 来绘制。
提供展示抽屉(drawers,比如:左边栏)、通知(snack bars) 以及 底部按钮(bottom sheets)。
我们可以将 Scaffold 理解为一个布局的容器。可以在这个容器中绘制我们的用户界面。

  1. Scaffold源码分析

  2. Scaffold 主要的属性说明

  • appBar:显示在界面顶部的一个 AppBar
    相关连接:https://flutterchina.club/catalog/samples/
  • body:当前界面所显示的主要内容
  • floatingActionButton: 在 Material 中定义的一个功能按钮。
  • persistentFooterButtons:固定在下方显示的按钮。https://material.google.com/components/buttons.html#buttons-persistent-footer-buttons
  • drawer:侧边栏控件
  • bottomNavigationBar:显示在底部的导航栏按钮栏。可以查看文档:Flutter学习之制作底部菜单导航
  • backgroundColor:背景颜色
  • resizeToAvoidBottomPadding: 控制界面内容 body
    是否重新布局来避免底部被覆盖了,比如当键盘显示的时候,重新布局避免被键盘盖住内容。默认值为 true。
  1. 代码示例
class Scaffold extends StatefulWidget {/// Creates a visual scaffold for material design widgets.const Scaffold({Key key,this.appBar, //横向水平布局,通常显示在顶部(*)this.body, // 内容(*)this.floatingActionButton, //悬浮按钮,就是上图右下角按钮(*)this.floatingActionButtonLocation, //悬浮按钮位置//悬浮按钮在[floatingActionButtonLocation]出现/消失动画this.floatingActionButtonAnimator, //在底部呈现一组button,显示于[bottomNavigationBar]之上,[body]之下this.persistentFooterButtons,//一个垂直面板,显示于左侧,初始处于隐藏状态(*)this.drawer,this.endDrawer,//出现于底部的一系列水平按钮(*)this.bottomNavigationBar,//底部持久化提示框this.bottomSheet,//内容背景颜色this.backgroundColor,//弃用,使用[resizeToAvoidBottomInset]this.resizeToAvoidBottomPadding,//重新计算布局空间大小this.resizeToAvoidBottomInset,//是否显示到底部,默认为true将显示到顶部状态栏this.primary = true,//this.drawerDragStartBehavior = DragStartBehavior.down,}) : assert(primary != null),assert(drawerDragStartBehavior != null),super(key: key);
  1. Scaffold.of 使用说明

关于 Scaffold.of 函数的说明:https://docs.flutter.io/flutter/material/Scaffold/of.html

显示 snackbar 或者 bottom sheet 的时候,需要使用当前的 BuildContext 参数调用 Scaffold.of 函数来获取 ScaffoldState 对象,然后使用 ScaffoldState.showSnackBar 和 ScaffoldState.showBottomSheet 函数来显示。

来自官方源码上面的例子。使用 SnackBar 的写法。

@overrideWidget build(BuildContext context) {return new RaisedButton(child: new Text('SHOW A SNACKBAR'),onPressed: () {Scaffold.of(context).showSnackBar(new SnackBar(content: new Text('Hello!'),));},);}

当 Scaffold 实际上是在同一个构建函数中创建时,构建函数的 BuildContext 参数不能用于查找 Scaffold(因为它位于返回的小部件的“上方”)。 因为在源码中 使用的是 return new Scaffold(app:xxxx),在这种情况下面,通过在 Scaffold 中使用一个 Builder 来提供一个新的 BuildContext:

@override
Widget build(BuildContext context) {return new Scaffold(appBar: new AppBar(title: new Text('Demo')),body: new Builder(// Create an inner BuildContext so that the onPressed methods// can refer to the Scaffold with Scaffold.of().builder: (BuildContext context) {return new Center(child: new RaisedButton(child: new Text('SHOW A SNACKBAR'),onPressed: () {Scaffold.of(context).showSnackBar(new SnackBar(content: new Text('Hello!'),));},),);},),);
}

按照官方的说法,可以将我们的构建函数拆分到多个 Widgets中。分别引入新的 BuildContext 来获取 Scaffold.

Flutter开发实战 高仿微信(一)首页相关推荐

  1. Flutter开发实战 高仿微信(二)发现页

    Flutter开发实战 高仿微信(二)发现页 Flutter开发实战 高仿微信(二)发现页 1.1 微信发现页面简述 1.2 APP框架优化 1.2.1 配置APP Logo和启动图片 1.2.2 配 ...

  2. flutter页面布局HTML,Flutter开发实战初级(2)页面布局详解

    初级根底系列 Flutter开发实战初级(1)ListView详解 Flutter开发实战初级(2)布局详解 项目实战系列 Flutter开发实战 高仿微信(1)主页 Flutter开发实战 高仿微信 ...

  3. Kotlin高仿微信-项目实践58篇

    Kotlin高仿微信项目实践主要包含5大模块: 1.Web服务器 2.Kotlin客户端 3.Xmpp即时通讯服务器 4.视频通话服务器 5.腾讯云服务器 另外也有Flutter版本高仿微信功能,Fl ...

  4. Flutter高仿微信-项目实践59篇

    Flutter高仿微信(支持Android和IOS系统) Flutter高仿微信主要包含5大模块: 1.Web服务器 2.Flutter客户端 3.Xmpp即时通讯服务器 4.视频通话服务器 5.腾讯 ...

  5. android开发百度地图坐标偏差,利用百度地图Android sdk高仿微信发送位置功能及遇到的问题...

    接触了百度地图开发平台半个月了,这2天试着模仿了微信给好友发送位置功能,对百度地图的操作能力又上了一个台阶 我在实现这个功能的时候,遇到一些困难,可能也是别人将会遇到的困难,特在此列出 1.在微信发送 ...

  6. Flutter高仿微信-第26篇-新的朋友

    Flutter高仿微信系列共59篇,从Flutter客户端.Kotlin客户端.Web服务器.数据库表结构.Xmpp即时通讯服务器.视频通话服务器.腾讯云服务器全面讲解. 详情请查看 效果图: 实现代 ...

  7. Flutter高仿微信-第57篇-添加好友

     Flutter高仿微信系列共59篇,从Flutter客户端.Kotlin客户端.Web服务器.数据库表结构.Xmpp即时通讯服务器.视频通话服务器.腾讯云服务器全面讲解. 详情请查看 效果图: 实现 ...

  8. Flutter高仿微信-第51篇-群聊-修改群名

     Flutter高仿微信系列共59篇,从Flutter客户端.Kotlin客户端.Web服务器.数据库表结构.Xmpp即时通讯服务器.视频通话服务器.腾讯云服务器全面讲解. 详情请查看 效果图: 实现 ...

  9. Flutter高仿微信-第59篇-同步数据

     Flutter高仿微信系列共59篇,从Flutter客户端.Kotlin客户端.Web服务器.数据库表结构.Xmpp即时通讯服务器.视频通话服务器.腾讯云服务器全面讲解. 详情请查看 实现代码: / ...

最新文章

  1. 新的小游戏发布啦。Pop Jungle
  2. 【Python】list转str
  3. [HNOI2010]公交线路
  4. php 命令行打印换行符_如何在命令行输出中打印换行符
  5. Linux 误删除 /boot分区 的解救办法
  6. cocos2d-x 中创建 CCSprite 精灵动画
  7. 我学Delphi心得及笔记----内存(第七讲)
  8. WEB系统中集成控制扫描仪解决方案
  9. 教你如何在github上提交代码(Window10示例,内含2021年github提交机制的更新变动)
  10. mir2的db数据库
  11. ORACLE 10G DATAGUARD
  12. veil-evasion介绍
  13. 模拟鼠标/键盘 .NET实现
  14. java中取值保留小数点后两位的四种方法
  15. Nim 博弈游戏详解
  16. ios平台微信的语音文件AUD格式其实就是AMR格式
  17. zabbix学习资料收集
  18. 电子商务案例分析php,2020知到《西安邮电大学网课电子商务案例分析》单元测试答案2020高校邦《ThinkPHP框架技术》答案免费...
  19. Java与C语言中的锁
  20. Ansible-事实管理与控制<六>

热门文章

  1. 通过微信无法下载APP的最佳解决方案
  2. leetcode之砖墙(C++)
  3. python设置程序最大内存_限制你的Python程序所能使用的最大内存
  4. 安装SPARK 环境变量设置
  5. 数据开放,对于货运行业来说有着怎样的现实意义?
  6. 202005 U盘使用: 对于目标文件系统,文件过大
  7. ​用 Python 和 Gensim 库进行文本主题识别
  8. java中bit和字节的常用知识
  9. 方法比知识重要,人品比能力重要(转)
  10. vue使用xlsx插件下载excel文件