写在前面

作为一个初学者,好像什么都不懂哦!

但是,我感觉Widget已经深深地刻入了我的心中,因为它在Flutter代码中真的是随处可见!

我百度了一番,翻译是小装置,真的是很可爱了!

所以,庞大的Flutter就好像一个变形金刚,是组装起来的。


说正经的,Flutter中的Widget的概念是非常广泛的,它不仅是ui元素,也可以是一些功能性的组件:比如手势检测的GestrueDetector,用于APP主题数据传递的Theme等等。

万物皆是小装置,今天就和大家一起学习一下小装置Widget

Widget接口,代码不愁

为了更好地认识Widget接口,我们从代码来认识一下接口吧

@immutable
abstract class Widget extends DiagnosticableTree {const Widget({ this.key });final Key? key;@protected@factoryElement createElement();@overrideString toStringShort() {final String type = objectRuntimeType(this, 'Widget');return key == null ? type : '$type-$key';}@overridevoid debugFillProperties(DiagnosticPropertiesBuilder properties) {super.debugFillProperties(properties);properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense;}@override@nonVirtualbool operator ==(Object other) => super == other;@override@nonVirtualint get hashCode => super.hashCode;static bool canUpdate(Widget oldWidget, Widget newWidget) {return oldWidget.runtimeType == newWidget.runtimeType&& oldWidget.key == newWidget.key;}...
}
  • @immutable代表Widget是不可变的,这会限制Widget中定义的属性必须是不可变的(final)

    • 为什么不允许定义的属性变化呢?

      • 属性发生变化会重新构建Widget树,所以Widget中定义的属性必须是final
  • Widget类继承自DiagnosticableTree,它是诊断树

    • 它可以提供调试信息
  • Key这个属性可以类比

    • React/Vue中list中的key,Flutter中也有类似diff算法的东西
    • 决定下一次build时是否复用widget
  • createElement()— 很少用到,不解释了

    • 不是,每次都会用,但不用去重写
  • debugFillProperties(...)复写父类的方法,主要是设置诊断树的一些特性
  • canUpdate(...)是一个静态方法,它主要用于在widget树重新build时复用widget

    • 这个感觉和key有些关系

  • 另外,Widget类本身是一个抽象类

    • 说实话和c++感觉很像呢
    • 最核心的是定义了createElement()接口
  • 我们一般来继承StatelessWidgetStatefulWidget来间接继承widget

Flutter有四棵树,你需要记住

  • 简单介绍一下布局,绘制的处理流程:

    • 1.根据 Widget树生成一个Element树,Element树中的节点都继承自Element
    • 2.根据Element树生成Render树,渲染树中的节点都继承自RenderObject
    • 3.根据渲染树生成Layer树,然后上屏显示,Layer树中的节点都继承自Layer

  • 如果Container设置了背景色,Container内部会创建一个新的ColoredBox来填充背景
  • image内部会通过RawImage来渲染图片
  • text内部会通过RichText来渲染文本

  • 个人感觉,Element树就是一个中间代理

StatelessWidget

1.简介

无状态装置,听起来没什么实力啊!让我们康康吧!

  • 相对于有状态装置是相对比较简单的,它继承自widget类,重写了createElement()方法:
@override
StatelessElement createElement() => StatelessElement(this);
  • StatelessWidget用于不需要维护状态的场景,它通常在build方法中通过嵌套其它 widget 来构建UI,康一个例子:
class Echo extends StatelessWidget  {const Echo({Key? key,required this.text,this.backgroundColor = Colors.grey, //默认为灰色}):super(key:key);final String text;final Color backgroundColor;@overrideWidget build(BuildContext context) {return Center(child: Container(color: backgroundColor,child: Text(text,style: const TextStyle(fontSize: 30,color:Colors.black, // 默认red,太吊了decoration:TextDecoration.none // 默认yellow下划线,太吊了
),),),);}
}
  • 很简单可以看出来,这个组件就是可以输入背景色和文本,然后是居中显示的
  • 我们来使用它
