flutter学习笔记

Flutter万物皆组件,dart万物皆对象

Flutter 工程目录结构 https://blog.csdn.net/u013491829/article/details/108624331

flutter介绍:

1.跨平台移动UI框架
2.与现有的代码一起工作(可以直接嵌入到原生代码中或直接把原生代码嵌入到自己的代码中运行)
3. 完全免费、开源


flutter优缺点

优点:

1.Flutter在Debug使用JIT编译,支持热重载,能够提高我们的开发效率,而Release中利用AOT直接编译成机器码,能够达到更好的性能,即时编译 (JIT) 或提前编译 (AOT)
2.跨多种平台,减少开发成本;支持插件,可以访问原生系统的调用。
3.有自己的engine引擎,渲染引擎skia和dartVM虚拟机
4.UI一致性  

  1. 美观:可对 UI 实现像素级的控制,且内置 UI 库 ( Material、Cupertino )

  2. 快速:硬件加速图形引擎、代码被编译成机器码

  3. 高效:保持应用状态的热重载 ( hot reload )

  4. 开放:完全开源的项目 ( BSD 开源协议 )

  5. Flutter之所以渲染效率高,是因为Flutter采用的是增量渲染的机制,判断一个Widget应该被更新重新渲染,是调用的
    static bool canUpdate(Widget oldWidget, Widget newWidget) {
      return oldWidget.runtimeType == newWidget.runtimeType
          && oldWidget.key == newWidget.key;
    }

缺点:

1.脱离不开原生,开发人员需要具备原生(Android、iOS)基础开发能力;
2.适配问题,开发工具版本升级后,修改量大;
3.原生集成第三方SDK后,兼容性适配是个令人头痛的问题;
4.代码可读性较差,对代码质量和管理要求较高;
5.打包后,apk/ipa要大很多;
6.Flutter packages和Dart packages上第三方sdk繁杂,适配性差,不可乱用;
7.目前几乎没有第三方开发者平台开发Flutter能力的SDK,需要原生去集成;
8.Widget的类型难以选择,糟糕的UI控件API

下列业务场景下,Flutter 明显不占据优势

1.如果你的业务场景是多框架混合开发,那 Flutter 明显不占据优势;

2.如果你的场景是需要很强的文本编辑和富文本场景,那 Flutter 明显不占据优势;

3.如果你的 KPI 对内存占用特别敏感,那 Flutter 也不是特别占据优势;

4.如果你需要热更新,那 Flutter 也并不占据优势;

flutter声明式UI的好处:从Flutter到Compose,为什么都在推崇声明式UI?


为现有的 Flutter 应用程序添加桌面支持

官网链接:Flutter | Desktop support for Flutter

要将桌面支持添加到现有 Flutter 项目,请从项目根目录在终端中运行以下命令:

flutter create --platforms=windows,macos,linux .

这会将必要的桌面文件和目录添加到您现有的 Flutter 项目中。要仅添加特定桌面平台,请将platforms列表更改为仅包含您要添加的平台

macOS下配置网络支持

1.编辑DebugProfile.entitlements文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>com.apple.security.app-sandbox</key>
	<true/>
	<key>com.apple.security.cs.allow-jit</key>
	<true/>
	<key>com.apple.security.network.server</key>
	<true/>
	<key>com.apple.security.network.client</key>
	<true/>
</dict>
</plist>

2.编辑release.entitlements文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>com.apple.security.app-sandbox</key>
	<true/>
	<key>com.apple.security.network.server</key>
    <true/>
    <key>com.apple.security.network.client</key>
    <true/>
</dict>
</plist>

判断一个页面是不是flutter写的页面:

手机设置-开发人员选项---显示布局边界

flutter页面是没有边界的

基础

1.widget

Flutter Widget 目录 - Flutter 中文文档 - Flutter 中文开发者网站 - Flutter

Widget 下面有五个子类, PreferredSizeWidget 下面总共有6个组件, ProxyWidget 下面总共有47个组件, RenderObjectWidget 下面总共有94个组件, StateFulWidget 下面总共有167个组件, StatelessWidget 下面总共有108个组件,加上自身5个组件,所以总共的组件有高达427之多,这可能也是很多人觉得Flutter很难学的原因之一,其实我们只要掌握一些基本常用的可以了。参考:Flutter深入浅出组件篇---继承关系图 | Jimi

  • PreferredSizeWidget:主要用于 AppBar 和 TabBar , 通过继承该类可实现自定义大小。
  • ProxyWidget: 是一个抽象类,主要用于提供给子 Widget 的抽象 Widget
  • RenderObjectWidget:是一个抽象类, RenderObjectWidgets 为 RenderObjectElements 提供配置,它包装 RenderObjects ,提供应用程序的实际渲染。
  • StateFulWidget:具有可变状态的 Widget
  • StatelessWidget:不需要可变状态的 Widget

图片

参考另一片文章:http://t.csdn.cn/eDKvF

按钮  
RaisedButton---被elevatedbutton替代 

OutlineButton---被OutlinedButton替代  

FlatButton---被textbutton替代

文字 
Text。 overflow:(设置溢出显示方式)。  Maxlines(设置行数)

strutStyle: const StrutStyle(forceStrutHeight: true)

height的值是设计图上的height/字体大小,如下面设计图,height就为24/18:

输入框 textfield。  Autofocus:false,(最好设置成false)
参考:https://blog.csdn.net/zl18603543572/article/details/103772941

Flutter TextField内容垂直居中
 decoration: InputDecoration(
        contentPadding: EdgeInsets.zero,
        border: OutlineInputBorder(borderSide: BorderSide.none),)

参考另一篇文章“flutter开发错误积累”中的8.TextField相关的问题

点击事件
在flutter 开发中用InkWell或者GestureDetector将某个组件包起来,可添加点击事件。
GestureDetector 使用点击无水波纹出现,InkWell可以实现水波纹效果。

GestureDetector

behavior 属性用于控制手势识别器与其他手势识别器的交互行为。

behavior 用来 解决手势冲突:当多个可交互的部件重叠或在同一区域时,通过设置不同的 behavior 可以确定哪个部件优先响应手势。


  /// How this gesture detector should behave during hit testing.
  ///
  /// This defaults to [HitTestBehavior.deferToChild] if [child] is not null and
  /// [HitTestBehavior.translucent] if child is null.
  final HitTestBehavior? behavior;


/// How to behave during hit tests.
enum HitTestBehavior {
  /// Targets that defer to their children receive events within their bounds
  /// only if one of their children is hit by the hit test.
  deferToChild,

  /// Opaque targets can be hit by hit tests, causing them to both receive
  /// events within their bounds and prevent targets visually behind them from
  /// also receiving events.
  opaque,

  /// Translucent targets both receive events within their bounds and permit
  /// targets visually behind them to also receive events.
  translucent,
}

HitTestBehavior.deferToChild(默认):如果子部件可以处理手势,则将手势检测委托给子部件。如果子部件不能处理手势,则父部件(包含 GestureDetector 的部件)尝试处理手势。例如,当有一个 Container 作为 GestureDetector 的子部件,并且 Container 内部还有其他可交互的小部件时,这个设置可以让内部的小部件优先响应手势。
HitTestBehavior.opaque:点击整个区域都会响应点击事件,但是点击事件不可穿透向下传递,注释翻译:阻止视觉上位于其后方的目标接收事件。
HitTestBehavior.translucent:同样是点击整个区域都会响应点击事件,和opaque的区别是点击事件是否可以向下传递,注释翻译:半透明目标既可以在其范围内接受事件,也可以允许视觉上位于其后方的目标接收事件

InkWell

inkwell去除水波纹效果添加属性

splashFactory: NoSplash.splashFactory,

InkWell 水波纹会超出 Container 的圆角,把 InkWell 与 Container 设置同样的圆角.
InkWell(
        onTap: (){
          print("点击事件");
        },
        borderRadius: BorderRadius.circular(10),
        child: Container(
          decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(10)
          ),
        ),
      )

    
日期。   flutter_cupertino_date_picker.第三方日期库    date_format日期格式化三方库.   flutter自带日期选择showdatepicker。 自带日期选择showtimepicker。

对话框。 showmodalbottomsheet.底部弹出对话框。       alertdialog普通对话框。   simpledialog选择对话框 

 卡片
card。   clipBehavior:  //对Widget截取的行为,比如这里 Clip.antiAlias 指抗锯齿

IntrinsicHeight:根据子元素的固定高度调整其子元素大小的组件,可以让其内部高度对齐,因为  IntrinsicHeight 在布局时会提前调用 child 的 getMaxIntrinsicHeight 获取 child 的高度,修改 parent 传递给 child 的约束信息。

缺点:IntrinsicHeight 推算布局的过程会比较费时,可能会到 O(N²),虽然 Flutter 里针对这部分计算结果做了缓存,但是不妨碍它的耗时。

=========================滚动组件start======================================

给滚动组件添加滚动条:Scrollbar是一个Material风格的滚动指示器(滚动条),如果要给可滚动组件添加滚动条,只需将Scrollbar作为可滚动组件的任意一个父级组件即可

SingleChildScrollView:类似于Android中的ScrollView,它只能接收一个子组件,没有“懒加载”模式,所以只在内容不会超过屏幕太多时使用,且内容没超过屏幕时不滑动

ListView:它可以沿一个方向线性排布所有子组件,并且它也支持基于Sliver的延迟构建模型(懒加载),listview()构造方法没有懒加载模式,listview.builder()构造函数有懒加载模式

使用属性:cacheextent设置缓存的条目个数

Listview 横向要设置宽高。   纵向的设置高
跳转到指定的位置。  添加控制器。var scrollController=new ScrollController();    scrollController.jumpTo(0.0);
移除 ListView,GridView 的间距,MediaQuery.removeViewPadding().
MediaQuery.removeViewPadding(
        context: context,
        child: ListView(),
        removeTop: true,
        removeBottom: true,
        removeLeft: true,
        removeRight: true,
      )

优化点:itemextent固定主轴大小,能提高性能

Dismissible组件可通过左滑或者右滑清除列表项

GridView:主要关心SliverGridDelegate,他有两个字类

  • SliverGridDelegateWithFixedCrossAxisCount该子类实现了一个横轴为固定数量子元素的layout算法
  • SliverGridDelegateWithMaxCrossAxisExtent该子类实现了一个横轴子元素为固定最大长度的layout算法
  • 如果你的子元素宽高比例不为1,那么你一定要设置childAspectRatio属性
  • 下面是参考文章
  • Flutter网格型布局 - GridView篇 - 简书

CustomScrollView:子组件必须都是sliver,使用场景:1.ListView和GridView相互嵌套场景,ListView嵌套GridView时,需要给GridView指定高度,但我们希望高度随内容而变化(不指定),ListView和GridView使用同一个滚动效果。
2.一个页面顶部是AppBar,然后是GridView,最后是ListView,这3个区域以整体来滚动,AppBar具有吸顶效果。

NestedScrollView:它是对CustomScrollView的封装,可以在其内部嵌套其他滚动视图的滚动视图,其滚动位置是固有链接的,有2个ScrollController:一个是inner,一个outer。 outer是负责headerSliverBuilder里面的滚动widgets inner是负责body里面的滚动widgets ,当outer滚动到底了之后,才会开始滚动inner

系统NestedScrollView的有两个问题:ExtendedNestedScrollView三方库解决了

相关文章链接:Flutter 扩展NestedScrollView (一)Pinned头引起的bug解决 - 掘金

1.当中的Pinned为true的Sliver组件对body里面滚动组件的影响

解决:pinnedHeaderSliverHeightBuilder是我从最外层传递进来的用于获取当时Pinned 为true的全部Sliver header的高度.在这里把outer最大的滚动extent减去了Pinned 的总的高度,就完美解决了

2.当在里面放上tabview,并且tab是缓存状态的时候,会出现滚动会互相影响的问题(列表滚动同步问题)

解决:提供一个容器,把inner里面的滚动列表包裹起来,并且设置它的tab 的唯一key

系统提供的ScrollPhysics有

  • AlwaysScrollableScrollPhysics:总是可以滑动
  • NeverScrollableScrollPhysics:禁止滚动
  • BouncingScrollPhysics :内容超过一屏 上拉有回弹效果
  • ClampingScrollPhysics :包裹内容 不会有回弹

ListWheelScrollView:它的渲染效果类似于车轮(或者滚筒)

