本文講述flutter中獲取元素的探索之旅,并總結(jié)獲取元素大小的方法。
前言
Flutter的布局體系中,帶有大小尺寸的元素并不多,比如SizedBox,ConstrainedBox,Padding等,通過精確設(shè)置元素大小來獲取某個容器的大小這種方法無論在哪種布局體系中都是不大現(xiàn)實的。那么flutter怎么獲取元素大小呢?
探索之旅:
和大小有關(guān)的類和方法、屬性
在Flutter中,所有的元素都是Widget,那么通過Wiget能不能獲得大小呢?看下Widget的屬性和方法哪個和大小有關(guān)的:看了一遍源碼之后結(jié)論是沒有,但是Widget有個createElement方法返回了一個Element。
Element是什么?看下Element的注釋:
An instantiation of a [Widget] at a particular location in the tree.
在“渲染”樹中的實際位置的一個Widget實例,顯然在渲染過程中,flutter實際使用的是Element,那么就必須要知道Element的大小。
這個是Element的定義,Element實現(xiàn)了BuildContext
···
abstract class Element extends DiagnosticableTree implements BuildContext {
···
注意到BuildContext的屬性和方法中,有findRenderObject和size方法
abstract class BuildContext {
....
RenderObject findRenderObject();
Size get size;
void visitAncestorElements(bool visitor(Element element));
void visitChildElements(ElementVisitor visitor);
....
這個size貌似可以獲取到元素大小,visitAncestorElements和visitChildElements提供了遍歷元素的方法,先放一放,看下RenderObject的方法和屬性哪些是和大小有關(guān)的:
···
abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {
...
/// Whether the constraints are the only input to the sizing algorithm (in
/// particular, child nodes have no impact).
bool get sizedByParent => false;
...
/// An estimate of the bounds within which this render object will paint.
Rect get paintBounds;
...
/// The bounding box, in the local coordinate system, of this
/// object, for accessibility purposes.
Rect get semanticBounds;
...
}
···
這里有三個方法和大小有關(guān),先記下。
獲取到Element
和大小有關(guān)的方法,大概這些,那么怎么來獲取到Element呢?顯然Flutter的布局體系不允許我們在build的時候保存一份Widget的實例的引用,只能使用Flutter提供的Key體系,看下Key的說明:
/// A [Key] is an identifier for [Widget]s, [Element]s and [SemanticsNode]s.
Key是一個Widget、Element、SemanticsNode的標(biāo)志。
這里我只找到了一種方法來獲取:使用GlobalKey,
類似這樣:
class _MyState extends State<MyWidget>{
GlobalKey _myKey = new GlobalKey();
...
Widget build(){
return new OtherWidget(key:_myKey);
}
...
void onTap(){
_myKey.currentContext;
}
}
通過GlobalKey的currentContext方法找到當(dāng)前的Element,這里如果有其他的方法,麻煩朋友在下面評論區(qū)留言。
下面到了實驗時間:
實驗一:非ScrollView
在這個實驗中,所有元素都是可視的。
GlobalKey _myKey = new GlobalKey();
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
),
body: new Column(
children: <Widget>[
new Container(
key:_myKey,
color:Colors.black12,
child: new Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new Text("獲取大小",style: new TextStyle(fontSize: 10.0),),
new Text("獲取大小",style: new TextStyle(fontSize: 12.0),),
new Text("獲取大小",style: new TextStyle(fontSize: 15.0),),
new Text("獲取大小",style: new TextStyle(fontSize: 20.0),),
new Text("獲取大小",style: new TextStyle(fontSize: 31.0),),
new Text("獲取大小",style: new TextStyle(fontSize: 42.0),),
],
),
),
new Padding(padding: new EdgeInsets.only(top:100.0),child: new RaisedButton(onPressed:(){
RenderObject renderObject = _myKey.currentContext.findRenderObject();
print("semanticBounds:${renderObject.semanticBounds.size} paintBounds:${renderObject.paintBounds.size} size:${_myKey.currentContext.size}");
}, child: new Text("獲取大小"), ),)
],
)
);
}