class MyApp extends StatelessWidget {const MyApp({Key? key}) : super(key: key);// This widget is the root of your application.@overrideWidget build(BuildContext context) {return MaterialApp(title: 'Flutter Demo',theme: ThemeData(primarySwatch: Colors.blue,),// home: const MyHomePage(title: 'Flutter Demo Home Page'),home: const Echo(text: "hello world"));}
}
  • 2.Context

    • build方法有一个context参数,它是BuildContext类的一个实例

      • 表示当前widgetwidget树中的上下文

      • 每一个widget都会对应一个contex对象

      • 实际上,context是当前widgetwidget树中位置中执行相关操作的一个泳道

        • 有点像boe,哈哈
    • 我们来看一个子widget获取父widget的代码例子:

      • 这里例子单独跑好像还有点问题
      • StatefulWidget-2.a有成功的例子
class ContextRoute extends StatelessWidget  {@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("Context测试"),),body: Container(child: Builder(builder: (context) {// 在 widget 树中向上查找最近的父级`Scaffold`  widget Scaffold scaffold = context.findAncestorWidgetOfExactType<Scaffold>();// 直接返回 AppBar的title, 此处实际上是Text("Context测试")return (scaffold.appBar as AppBar).title;}),),);}
}

StatefulWidget

  • StatelessWidget一样,StatefulWidget也是继承自widget类,并重写了createElement()方法,不同的是返回的Element对象并不相同;另外StatefulWidget类中添加了一个新的接口createState()
  • 从下面的代码来认识一下
abstract class StatefulWidget extends Widget {const StatefulWidget({ Key key }) : super(key: key);@overrideStatefulElement createElement() => StatefulElement(this);@protectedState createState();
}
  • StatefulElement间接继承自Element类,与StatefulWidget相对应

    • StatefulElement可能会多次调用createState()来创建状态(State)对象
  • createState()用于创建和StatefulWidget相关的状态,它在StatefulWidget的生命周期中会被多次调用

    • 怎么理解这个多次调用呢?

      • 感觉就是这里写好了一个组件
      • 你会多次使用这个组件
      • 类似类组件和函数组件,就是你创建了一个组件实例
      • 它变成了一个对象
  • 本质上,当一个 StatefulWidget 同时插入到 widget 树的多个位置时,Flutter 框架就会调用该方法为每一个位置生成一个独立的State实例,其实,本质上就是一个StatefulElement对应一个State实例。

1.State

a.简介

一个StatefulWidget类会对应一个State类,State表示与其对应的StatefulWidget要维护的状态,State中保存的状态满足:

  • widget构建时可以被同步读取
  • widget生命周期中可以被改变,当State被改变时,可以手动调用其setState()方法通知Flutter框架状态发生改变,Flutter框架在收到消息后,会重新调用其build方法重新构建widget树,从而达到更新ui的目的

    • 这里的setState()和react神似好吧!

State中有两个常用属性:

  • widget它表示与该State实例关联的widget实例,由Flutter框架动态设置

    • 不用管,自动的

    • 但是这种关联并非永久的

      • 因为ui树上的某节点在重构的时候可能发生变化
      • State实例只会在第一次插入到树中时被创建
      • 当在重新构建时,如果widget被修改了,框架会自动更新,咱别管就行了,wryyyyy!
  • contextBuildContext

b.State生命周期

  • 对比React/Vue,生命周期还是比较重要的

    • 比如Vue3中的setup()代表实例创建前和创建后
    • 比如React中useEffect()代表了创建后,检测到更新,销毁时生命周期
    • 当然,上面两种是React/Vue中比较常用到的生命周期

下面就让我们康康State的生命周期吧!

  • 以计数器为例,实现一个计数器CounterWidget组件

    • 点击它可以使计数器加1
    • 要保存计数器的数值状态
    • 我们要使用到StatefulWidget
class CounterWidget extends StatefulWidget {const CounterWidget({Key? key, this.initValue = 0});final int initValue;@override_CounterWidgetState createState() => _CounterWidgetState();
}
  • CounterWidget接收一个initValue整型参数,它表示计数器的初始值。
  • 下面康康State的代码
class _CounterWidgetState extends State<CounterWidget> {int _counter = 0;@overridevoid initState() {super.initState();//初始化状态_counter = widget.initValue;print("initState");}@overrideWidget build(BuildContext context) {print("build");return Scaffold(body: Center(child: TextButton(child: Text('$_counter'),//点击后计数器自增onPressed: () => setState(() => ++_counter,),),),);}@overridevoid didUpdateWidget(CounterWidget oldWidget) {super.didUpdateWidget(oldWidget);print("didUpdateWidget ");}@overridevoid deactivate() {super.deactivate();print("deactivate");}@overridevoid dispose() {super.dispose();print("dispose");}@overridevoid reassemble() {super.reassemble();print("reassemble");}@overridevoid didChangeDependencies() {super.didChangeDependencies();print("didChangeDependencies");}
}
  • 接下来,我们创建一个新路由,在新路由中,我们只显示一个CounterWidget

    • 使用Navigator.pushMatericalPageRoute可以实现路由跳转
class StateLifecycleTest extends StatelessWidget {const StateLifecycleTest({Key? key}) : super(key: key);@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('生命周期'),),body:Center(child: CounterWidget(),),);}
}

  • 我们运行应用打开该路由页面,新路由中央就会出现一个数字0,然后控制台日志输出

    • 插入到widget树首先调用initState方法
    • 然后是didChangeDependencies方法
    • 最后是build
  • 然后我们点击热重载,控制台日志如下:

    • 没反应点一下屏幕中间的数字出发更新
    • 就会出现红框的print
    • 热更新会依次触发reassemble,disUpdateWidget方法,最后是build
  • 接下来,我们在widget树中移除CounterWidget,将build方法更改
