最近做的项目有一个需求, 就是有不同的主题, 那么在设置页面就应该让生效, 这就是全局状态的一个管理了, Flutter 系统提供了 InheritedWidget, 但是这里我们来使用 scoped_model(基于InheritedWidget进行了封装).

InheritedWidget

我们先来看一下InheritedWidget是如何实现共享数据的.

以下是官网上的一个小例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
class ShareDataWidget extends InheritedWidget {
ShareDataWidget({
@required this.data,
Widget child
}) :super(child: child);

final int data; //需要在子树中共享的数据,保存点击次数

//定义一个便捷方法,方便子树中的widget获取共享数据
static ShareDataWidget of(BuildContext context) {
return context.inheritFromWidgetOfExactType(ShareDataWidget);
}

//该回调决定当data发生变化时,是否通知子树中依赖data的Widget
@override
bool updateShouldNotify(ShareDataWidget old) {
//如果返回true,则子树中依赖(build函数中有调用)本widget
//的子widget的`state.didChangeDependencies`会被调用
return old.data != data;
}
}

class _TestWidget extends StatefulWidget {
@override
__TestWidgetState createState() => new __TestWidgetState();
}

class __TestWidgetState extends State<_TestWidget> {
@override
Widget build(BuildContext context) {
//使用InheritedWidget中的共享数据
return Text(ShareDataWidget
.of(context)
.data
.toString());
}

@override
void didChangeDependencies() {
super.didChangeDependencies();
//父或祖先widget中的InheritedWidget改变(updateShouldNotify返回true)时会被调用。
//如果build中没有依赖InheritedWidget,则此回调不会被调用。
print("Dependencies change");
}
}

class InheritedWidgetTestRoute extends StatefulWidget {
@override
_InheritedWidgetTestRouteState createState() => new _InheritedWidgetTestRouteState();
}

class _InheritedWidgetTestRouteState extends State<InheritedWidgetTestRoute> {
int count = 0;

@override
Widget build(BuildContext context) {
return Center(
child: ShareDataWidget( //使用ShareDataWidget
data: count,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(bottom: 20.0),
child: _TestWidget(),//子widget中依赖ShareDataWidget
),
RaisedButton(
child: Text("Increment"),
//每点击一次,将count自增,然后重新build,ShareDataWidget的data将被更新
onPressed: () => setState(() => ++count),
)
],
),
),
);
}
}

这里需要注意:

  • context.inheritFromWidgetOfExactType(ShareDataWidget)来获取到指定InheritedWidget中的数据, 实际通知的时候是父Widget往下传递还是子Widget往上遍历呢?

接下lai 探索原因

InheritedWidget的源码很简单, 继承了ProxyWidget, 也没有实现太多逻辑, 对createElement进行了实现, 还定义了updateShouldNotify, 该方法的意思就是更新的时候是否应该通知在 build 阶段通过inheritFromWidgetOfExactType查找该 Widget 的子 Widget.

1
2
3
4
5
6
7
8
9
10
abstract class InheritedWidget extends ProxyWidget {
const InheritedWidget({ Key key, Widget child })
: super(key: key, child: child);

@override
InheritedElement createElement() => InheritedElement(this);

@protected
bool updateShouldNotify(covariant InheritedWidget oldWidget);
}

顺藤摸瓜, 我们去就去看InheritedElement(this)做了什么, 进去之后我们可以发现_updateInheritance方法.

1
2
3
4
5
6
7
8
9
void _updateInheritance() {
assert(_active);
final Map<Type, InheritedElement> incomingWidgets = _parent?._inheritedWidgets;
if (incomingWidgets != null)
_inheritedWidgets = HashMap<Type, InheritedElement>.from(incomingWidgets);
else
_inheritedWidgets = HashMap<Type, InheritedElement>();
_inheritedWidgets[widget.runtimeType] = this;
}

同时我们对比一下普通的Element的该方法实现, 只是简单的将父Element_inheritedWidgets属性保存到自身(这样就保证了父级的向子集传递特性).

1
2
3
4
void _updateInheritance() {
assert(_active);
_inheritedWidgets = _parent?._inheritedWidgets;
}

但是这个_inheritedWidgets属性又是在哪里出现呢? 它定义在 Element中, 每一个实例都有这个属性. 它的作用是存储上级节点WidgetElement之间的映射.