ReorderableListView:是通过长按拖动某一项到另一个位置来重新排序的列表组件。

参考:

学习记录:Flutter中的滑动_flutter 滚动_田螺与呆瓜的博客-CSDN博客

=========================滚动组件end======================================

=========================sliver系列start=====================================
控件:
1.SliverPersistentHeader:可以固定在顶部

2.SliverList。 

3.SliverFixedExtentList是sliver系列组件之一,和SliverList用法一样,唯一的区别就是SliverFixedExtentList是固定子控件的高度的,SliverFixedExtentList比SliverList更加高效,因为SliverFixedExtentList无需计算子控件的布局

4.SliverFillRemaining是sliver系列组件之一,此组件充满视口剩余空间,通常用于最后一个sliver组件,以便于没有任何剩余控件。

5.SliverPrototypeExtentList和SliverList用法一样,区别是SliverPrototypeExtentList的高度由prototypeItem控件决定。

SliverPrototypeExtentList 比SliverList更加高效,因为SliverFixedExtentList无需计算子控件的布局。

SliverPrototypeExtentList比SliverFixedExtentList更加灵活,因为SliverPrototypeExtentList不必指定像素高度。

SliverPrototypeExtentList通常用于不确定item高度,随prototypeItem变化的场景,比如调整整个App字体的大小,字体越大,需要的高度越高,如果使用SliverFixedExtentList指定具体的高度,会出现字体显示不全的状况。

6.SliverFillViewport生成的每一个item都占满全屏,viewportFraction表示比率,默认是1,表示占满全屏,如果设置0.8,则在开始和结尾处出现空白

属性:
floating 设置为true时,向下滑动时,即使当前CustomScrollView不在顶部,SliverAppBar也会跟着一起向下出现

pinned 设置为true时,当SliverAppBar内容滑出屏幕时,将始终渲染一个固定在顶部的收起状态

const SliverAppBar({
    Key key,
    this.leading,         //在标题左侧显示的一个控件,在首页通常显示应用的 logo;在其他界面通常显示为返回按钮
    this.automaticallyImplyLeading = true,//? 控制是否应该尝试暗示前导小部件为null
    this.title,               //当前界面的标题文字
    this.actions,          //一个 Widget 列表,代表 Toolbar 中所显示的菜单,对于常用的菜单,通常使用 IconButton 来表示;对于不常用的菜单通常使用 PopupMenuButton 来显示为三个点,点击后弹出二级菜单
    this.flexibleSpace,        //一个显示在 AppBar 下方的控件,高度和 AppBar 高度一样, // 可以实现一些特殊的效果,该属性通常在 SliverAppBar 中使用
    this.bottom,         //一个 AppBarBottomWidget 对象,通常是 TabBar。用来在 Toolbar 标题下面显示一个 Tab 导航栏
    this.elevation,            //阴影
    this.forceElevated = false, 
    this.backgroundColor,       //APP bar 的颜色,默认值为 ThemeData.primaryColor。改值通常和下面的三个属性一起使用
    this.brightness,   //App bar 的亮度,有白色和黑色两种主题,默认值为 ThemeData.primaryColorBrightness
    this.iconTheme,  //App bar 上图标的颜色、透明度、和尺寸信息。默认值为 ThemeData().primaryIconTheme
    this.textTheme,    //App bar 上的文字主题。默认值为 ThemeData().primaryTextTheme
    this.primary = true,  //此应用栏是否显示在屏幕顶部
    this.centerTitle,     //标题是否居中显示,默认值根据不同的操作系统,显示方式不一样,true居中 false居左
    this.titleSpacing = NavigationToolbar.kMiddleSpacing,//横轴上标题内容 周围的间距
    this.expandedHeight,     //展开高度
    this.floating = false,       //是否随着滑动隐藏标题
    this.pinned = false,  //是否固定在顶部
    this.snap = false,   //与floating结合使用
  })

=========================sliver系列end=====================================

=====================PageView和TarBarView相关start======================

PageView控件可以实现一个“图片轮播”的效果,PageView不仅可以水平滑动也可以垂直滑动

如果你在使用 TarBarView ,并且使用了 KeepAlive 的话,那么我推荐你直接使用 PageView 。因为目前到 1.2 的版本,在 KeepAlive 的 状态下,跨两个页面以上的 Tab 直接切换, TarBarView 会导致页面的 dispose 再重新 initState。尽管 TarBarView 内也是封装了 PageView + TabBar 。

你可以直接使用 PageView + TabBar 去实现,然后 tab 切换时使用 _pageController.jumpTo(MediaQuery.of(context).size.width * index); 可以避免一些问题。当然,这时候损失的就是动画效果了。事实上 TarBarView 也只是针对 PageView + TabBar 做了一层封装。

除了这个,其实还有第二种做法,使用如下方 PageStorageKey 保持页面数状态,但是因为它是 save and restore values ,所以的页面的 dispose 再重新 initState 方法,每次都会被调用。

 return new Scaffold(
      key: new PageStorageKey<your value type>(your value)
    )

关联TabBar和TabBarView这两个组件的桥梁:

TabController,与其并列的还有DefaultTabController,两者的区别是TabController一般放在有状态组件中使用,而DefaultTabController一般放在无状态组件中使用

 去掉 TabBar 点击时的阴影以及波纹效果

Theme(
  data: ThemeData(
    splashColor: Colors.transparent, // 点击时的水波纹颜色设置为透明
    highlightColor: Colors.transparent, // 点击时的背景高亮颜色设置为透明
  ),
  child: TabBar(
  	...
  ),
)

=====================PageView和TarBarView相关start======================

Stack:帧布局 ,Positioned用于定位Stack子组件,Positioned必须是Stack的子组件,只能用于stack中    

Stack 组件大小计算的原理:如果Stack中的所有子组件都没有定位(即没有使用Positioned包裹),那么Stack的大小将会适应其最大的子组件的大小。如果Stack中有定位的子组件,那么它们不会影响Stack的大小,Stack的大小将由未定位的子组件决定。

alignment默认值: Alignment.topLeft, alignment设置为Alignment(0, 0)时,左上角是Alignment(-1, -1),中间Alignment(0, 0),右下角Alignment(1, 1)

大小:取决于没有Positioned包裹的子组件的最大个的大小,子组件都被Positioned包裹时,大小是越大越好

fit默认值:StackFit.loose(类似松约束:让子组件在0-stack组件大小范围内),StackFit.expand(让子组件尽量填满stack父组件的约束空间),StackFit.passthrough(直接把stack父组件的约束传递到stack子组件中)

Positioned:横轴可以设置left、right、width,但同时最多只能设置两个属性,竖轴同理

clipBehavior默认值:Clip.hardEdge(被裁剪),Clip.none(不被裁剪,可以溢出,但是如果设置了点击事件溢出的部分不被点击)

Column:线性布局,垂直方向  

Row:线性布局,水平方向  mainAxisAlignment(主轴的排序方式): MainAxisAlignment.spaceEvenly,(平分比较常用)   crossAxisAlignment(次轴的排序方式,这里是纵轴,用的比较少) 

Expanded、Flexible和Spacer都是具有权重属性的组件,可以控制Row、Column、Flex的子控件如何布局的控件。   

Row 和 UnconstrainedBox 一样, 不会对其子代施加任何约束,而是让它们成为所需的任意大小。

Row 要么使用子级的宽度,要么使用Expanded 和 Flexible 从而忽略子级的宽度

SizeBox(height:10)SizeBox(width:10)或可以使两个布局中间加10的间距

Divider()添加分割线

=========================尺寸限制类容器start================================

尺寸限制类容器用于限制容器大小,Flutter中尺寸限制类容器组件包括ConstrainedBox、UnconstrainedBox、SizedBox、AspectRatio、FractionallySizedBox、LimitedBox、Container

ConstrainedBox有多级嵌套时,多级BoxConstraints嵌套约束最大值最终值等于多个BoxConstraints约束中的最小值,同理嵌套约束最小值等于多个BoxConstraints约束中的最大值;适用于需要设置最大/小宽高,组件大小依赖子组件大小,但不能超过设置的界限。

如下代码:显示的是父布局的宽高

ConstrainedBox(
  constraints: const BoxConstraints(
    minWidth: 70,
    minHeight: 70,
    maxWidth: 150,
    maxHeight: 150),
  child: Container(color: red, width: 10, height: 10))

解释:你可能会猜想 Container 的尺寸会在 70 到 150 像素之间,但并不是这样。 ConstrainedBox 仅对其从其父级接收到的约束下施加其他约束。

在这里,屏幕迫使 ConstrainedBox 与屏幕大小完全相同,因此它告诉其子 Widget 也以屏幕大小作为约束,从而忽略了其 constraints 参数带来的影响。

UnconstrainedBox虽然不限制其子控件的大小,但仍然受父控件的约束,子控件的大小超出父控件的区域将会溢出。

OverflowBox 与 UnconstrainedBox 类似,但不同的是,如果其子级超出该空间,它将不会显示任何警告。

SizedBox是具有固定宽高的组件,直接指定具体的宽高;适用于固定宽高的情况,常用于当作2个组件之间间隙组件。

AspectRatio组件是固定宽高比的组件,如果组件的宽度固定,希望高是宽的1/2,可以用AspectRatio实现此效果;适用于固定宽高比的情况。

FractionallySizedBox当我们需要一个控件的尺寸是相对尺寸时,比如当前按钮的宽度占父组件的70%,可以使用FractionallySizedBox来实现此效果;适用于占父组件百分比的情况。

LimitedBox:它受到父组件的约束,此时LimitedBox将会不做任何操作,我们可以认为没有这个组件;如:父组件宽度100,它的宽度50,最终显示的效果宽度是100;适用于没有父组件约束的情况。它限制仅在获得无限约束时才适用。如:limitedbox父布局是column时,他设置的最大高度才生效了

FittedBox :只能在有限制的宽高中对子 widget 进行缩放(宽度和高度不会变得无限大)。否则,它将无法渲染任何内容,并且你会在控制台中看到错误。深入理解 Flutter 布局约束 | Flutter 中文文档 | Flutter 中文开发者网站

Container适用于不仅有尺寸的约束,还有装饰(颜色、边框、等)、内外边距等需求的情况

1.有child时就匹配尺寸,除非设置了对齐方式alignment

2.没有child时就越大越好,除非父布局高度无限大时,它的高度为0,因为看源码child为空时,嵌套了一个LimitedBox

Flutter Widgets 之 Container_老孟Flutter-CSDN博客

装饰器
BoxDecoration({
  Color color, //颜色
  DecorationImage image,//图片
  BoxBorder border, //边框
  BorderRadiusGeometry borderRadius, //圆角
  List<BoxShadow> boxShadow, //阴影,可以指定多个
  Gradient gradient, //渐变
  BlendMode backgroundBlendMode, //背景混合模式
  BoxShape shape = BoxShape.rectangle, //形状
})
//容器左上角和右下角有弧度
borderRadius: BorderRadius.only(
            topLeft: Radius.circular(20.0),
            topRight: Radius.zero,
            bottomLeft: Radius.zero,
            bottomRight: Radius.circular(20.0)),
      )

//容器左右两边有弧度
borderRadius:BorderRadius.all(Radius.circular(Dimens.rgap_dp25))

//3像素圆角
borderRadius: BorderRadius.circular(3.0), 

//容器底部加根线
decoration: BoxDecoration(
         color:Colors.white,
         border: Border(
           bottom: BorderSide(width:0.5,color:Colors.black12)
         )
       )

//使用decoration实现颜色的渐变(左右渐变)
decoration: BoxDecoration(
          gradient: LinearGradient(
              begin: isOrientationLeftRight
                  ? Alignment.centerLeft
                  : Alignment.topCenter,
              end: isOrientationLeftRight
                  ? Alignment.centerRight
                  : Alignment.bottomCenter,
              colors: [gradientStart, gradientEnd]),
          /*阴影设置
              boxShadow: [
                new BoxShadow(
                  color: Colors.grey[500],
                  blurRadius: 20.0,
                  spreadRadius: 1.0,
                )
              ]*/
        ),

========================尺寸限制类容器end=================================

