flutter中获取元素的大小

本文讲述flutter中获取元素的探索之旅,并总结获取元素大小的方法。

前言

Flutter的布局体系中,带有大小尺寸的元素并不多,比如SizedBox,ConstrainedBox,Padding等,通过精确设置元素大小来获取某个容器的大小这种方法无论在哪种布局体系中都是不大现实的。那么flutter怎么获取元素大小呢?

探索之旅:

和大小有关的类和方法、属性

在Flutter中,所有的元素都是Widget,那么通过Wiget能不能获得大小呢?看下Widget的属性和方法哪个和大小有关的:看了一遍源码之后结论是没有,但是Widget有个createElement方法返回了一个Element。

Element是什么?看下Element的注释:

An instantiation of a [Widget] at a particular location in the tree.

在“渲染”树中的实际位置的一个Widget实例,显然在渲染过程中,flutter实际使用的是Element,那么就必须要知道Element的大小。

这个是Element的定义,Element实现了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的方法和属性哪些是和大小有关的:

···
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;
...
}

···

这里有三个方法和大小有关,先记下。

获取到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("获取大小"), ),)



        ],


      )
    );

  }
42977714-5b16045aed975_articlex.png

打印结果:

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("获取大小"), ),)



        ],


      )
    );

  }

[图片上传失败...(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

结论:即使在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;
  }

}

打印:

3932574815-5b160a5f94a59_articlex.png

把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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容