1
2
3
4
5
6
7
8
9
10
11
abstract class Element extends DiagnosticableTree implements BuildContext {
/// Creates an element that uses the given widget as its configuration.
///
/// Typically called by an override of [Widget.createElement].
Element(Widget widget)
: assert(widget != null),
_widget = widget;

Element _parent;
...
Map<Type, InheritedElement> _inheritedWidgets;

现在我们回到InheritedElement的实现.

1
2
3
4
5
6
7
8
9
10
void _updateInheritance() {
assert(_active);
final Map<Type, InheritedElement> incomingWidgets = _parent?._inheritedWidgets;
if (incomingWidgets != null)
_inheritedWidgets = HashMap<Type, InheritedElement>.from(incomingWidgets);
else
_inheritedWidgets = HashMap<Type, InheritedElement>();
// 添加
_inheritedWidgets[widget.runtimeType] = this;
}

InheritedElement会将自身的信息添加到_inheritedWidgets属性中, 然后子孙都可以通过他们自身的该属性访问当前的InheritedElement了.

现在我们知道如何访问_inheritedWidgets属性以及包含的内容了, 那么通知机制是如何实现呢?

一开始例子中就是使用inheritFromWidgetOfExactType(Type)方法去获取到指定的InherientWidget的, 那么这个方法是怎么实现呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
InheritedWidget inheritFromWidgetOfExactType(Type targetType, { Object aspect }) {
assert(_debugCheckStateIsActiveForAncestorLookup());
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
if (ancestor != null) {
assert(ancestor is InheritedElement);
return inheritFromElement(ancestor, aspect: aspect);
}
_hadUnsatisfiedDependencies = true;
return null;
}

@override
InheritedWidget inheritFromElement(InheritedElement ancestor, { Object aspect }) {
assert(ancestor != null);
_dependencies ??= HashSet<InheritedElement>();
_dependencies.add(ancestor);
ancestor.updateDependencies(this, aspect);
return ancestor.widget;
}

首先会获取Element(Context)_inheritedWidgets指定类型的Element, 如果获取到了, 则会添加到自身的依赖列表中, 祖先节点也有记录这个依赖, 这样在更新时候就可以直接通过_dependencies属性来进行通知了.

每一次InherientElement更新的时候, 都会调用notifyClients方法来通知子节点, 调用子节点的didChangeDependencies方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void notifyClients(InheritedWidget oldWidget) {
if (!widget.updateShouldNotify(oldWidget))
return;
assert(_debugCheckOwnerBuildTargetExists('notifyClients'));
for (Element dependent in _dependents) {
assert(() {
// check that it really is our descendant
Element ancestor = dependent._parent;
while (ancestor != this && ancestor != null)
ancestor = ancestor._parent;
return ancestor == this;
}());
// check that it really depends on us
assert(dependent._dependencies.contains(this));
dependent.didChangeDependencies();
}
}

以上就是InherientWidget的原理, 接下来就是看看scoped_model做了怎样的封装.

scoped_model

该库主要的类有:

abstract class Model extends Listenable
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
abstract class Model extends Listenable {
final Set<VoidCallback> _listeners = Set<VoidCallback>();
int _version = 0;
int _microtaskVersion = 0;

void addListener(VoidCallback listener) {
debugPrint("添加监听器");
listener();
_listeners.add(listener);
}

@override
void removeListener(VoidCallback listener) {
print("监听器被去除");
_listeners.remove(listener);
}

int get listenerCount => _listeners.length;

@protected
void notifyListeners() {
if (_microtaskVersion == _version) {
_microtaskVersion++;
scheduleMicrotask(() {
_version++;
_microtaskVersion = _version;
_listeners.toList().forEach((VoidCallback listener) => listener());
});
}
}
}

Model继承了Listenable, 值在改变的时候调用notifyListeners既可以通知到所有的监听器.

class ScopedModel<T extends Model> extends StatelessWidget
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class ScopedModel<T extends Model> extends StatelessWidget {

final T model;

final Widget child;

ScopedModel({@required this.model, @required this.child})
: assert(model != null),
assert(child != null);

@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: model,
builder: (context, _) => _InheritedModel<T>(model: model, child: child),
);
}
// 找到 ScopeModel, 并且让子节点和祖先节点建立依赖
static T of<T extends Model>(
BuildContext context, {
bool rebuildOnChange = false,
}) {
final Type type = _type<_InheritedModel<T>>();

Widget widget = rebuildOnChange
? context.inheritFromWidgetOfExactType(type)
: context.ancestorInheritedElementForWidgetOfExactType(type)?.widget;

if (widget == null) {
throw ScopedModelError();
} else {
return (widget as _InheritedModel<T>).model;
}
}