========================高斯模糊的效果start=================================
1.BackdropFilter使用
///1。BackdropFilter是通过在背景上面盖上一个模糊层从而达到高斯模糊的效果,因此要做模糊的背景图必须在BackdropFilter底下,所以通常使用stack实现效果,
///2。BackdropFilter实现的模糊遮罩层是填充整个父组件的,需要使用ClipRect将其裁切
///3。BackdropFilter它的孩子没有模糊效果
 Stack(
          children: <Widget>[
            _buildImage(),
            Positioned.fill(
              child: ClipRect(
                child: BackdropFilter(
                  filter: ImageFilter.blur(sigmaX: _sigmaX, sigmaY: _sigmaY),
                  child: Container(
                    color: Colors.black.withAlpha(0),
                  ),
                ),
              ),
            )
2.ImageFiltered:可以对任意组件进行特效处理,包括但不限于高斯模糊、颜色滤镜、变换等

========================高斯模糊的效果end=================================

align 

center

padding组件处理容器与子元素直接的间距

Wrap组件,可以实现流式布局,   direction,主轴的方向,默认水平。        Alignment:主轴的对齐方式       spacing:主轴方向上的间距。       Runspacing:子轴的间距。

=======================显示隐藏start========================================

Offstage控制是否显示组件.   当offstage为true,控件隐藏;类似于Android中View的gone

Offstage({ 
    Key key, 
    this.offstage = true, 
    Widget child 
  })

Visibility控制子组件隐藏/可见的组件

Visibility({
    Key key,
    @required this.child,
    this.replacement = const SizedBox.shrink(),//不可见时显示的组件(当maintainState=false)
    this.visible = true,//子组件是否可见,默认true(可见)
    this.maintainState = false,//不可见时是否维持状态,默认为false
    this.maintainAnimation = false,//不可见时,是否维持子组件中的动画
    this.maintainSize = false,//不可见时是否留有空间
    this.maintainSemantics = false,//不可见时是否维持它的语义
    this.maintainInteractivity = false,//不可见时是否具有交互性
  })

Offstage Widget用于在布局中隐藏子Widget,同时仍然是树的一部分。它可以用于有条件地显示或隐藏子Widget,而无需重新构建整个树。

Opacity Widget用于控制子Widget的透明度。它接受一个介于0.0和1.0之间的值,其中0.0表示完全透明,1.0表示完全不透明。注:移除控件同时它的位置依然保留,类似于Android中View的invisible。

Visibility Widget用于控制子Widget的可见性。它可以用于有条件地显示或隐藏子Widget,而无需重新构建整个树。

所有三个Widget都用于控制子Widget的显示,但它们的方式不同。

Offstage控制布局,Opacity控制透明度,Visibility控制可见性。

Offstage与Visibility比较:
Offstage是控制组件隐藏/可见的组件,当Offstage不可见的时候,如果child有动画等,需要手动停掉,Offstage并不会停掉动画等操作,如果感觉有些单调功能不全,我们可以使用Visibility,
Visibility也是控制子组件隐藏/可见的组件。不同是的Visibility有隐藏状态是否留有空间、隐藏状态下是否可调用等功能。

========================显示隐藏end========================================

 2.布局(向下传递约束,向上传递尺寸,上层决定下层的位置)         (快捷键Alt+enter)

深入理解 Flutter 布局约束 | Flutter 中文文档 | Flutter 中文开发者网站

3.路由

获取当前页路由名:

var routePath = ModalRoute.of(context).settings.name;
print("current route: $name")

普通路由:

    //打开到新的页面:
    Navigator.push(context, MaterialPageRoute<void>(
      builder: (BuildContext context) {
        return NextScreen();
      },
    ));

    //返回
    Navigator.pop(context);

    //打开新页面,并且用新页面替换旧页面(删除旧页面):
    Navigator.pushReplacement(context, MaterialPageRoute<void>(
      builder: (BuildContext context) {
        return NextScreen();
      },
    ));

    //打开新页面并删除之前的所有路由:
    Navigator.pushAndRemoveUntil(
      context,
      MaterialPageRoute<void>(
        builder: (BuildContext context) {
          return NextScreen();
        },
      ),
      (Route<dynamic> route) => false,
    );

    //导航到新页面,在返回时接收返回数据:
    var data = await Navigator.push(context, MaterialPageRoute<void>(
      builder: (BuildContext context) {
        return NextScreen();
      },
    ));

    //带返回值返回前一个路由,配合上面使用:
    Navigator.pop(context, 'success');

 替换路由
Navigator.of(context).pushReplacementNamed('/registerSecond');

命名路由单独抽离到一个文件
final Map routes = { '/':(context,{arguments})=>Tabs(), '/search':(context,{arguments}) =>SearchPage(arguments: arguments), '/form': (context,{arguments}) =>FormPage(arguments: arguments), };
var onGenerateRoute=(RouteSettings settings) { // 统一处理 final String name = settings.name; final Function pageContentBuilder = routes[name]; if (pageContentBuilder != null) { final Route route = MaterialPageRoute( builder: (context) => pageContentBuilder(context, arguments: settings.arguments)); return route; } };
然后在根页面:MaterialApp( // home:Tabs(), initialRoute: '/', onGenerateRoute: onGenerateRoute );

使用getx库的路由功能参考:https://juejin.cn/post/6905367558008078350

参考:Flutter大型项目架构:路由管理篇

常见问题:路由守卫、路由拦截、路由不带参数跳转、路由带参数跳转、路由回传

疑问:返回到指定界面怎么处理

double.infinity 和 double.maxFinite

double.infinity 和double.maxFinite可以让当前元素的width或者height达到父元素的尺寸。

static const double nan = 0.0 / 0.0;
static const double infinity = 1.0 / 0.0;
static const double negativeInfinity = -infinity;
static const double minPositive = 5e-324;
static const double maxFinite = 1.7976931348623157e+308;

积累

其它:

免费字体:阿里巴巴普惠体

网络库相关的文章:Flutter Dio进阶:使用Flutter Dio拦截器实现高效的API请求管理和身份验证刷新-阿里云开发者社区

通过 PopScope 组件累定义得知:

  • 当 canPop 为 true,则系统返回手势将导致封闭的 Navigator 照常接收弹出。会调用 onPopInvoked,此时didPoptrue
  • 当 canPop 为 false,则系统返回手势不会将路由从封闭的 Navigator 中弹出,但仍然会调用 onPopInvoked 方法,此时 didPop 为 false,此时进行逻辑判断或者插入其他需要执行的代码,如果需要返回则再执行 Navigator.of(context).pop(); 。
  • 注意此时 onPopInvoked 又会被再次调用,但此时 didPop 为 true
  • onPopInvoked中,需要判断一下 didPop,如果为 true, 则 return
  • 注:在iOS手机上,当 canPop 为 false时,禁用系统返回手势后没有调用onPopInvoked方法,当 canPop 为 true时,系统返回手势在页面退出后才调用的onPopInvoked方法
  • 参考:https://ducafecat.com/blog/migrating-from-willpopscope-to-popscope-in-flutter

flutter中的图片路径:Flutter会打把assets下的文件打包到Android中的assets中,具体为app/assets/flutter_assets/

RestorationMixin   说明:Flutter 1.22 正式发布 - 知乎

Android studio 右侧的Flutter Performance打开方法:
在profile模式下不能用,在debug模式下可以
Flutter应用如何调试--DevTools介绍(下)参考链接 https://www.jianshu.com/p/0e53a168b367

Flutter 的生命周期https://blog.csdn.net/sinat_17775997/article/details/94733411

去掉右边debug图标:在根页面添加.    debugShowCheckedModeBanner: false,

屏幕宽度MediaQuery.of(context).size.width,

通过Listener直接识别原始指针事件来解决冲突

接口调试可以创建假数据(动态)。  Easymock.    https://easy-mock.com/

mounted 是 bool 类型,表示当前 State 是否加载到树⾥。常用于判断页面是否释放。

通过addPostFrameCallback可以做一些安全的操作,在有些时候是很有用的,它会在当前Frame绘制完后进行回调,并只会回调一次,如果要再次监听需要再设置
 WidgetsBinding.instance.addPostFrameCallback((callback) {
      _showHint();
    });

解决滑动冲突:外层嵌套ScrollNotificationInterceptor(没验证过)

系统的AppBar中的控件宽高自己设置不起作用

比较有深度的博客

linxunfeng的博客:https://juejin.cn/user/1820446984512392

北海道浪子的博客:https://juejin.cn/user/729731450022440

恋猫de小郭的博客:https://juejin.cn/user/817692379985752

张风捷特烈的博客:https://juejin.cn/user/149189281194766

多渠道(多环境)配置

参考:https://juejin.cn/post/6961244443693285384

避免使用 MediaQuery.of(context).size

当你在Flutter中使用MediaQuery.of(context).size时,Flutter会将你的小部件与MediaQuery的大小相关联。这意味着每次调用MediaQuery.of(context).size时,Flutter会检测MediaQuery的大小是否发生变化,从而可能导致不必要的重建(rebuilds)。

使用MediaQuery.sizeOf(context)来避免这些不必要的重建,从而提高应用程序的响应性。通过使用MediaQuery.sizeOf(context),你可以绕过与MediaQuery大小相关的重建过程,从而减少不必要的性能开销。

类似的优化方法也适用于其他MediaQuery方法。举例来说,建议使用MediaQuery.platformBrightnessOf(context)而不是MediaQuery.of(context).platformBrightness,以避免不必要的重建,从而提高应用的响应性。

flutter自定义View(CustomPainter) 之 canvas的方法总结

http://t.csdnimg.cn/wHWmu

flutter混淆

在构建 release 版本的 Flutter 应用时,可以通过以下命令行参数来实现混淆:

flutter build apk --obfuscate --split-debug-info=./out/android/app.android-arm64.symbols

其中,--obfuscate 参数表示启用混淆功能,--split-debug-info 参数用于指定输出调试信息文件的位置。这将生成一个符号映射表,用于混淆后的代码与原始代码之间的映射关系。值得注意的是,目前该命令支持多种目标平台,包括 apk、appbundle、ios 和 ios-framework 等。

请注意,--split-debug-info标志也可以单独使用。实际上,它可以大大减少代码大小。有关应用程序大小的更多信息,请参阅测量应用程序的大小

混淆成功后,需要保存符号映射表以备将来调试使用。为了读取混淆后的堆栈跟踪信息,可以执行以下步骤:

找到符号映射表文件,例如在 Android arm64 平台下发生 crash,可以分析 app.android-arm64.symbols 文件。
运行 flutter symbolize 命令,并指定堆栈跟踪文件和符号映射表文件,例如:

flutter symbolize -i <stack trace file> -d ./out/android/app.android-arm64.symbols

参考链接:https://blog.csdn.net/gfdjhg/article/details/137271237

Flutter 代码混淆 混淆Dart代码_dart语言代码混淆-CSDN博客

常用的工具

捕捉网页截图谷歌浏览器扩展程序:FireShot、印象笔记(有限制不好用)

在线原型图制作:axure

抓包工具:Charles

api调试工具:apifox  、postman

android实时投屏软件:qtscrcpy

在线图表编辑工具:draw.io

管理工具:Jetbrains toolbox 下载地址:JetBrains Toolbox App: Manage Your Tools with Ease

Sourcetree:简化了如何与Git存储库进行交互

系统字体缩放

现在的手机一般都提供字体缩放,这给应用开发的适配上带来一定工作量,所以大多数时候我们会选择禁止应用跟随系统字体缩放。

在 Flutter 中字体缩放也是和 MediaQueryData 的 textScaleFactor 有关。所以我们可以在需要的页面,通过最外层嵌套如下代码设置,将字体设置为默认不允许缩放。

 MediaQuery(
      data: MediaQueryData.fromWindow(WidgetsBinding.instance.window).copyWith(textScaleFactor: 1),
      child: new Container(),
    );

Margin 和 Padding如何设置负数

在使用 Container 的时候我们经常会使用到 margin 和 padding 参数,其实在上一篇我们已经说过, Container 其实只是对各种布局的封装,内部的 margin 和 padding 其实是通过 Padding 实现的,而 Padding 不支持负数,所以如果你需要用到负数的情况下,推荐使用 Transform 。

  Transform(
      transform: Matrix4.translationValues(10, -10, 0),
      child: new Container(),
    );

测量自己的尺寸、测量Parent的尺寸、测量Child的尺寸参考:FlutterComponent最佳实践之Widget尺寸 · 语雀

小技巧

3.10 现在可以通过将  MediaQuery.of 获取参数的方式替换成  MediaQuery.******Of(context); 来减少不必要的 rebuild

参考:Flutter 小技巧之 3.10 全新的 MediaQuery 优化与 InheritedModel

flutter事件分发机制

FlutterFlutter原理篇:事件机制传播与响应机制与HitTestBehavior的介绍

注解:

Flutter 注解处理及代码生成 - 掘金

flutter动态化相关

Deferred Components-实现Flutter运行时动态下发Dart代码 | 京东云技术团队 - 掘金

58 开源 Flutter 终极动态化解决方案-Fair 框架介绍:

Github地址:https://github.com/wuba/fair
Fair官网:https://fair.58.com/

快速集成 Flutter Shorebird 热更新 | 博客 | 猫哥在线课堂

flutter字体:

  • 默认在 iOS 上:
    • 中文字体:PingFang SC (繁体还有 PingFang TCPingFang HK )
    • 英文字体:.SF UI Text / .SF UI Display
  • 默认在 Android 上:
    • 中文字体:Source Han Sans / Noto
    • 英文字体:Roboto

参考:Flutter 小技巧之玩转字体渲染和问题修复 - 掘金

三方库依赖相关:

===================三方库依赖相关start===================================

依赖已发布的三方库的几种方式已url_launcher举例:

1.url_launcher: '5.4.2'   #指定版本号

如果有两个依赖该库的三方库使用的版本号不一致,会出现版本冲突,可以通过下面方式制定版本号,但不推荐

dependency_overrides: url_launcher: '5.4.2'

2.url_launcher:             #不指定版本号

如果不指定版本号,就表示项目可以使用url_launcher的任意版本,容易出现版本冲突

3.url_launcher:  '>=5.4.2<6.0.0'     #指定最大和最小版本号的方式

4.url_launcher:  ^5.4.2     # ^标签表示使用该库与当前接口兼容的最新版本,这里的^5.4.2等效于[5.4.2,6)    推荐使用

5.url_launcher:  any    #any会自动调用pub的版本分析器,寻找合适的能够避免冲突的依赖版本并下载,在实际项目开发中,拥有不确定性的版本号会成为未来应用崩溃的一个极大的隐患。这可能将会使您的应用难以调试。所以不要在你的项目中留下any关键字!,所以我们需要将正确的依赖版本把any关键字替换掉!

 any也有好处,当出现冲突后使用any,当系统查找到避免冲突的版本号后(在pubspec.lock文件查找),在把any关键字替换掉

依赖未开源的三方库

1.如果要依赖的库还保存在本地的磁盘中,可以直接通过指定库的位置导入库。具体声明方法如下:

dependencies:
   package1:
     path: ../package1
这里的path属性就指定了package1相对于这个项目的根目录的路径

2.如果要依赖的库还放在GitHub或者其他Git仓库上,可以直接通过下面指这种方式导入。这里通过指定package1的地址导入该库。
 dependencies:
    package1:
      git:
        url: git://github.com/flutter/package1.git
这里的url就指定了Git仓库的地址。此时,项目中就会依赖这个Git仓库中根目录所对应的库了。

3.如果要依赖的库在Git仓库中的子目录中,可以使用下面这种声明方式。
dependencies:
  package1:
    git:
      url: git://github.com/flutter/packages.git
      path: packages/package1

这里通过path属性指定所要依赖的库相对于Git仓库根目录的路径,在终端执行flutter pub get后就能成功将该库安装到本地了。

===================三方库依赖相关end===================================

去除 Flutter 中点击按钮、底部导航栏的水波纹效果:

===================去除 Flutter 中点击按钮、底部导航栏的水波纹效果start=============

如何全局去除 Flutter 中点击按钮、底部导航栏的水波纹效果

@override
Widget build(BuildContext context) {
  return MaterialApp(
    theme: ThemeData(
      splashColor: Colors.transparent, // 点击时的高亮效果设置为透明
      highlightColor: Colors.transparent, // 长按时的扩散效果设置为透明
    ),
  );
}

局部设置需要要求组件自带 splashColor 属性和 highlightColor 属性,比如 InkWell 、RaisedButton 等组件

InkWell(
	onTap: () {},
	child: Text('InkWell 组件', style: TextStyle(fontSize: 25)),
    highlightColor: Colors.transparent, // 透明色
    splashColor: Colors.transparent, // 透明色
),

注意:像 RaisedButton 类型的组件比较特殊,即使你设置了 splashColor 属性和 highlightColor 属性,点击时看着好像还是有水波纹效果,那是因为点击 RaisedButton 时还有阴影效果的属性在控制着

RaisedButton(
	onPressed: () {},
    child: Text('按钮'),
    splashColor: Colors.transparent,
    highlightColor: Colors.transparent,
    highlightElevation: 0, // 控制按钮下方阴影的大小,默认值为 8
    elevation: 0, // 凸起按钮下方阴影的大小,默认值为 2
),

去掉 TabBar 点击时的阴影以及波纹效果

Theme(
  data: ThemeData(
    splashColor: Colors.transparent, // 点击时的水波纹颜色设置为透明
    highlightColor: Colors.transparent, // 点击时的背景高亮颜色设置为透明
  ),
  child: TabBar(
  	...
  ),
)

===================去除 Flutter 中点击按钮、底部导航栏的水波纹效果end=============

json数据解析相关:

在线json转dart实体类:JSON转dart-BeJSON.com

本地json数据解析:

///本地json数据
{
 "categoryList": [
        {
            "id": 1,
            "categoryName": "翡翠",
 "childList": [
        {
            "id": 11,
            "categoryName": "翡翠"
        }
    ]
        }
         
    ]
}

实体类:

class SaleCategoryBean {
  List<CategoryList>? categoryList;

  SaleCategoryBean({this.categoryList});

  SaleCategoryBean.fromJson(Map<String, dynamic> json) {
    if (json['categoryList'] != null) {
      categoryList = [];
      json['categoryList'].forEach((v) {
        categoryList!.add(new CategoryList.fromJson(v));
      });
    }
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    if (this.categoryList != null) {
      data['categoryList'] = this.categoryList!.map((v) => v.toJson()).toList();
    }
    return data;
  }
}

class CategoryList {
  int? id;
  String? categoryName;
  List<ChildList>? childList;

  CategoryList({this.id, this.categoryName, this.childList});

  CategoryList.fromJson(Map<String, dynamic> json) {
    id = json['id'];
    categoryName = json['categoryName'];
    if (json['childList'] != null) {
      childList = [];
      json['childList'].forEach((v) {
        childList!.add(new ChildList.fromJson(v));
      });
    }
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['id'] = this.id;
    data['categoryName'] = this.categoryName;
    if (this.childList != null) {
      data['childList'] = this.childList!.map((v) => v.toJson()).toList();
    }
    return data;
  }
}

class ChildList {
  int? id;
  String? categoryName;

  ChildList({this.id, this.categoryName});

  ChildList.fromJson(Map<String, dynamic> json) {
    id = json['id'];
    categoryName = json['categoryName'];
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['id'] = this.id;
    data['categoryName'] = this.categoryName;
    return data;
  }
}

解析:

import 'package:flutter/services.dart';
 
rootBundle.loadString('assets/json/category.json').then((String value) {
        SaleCategoryBean _bean = SaleCategoryBean.fromJson(json.decode(value));
       List<CategoryList> _list = _bean.categoryList ?? [];
        setState(() {});
      });

/类型转换start

1.flutter中可以使用split() 把字符串转换成list

var str='香蕉-苹果-西瓜';

var list=str.split('-');

print(list);    //['香蕉','苹果','西瓜']

print(list is List);   //true

2.Flutter中List转换成字符串

List myList = ['香蕉', '苹果', '西瓜'];

    var str = myList.join('-'); //list转换成字符串

    print(str); //香蕉-苹果-西瓜

    print(str is String); //true

3.json和map的转换。 导包。 import 'dart:convert';

Flutter 中一般 json 数据从 String 转为 Object 的过程中都需要先经过 Map 类型。
map转json。    Map userInfo = {"username":"zhangsan","age":20};     Var a = json.encode(userInfo);
json转map.   String userInfo = '{"username":"zhangsan","age":20}';  Map u = json.decode(userInfo);

4.json转数组

String info = "[\"嗯嗯嗯\",\"让我\",\"我无法答复\",\"问问\",\"我让人防\"]";
List<dynamic> list =(json.decode(info) as List<dynamic>) .cast<String>();

/类型转换end///

三方库

flutter_secure_storage 库是基于各个平台(Android 和 iOS)的原生安全存储机制来实现的参考:构建安全Flutter应用 - 6个实用技巧

flutter_easyloading   常见的加载对话框

convex_bottom_bar  底部导航栏

flutter_staggered_animations 轻松地将交错动画添加到您的 ListView、GridView、Column 和 Row 子项

wechat_assets_picker :基于 微信 UI 的 Flutter 图片选择器(同时支持视频和音频)。 该插件基于 photo_manager 实现资源相关功能, extended_image 用于查看图片, provider 用于协助管理选择器的状态

flutter_pickers:GitHub - longer96/flutter_pickers: flutter 选择器库,包括日期及时间选择器(可设置范围)、单项选择器(可用于性别、民族、学历、星座、年龄、身高、体重、温度等)、城市地址选择器(分省级、地级及县级)、多项选择器等…… 欢迎Fork & pr贡献您的代码,大家共同学习

connectivity:用于发现Android和iOS上的网络(WiFi和移动/蜂窝)连接状态。

Flutter常用工具类库依赖于Dart常用工具类库common_utils,以及对其他第三方库封装,致力于为大家分享简单易用工具类。如果你有好的工具类欢迎PR.目前包含SharedPreferences Util, Screen Util, Directory Util, Widget Util, Image Util。     flustars
common_utils:Dart常用工具类库。包含日期,正则,倒计时,时间轴等工具类. flutter_screenutil:屏幕适配

实现局部刷新

  • 通过抽取widget为StateFulWidget包裹,使用setState刷新制定widget
  • 使用StreamBuilder实现局部刷 参考:十一、全面深入理解Stream · GitBook告别setState()! 优雅的UI与Model绑定 Flutter DataBus使用~ - 掘金 
  • Provide的解决方案设定顶级Widget,然后用consumer包裹子控件,调用更新,provider可以实现跨组件访问,(跨组件访问也可以通过context向上查询,这种方式没试过)
  • ValueListenableBuilder组件:可以监听一个值,当其变化时通过builder回调能重建界面,避免使用setState刷新
  • GlobalKey实现控件的局部刷新:将需要单独刷新的widget从复杂的布局中抽离出去,然后通过传GlobalKey引用,这样就可以通过GlobalKey实现跨组件的刷新了。
  • StatefulBuilder组件:需要传入builder属性进行构造组件,在build中可以使用StateSetter改变构造子组件的状态,即可以不用创建类而实现一个局部刷新的组件
 int a = 0;
 int b = 0;
 
 // 1、定义一个叫做“aState”的StateSetter类型方法;
 StateSetter? aState;
 
 @override
 Widget build(BuildContext context) {
   return Scaffold(
     body: Center(
       child: Column(
         mainAxisAlignment: MainAxisAlignment.center,
         children: <Widget>[
           // 2、将第一个“ElevatedButton”组件嵌套在“StatefulBuilder”组件内;
           StatefulBuilder(
             builder: (BuildContext context, StateSetter setState) {
               aState = setState;
               return ElevatedButton(
                 onPressed: () {
                   a++;
                   // 3、调用“aState”方法对“StatefulBuilder”内部进行刷新;
                   aState(() {});
                 },
                 child: Text('a : $a'),
               );
             },
           ),
           ElevatedButton(
             onPressed: () {
               b++;
               setState(() {});
             },
             child: Text('b : $b'),
           ),
         ],
       ),
     ),
   );
 }

setState(() {});

  • 使用:把需要刷新的字段代码写到大括号里面(注:里面和外面都能刷新,写到里面的目的是为了判断字段代码多的情况下,辨别哪些字段需要刷新)
  • 内部原理:执行了方法: _element!.markNeedsBuild();
  • 可以通过下面方式实现刷新:(官方不推荐使用,查看BuildContext源码注释)
    (context as Element).markNeedsBuild();
  • 连续setstate1万次,每次执行时都会触发重构吗:不是,通过看源码,标脏那里有个判断语句,为true时才标脏

  • setState 其实是调用了 markNeedsBuild ,markNeedsBuild 内部标记 element 为 diry ,然后在下一帧 WidgetsBinding.drawFrame 才会被绘制,这可以看出 setState 并不是立即生效的哦。
  • setState 调用时会自己添加到 BuildOwner _dirtyElements 脏链表中,然后调用 window.scheduleFrame()来注册Vsync回调。当下一次vsync信号的到来时会执行handleBeginFrame()handleDrawFrame()来更新UI这就是整个setState的更新流程。

BuildContext:

只是抽象类接口,而  Element 实现了它,向上查找

使用:获取页面宽高大小: print(((context as Element).findRenderObject() as RenderBox).size);

  abstract class BuildContext {
  ///查找父节点中的T类型的State
  T? findAncestorStateOfType<T extends State>();
  ///查找父节点中的T类型的 InheritedWidget 例如 MediaQuery 等
  T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({ Object? aspect });
  ///遍历子元素的element对象
  void visitChildElements(ElementVisitor visitor);
   
 
 RenderObject? findRenderObject();

  /// be called apply to this method as well.
  InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect });

  InheritedElement? getElementForInheritedWidgetOfExactType<T extends InheritedWidget>();
 
  /// a subtree.
  T? findRootAncestorStateOfType<T extends State>();

  T? findAncestorRenderObjectOfType<T extends RenderObject>();
  
  void visitAncestorElements(bool Function(Element element) visitor);
 
  void dispatchNotification(Notification notification);
 
  DiagnosticsNode describeElement(String name, {DiagnosticsTreeStyle style = DiagnosticsTreeStyle.errorProperty});
 
  DiagnosticsNode describeWidget(String name, {DiagnosticsTreeStyle style = DiagnosticsTreeStyle.errorProperty});
 
  List<DiagnosticsNode> describeMissingAncestor({ required Type expectedAncestorType });
  
  DiagnosticsNode describeOwnershipChain(String name);
}

flutter3.0新规则:await 后面不要用context,正确使用方法如:

正确使用:

   final navigator = Navigator.of(context);
    await loadData();
    navigator.pop();


错误用法:

    await loadData();
    Navigator.of(context).pop();

flutter回调的几种写法:

=======flutter回调的几种写法start================

方法的使用     加括号:调用方法。 不加括号:把方法赋值给这个属性
比如:  void _ab(){}   调用方法: child:_ab(),       方法赋值: child:_ab或child:()=>_ab(),

写button的回调方法时只能以"方法赋值"写,不要以"调用方法"写,因为以"调用方法"写第一次加载布局时会调用一次方法

函数回调的写法1:Function

///定义
class CallbackExampleWidget extends StatelessWidget {
  final Function() onSelected;
  const CallbackExampleWidget({Key? key, required this.onSelected}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      
    );
  }
}


///使用
 CallbackExampleWidget(onSelected: (){},);



///多个参数传值写法
final Function(int, String) onSelected;
///使用
CallbackExampleWidget(onSelected: (id, name) {},);

函数回调的写法2:Function

通过方式1可以发现,函数回调无法限定参数和返回值,这样在编码中容易出现很多潜在的错误,因此可以使用typedof进行限制,代码如下:

ps:typedef的含义是给某一种特定的函数类型起了一个名字,可以认为是一个类型的别名。这样在使用过程中就会进行类型检查



// typedef FunctionTest = Function();//定义成这样也是可以的
typedef Function2<int, String> = void Function(
    int result, String str); //限定参数和返回值

class CallbackExampleWidget extends StatelessWidget {
  final Function2<int, String> onSelected;
  const CallbackExampleWidget({Key? key, required this.onSelected})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container();
  }
}