打印結(jié)果:
flutter: semanticBounds:Size(168.0, 182.0) paintBounds:Size(168.0, 182.0) size:Size(168.0, 182.0)
結(jié)論:在一般情況下(不在ScrollView中,不是ScrollView),可以通過BuildContext的size方法獲取到大小,也可以通過renderObject的paintBounds和semanticBounds獲取大小。
實驗二:含有ScrollView
不是所有元素都可視,有些被ScrollView遮擋住了。
GlobalKey _myKey = new GlobalKey();
GlobalKey _myKey1 = new GlobalKey();
List<Color> colors = [ Colors.greenAccent,Colors.blueAccent,Colors.redAccent ];
List<Widget> buildRandomWidgets(){
List<Widget> list = [];
for(int i=0; i < 100; ++i){
list.add(new SizedBox(
height: 20.0,
child: new Container(
color: colors[ i %colors.length ] ,
),
));
}
return list;
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
),
body: new Column(
children: <Widget>[
new Expanded(child: new SingleChildScrollView(
child: new Container(
key:_myKey,
color:Colors.black12,
child: new Column(
mainAxisSize: MainAxisSize.min,
children: buildRandomWidgets(),
),
),
)),
new SizedBox(child:new Container(color:Colors.black),height:10.0),
new Expanded(child: new ListView(
key:_myKey1,
children: <Widget>[
new Container(
child:new Column(
mainAxisSize: MainAxisSize.min,
children: buildRandomWidgets(),
),
)
],
)),
new Padding(padding: new EdgeInsets.only(top:10.0),child: new RaisedButton(onPressed:(){
RenderObject renderObject = _myKey.currentContext.findRenderObject();
print("semanticBounds:${renderObject.semanticBounds.size} paintBounds:${renderObject.paintBounds.size} size:${_myKey.currentContext.size}");
renderObject = _myKey1.currentContext.findRenderObject();
print("semanticBounds:${renderObject.semanticBounds.size} paintBounds:${renderObject.paintBounds.size} size:${_myKey1.currentContext.size}");
}, child: new Text("獲取大小"), ),)
],
)
);
}
[圖片上傳失敗...(image-8128c4-1535626859442)]
輸出
flutter: semanticBounds:Size(375.0, 2000.0) paintBounds:Size(375.0, 2000.0) size:Size(375.0, 2000.0)
flutter: semanticBounds:Size(375.0, 2000.0) paintBounds:Size(375.0, 2000.0) size:Size(375.0, 2000.0)
注意ScrollView的元素如果不在渲染樹中,GlobalKey.currentContext是null
結(jié)論:即使在ScrollView中,也一樣。
實驗三:含有Sliver系列的固定頭部等元素:
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
),
body: new Column(
children: <Widget>[
new Expanded(child: new CustomScrollView(
slivers: <Widget>[
new SliverPersistentHeader(delegate:new _MyFixHeader(),pinned: true,floating: true,),
new SliverList(
key:_myKey,
delegate: new SliverChildBuilderDelegate( (BuildContext context,int index){
return new Column(
mainAxisSize: MainAxisSize.min,
children: buildRandomWidgets(),
);
},childCount: 1))
],
)),
new Padding(padding: new EdgeInsets.only(top:10.0),child: new RaisedButton(onPressed:(){
RenderObject renderObject = _myKey.currentContext.findRenderObject();
print("semanticBounds:${renderObject.semanticBounds.size} paintBounds:${renderObject.paintBounds.size} size:${_myKey.currentContext.size}");
// renderObject = _myKey1.currentContext.findRenderObject();
// print("semanticBounds:${renderObject.semanticBounds.size} paintBounds:${renderObject.paintBounds.size} size:${_myKey1.currentContext.size}");
}, child: new Text("獲取大小"), ),)
],
)
);
}
_MySliverHeader:
class _MySliverHeader extends SliverPersistentHeaderDelegate{
@override
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
return new Container(
color: Colors.grey,
);
}
// TODO: implement maxExtent
@override
double get maxExtent => 200.0;
// TODO: implement minExtent
@override
double get minExtent => 100.0;
@override
bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) {
return true;
}
}
打印:

把key換到內(nèi)部的Column上:
return new Column(
key:_myKey,
mainAxisSize: MainAxisSize.min,
children: buildRandomWidgets(),
);
結(jié)果:
flutter: semanticBounds:Size(375.0, 2000.0) paintBounds:Size(375.0, 2000.0) size:Size(375.0, 2000.0)
結(jié)論:SliverList等Sliver系列的Widget,不能直接使用上述放大獲得大小,必須用內(nèi)部的容器間接獲取
總結(jié)一下
1 、可以使用GlobalKey找到對應(yīng)的元素的BuildContext對象
2 、通過BuildContext對象的size屬性可以獲取大小,Sliver系列Widget除外
3 、可以通過findRender方法獲取到渲染對象,然后使用paintBounds獲取到大小。
交流qq群: 854192563