static Type _type<T>() => T;
}

通知方法

作者在构造ScopedModel的时候, 使用了AnimationBuilder, 这里会注册一个监听器到 Model中, 然后每一次值的改变都会调用AnimationBuilder.builder方法, 然后就会触发InherientWidget的改变, 根据updateShouldNotify来决定是否通知子孙控件更新, 但是在这里我们并没有看到InherientWidget的影子, 让我们接着往下看.

class _InheritedModel<T extends Model> extends InheritedWidget
1
2
3
4
5
6
7
8
9
10
11
12
13
class _InheritedModel<T extends Model> extends InheritedWidget {
final T model;
final int version;

_InheritedModel({Key key, Widget child, T model})
: this.model = model,
this.version = model._version,
super(key: key, child: child);

@override
bool updateShouldNotify(_InheritedModel<T> oldWidget) =>
(oldWidget.version != version);
}

看到这个类, 我们就可以发现作者的实现方式了, _InheritedModel继承自InheritedWidget, 上方ScopedModel.build()方法, 里面就返回了该inherientWidget对象, 但是与上方的例子还缺少子类对父类进行依赖的一步.

class ScopedModelDescendant<T extends Model> extends StatelessWidget
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
typedef Widget ScopedModelDescendantBuilder<T extends Model>(
BuildContext context,
Widget child,
T model,
);

class ScopedModelDescendant<T extends Model> extends StatelessWidget {
final ScopedModelDescendantBuilder<T> builder;

final Widget child;

final bool rebuildOnChange;

ScopedModelDescendant({
@required this.builder,
this.child,
this.rebuildOnChange = true,
});

@override
Widget build(BuildContext context) {
return builder(
context,
child,
ScopedModel.of<T>(context, rebuildOnChange: rebuildOnChange),
);
}
}

ScopedModelDescendant就是ScopedModel的子孙, 那么可以看到它的 Build方法, 里面有调用ScopedModel.of<T>(context, rebuildOnChange: rebuildOnChange), 我们把这个方法在拿出来看一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static T of<T extends Model>(
BuildContext context, {
bool rebuildOnChange = false,
}) {
final Type type = _type<_InheritedModel<T>>();

Widget widget = rebuildOnChange
? context.inheritFromWidgetOfExactType(type)
: context.ancestorInheritedElementForWidgetOfExactType(type)?.widget;

if (widget == null) {
throw ScopedModelError();
} else {
return (widget as _InheritedModel<T>).model;
}
}

static Type _type<T>() => T;

到这里, 就可以发现它和inherient做法相同了, 首先获取到InheritedWidget的类型, _type<_InheritedModel<T>>()得到_InheritedModel<T>, 然后判断是否需要在改变的时候重绘, 默认是True, 如果需要重绘就会调用inheritFromWidgetOfExactType去建立依赖, 如果为false, 则会调用ancestorInheritedElementForWidgetOfExactType, 这个方法不会建立依赖, 所以在改变的时候不会收到通知并重绘, 官方的注释有这么一句:
This method does not establish a relationship with the target in the way that [inheritFromWidgetOfExactType] does.

最后贴一下作者提供的小例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';

void main() {
CounterModel model = CounterModel();
runApp(MyApp(
model: model,
));
}

class MyApp extends StatelessWidget {
final CounterModel model;

const MyApp({Key key, @required this.model}) : super(key: key);

@override
Widget build(BuildContext context) {
// At the top level of our app, we'll, create a ScopedModel Widget. This
// will provide the CounterModel to all children in the app that request it
// using a ScopedModelDescendant.
return ScopedModel<CounterModel>(
model: model,
child: MaterialApp(
title: 'Scoped Model Demo',
home: CounterHome('Scoped Model Demo'),
),
);
}
}