///使用
 CallbackExampleWidget(onSelected: (int result, String str) {});

函数回调的写法3:系统默认

VoidCallback无参回调

class CallbackExampleWidget extends StatelessWidget {
  final VoidCallback onSelected;
  const CallbackExampleWidget({Key? key, required this.onSelected})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

///使用
 CallbackExampleWidget(onSelected: () {});

ValueChanged有参回调. 注:只能有一个参数

class CallbackExampleWidget extends StatelessWidget {
  final ValueChanged<int> onSelected;
  const CallbackExampleWidget({Key? key, required this.onSelected})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container();
  }
}


///使用
CallbackExampleWidget(onSelected: (int result) {});

总结:推荐使用方式3,当参数较多时推荐用方式2

=======flutter回调的几种写法end================

键盘相关:

=======================键盘相关start=======================================
解决弹出键盘时多条线的问题(或超过屏幕):在最外层包裹一层SingleChildScrollView(),里面有list view时会有滑动冲突

 /// 关闭输入法,避免弹出FocusManager.instance.primaryFocus?.unfocus();

防止空指针异常 : List travelItems;   travelItems?.length ?? 0


 // 防止键盘弹出,提交按钮升起。。。 resizeToAvoidBottomInset: false,

隐藏软件盘
// 隐藏键盘
// 建议传入对应的 context,才能获取当前页面的键盘是否被拉起
hiddenKeyBoard({BuildContext context}) {
  Future.delayed(Duration.zero).then((value) {
    FocusScopeNode _node = FocusScope.of(context );
    print('_node.hasFocus: ${_node.hasFocus}');
    // 根据键盘是否被拉起,来决定是否收起键盘
    if (_node.hasFocus) _node.requestFocus(FocusNode());
  });
}