Widget build(BuildContext context) {//移除计数器 //return CounterWidget ();//随便返回一个Text()return Text("41");
}
  • 然后我们点击热重载,控制台日志应该如下:

    • 但是刷新把日志吃掉了
    • 但是我们要理解是这样
    • 这就是生命周期
reassemble
deactive
dispose
  • 弃我去者,昨日之日不可留。不康也罢!
  • 后面发现切换路由会有后面两行!


c.详细看看各个回调函数

  • initStatewidget第一次插入到widget树的时候会被调用

    • 类似setup(),useEffect(()=>{},[])

    • 不能在该回调中调用BuildContext.dependOnInheritedWidgetOfExactType(该方法用于在 widget 树上获取离当前 widget 最近的一个父级InheritedWidget

      • 原因是在初始化完成后, widget 树中的InheritFrom widget也可能会发生变化,所以正确的做法应该在在build()方法或didChangeDependencies()中调用它。
  • didChangeDependencies():当State对象的依赖发生变化时会被调用;

    • 例如:在之前build() 中包含了一个InheritedWidget,然后在之后的build()Inherited widget发生了变化
    • 那么此时InheritedWidget的子 widget 的didChangeDependencies()回调都会被调用。
    • 典型的场景是当系统语言 Locale 或应用主题改变时,Flutter 框架会通知 widget 调用此回调。
    • 需要注意,组件第一次被创建后挂载的时候(包括重创建)对应的didChangeDependencies也会被调用。
  • build():widget灵魂,不解释!

    • 在调用initState()之后。
    • 在调用didUpdateWidget()之后。
    • 在调用setState()之后。
    • 在调用didChangeDependencies()之后。
    • 在State对象从树中一个位置移除后(会调用deactivate)又重新插入到树的其它位置之后。
  • reassemble():此回调是专门为了开发调试而提供的,在热重载(hot reload)时会被调用,此回调在Release模式下永远不会被调用。
  • didUpdateWidget ():在 widget 重新构建时,Flutter 框架会调用widget.canUpdate来检测 widget 树中同一位置的新旧节点,然后决定是否需要更新

    • 如果widget.canUpdate返回true则会调用此回调。
    • 正如之前所述,widget.canUpdate会在新旧 widget 的 keyruntimeType 同时相等时会返回true
    • 也就是说在在新旧 widget 的keyruntimeType同时相等时
    • didUpdateWidget()就会被调用。
  • deactivate():当 State 对象从树中被移除时,会调用此回调。

    • 在一些场景下,Flutter 框架会将 State 对象重新插到树中,如包含此 State 对象的子树在树的一个位置移动到另一个位置时(可以通过GlobalKey 来实现)。
    • 如果移除后没有重新插入到树中则紧接着会调用dispose()方法。
  • dispose():当 State 对象从树中被永久移除时调用;通常在此回调中释放资源。

2.在Widget树中获取State对象

由于 StatefulWidget 的的具体逻辑都在其 State 中,所以很多时候,我们需要获取 StatefulWidget 对应的State 对象来调用一些方法,比如Scaffold组件对应的状态类ScaffoldState中就定义了打开 SnackBar(路由页底部提示条)的方法。我们有两种方法在子 widget 树中获取父级 StatefulWidget 的State 对象。

a.通过Context获取

  • context对象有一个findAncestorStateOfType()方法,该方法可以从当前节点沿着 widget 树向上查找指定类型的 StatefulWidget 对应的 State 对象。下面是实现打开 SnackBar的示例:
class GetStateObjectRoute extends StatefulWidget {const GetStateObjectRoute({Key? key}) : super(key: key);@overrideState<GetStateObjectRoute> createState() => _GetStateObjectRouteState();
}class _GetStateObjectRouteState extends State<GetStateObjectRoute> {@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("子树中获取State对象"),),body: Center(child: Column(children: [Builder(builder: (context) {return ElevatedButton(onPressed: () {// 查找父级最近的Scaffold对应的ScaffoldState对象ScaffoldState _state = context.findAncestorStateOfType<ScaffoldState>()!;// 打开抽屉菜单_state.openDrawer();},child: Text('打开抽屉菜单1'),);}),],),),drawer: Drawer(),);}
}
  • 一般来说,如果StatefulWidget的状态是私有的,不能直接获取State对象
  • C++相似,那么就会有一个公开的方法来访问

    • context.findAncestorStateOfType获取StatefulWidget的状态的方法是通用的
  • 我们不能指定StatefulWidget的状态是否私有,所以在Fluyyer开发中有了一个约定

    • 如果希望状态是暴露出的

      • 应当在StatefulWidget中提供一个of静态方法来获取State对象
    • 如果不希望State暴露,则不提供of方法

  • 我们康康如何使用of方法
Builder(builder: (context) {return ElevatedButton(onPressed: () {// 直接通过of静态方法来获取ScaffoldStateScaffoldState _state=Scaffold.of(context);// 打开抽屉菜单_state.openDrawer();},child: Text('打开抽屉菜单2'),);
}),
  • 又比如我们想显示snack bar,可以通过ofshowSnackBar
Builder(builder: (context) {return ElevatedButton(onPressed: () {ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("我是SnackBar")),);},child: Text('显示SnackBar'),);
}),

  • 点击显示SnackBar 出现snackbar!

b.通过GlobalKey

Flutter还有一种通用的获取State对象的方法—通过GlobalKey来获取!步骤分两步:

  • 1.给目标StatefulWidget添加GlobalKey
//定义一个globalKey, 由于GlobalKey要保持全局唯一性,我们使用静态变量存储
static GlobalKey<ScaffoldState> _globalKey= GlobalKey();
...
Scaffold(key: _globalKey , //设置key...
)
  • 2.通过GlobalKey来获取State对象
_globalKey.currentState.openDrawer()
  • GlobalKey 是 Flutter 提供的一种在整个 App 中引用 element 的机制。

    • 如果一个 widget 设置了GlobalKey

      • 那么我们便可以通过globalKey.currentWidget获得该 widget 对象
      • globalKey.currentElement来获得 widget 对应的element对象
      • 如果当前 widget 是StatefulWidget,则可以通过globalKey.currentState来获得该 widget 对应的state对象。
  • 震惊!这个不就是React中的Redux,Vue中的pinia???

    • 所以,这个肯定开销还是比较大的,要避免使用
    • 然后,需要保证GlobalKey在整个widget树中必须是唯一的

通过RenderObject自定义Widget

  • 这个属于扫盲,新手用组件库就好啦!
  • StatelessWidgetStatefulWidget都是用于组合其他组件的,它们本身没有对应的RenderObject
  • Flutter组件库中有很多基础组件都不是通过StatelessWidgetStatefulWidget来实现的

    • 比如TextColumnAlign
    • 实际上Flutter最原始的定义组件的方式就是通过定义RenderObject来实现
  • 简单演示一下通过RenderObject定义组件的方式:
class CustomWidget extends LeafRenderObjectWidget{@overrideRenderObject createRenderObject(BuildContext context) {// 创建 RenderObjectreturn RenderCustomObject();}@overridevoid updateRenderObject(BuildContext context, RenderCustomObject  renderObject) {// 更新 RenderObjectsuper.updateRenderObject(context, renderObject);}
}class RenderCustomObject extends RenderBox{@overridevoid performLayout() {// 实现布局逻辑}@overridevoid paint(PaintingContext context, Offset offset) {// 实现绘制}
}
  • 特别地,如果组件不包含子组件,则我们可以直接继承自LeafRenderObjectWidget,它是RenderObjectWidget的子类,而RenderObjectWidget继承自Widget,我们可以看一下它的实现:
abstract class LeafRenderObjectWidget extends RenderObjectWidget {const LeafRenderObjectWidget({ Key? key }) : super(key: key);@overrideLeafRenderObjectElement createElement() => LeafRenderObjectElement(this);
}
  • 简单来说就是重写了createElement方法

Flutter SDK内置组件库介绍

  • Flutter提供了一套丰富,强大的基础组件,在基础组件库之上Flutter又提供了一套Material风格(Android默认的视觉风格)和一套Cupertino风格(IOS视觉风格)的组件库,导入就可以使用了。

    • 两个组件库是要一统天下啊,哈哈
  • 1.基础组件

import 'package:flutter/widgets.dart';
  • 比较语义化了!

  • Text (opens new window):文本

  • Row (opens new window)Column (opens new window):类似css里面的Flex布局中的flex-direction,指定排列方向

  • Stack (opens new window):允许子widget堆叠,你可以使用Positioned (opens new window)来定位他们相对于Stack的上下左右四条变的位置。

  • 这个类比绝对布局absolute

  • Container (opens new window):可以理解为一个盒子模型吧,div

  • BoxDecoration (opens new window):如 background、一个边框、或者一个阴影

2.Material组件

Flutter 提供了一套丰富 的Material 组件,它可以帮助我们构建遵循 Material Design 设计规范的应用程序。Material 应用程序以MaterialApp (opens new window) 组件开始, 该组件在应用程序的根部创建了一些必要的组件,比如Theme组件,它用于配置应用的主题。 是否使用MaterialApp (opens new window)完全是可选的,但是使用它是一个很好的做法。在之前的示例中,我们已经使用过多个 Material 组件了,如:ScaffoldAppBarTextButton等。要使用 Material 组件,需要先引入它:

import 'package:flutter/material.dart';

3.Cupertino组件

Flutter 也提供了一套丰富的 Cupertino 风格的组件,尽管目前还没有 Material 组件那么丰富,但是它仍在不断的完善中。值得一提的是在 Material 组件库中有一些组件可以根据实际运行平台来切换表现风格,比如MaterialPageRoute,在路由切换时,如果是 Android 系统,它将会使用 Android 系统默认的页面切换动画(从底向上);如果是 iOS 系统,它会使用 iOS 系统默认的页面切换动画(从右向左)。由于在前面的示例中还没有Cupertino组件的示例,下面我们实现一个简单的 Cupertino 组件风格的页面:

//导入cupertino  widget 库
import 'package:flutter/cupertino.dart';class CupertinoTestRoute extends StatelessWidget  {@overrideWidget build(BuildContext context) {return CupertinoPageScaffold(navigationBar: CupertinoNavigationBar(middle: Text("Cupertino Demo"),),child: Center(child: CupertinoButton(color: CupertinoColors.activeBlue,child: Text("Press"),onPressed: () {}),),);}
}

总结

  • 1.Flutter的widget类型有两种

    • a.StatefulWidget有状态widget—受控组件
    • b.StatelessWidget无状态widget—非受控组件
  • 2.组件库的使用

    • 可以随意使用,不必担心引入过多的组件库让安装包变大

    • MaterialCupertino都是在基础组件库之上的

      • 内部引入了flutter.widgets.dart基础库了
  • 3.其实不难发现

    • Flutter的代码可读性其实还不错

    • 写代码和搭积木一样,一层套一层

    • 当然本篇文章只是浅浅地认识

      • 后面再一起学习其他好康的!

Widget是一切,Widget简介相关推荐

  1. VTK修炼之道77:交互部件_分割/配准类Widget与其他Widget

    1.分割/配准交互部件 图像分割与配准是数字图像处理技术两大主要的应用领域,特别是在医学图像处理中. 著名的医学图像分割与配准工具包ITK(Insight Segmentation & Reg ...

  2. 【Flutter从入门到实战】⑪、豆瓣案例-1、星星评分Widget、虚线Widget、TabbarWidget、BottomNavigationBarItem的封装、初始化配置抽取

    Flutter从入门到实战 一共分为23个系列 ①(Flutter.Dart环境搭建篇) 共3个内容 已更新 ②(Dart语法1 篇) 共4个内容 已更新 ③(Dart语法2 篇) 共2个内容 已更新 ...

  3. 关于iOS Widget(Locket Widget App)

    最近一款UI风格很像Clubhouse的App,功能简单到不能再简单的Locket app火了,其主要功能就是通过桌面Widget显示一张朋友分享的照片,死气沉沉的互联网上终于出现了一点新东西.其实w ...

  4. Widget星球—widget web 网页Widget 最好的widget widget开发

    本文摘要: 而他们更切实的交汇点发生于比特层面.2005年以来,这三个人先后从不同角度介入到一种名为Widget的微不足道的技术中,并很可能依靠这种小则几百K.大则几兆的微型软件改变人们使用手机.社交 ...

  5. 为什么用 php widget,13.6 Widget扩展

    # Widget扩展 [上一页](# "上一页")[下一页](# "下一页") Widget扩展用于在页面根据需要输出不同的内容,Widget扩展的定义是在项目 ...

  6. 为什么用 php widget,ThinkPHP的Widget扩展实例

    ThinkPHP的Widget扩展用于根据页面需要输出不同内容,它在项目目录中的Lib/Widget下定义. 具体定义如下: class NewsListWidget extends Widget{ ...

  7. qt widget 嵌套与弹出_Gtk widget 与 Qt Widget的相互嵌套

    简述 gtk qt窗口的相互嵌套.主要是使用了Gtk中的socket跟plug的模式,与Qt中的QX11EmbedContainer.这是两个不同的UI库的相互嵌套的方法.google了下发现有个老哥 ...

  8. android widget 布局,Android Widget Attributes——View

    最近因为想着重新把这个插件捡起来,把view的属性慢慢的全部补上,所以顺便了解一下各个属性,在这里列举View的属性,含义,以及代码设置. 简书Markdown编辑器实在是渣,只能把表格换成下面这种形 ...

  9. Flutter视图基础简介--Widget、Element、RenderObject

    前言:Flutter官方文档里的一句话:you build your UI out of widgets(使用Flutter开发UI界面时,都是使用Widget),然而,Widget并不是我们真正看到 ...

最新文章

  1. 编程25年后,现实将我打回菜鸟程序员的起点
  2. linux mysql 日志乱码_Linux下MySQL保存进去数据为乱码的解决办法
  3. stdafx.h头文件
  4. java websocket 客户端
  5. linux驱动日志格式,( 转)嵌入式Linux驱动Makefile
  6. linux内存管理---虚拟地址、逻辑地址、线性地址、物理地址的区别(一)
  7. 链接与加载过程中,几个关键的概念
  8. 记一次PHP服务器500错误的解决方法
  9. 读源代码学Asp.net Ajax(一)
  10. 【渝粤教育】国家开放大学2018年秋季 0556-22T广告摄影 参考试题
  11. EF Core学习之路02
  12. Nginx解决无法代理域名问题
  13. Unity快速入门之台球小游戏(二)
  14. 食物也疯狂!KOOCAN盘点因为食物毁掉的中国电视剧
  15. 从《缩小帽》分析塞尔达系列的关卡结构
  16. 工程学导论的学习感悟
  17. 开源数据集网站推荐,持续更新!
  18. 工业机器人什么情况下会出现奇点_机器人奇点是什么?他们怎么会像黑洞一样?...
  19. 使用python在Lotus Notes发送邮件
  20. ARK(xuetr)与Rootkit

热门文章

  1. 作为一名新晋码农,以下是我个人的工作心得,希望能帮到刚步入编程殿堂和使用vue-cli和ant-design-vue UI组件库的童鞋们
  2. 简单理解钽电容和电解电容的区别
  3. PCI/PCIe 的那些事(1)- 总线基本知识
  4. 为什么学完这个他们都跳槽/涨薪了?
  5. Ceres Solver 官方教程学习笔记(十二)——非线性最小二乘法建模Modeling Non-linear Least Squares (下)
  6. android 裁剪图片工具,图片裁剪助手app下载-图片裁剪助手软件下载 v4.0安卓版_5577安卓网...
  7. 【老李的模拟赛】【#4】【2014-08-09】
  8. 不再纠结devDependencies与dependencies
  9. mysql映射mapper_这下够清楚了吧!详解Mybatis的Mapper映射文件
  10. JAVA处理订购机票案例