Agora Flutter SDK:一套代码,实现双端通话(二)

阅读数:18 2019 年 11 月 30 日 15:04

Agora Flutter SDK:一套代码,实现双端通话(二)

绿色部分为我们定义的 Stingy,红色小方块为 Stingy 的 child ,这里是一个 Container

代码中的输入如下 (iphone 6 尺寸):

复制代码
flutter: constraints: BoxConstraints(100.0<=w<=375.0, 100.0<=h<=300.0)
flutter: childParentData: offset=Offset(275.0, 200.0)
flutter: size: Size(375.0, 300.0)

上述我们自定义 RenderBox 的 performLayout() 中做的事情可大概分为如下三个步骤:

  • 使用 child.layout(…) 来布局 child,这里是为 child 根据 parent 传递过来的约束选择一个大小
  • child.parentData.offset , 这是在为 child 如何摆放设置一个偏移量
  • 设置当前 widget 的 size

在我们的例子中,Stingy 的 child 是一个 Container,并且 Container 没有 child,因此他会使用 child.layout(…) 中设置的最大约束。通常,每个 widget 都会以不同的方式来处理提供给他的约束。如果我们使用 RaiseButton 替换 Container:

复制代码
Stingy(
child: RaisedButton(
child: Text('Button'),
onPressed: (){}
)
)

效果如下:

Agora Flutter SDK:一套代码,实现双端通话(二)

可以看到,RaisedButton 的 width 使用了 parent 给他传递的约束值 100,但是高度很明显没有 100,RaisedButton 的高度默认为 48 ,由此可见 RaisedButton 内部对 parent 传递过来的约束做了一些处理。

我们上面的 Stingy 继承的是 SingleChildRenderObjectWidget,也就是只能有一个 child。那如果有多个 child 怎么办,不用担心,这里还有一个 MultiChildRenderObjectWidget,而这个类有一个子类叫做 CustomMultiChildLayout,我们直接用这个子类就好。

先来看看 CustomMultiChildLayout 的构造方法如下:

复制代码
/// The [delegate] argument must not be null.
CustomMultiChildLayout({
Key key,
@required this.delegate,
List<Widget> children = const <Widget>[],
})
  • key:widget 的一个标记,可以起到标识符的作用
  • delegate:这个特别重要,注释上明确指出这个参数一定不能为空,我们在下会说
  • children:这个就很好理解了,他是一个 widget 数组,也就是我们们需要渲染的 widget

上面的 delegate 参数类型如下:

复制代码
/// The delegate that controls the layout of the children.
final MultiChildLayoutDelegate delegate;

可以看出 delegate 的类型为 MultiChildLayoutDelegate,并且注释也说明了它的作用:控制 children 的布局。也就是说,我们的 CustomMultiChildLayout 里面要怎么布局,完全取决于我们自定义的 MultiChildLayoutDelegate 里面的实现。所以 MultiChildLayoutDelegate 中也会有类似的 performLayout(…) 方法。

另外,CustomMultiChildLayout 中的每个 child 必须使用 LayoutId 包裹,注释如下:

复制代码
/// Each child must be wrapped in a [LayoutId] widget to identify the widget for /// the delegate.

LayoutId 的构造方法如下:

复制代码
/// Marks a child with a layout identifier.
/// Both the child and the id arguments must not be null.
LayoutId({
Key key,
@required this.id,
@required Widget child
})

注释的大概意思说的是:使用一个布局标识来标识一个 child;参数 child 和 参数 id 不定不能为空。 我们在布局 child 的时候会根据 child 的 id 来布局。

下面我们来使用 CustomMultiChildLayout 实现一个用于展示热门标签的效果:

复制代码
Container(
child: CustomMultiChildLayout(
delegate: _LabelDelegate(itemCount: items.length, childId: childId),
children: items,
),
)

我们的 _LabelDelegate 里面接受两个参数,一个为 itemCount,还有是 childId。

_LabelDelegate 代码如下:

复制代码
class _LabelDelegate extends MultiChildLayoutDelegate {
final int itemCount;
final String childId;
// x 方向上的偏移量
double dx = 0.0;
// y 方向上的偏移量
double dy = 0.0;
_LabelDelegate({@required this.itemCount, @required this.childId});
@override
void performLayout(Size size) {
// 获取父控件的 width
double parentWidth = size.width;
for (int i = 0; i < itemCount; i++) {
// 获取子控件的 id
String id = '${this.childId}$i';
// 验证该 childId 是否对应一个 非空的 child
if (hasChild(id)) {
// layout child 并获取该 child 的 size
Size childSize = layoutChild(id, BoxConstraints.loose(size)); // 换行条件判断
if (parentWidth - dx < childSize.width) {
dx = 0;
dy += childSize.height;
}
// 根据 Offset 来放置 child
positionChild(id, Offset(dx, dy));
dx += childSize.width;
}
}
}
/// 该方法用来判断重新 layout 的条件
@override
bool shouldRelayout(_LabelDelegate oldDelegate) {
return oldDelegate.itemCount != this.itemCount;
}
}

在 _LabelDelegate 中,重写了 performLayout(…) 方法。方法中有一个参数 size,这个 size 表示的是当前 widget 的 parent 的 size,在我们这个例子中也就表示 Container 的 size。我们可以看看 performLayout(…) 方法的注释:

复制代码
/// Override this method to lay out and position all children given this
/// widget's size.
///
/// This method must call [layoutChild] for each child. It should also specify
/// the final position of each child with [positionChild].
void performLayout(Size size);

还有一个是 hasChild(…) 方法,这个方法接受一个 childId,childId 是由我们自己规定的,这个方法的作用是判断当前的 childId 是否对应着一个非空的 child。

满足 hasChild(…) 之后,接着就是 layoutChild(…) 来布局 child , 这个方法中我们会传递两个参数,一个是 childId,另外一个是 child 的约束(Constraints),这个方法返回的是当前这个 child 的 Size。

布局完成之后,就是如何摆放的问题了,也就是上述代码中的 positionChild(…) 了,此方法接受一个 childId 和 一个当前 child 对应的 Offset,parent 会根据这个 Offset 来放置当前的 child。

最后我们重写了 shouldRelayout(…) 方法用于判断重新 Layout 的条件。

完整源码在文章末尾给出。

效果如下:

Agora Flutter SDK:一套代码,实现双端通话(二)

2 Flutter 和 Native 的交互

我们这里说的 Native 指的是 Android 平台。

评论

发布