获取键盘高度要放在build里面获取:

  //键盘高度
  double? keyHeight;
  
  @override
  Widget build(BuildContext context) {
      //获取键盘高度
    if(MediaQuery.of(context).viewInsets.bottom!=0.0){
      //键盘高度是会变的,键盘隐藏的时候会返回0,所以为0是不赋值
      keyHeight = MediaQuery.of(context).viewInsets.bottom;
    }
  }

=======================键盘相关end=======================================

保持页面状态:

=============================保持页面状态start==============================
保持页面状态,如何让页面保持原来的状态,而不是每次都要重新加载刷新数据呢两种方式:

第一种方式:采用IndexedStack
IndexdStack和Stack一样,都是层布局控件,可以在一个控件上面放置另一个控件,但唯一不同的是,IndexdStack在同一时刻只能显示子控件中的一个控件,通过index属性来设置显示的控件
注意:

  • IndexedStack会同时加载所有的子组件,所以如果子组件比较多或者占用内存较大,这种方式可能会对性能产生影响。

  • 当需要动态切换子组件时,可以通过修改index来实现,比如将index设置为一个变量,然后在需要切换时,修改这个变量即可。

代码如下:
body:  IndexedStack(
       index: currentIndex,
       children: tabBodies
     ),

第二种方式:AutomaticKeepAliveClientMixin
如果所有的页面都需要保持页面状态,那么就使用indexdStack;如果有些页面需要保持页面状态,有些页面需要进来就刷新,那么我们就需要使用AutomaticKeepAliveMixin这个类来单独控制某个页面的状态
代码如下:
1.     with AutomaticKeepAliveClientMixin

2.@override
bool get wantKeepAlive =>true;
===========================保持页面状态end============================== 

flutter改成如何更改包名和应用名:

===================flutter改成如何更改包名和应用名start==========================

android下修改应用名称:修改AndroidManifest.xml文件中的label属性。

<application
    android:name="io.flutter.app.FlutterApplication"
    android:label="NewName" ...

android下修改包名:修改app下的build.gradle

defaultConfig {
    applicationId "your.package.name"
    minSdkVersion 16
    targetSdkVersion 27
    versionCode 1
    versionName "1.0"
    testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}

ios下修改包名和应用名称

 

===================flutter改成如何更改包名和应用名end==========================

状态栏相关的

1.状态栏字体颜色修改
方法一:AppBar里面的Brightness 或者 ThemeData 去设置状态栏颜色
方法二:AnnotatedRegion

不想用 AppBar ,那么你可以嵌套 AnnotatedRegion<SystemUiOverlayStyle> 去设置状态栏样式,通过 SystemUiOverlayStyle 就可以快速设置状态栏和底部导航栏的样式。

同时你还可以通过 SystemChrome.setSystemUIOverlayStyle 去设置,前提是你没有使用 AppBar 。需要注意的是,所有状态栏设置是全局的, 如果你在 A 页面设置后,B 页面没有手动设置或者使用 AppBar ,那么这个设置将直接呈现在 B 页面。

AnnotatedRegion<SystemUiOverlayStyle>(
          value: SystemUiOverlayStyle.dark
              .copyWith(systemNavigationBarColor: Colors.white),
child:child)

方式三:设置Scaffold的extendBodyBehindAppBar属性