// Start by creating a class that has a counter and a method to increment it.
//
// Note: It must extend from Model.
class CounterModel extends Model {
int _counter = 0;

int get counter => _counter;

void increment() {
// First, increment the counter
_counter++;

// Then notify all the listeners.
notifyListeners();
}
}

class CounterHome extends StatelessWidget {
final String title;

CounterHome(this.title);

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('You have pushed the button this many times:'),
// Create a ScopedModelDescendant. This widget will get the
// CounterModel from the nearest parent ScopedModel<CounterModel>.
// It will hand that CounterModel to our builder method, and
// rebuild any time the CounterModel changes (i.e. after we
// `notifyListeners` in the Model).
ScopedModelDescendant<CounterModel>(
builder: (context, child, model) {
return Text(
model.counter.toString(),
style: Theme.of(context).textTheme.display1,
);
},
),
],
),
),
// Use the ScopedModelDescendant again in order to use the increment
// method from the CounterModel
floatingActionButton: ScopedModelDescendant<CounterModel>(
builder: (context, child, model) {
return FloatingActionButton(
onPressed: model.increment,
tooltip: 'Increment',
child: Icon(Icons.add),
);
},
),
);
}
}

这里也能验证我们上方所分析的, 首先需要构建 ScopedModel, 然后共享状态的子孙节点通过ScopedModelDescendant来添加.

自从 Flutter 推出之后, 一直是备受关注, 有看好的也有不看好的, 作为移动开发人员自然是要尝试一下的(但是它的嵌套写法真的难受), 本着学一个东西, 就一定要动手的态度, 平时又喜欢看一些猫狗的图片, 就想着做一个加载猫狗图片你的 APP, 界面图如下(界面不是很好看).

主要模块

NetWork

api.dart文件中, 分别定义了DogApi, CatApi两个类, 一个用于处理获取猫的图片的类, 一个用于处理狗的图片的类.

http_request.dart文件封装了Http请求, 用于发送和接收数据.

url.dart文件封装了需要用到的Api接口, 主要是为了方便和统一管理而编写.

Models文件夹下分别定义不同API接口返回数据的模型.

图片页

瀑布流使用的flutter_staggered_grid_view库, 作者自定义了Delegate计算布局, 使用起来非常简单.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
Widget scene = new StaggeredGridView.countBuilder(
physics: BouncingScrollPhysics(),
itemCount: this.breedImgs != null ? this.breedImgs.urls.length : 0,
mainAxisSpacing: 4.0,
crossAxisSpacing: 4.0,
crossAxisCount: 3,
itemBuilder: (context, index) {
return new GestureDetector(
onTapUp: (TapUpDetails detail) {
// 展示该品种的相关信息
dynamic breed = this.breeds[this.selectedIdx].description;
// TODO: 取出当前点击的然后所有往后的
List<String> unreadImgs = new List<String>();
for (int i = index; i < this.breedImgs.urls.length; i++) {
unreadImgs.add(this.breedImgs.urls[i]);
}
AnimalImagesPage photoPage = new AnimalImagesPage(
listImages: unreadImgs,
breed: this.breeds[this.selectedIdx].name,
imgType: "Cat",
petInfo: this.breeds[this.selectedIdx],
);
Navigator.of(context)
.push(new MaterialPageRoute(builder: (context) {
return photoPage;
}));
},
child: new Container(
width: 100,
height: 100,
color: Color(0xFF2FC77D), //Colors.blueAccent,
child: new CachedNetworkImage(
imageUrl: this.breedImgs.urls[index],
fit: BoxFit.fill,
placeholder: (context, index) {
return new Center(child: new CupertinoActivityIndicator());
},
),
),
);
},
// 该属性可以控制当前 Cell 占用的空间大小, 用来实现瀑布的感觉
staggeredTileBuilder: (int index) =>
new StaggeredTile.count(1, index.isEven ? 1.5 : 1),
);
  • 组装PickerView

