flutter中獲取元素的大小

本文講述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

和大小有關的方法,大概這些,那麼怎麼來獲取到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,這裏若是有其餘的方法,麻煩朋友在下面評論區留言。

下面到了實驗時間:

實驗一:非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("獲取大小"), ),)



        ],


      )
    );

  }

打印結果:

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

不是全部元素均可視,有些被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中,也同樣。

實驗三:含有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換到內部的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

相關文章
相關標籤/搜索