return Scaffold(
  extendBodyBehindAppBar: true,
  appBar: AppBar(
    title: Text(widget.title),
    backgroundColor: Colors.transparent,
    elevation: 0,
  ),

2.可以用这种方式设置渐变的AppBar
            flexibleSpace: GradualChangeView(
                rotation: Rotation.LR,
                colors: [Colors.cyan, Colors.blue, Colors.blueAccent]),

3.沉浸式状态栏SafeArea

4.布局去掉沉浸式效果和布局设置占满全屏却无效的问题
使用Scaffold的body的布局默认是沉浸式的,将状态栏一起包含了,可以通过在body后添加一层SafeArea即可.

5.获取状态栏高度和安全布局

如果你看过 MaterialApp 的源码,你应该会看到它的内部是一个 WidgetsApp ,而 WidgetsApp 内有一个 MediaQuery,熟悉它的朋友知道我们可以通过 MediaQuery.of(context).size 去获取屏幕大小。

其实 MediaQuery 是一个 InheritedWidget ,它有一个叫 MediaQueryData 的参数,这个参数是通过如下图设置的,再通过源码我们知道,一般情况下 MediaQueryData 的 padding 的 top 就是状态栏的高度。

所以我们可以通过 MediaQueryData.fromWindow(WidgetsBinding.instance.window).padding.top 获取到状态栏高度,当然有时候可能需要考虑 viewInsets 参数。

至于 AppBar 的高度,默认是 Size.fromHeight(kToolbarHeight + (bottom?.preferredSize?.height ?? 0.0)),kToolbarHeight 是一个固定数据,当然你可以通过实现 PreferredSizeWidget 去自定义 AppBar

同时你可能会发现,有时候在布局时发现布局位置不正常,居然是从状态栏开始计算,这时候你需要用 SafeArea 嵌套下,至于为什么,看源码你就会发现 MediaQueryData 的存在。

参考:FlutterComponent最佳实践之沉浸式 · 语雀

Isolate(参考:Flutter关于Isolate的初步学习应用 - 简书

在Dart中实现并发可以用Isolate,它是类似于线程(thread)但不共享内存的独立运行的worker,是一个独立的Dart程序执行环境。其实默认环境就是一个main isolate。

 isolate线程管理使用:在大批量调用渲染和网络请求等“高消耗”的操作下,Flutter Ui视图会造成卡顿现象,这时候要开启一个线程去跑这些操作。在使用isolate过程中注意使用完后关闭isolate并释放掉内存,否则会因内存占用大而导致应用奔溃。

基本使用:

library flutter_flutterisolate2;
import 'dart:async';
import 'dart:isolate';

main() async{
  //1.创建一个和isoLate环境交流的Port
 var receivePort = new ReceivePort();
 //2.创建一个隔离isolate并且提供用于回执的sendPort,receivePort.sendPort是一个给当前receive发消息的sendPort
 await Isolate.spawn(speak, receivePort.sendPort);
 //5.现在需要一个sendPort给isolate发送消息
  SendPort sendPort = await receivePort.first;
  //6. 利用sendPort给isoLate发送一个消息
  var resultFromIsoLate = await sendMessage2IsoLate(sendPort, 'apple');
  //8.打印来自于isolate的执行结果
  print(resultFromIsoLate);
  resultFromIsoLate = await sendMessage2IsoLate(sendPort, 'banana');
  print(resultFromIsoLate);
}

//3.创建用于在新isolate执行的函数speak
speak(SendPort sendPort) async{
  //4.现在提供给主isolate一个用于给子isolate发消息的sendPort
  var receivePort = new ReceivePort();
  sendPort.send(receivePort.sendPort);

  //单次读取
  // var msgFromMainIsoLate = await receivePort.first;
  // var msg = msgFromMainIsoLate[0];
  // SendPort replyTo = msgFromMainIsoLate[1];
  // replyTo.send("i like eat "+ msg);

  //7. 读取receivePort并且回传消息, receivePort看起来是一个可迭代器
  await for (var r in receivePort){
    var msg = r[0];
    SendPort replyTo = r[1];
    replyTo.send("i like eat "+ msg);
  }
}
//如果需要关闭,使用receivePort.close

//发送一条消息给isolate
Future sendMessage2IsoLate(SendPort sendPort, String msg)  {
  ReceivePort receivePort = ReceivePort();
  sendPort.send([msg, receivePort.sendPort]);
  return  receivePort.first;
}

flutter常用命令

which flutter 查看flutter安装目录

flutter upgrade 更新flutter到最新的兼容版本

flutter upgrade v2.2.3 升级到指定版本

flutter downgrade v2.2.3 回退到指定版本

flutter pub outdated 查询展示依赖包可更新的版本信息(判断哪些过时了的package依赖以及获取更新建议 )

flutter pub upgrade --dry-run 报告哪些依赖项将更改,但不要更改任何依赖项

flutter pub upgrade --major-versions 将软件包升级到最新的可解析版本,并更新 pubspec.yaml。或 flutter pub upgrade 将pubspec.yaml文件里列出的所有依赖更新到最新的兼容版本

flutter pub deps :查看使用的第三方插件的依赖关系

flutter run 运行项目
flutter clean 清空

flutter pub cache clean 清空本地缓存(即清空.pub-cache文件)
flutter --version 查看flutter版本信息

查看更详细的错误  flutter build apk  --release -vv 

android端打apk包  flutter build apk

android端打aab包  flutter build appbundle

新创建flutter项目如果想指定编程语言,比如iOS编程语言为Objective-C,Android的编程语言为Java
flutter create -i objc -a java 项目名

创建指定平台项目(注意最后面有个点)  flutter create --platforms=windows,macos,linux .

创建只生成android和iOS并指定编程语言(注意后面没有点):flutter create --platforms=android,ios -i objc -a java 项目名

创建flutter plugin项目flutter create --org com.example --template=plugin pluginname

创建flutter module项目:flutter create -t module --org com.example flutter_module

查看更详细信息 flutter doctor -v

q退出

r或R重新加载

clean项目:1.在android目录下用命令行   gradlew  clean   2.在最外层目录下用命令行    flutter clean

dart fix 对整个工程应用修复此工具有两个可用选项:

  • 若要查看可用更改的完整列表,请执行以下命令:

    dart fix --dry-run
    
  • 若要批量应用所有更改,请执行以下命令:

    dart fix --apply

flutter analyze --suggestions 以验证是否由于 Java SDK 和 Gradle 版本之间的不兼容,要了解修复此错误的不同方法,请查看我们的迁移指南:https://docs.flutter.dev/go/android-java-gradle-error

flutter build apk 命令支持以下参数:

  • --target=<path>:指定应用程序的入口点。默认情况下,Flutter 会查找 lib/main.dart 文件作为应用程序的入口点。
  • --obfuscate --split-debug-info=/<project-name>/<directory>: 开启Dart混淆,并将符号表导出到指定目录

可以在命令行中使用 flutter build apk --help 命令来获取完整的参数列表和说明
 

参考:Flutter —— Pub命令 - Belinda_sl - 博客园

日常笔记-Flutter build命令参数_吴唐人的博客-CSDN博客

dart基础

字符串的定义方式三个双引号声明:大段落的字符串可以换行

1、常量和变量

变量:可以用Object、var与dynamic声明的变量赋任何类型的值
Object声明的变量可以是任意类型
var:声明的变量在赋值的那一刻,决定了它是什么类型,是编译期的“语法糖”。
dynamic:被编译后,实际是一个 object 类型,在编译期间不进行任何的类型检查,而是在运行期进行类型检查,是运行期的“语法糖”。
常量:final。const.   

  • const 和 final 同样适用于自动类型推倒!
  • const 和 final 不能修饰 var 声明的变量!
  • const修饰符一开始就必须进行赋值,且值不可改变!
  • final修饰符一开始可以不赋值,但只能赋值一次,之后不可改变!
  • final是运行时常量,且是惰性初始化,即在运行时第一次使用前才初始化!
  • const是编译器常量,它的值在编译期就可以确定,编译时常量能够让代码运行的更高效。
  • final 表示引用不可变,而内容是可变的。const 表示内容和引用都不可变。

注:类的常量可以为 `final` 但是不能是 `const` 。如果 const 常量在类中,需要定义为`static const`静态常量

例子1
//正确,已经确定的值
const a = 1;
const b = a + 1;
//错误,final不能在编译时确定值,因此const也不能确定值
final a = 1;
const c = a + 1;

例子2
// list
var list = const [1,2,3];
list.add(4); // 运行时报错,const list 不可新增元素

var list =  final [1,2,3,4];//编译时报错,Error: Expected an identifier

例子3 final 表示引用不可变,而内容是可变的。const 表示内容和引用都不可变
final numbers = [1,2,3];
numbers.add(4); // 没有问题
numbers = [4,5,6]; // 编译时报错‘*The final variable can only be set once.’

const nums = [1,2,3];
nums.add(4);// 报错‘Cannot add to an unmodifiable list’ 
nums = [4,5,6];// 编译时报错‘Constant variables can’t be assigned a value’

例子4 final修饰符一开始可以不赋值,const修饰符一开始就必须进行赋值
const int c; //报错,const修饰必须一开始就必须赋值
final int a; //不直接赋值声明

2、Dart 中 if 等语句只支持 bool 类型,switch 支持 String 类型。

3、Dart 中 number 类型分为 int 和 double ,没有 float 类型。

数据.clamp(num lowerLimit, num upperLimit)可以限制数据的取值区间

 /// var result = 10.5.clamp(5, 10.0); // 10.0
  /// result = 0.75.clamp(5, 10.0); // 5
  /// result = (-10).clamp(-5, 5.0); // -5
  /// result = (-0.0).clamp(-5, 5.0); // -0.0
  num clamp(num lowerLimit, num upperLimit);
删除 Dart 中的小数点
方式一:
当您将 466.62 乘以 100 时,您的最终结果也将保持为两倍。这就是您看到 46662.0.0 的原因。所以你需要将它转换为 Int 值

double vDouble = 466.62 *100
String vString = vDouble.toInt().toString();

方式二:
String str = '466.62';

//split string
var arr = str.split('.');

print(arr[0]); //will print 466
print(are[1]); //will print 62

方式三:
参考链接:https://api.dart.dev/stable/2.8.4/dart-core/num/toStringAsFixed.html
1.toStringAsFixed(3);  // 1.000
(4321.12345678).toStringAsFixed(3);  // 4321.123
(4321.12345678).toStringAsFixed(5);  // 4321.12346
123456789012345.toStringAsFixed(3);  // 123456789012345.000
10000000000000000.toStringAsFixed(4); // 10000000000000000.0000
5.25.toStringAsFixed(0); // 5

4、运算符

 print('==除法${5 / 2}===取余操作${5 % 2}=====取整操作${5 ~/ 2}');
    //打印结果:==除法2.5===取余操作1=====取整操作2

#################(...)和(...?)使用start###################
Dart 2.3引入了散布运算符(...)和可识别null的散布运算符(...?),它们提供了一种插入多个简明格式的简洁方法元素添加到集合中.
例如,您可以使用传播运算符(...)将列表的所有元素插入另一个列表:

 var list = [1, 2, 3];
var list2 = [0, ...list];
assert(list2.length == 4);
 
如果扩展运算符右边的表达式可能为null,则可以使用可识别null的扩展运算符(...?)来避免出现异常:

var list;
var list2 = [0, ...?list];
assert(list2.length == 1);

如下widget布局里面使用:

// 很啰嗦
@override
Widget build(BuildContext context) {
  bool isTrue = true;
  return Scaffold(
    body: Column(
      children: [
        isTrue ? const Text('One') : Container(),
        isTrue ? const Text('Two') : Container(),
        isTrue ? const Text('Three') : Container(),
      ],
    ),
  );
}

// 才知道可以这样用 ... 符号
@override
Widget build(BuildContext context) {
  bool isTrue = true;
  return Scaffold(
    body: Column(
      children: [
        if(isTrue)...[
          const Text('One'),
          const Text('Two'),
          const Text('Three')
        ]
      ],
    ),
  );
}


#################(...)和(...?)使用end###################

### 类型判定操作符
| `as`   | 类型转换                       |
| `is`   | 如果对象是指定的类型返回 True  |
| `is!`  | 如果对象是指定的类型返回 False |

###赋值操作符
`=`、`+=`、`\=`、`*=`这些不必多说,还有一个 `??=` 操作符用来指定 值为 null 的变量的值
例子:

AA ?? "999"  ///表示如果 AA 为空,返回999AA ??= "999" ///表示如果 AA 为空,给 AA 设置成 999AA ~/999 ///AA 对于 999 整除

### 条件表达式
Dart 有两个特殊的操作符可以用来替代 [if-else](http://dart.goodev.org/guides/language/language-tour#if-and-else) 语句:

- `condition ? expr1 : expr2`

  如果 *condition* 是 true,执行 *expr1* (并返回执行的结果); 否则执行 *expr2* 并返回其结果。

- `expr1 ?? expr2`

  如果 *expr1* 不为null,返回其值; 否则执行 *expr2* 并返回其结果。


?.和??的组合使用 String content = response.data?.toString() ?? '';


###级联操作符:级联操作符 (`..`) 可以在同一个对象上 连续调用多个函数以及访问成员变量。

//Bad
var paint = Paint();
paint.color = Colors.black;
paint.strokeCap = StrokeCap.round;
paint.strokeWidth = 5.0;

//Good
var paint = Paint()
  ..color = Colors.black
  ..strokeCap = StrokeCap.round
  ..strokeWidth = 5.0;

###安全操作符:Dart提供了 `?.`操作符。左边的操作对象 如果 为 null 则返回 null

###流程控制

// if-else语句
if ((isValid1())) {
  ...
} else if (isValid2()) {
  ...
} else {
  ...
}

//for语句
for (var i = 0; i < 5; i++) {
  ...
}
for (var item in list) {
  item.do();
}

//while语句
while (!isValid()) {
  doSomething();
}

do {
  doSomething();
} while (!isValid());

//break & continue
var i = 0
while (true) {
  if (i > 2) break;
  print('$i');
  i++;
} // 输出 0,1,2

for (int i = 0; i < 10; i++) {
  if (i % 2 == 0) continue;
  print('$i');
}// 输出 1,3,5,7,9

//switch-case
var command = 'OPEN';
switch (command) {
  case 'CLOSED': 
  case 'PENDING': // 两个 case 共用逻辑
    executePending();
    break; // 必须有 break
  case 'APPROVED':
    executeApproved();
    break;
  case 'DENIED':
    executeDenied();
    break;
  case 'OPEN':
    executeOpen();
    break;
  default: // 当所有 case 都未命中时执行 default 逻辑
    executeUnknown();
}
  • break用于跳出循环。

  • continue用于跳过当前循环的剩余代码。

  • Dart 中的 switch-case 支持 String、int、枚举的比较。

5、作用域:

Dart 没有关键词 public 、private 等修饰符,_ 下横向直接代表 private ,但是有 @protected 注解 。

6、getter setter 重写

Dart 中所有的基础类型、类等都继承 Object ,默认值是 NULL, 自带 getter 和 setter ,而如果是 final 或者 const 的话,那么它只有一个 getter 方法,Object 都支持 getter、setter 重写:

 @override  Size get preferredSize {    return Size.fromHeight(kTabHeight + indicatorWeight);  }

7、Assert(断言)

assert 是一种用于调试的语句,用于检查我们的代码是否符合预期。在Flutter语法中,经常使用assert进行断言,通常用于参数检查、测试代码、调试代码等场景。在使用时需要注意:

  • assert只在开发环境有效,在发布环境下将被忽略
  • assert的条件应该是纯函数,不应有任何副作用
  • assert的错误信息因该尽可能清晰明了,以便于定位问题
  • assert的条件不应该包含任何副作用,否则可能会影响程序的正确性

如下参数检查示例:

说明:先使用assert检查a是否为null,如果为null,程序将终止执行,并输出错误信息“a不能为null”。如果a不为null,程序将继续执行,输出“a = $a"。

void printInteger(int a){
    assert(a != null,'a不能为null');
    print('a=$a');
}

8、关键字   

dart3.0新特性:类修饰符使 API 作者能够仅支持一些特定的功能,而默认值保持不变,例如:abstractbase 、finalinterfacesealedmixin(参考:Dart 3 发布,快来看看有什么更新吧 · GitBook

mixins的中文意思是混入,就是在类中混入其他功能。mixins弥补了接口和继承的不足,继承只能单继承,而接口无法复用实现,mixins却可以多混入并且能利用到混入类的具体实现

在Dart中可以使用mixins实现类似多继承的功能

1.mixin不能拥有构造函数以及extends子句和with子句,但支持定义抽象成员,和支持 implements 子句。

2.mixin 可以拥有 on 子句,但是常规类 class 不能用 on 子句,就连mixin class 也不能用 on 子句

3.混入多个mixin时:

(1)当前类没有重写mixin的方法,则会使用距离with最远的那个mixin的方法。

(2)当前类重写了mixin的方法,则会调用当前类重写的方法。

因为mixins使用的条件,随着Dart版本一直在变,这里讲的是Dart2.x中使用mixins的条件:
  1、作为mixins的类只能继承自Object,不能继承其他类
  2、作为mixins的类不能有构造函数
  3、一个类可以mixins多个mixins类
  4、mixins绝不是继承,也不是接口,而是一种全新的特性
  5.mixins的类型就是其超类的子类型

with关键字,后跟一个或多个mixin或者普通类。

on关键字:要指定只有某些特定类型可以使用mixin,使用on来指定所需的超类或者mixin,可以让你编写的mixin可以调用它未定义的方法,并且可以使用super像继承一样调用父类方法。
参考:Dart 的新特性:mixin
extension关键字(扩展):https://www.jianshu.com/p/f9d00020b3a5

==================Dart中扩展extension关键字   start============================

Dart2.7 扩展对枚举的支持

例子:

enum Month { jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec }

extension MonthExtension on Month {
  int get value => this.index + 1;
  String get cn =>
      [
        "一",
        "二",
        "三",
        "四",
        "五",
        "六",
        "七",
        "八",
        "九",
        "十",
        "十一",
        "十二"
      ][this.index] +
      "月";

  String get eng => [
        "Jan",
        "Feb",
        "Mar",
        "Apr",
        "May",
        "Jun",
        "Jul",
        "Aug",
        "Sep",
        "Oct",
        "Nov",
        "Dec"
      ][this.index];
}

使用:
   final m = Month.jan;
    print('value: ${m.value},cn: ${m.cn},eng: ${m.eng}');
打印结果:value: 1,cn: 一月,eng: Jan


==================Dart中扩展extension关键字   end============================

operator关键字:它和运算符(如=)一起使用,表示一个 运算符重载函数,在理解时可将operator和运算符(如operator=)视为一个函数名。

//关键词operator表示重载运算符,这里重载的+,即重新定义加号运算符。
class BaseDart {
    double x;
    double y;
    
    BaseDart(this.x, this.y);
    operator +(p) => BaseDart(this.x + p.x, this.y + p.y);
}

//具体使用
var p1 = BaseDart(1,2);
var p2 = BaseDart(3,4);
var p3 = p1 + p2; // BaseDart(4,6)

9、类、接口、继承

Dart 中没有接口,类都可以作为接口,把某个类当做接口实现时,只需要使用 implements,然后复写父类方法即可。

Dart中抽象类: Dart抽象类主要用于定义标准,子类可以继承抽象类,也可以实现抽象类接口。
1、抽象类通过abstract 关键字来定义
 2、Dart中的抽象方法不能用abstract声明,Dart中没有方法体的方法我们称为抽象方法。
 3、如果子类继承抽象类必须得实现里面的抽象方法
 4、如果把抽象类当做接口实现的话必须得实现抽象类里面定义的所有属性和方法。
 5、抽象类抽象类无法直接创建实例,但是可以通过实现工厂构造方法来间接实现抽象类的实例化!如Map     https://juejin.cn/post/6844904182613278733
extends抽象类 和 implements的区别:
 1、如果要复用抽象类里面的方法,并且要用抽象方法约束子类的话我们就用extends继承抽象类,子类里面不一定需要重写非抽象方法
 2、如果只是把抽象类当做标准的话我们就用implements实现抽象类,子类必须得实现类里面定义的所有属性和方法

Datr中的多态:
    子类的实例赋值给父类的引用。 
    多态就是父类定义一个方法不去实现,让继承他的子类去实现,每个子类有不同的表现。

dart中的接口:
     普通类或抽象类都可以作为接口被实现。建议使用抽象类定义接口。
     同样使用implements关键字进行实现。
     实现该接口,必须得实现类里面定义的所有属性和方法

继承(关键字 extends)单继承
混入 mixins (关键字 with)多混入
接口实现(关键字 implements)
这三种关系可以同时存在,但是有前后顺序:
extends -> mixins -> implements
extens在前,mixins在中间,implements最后,接下来看具体的例子。
参考链接:https://www.jianshu.com/p/dd11429ba80e

10.Record 和 Patterns

Record:详细可以参考官方文档:dart.dev/language/re… 或者之前相关的中文资料: juejin.cn/post/719474…

参考:Google I/O 2023 - Dart 3 正式版发布,快来看看有什么更新吧 - 掘金

11.dart是值传递还是引用传递

dart是值传递,每次调用函数,传递过去的都是对象的内存地址,而不是这个对象的复制。

是值传递。 当一个实例对象作为参数被传递到方法中时,参数的值就是该对象的引用的一个副本。指向同一个对象,对象的内容可以在被调用的方法内改变,但对象的引用(不是引用的副本 ) 是永远不会改变的
class Test {
  int value = 1;
  Test(int newValue) {
    this.value = newValue;
  }
}

setValue(Test s) {
  s.value = 100;
}

setValue2(int s) {
  s += 100;
}

void main() {
  Test a = new Test(5);
  print('a的初始值为:${a.value} ===${a.hashCode}');
  setValue(a);
  print("修改后a的值为: ${a.value} ===${a.hashCode}");

  int b = 5;
  print("b的初始值为:$b");
  setValue2(b);
  print('修改后b的值为: $b');
}

打印:
a的初始值为:5 ===1018464526
修改后a的值为: 100 ===1018464526
b的初始值为:5
修改后b的值为: 5

12.方法:可选参数 & 命名参数

对于非必要的参数,可将其声明为可选参数,调用方式时,可不传实参。


bool isValid(int value1, [int value2 = 2, int value3 = 3]){...}

var ret = isValid(1) // 不传任何可选参数
var ret2 = isValid(1,2) // 传入1个可选参数
var ret3 = isValid(1,2,3) // 传入2个可选参数

//如果上面只想给value1,value3 传参,上面就没法做到。可通过{}实现。
bool isValid(int value1, {int value2 = 2, int value3 = 3}) {...}//可选命名参数

var ret = isValid(1, value3 : 3)

bool isValid(int value1, {int value2, required int value3}) {...}
  • 可选参数用[]包裹,在声明方法时需要为可选参数提供默认值,以便在未提供实参时使用。

  • 可选参数用{}包裹,这些参数称为可选命名参数。

  • 关键词required指定在众多可选命名参数中哪些是必选的。

[Flutter] Dart基础语法

flutter相关:

1、Flutter 和 React Native 不同主要在于 Flutter UI是直接通过 skia 渲染的 ,而 React Native 是将 js 中的控件转化为原生控件,通过原生去渲染的 。

2、Flutter 中存在 Widget 、 Element 、RenderObject 、Layer 四棵树。详情参考:Flutter的核心渲染模块三棵树_ailinghao的博客-CSDN博客_flutter 三棵树

3、Flutter 中默认主要通过 runtimeType 和 key 判断更新:

static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }
}

4、Flutter 中 InheritedWidget 一般用于状态共享

Theme 、Localizations 、 MediaQuery 等,都是通过它实现共享状态,这样我们可以通过 context 去获取共享的状态,比如 ThemeData theme = Theme.of(context);

在 Element 的 inheritFromWidgetOfExactType 方法实现里,有一个 Map<Type, InheritedElement> _inheritedWidgets 的对象。

_inheritedWidgets 一般情况下是空的,只有当父控件是 InheritedWidget或者本身是 InheritedWidgets 时才会有被初始化,而当父控件是 InheritedWidget 时,这个 Map 会被一级一级往下传递与合并 。

所以当我们通过 context 调用 inheritFromWidgetOfExactType 时,就可以往上查找到父控件的 Widget 。

5、flutter中的生命周期详情参考:flutter中state生命周期和app生命周期_ailinghao的博客-CSDN博客

6、Flutter 中 runApp 启动入口:

其实是一个 WidgetsFlutterBinding ,它主要是通过 BindingBase 的子类 GestureBinding 、ServicesBinding 、 SchedulerBinding 、PaintingBinding 、SemanticsBinding 、 RendererBinding 、WidgetsBinding 等,通过 mixins 的组合而成的。

7、Flutter 中的 Dart 的线程:

是以事件循环和消息队列的形式存在,包含两个任务队列,一个是 microtask 内部队列,一个是 event 外部队列,而 microtask 的优先级又高于 event 。

默认的在 Dart 中,如 点击、滑动、IO、绘制事件 等事件都属于 event 外部队列,microtask 内部队列主要是由 Dart 内部产生,而 Stream 中的执行异步的模式是 scheduleMicrotask 了。

因为 microtask 的优先级又高于 event, 同时会阻塞event 队列,所以如果 microtask 太多就可能会对触摸、绘制等外部事件造成阻塞卡顿哦。

Flutter 中存在四大线程,分别为 UI RunnerGPU RunnerIO Runner, Platform Runner(原生主线程) ,同时在 Flutter 中可以通过 isolate 或者 compute 执行真正的跨线程异步操作。

8、Flutter 的 Debug 下是 JIT 模式,release下是AOT模式。

9、PlatformView

Flutter 中通过 PlatformView 可以嵌套原生 View 到 Flutter UI 中,这里面其实是使用了 Presentation + VirtualDisplay + Surface 等实现的,大致原理就是:

使用了类似副屏显示的技术,VirtualDisplay 类代表一个虚拟显示器,调用 DisplayManager 的 createVirtualDisplay() 方法,将虚拟显示器的内容渲染在一个 Surface 控件上,然后将 Surface 的 id 通知给 Dart,让 engine 绘制时,在内存中找到对应的 Surface 画面内存数据,然后绘制出来。  实时控件截图渲染显示技术。

10、Platform Channel

Flutter 中可以通过 Platform Channel 让 Dart 代码和原生代码通信的:

  • BasicMessageChannel :用于传递字符串和半结构化的信息。

  • MethodChannel :用于传递方法调用(method invocation)。

  • EventChanne l: 用于数据流(event streams)的通信。

同时 Platform Channel 并非是线程安全的 ,更多详细可查阅闲鱼技术的 《深入理解Flutter Platform Channel》

其中基础数据类型映射如下:

11、Android 启动页

Android 中 Flutter 默认启动时会在 FlutterActivityDelegate.java 中读取 AndroidManifset.xml 内 meta-data 标签,其中 io.flutter.app.android.SplashScreenUntilFirstFrame 标志位如果为 ture ,就会启动 Splash 画面效果(类似IOS的启动页面)。

启动时原生代码会读取 android.R.attr.windowBackground 得到指定的 Drawable , 用于显示启动闪屏效果,之后并且通过 flutterView.addFirstFrameListener,在onFirstFrame中移除闪屏。

12.flutter中key

直接子类主要有:LocalKeyGlobalKey

LocalKey 是增量算法的核心,决定哪个Element要保留,哪个Element要删除。以下是LocalKey的三个子类。

  • ValueKey:以值作为参数(数字、字符串);
  • ObjectKey:以对象作为参数
  • UniqueKey:创建唯一标识;通常用于动画的过渡当中

PageStorageKey

当你有一个滑动列表,你通过某一个 Item 跳转到了一个新的页面,当你返回之前的列表页面时,你发现滑动的距离回到了顶部。这时候,给 Sliver 一个 PageStorageKey!它将能够保持 Sliver 的滚动状态。

GlobalKey:用来帮助我们确定某一个WidgetElement或者State,来访问其信息;它在整个程序中是唯一的

用途1:获取配置、状态以及组件位置尺寸等信息

(1)_globalKey.currentWidget:获取当前组件的配置信息(存在widget树中)

(2)_globalKey.currentState:获取当前组件的状态信息(存在Element树中)

(3)_globalKey.currentContext:获取当前组件的大小以及位置信息

//获取控件的大小和位置
RenderBox renderBoxRed = _globalKey.currentContext.findRenderObject();
renderBoxRed.size;//大小
renderBoxRed.localToGlobal(Offset.zero);//位置

用途2:实现控件的局部刷新

将需要单独刷新的widget从复杂的布局中抽离出去,然后通过传GlobalKey引用,这样就可以通过GlobalKey实现跨组件的刷新了。

GlobalKey 与 ValueKey 的区别?

GlobalKey 是全局使用的 key,在跨小部件的场景时,你就可以使用它去刷新其它小部件。但,它是很昂贵的,如果你不需要访问 BuildContext、Element 和 State,应该尽量使用 LocalKey。

而 ValueKey 和 ObjectKey、UniqueKey 一样都归属于局部使用的 LocalKey,无法跨容器使用,ValueKey 比较的是 Widget 的值,而 ObjectKey 比较的是对象的 key,UniqueKey 则每次都会生成一个不同的值。

总结:

  • Flutter里的key分为两类,一类是LocalKey,实现类有ValueKey、ObjectKey、UniqueKey;一类是GlobalKey,实现类有LabeledGlobalKey、GlobalObjectKey。
  • Key是所有keys类的基类,其默认实现是String类型的ValueKey。
  • 相同parent下的key是不能一样的,比如不能再同一个page里使用VlaueKey(1),但是不同parent下是可以存在一样的key的,比如在两个界面里都使用ValueKey(1)。
  • UniqueKey只和自己相等,其并没有重写==和hashCode方法,也没有const修饰的构造函数。当调用Element的updateChild方法时,Widget.canUpdate肯定返回false,所以如果你想让widget每次都去创建新的element而不复用old element,那么就给此widget使用UniqueKey。
  • GlobalKey的默认实现是LabeledGlobalKey,其没有实现==和hashCode方法,也没有const修饰的构造函数,所以肯定能保证其全局唯一性。
  • 所有的GlobalKey都保存在BuildOwner类中,其内部维护了一个map用来保存GlobalKey与其对应的Element。
  • GlobalObjectKey是特殊的GlobalKey,内部维护了一个Object属性,并实现了==和hashCode方法,通过判断runtimeType以及Object属性是否一致来判断两个GlobalObjectKey是否相等。
  • 使用GlobalObjectKey时,为了保证GlobalObjectKey的全局唯一性,最佳实践是继承自GlobalObjectKey实现一个private的内部类,可以有效避免多人开发时可能造成的GlobalObjectKey冲突的问题。

参考:Flutter(三十七)-GlobalKey的使用

Flutter中的Key_flutter key_一叶飘舟的博客-CSDN博客

Flutter中的Key详解 - 知乎

https://juejin.cn/post/7236413212094021690
 

ffi相关

ffi基础学习:09、Flutter FFI Dart Native API_eieihihi的专栏-CSDN博客

 代码例子:   NativeDemo: flutter ffi 调用demo

Flutter FFI实践_落叶挽歌的博客-CSDN博客

flutter 代码仓库_flutter使用C代码库—IOS篇_公子大白0m0-华为云开发者联盟

网站链接

flutter并发请求如何设置最大并发量

https://uutool.cn/id-photo/ 在线证件照换底色工具

windows上搭建iOS开发环境:

http://t.csdn.cn/fukyL

flutter windows搭建ios开发环境_年轻的古尔丹的博客-CSDN博客

首页 | 猫哥在线课堂


图片压缩  TinyPNG – Compress WebP, PNG and JPEG images intelligently

GSY的文章在线阅读地址:前言 · GitBook

flutter开发模版:Flutter项目开发模版,开箱即用_flutter 模板-CSDN博客

 flutter线上异常用bugly监控 - 简书  flutter开发模版demo :https:/gitee.com/kuaipai/jd_flutter

Flutter 超完整的开源项目 https://github.com/CarGuo/gsy_github_app_flutter

咸鱼技术文章: https://www.yuque.com/xytech/flutter

Flutter Dio 封装:https://juejin.cn/post/7020451065371820062

Flutter 状态管理Provider:https://juejin.cn/post/6844903864852807694

Flutter 文章集合:https://juejin.cn/column/6960546078202527774

Flutter实战:https://book.flutterchina.club/chapter14/

Flutter中文文档:https://flutter.cn/docs

Flutter收藏集:https://juejin.cn/collection/7130691435556241421

Flutter Api:https://api.flutter-io.cn/

Flutter Wiki:https://github.com/flutter/flutter/wiki

这个人的文章值得一看,就是很古老:https://gityuan.com/archive/

Flutter Engine编译与调试:https://juejin.cn/post/7215854856731508797

【Flutter引擎底层绘制原理及实例演示(含SceneBuilder、PictureRecorder、EngineLayer等)】 https://www.bilibili.com/video/BV1kS4y1c7TU/?share_source=copy_web&vd_source=072a7da0342dc62fb01a5d394c617c99


今天是第几周,星期几,今天是2022年的第几周-万年历

dart在线编辑器需要科学上网:DartPad

不需要科学上网:DartPad - 由郑州玩码科技有限公司维护

dartpad使用:【Flutter】DartPad 终极在线Dart编程环境

flutter与前端比较 :前端 Flutter 入门指南

flutter 例子:Flutter 项目开发指导 从基础入门到精通使用目录_早起的年轻人的博客-CSDN博客

慕课网写的比较好的文章  老菜和尚的 文章

对应的列子代码:https://github.com/ACE-YANGCE/FlutterApp

在线json转dart:JSON 转Dart - CrazyCodeBoy的技术博客官网|CrazyCodeBoy|Devio|专注移动技术开发(Android&IOS)、Flutter开发、Flutter教程、React Native开发、React Native教程、React Native博客

10月Flutter最新学习资料汇总 - 贾鹏辉的技术博客官网|CrazyCodeBoy|Devio|专注移动技术开发(Android&IOS)、Flutter开发、Flutter教程、React Native开发、React Native教程、React Native博客

jFlutter 123: 图解简易 GroupList 二级分组列表 - 简书

可以用来当做API控件功能查询使用的:

 Flutter 集录指南FlutterUnit. https://github.com/toly1994328/FlutterUnit

https://github.com/xuyisheng/flutter_dojo

flutter例子 

更多实例参考:flutter-do 、 awesome-flutter(这个是国外大神维护的)

ui组件库: bruno getwidget 、 fsuper 、国内的 fluttercandies 

https://juejin.cn/post/7147437014026043399    这个博客中有对各种功能的三方库推荐

功能型例子项目:GitHub - yixiaolunhui/flutter_xy: Android小样公众号对应Flutter的demo集合

猫哥的项目:GitHub - ducafecat/flutter_develop_tips: flutter技巧 https://ducafecat.com/blog

一个大佬高度封装的抖音 Flutter 3可运行:https://github.com/mjl0602/flutter_tiktok.git

比较全的ui框架及开源项目:Flutter Awesome

flutter例子  https://github.com/syncfusion/flutter-examples

飞猪团队 :https://github.com/Fliggy-Mobile

飞猪团队陈冰:https://github.com/chenBingX

flutter个人技术文章    Nayuta 的个人主页 - 文章 - 掘金

 (Flutter Widgets 大全)老孟博客(在线阅读地址):http://laomengit.com/flutter/widgets/widgets_structure.html

老孟  https://github.com/LaoMengFlutter/flutter-do

https://gitcode.com/781238222/flutter-do/

 Flutter | 老孟

https://github.com/smartbackme

Flutter核心技术与实战电子书:前言 · Flutter核心技术与实战 · 看云

flutter实战电子书。 https://book.flutterchina.club/
flutter桌面支持官方网址:https://flutter.dev/desktop

flutter中文开发者网站   flutter.cn

flutter中文网:  https://flutterchina.club/flutter-for-android/

国际化Flutter App - Flutter中文网
dart中文网:    https://www.dartcn.com/guides/language/language-tour
dart学习:http://dart.goodev.org/guides/language/language-tour
技术胖的博客地址:https://jspang.com
flutter库:https://pub.dartlang.org/     https://pub.flutter-io.cn/
将 Flutter 代码部署到 Web 端 https://blog.csdn.net/weixin_39901213/article/details/111213201
wanandroid:https://www.wanandroid.com/
codekk:http://p.codekk.com/
技术周报:https://www.androidweekly.cn/
在线作图:https://www.processon.com/

前端vant控件  https://youzan.github.io/vant/#/zh-CN/skeleton

接口测试:https://getman.cn/

leancloud后端云支持  https://www.leancloud.cn
咸鱼技术简书 https://www.jianshu.com/u/cf5c0e4b1111

唯鹿github地址: https://github.com/simplezhli

图片类
阿里巴巴矢量图标库
materialdesign。  https://material.io/resources/icons/?style=sharp
AndroidAssetStudio    http://romannurik.github.io/AndroidAssetStudio/
图标工厂   https://icon.wuruihong.com/ 
hotpot    https://hotpot.ai/

百度地图拾取坐标系统  http://api.map.baidu.com/lbsapi/getpoint/index.html


工具
typora    打开md格式的文件

设计模式https://www.runoob.com/design-pattern/factory-pattern.html


开眼快创Flutter实践:http://w4lle.com/2021/04/12/kidea-flutter/

开源项目
停车场系统。https://github.com/981011512/--

flutter项目

getx项目实战https://juejin.cn/post/6913917533997891591

仿小米商城项目:GitHub - deepindo/DoXiaoMiMall: 使用flutter编写的小米商城app项目

wechat_flutter Flutter版本微信,一个优秀的Flutter即时通讯IM开源库   https://github.com/fluttercandies/wechat_flutter

GitHub - fmtjava/Flutter_Eyepetizer: Flutter + 组件化实现的一款精美的仿开眼视频(Eyepetizer )跨平台App,适合入门,快速掌握Dart语言以及上手flutter开发(提供Kotlin、React Native、小程序版本 😁 ),希望和大家共同成长,喜欢的话,欢迎start或fork!

一款开源习惯打卡APP,动画为主:
Flutter 开源项目 FlutterApp   https://github.com/shichunlei/flutter_app
Flutter 开源项目 https://github.com/iotjin/jh_flutter_demo
Flutter 练习项目(包括集成测试、可访问性测试)。内含完整UI设计图,更贴近真实项目的练习https://github.com/simplezhli/flutter_deer
flutter_deer 问题总结 https://segmentfault.com/a/1190000020329714?utm_source=tag-newest

Flutter使用的一些骚操作。https://github.com/CNAD666/flutter_use

Flutter 三方库使用和技术文章 https://github.com/jiang111/flutter_code

基于Google Flutter的WanAndroid客户端https://github.com/Sky24n/flutter_wanandroid
flutter项目。 https://github.com/Sky24n/Moss

仿阿里 Flutter-go v1.0 效果,并使用 玩Android 站点提供的 API 实现的 Flutter App
博客说明:https://longyi.blog.csdn.net/article/details/107045022
源码链接 https://github.com/YGragon/Flutter-WanAndroid


flutter开源项目
https://mp.weixin.qq.com/s/fQNK_4Lm4_DcCueA8V01Ug
https://github.com/cnad666/flutter_use
git clone git@github.com:cnad666/flutter_use.git
git clone git@github.com:toly1994328/flutterunit.git
git clone https://github.com/sunyongsheng/Allpass.git
git clone https://gitee.com/shizidada/moose_app
git clone git@github.com:ertcs/smart_home.git

Flutter仿京东客户端开源项目 https://www.jianshu.com/p/60588d6a076b
Flutter仿京东APP  https://github.com/DiscoverForever/learn_flutter
flutter工具类https://github.com/Cheney2006/flutter_utils/tree/master/lib (相关博客https://www.cnblogs.com/maqingyuan/p/13656283.html)
flutter项目 https://www.jianshu.com/p/56b9cccf29f0
Flutter 仿携程网App  https://github.com/wkl007/flutter_trip

flutterapp开发常用的轮子分享 https://github.com/826327700/flutter_plugins_demo 

Flutter面试题汇总 Flutter面试题汇总 - 简书

优质文章:

jimi的flutter优质博客:Jimi

Flutter学习笔记&学习资料推荐_pan.flutter实战第二版-CSDN博客

积累的问题

1.通过给下个页面传对象,在下个页面修改对象里面的字段,修改当前对象里的字段,需要监听下路由(with RouteAware)才能在下个页面修改当前页面的字段

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值