系统默认的 PickerView 在每一次切换都会回调, 而且没有确定和取消事件,
如果直接使用会造成频繁的网络请求, 内存消耗也太快, 所以组装了一下, 增加确定和取消才去执行网络请求, 这样就解决了这个问题.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
Widget column = Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new Container(
width: MediaQuery.of(context).size.width,
height: 40,
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
new Padding(
padding: EdgeInsets.only(left: 10.0),
child: new GestureDetector(
onTapUp: (detail) {
// 点击了确定按钮, 退出当前页面
Navigator.of(context).pop();
// 回调操作
this.submit(this.selectedIndex);
},
child: new Text(
"确定",
style: TextStyle(
decoration: TextDecoration.none,
color: Colors.white,
fontSize: 18),
),
),
),
new Padding(
padding: EdgeInsets.only(right: 10.0),
child: new GestureDetector(
onTapUp: (detail) {
// 点击了确定按钮, 退出当前页面
Navigator.of(context).pop();
},
child: new Text(
"取消",
style: TextStyle(
decoration: TextDecoration.none,
color: Colors.white,
fontSize: 18),
),
),
)
],
),
),
],
),
new Container(
height: 1,
color: Colors.white,
),
// Picker
new Expanded(
child: new CupertinoPicker.builder(
backgroundColor: Colors.transparent,
itemExtent: 44,
childCount: this.names.length,
onSelectedItemChanged: (int selected) {
this.selectedIndex = selected;
this.onSelected(selected);
},
itemBuilder: (context, index) {
return new Container(
width: 160,
height: 44,
alignment: Alignment.center,
child: new Text(
this.names[index],
textAlign: TextAlign.right,
style: new TextStyle(
color: Colors.white,
fontSize: 16,
decoration: TextDecoration.none),
),
);
}),
)
],
);
详情页
  • Column 包含 ListView

详情页中, 上方是一个图片, 下方是关于品种的相关信息, 下方是通过 API获取到的属性进行一个展示, 需要注意一点是, 如果Column封装了MainAxis相同方向的滚动控件, 必须设置Width/Height, 同理, Row也是需要注意这一点的.

我在这里的做法是通过一个Container包裹 ListView.

1
2
3
4
5
6
new Container(
margin: EdgeInsets.only(bottom: 10, top: 10),
height: MediaQuery.of(context).size.height - MediaQuery.of(context).size.width / 1.2 - 80,
width: MediaQuery.of(context).size.width,
child: listView,
),
  • 图片动画

这一部分稍微复杂一些, 首先需要监听滑动的距离, 来对图片进行变换, 最后根据是否达到阈值来进行切换动画, 这里我没有实现在最后一张和第一张图片进行切换以至于可以无限循环滚动, 我在边界阈值上只是阻止了下一步动画.

动画我都是通过Matrix4来设置不同位置的属性, 它也能模拟出 3D 效果,

动画的变换都是Tween来管理.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
void _initAnimation() {
// 透明度动画
this.opacityAnimation = new Tween(begin: 1.0, end: 0.0).animate(
new CurvedAnimation(
parent: this._nextAnimationController, curve: Curves.decelerate))
..addListener(() {
this.setState(() {
// 通知 Fluter Engine 重绘
});
});
// 翻转动画
// 第三个值是角度
var startTrans = Matrix4.identity()..setEntry(3, 2, 0.006);
var endTrans = Matrix4.identity()
..setEntry(3, 2, 0.006)
..rotateX(3.1415927);
this.transformAnimation = new Tween(begin: startTrans, end: endTrans)
.animate(new CurvedAnimation(
parent: this._nextAnimationController, curve: Curves.easeIn))
..addListener(() {
this.setState(() {});
});
// 缩放
var saveStartTrans = Matrix4.identity()..setEntry(3, 2, 0.006);
// 平移且缩放
var saveEndTrans = Matrix4.identity()
..setEntry(3, 2, 0.006)
..scale(0.1, 0.1)
..translate(-20.0, 20.0); // MediaQuery.of(context).size.height
this.saveToPhotos = new Tween(begin: saveStartTrans, end: saveEndTrans)
.animate(new CurvedAnimation(
parent: this._saveAnimationController, curve: Curves.easeIn))
..addListener(() {
this.setState(() {});
});
}

