本文讲述flutter中获取元素的探索之旅,并总结获取元素大小的方法。node
Flutter的布局体系中,带有大小尺寸的元素并很少,好比SizedBox,ConstrainedBox,Padding等,经过精确设置元素大小来获取某个容器的大小这种方法不管在哪一种布局体系中都是不大现实的。那么flutter怎么获取元素大小呢?app
在Flutter中,全部的元素都是Widget,那么经过Wiget能不能得到大小呢?看下Widget的属性和方法哪一个和大小有关的:看了一遍源码以后结论是没有,可是Widget有个createElement方法返回了一个Element。dom
Element是什么?看下Element的注释:ide
An instantiation of a [Widget] at a particular location in the tree.
在“渲染”树中的实际位置的一个Widget实例,显然在渲染过程当中,flutter实际使用的是Element,那么就必需要知道Element的大小。布局
这个是Element的定义,Element实现了BuildContextui
abstract class Element extends DiagnosticableTree implements BuildContext {
注意到BuildContext的属性和方法中,有findRenderObject和size方法this
abstract class BuildContext { RenderObject findRenderObject(); ... Size get size; ... void visitAncestorElements(bool visitor(Element element)); void visitChildElements(ElementVisitor visitor);
这个size貌似能够获取到元素大小,visitAncestorElements和visitChildElements提供了遍历元素的方法,先放一放,看下RenderObject的方法和属性哪些是和大小有关的:spa
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; ... }
这里有三个方法和大小有关,先记下。code
和大小有关的方法,大概这些,那么怎么来获取到Element呢?显然Flutter的布局体系不容许咱们在build的时候保存一份Widget的实例的引用,只能使用Flutter提供的Key体系,看下Key的说明:对象
/// A [Key] is an identifier for [Widget]s, [Element]s and [SemanticsNode]s.
Key是一个Widget、Element、SemanticsNode的标志。
这里我只找到了一种方法来获取:使用GlobalKey,
相似这样:
class _MyState extends State<MyWidget>{ GlobalKey _myKey = new GlobalKey(); ... Widget build(){ return new OtherWidget(key:_myKey); } ... void onTap(){ _myKey.currentContext; } }
经过GlobalKey的currentContext方法找到当前的Element,这里若是有其余的方法,麻烦朋友在下面评论区留言。
下面到了实验时间:
在这个实验中,全部元素都是可视的。
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("获取大小"), ),) ], ) ); }
打印结果:
flutter: semanticBounds:Size(168.0, 182.0) paintBounds:Size(168.0, 182.0) size:Size(168.0, 182.0)
结论:在通常状况下(不在ScrollView中,不是ScrollView),能够经过BuildContext的size方法获取到大小,也能够经过renderObject的paintBounds和semanticBounds获取大小。
不是全部元素均可视,有些被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("获取大小"), ),) ], ) ); }
输出
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
结论:即便在ScrollView中,也同样。
@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换到内部的Column上:
return new Column( key:_myKey, mainAxisSize: MainAxisSize.min, children: buildRandomWidgets(), );
结果:
flutter: semanticBounds:Size(375.0, 2000.0) paintBounds:Size(375.0, 2000.0) size:Size(375.0, 2000.0)
结论:SliverList等Sliver系列的Widget,不能直接使用上述方法得到大小,必须用内部的容器间接获取
1 、可使用GlobalKey找到对应的元素的BuildContext对象
2 、经过BuildContext对象的size属性能够获取大小,Sliver系列Widget除外
3 、能够经过findRender方法获取到渲染对象,而后使用paintBounds获取到大小。
交流qq群: 854192563