Widget引用这个属性来执行动画.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
Widget pet = new GestureDetector(
onVerticalDragUpdate: nextUpdate,
onVerticalDragStart: nextStart,
onVerticalDragEnd: next,
child: new Transform(
transform: this.dragUpdateTransform,
child: Container(
child: new Transform(
alignment: Alignment.bottomLeft,
transform: transform,
child: new Opacity(
opacity: opacity,
child: Container(
width: MediaQuery.of(context).size.width / 1.2,
height: MediaQuery.of(context).size.width / 1.5 - 30,
child: new Padding(
padding: EdgeInsets.all(0),
child: new CachedNetworkImage(
imageUrl: this.widget.listImages[item],
fit: BoxFit.fill,
placeholder: (context, content) {
return new Container(
width: MediaQuery.of(context).size.width / 2.0 - 40,
height: MediaQuery.of(context).size.width / 2.0 - 60,
color: Color(0xFF2FC77D),
child: new Center(
child: new CupertinoActivityIndicator(),
),
);
},
),
),
),
),
),
),
),
);
Firebase_admob

注意: 这里需要去 firebase 官网注册 APP, 然后分别下载 iOS, Android 的配置文件放到指定的位置, 否则程序启动的时候会闪退.

iOS info.plist: GADApplicationIdentifier也需要配置, 虽然在 Dart 中会启动的时候就注册ID, 但是这里也别忘了配置.

Android Manifst.xml 也需要配置

1
2
3
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value=""/>

这里说一下我因为个人编码导致的问题, 我尝试自己来控制广告展示, 加了一个读秒跳过按钮(想强制观看一段时间), 点击跳过设置setState, 但是在 build 方法中又请求了广告, 导致了一个死循环, 最后由于请求次数过多还没有设置自己的设备为测试设备也不是使用的测试ID, 账号被暂停了, 所以大家使用的时候要避免这个问题, 尽量还是将自己的设备添加到测试设备中.

使用的话比较简单(官方的演示代码直接复制也可以用).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
class AdPage {
MobileAdTargetingInfo targetingInfo;

InterstitialAd interstitial;

BannerAd banner;

void initAttributes() {
if (this.targetingInfo == null) {
this.targetingInfo = MobileAdTargetingInfo(
keywords: ["some keyword for your app"],
// 防止被Google 认为是无效点击和展示.
testDevices: ["Your Phone", "Simulator"]);

bool android = Platform.isAndroid;

this.interstitial = InterstitialAd(
adUnitId: InterstitialAd.testAdUnitId,
targetingInfo: this.targetingInfo,
listener: (MobileAdEvent event) {
if (event == MobileAdEvent.closed) {
// 点击关闭
print("InterstitialAd Closed");
this.interstitial.dispose();
this.interstitial = null;
} else if (event == MobileAdEvent.clicked) {
// 关闭
print("InterstitialAd Clicked");
this.interstitial.dispose();
this.interstitial = null;
} else if (event == MobileAdEvent.loaded) {
// 加载
print("InterstitialAd Loaded");
}
print("InterstitialAd event is $event");
},
);

// this.banner = BannerAd(
// targetingInfo: this.targetingInfo,
// size: AdSize.smartBanner,
// listener: (MobileAdEvent event) {
// if (event == MobileAdEvent.closed) {
// // 点击关闭
// print("InterstitialAd Closed");
// this.interstitial.dispose();
// this.interstitial = null;
// } else if (event == MobileAdEvent.clicked) {
// // 关闭
// print("InterstitialAd Clicked");
// this.interstitial.dispose();
// this.interstitial = null;
// } else if (event == MobileAdEvent.loaded) {
// // 加载
// print("InterstitialAd Loaded");
// }
// print("InterstitialAd event is $event");
// });
}
}

@override
void show() {
// 初始化数据
this.initAttributes();
// 然后控制跳转
if (this.interstitial != null) {
this.interstitial.load();
this.interstitial.show(
anchorType: AnchorType.bottom,
anchorOffset: 0.0,
);
}
}
}

项目比较简单, 但是编写的过程中也遇到了许多问题, 慢慢解决的过程也学到了挺多.

一些资源

Public APIs
代码地址

遗传算法

1、遗传算法理论的由来

2、生物学的启发

3、遗传算法定义

4、遗传算法具体步骤

* 初始化
* 适应度函数
* 选择
* 交叉
* 变异

5、遗传算法的应用

* 特征选取

类比

我们先假设一个情景,现在你是一国之王,为了让你的国家免于灾祸,你实施了一套法案:

1. 你选出所有的好人,要求其通过生育来扩大国民数量。
2. 这个过程持续进行了几代。
3. 你将发现,你已经有了一整群的好人。

这个例子虽然不太可能,但是我用它是想帮助你理解概念。也就是说,我们改变了输入值(比如:人口),就可以获得更好的输出值(比如:更好的国家)。现在,我假定你已经对这个概念有了大致理解,认为遗传算法的含义应该和生物学有关系。那么我们就快速地看一些小概念,这样便可以将其联系起来理解。

过程

回到前面讨论的那个例子,并总结一下我们做过的事情。

  1. 首先,我们设定好了国民的初始人群大小。
  2. 然后,我们定义了一个函数,用它来区分好人和坏人。
  3. 再次,我们选择出好人,并让他们繁殖自己的后代。
  4. 最后,这些后代们从原来的国民中替代了部分坏人,并不断重复这一过程。

遗传算法实际上就是这样工作的,也就是说,它基本上尽力地在某种程度上模拟进化的过程。

因此,为了形式化定义一个遗传算法,我们可以将它看作一个优化方法,它可以尝试找出某些输入,凭借这些输入我们便可以得到最佳的输出值或者是结果。遗传算法的工作方式也源自于生物学,具体流程见下图:

算法的思想是非常简单的, 但是作用却是非常大的.

从一个实际问题来求解.

问题描述

名字\属性 重量/kg 价值
睡袋 15 15
绳子 3 7
折叠刀 2 10
火把 5 5
瓶子 9 8
葡萄糖 20 17

有一个可以装30kg的背包, 怎么装效益最高.

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# 背包问题求解(动态规划也是求解这种问题的一个好方法)

max_weights = 30

weights = np.array([15, 3, 2, 5, 9, 20])

values = np.array([15, 7, 10, 5, 8, 17])

## 随机初始化一个族群
features = len(weights)
# 初始样本数
init_sample_nums = np.random.randint(int(2 ** features / 3), 2 ** features)
samples = np.ones((init_sample_nums, features))
# 随机给定特性(基因)
for sample in range(init_sample_nums):
probs = np.random.normal(0, 1, features) > 0.5
random_sample = np.zeros((features, ))
random_sample[probs] = 1
samples[sample] = random_sample

# 定义函数去筛选
## 筛选, 去除超过总重量的
def remove_the_not_fitness_sample(samples):
total_weights = np.sum(samples, axis=1)
not_fitness = total_weights > 30
# 保留下来的
fitness = samples[~not_fitness]
return fitness

# 评分函数
### 用于族内对比
def compare_score(samples, remove=2):
# 如果直接通过最高评分判断
scores = np.zeros((len(samples, )))
for sample_index in range(len(samples)):
has_feature_index = samples[sample_index] == 1
scores[sample_index] = np.sum(weights[has_feature_index])
# 排分
# 从小到大
sort_scores = np.argsort(scores)
# 默认剔除最小的两个
return samples[sort_scores[2:]]

## 杂交
def crossover(samples, pairs=5, feature_prob=0.5):
# 随机选取样本开始杂交
# 每一次随机选择5对开始杂交
features = len(samples[0])
copy_samples = np.copy(samples)
for pair_index in range(pairs):
selected = np.random.choice(range(len(samples)), 2)
# 每一个位置的Gene有多大的可能性会进行杂交
cross_indexes = np.random.normal(0, 1, features) > feature_prob
print(cross_indexes, selected)
for cross_index in range(len(cross_indexes)):
if cross_indexes[cross_index]:
samples[selected[0]][cross_index] = samples[selected[1]][cross_index]
copy_samples = np.append(copy_samples, samples[selected], axis=0)
return copy_samples

## 变异
def mutation(samples, feature_unmutation_prob=0.8):
# 每个基因(feature)的变异几率为0.2
for sample in samples:
for feature in range(len(sample)):
if np.random.normal(0, 1, 1) > feature_unmutation_prob:
sample[feature] = 0 if sample[feature] else 1
return samples

# 执行生物论
fitness = remove_the_not_fitness_sample(samples)
filter_samples = compare_score(samples)
crossover_samples = crossover(filter_samples)
mutation_samples = mutation(crossover_samples)

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×