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



        ],


      )
    );

  }

打印结果:

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


flutter探索之路
本专栏分享、收集flutter优秀中文资料,欢迎一起探索flutter。

A simple way to solving problems is using tools like docker/Spring boot/React Native/React/Vue… T...

1.2k 声望
335 粉丝
0 条评论
推荐阅读
CSplitterWnd动态分割
静态分割不提了,网上一大堆。关键是动态分割要怎么办?1、从未切分到切分2、从切分到未切分3、从切分状态m到切分状态n的转变比如这里要实现一个通达信的看盘窗口,分成主窗口和指标窗口。指标窗口可随时关闭,也...

jzoom阅读 1.1k

手把手教你写一份优质的前端技术简历
不知不觉一年一度的秋招又来了,你收获了哪些大厂的面试邀约,又拿了多少offer呢?你身边是不是有挺多人技术比你差,但是却拿到了很多大厂的offer呢?其实,要想面试拿offer,首先要过得了简历那一关。如果一份简...

tonychen152阅读 17.8k评论 5

封面图
正则表达式实例
收集在业务中经常使用的正则表达式实例,方便以后进行查找,减少工作量。常用正则表达式实例1. 校验基本日期格式 {代码...} {代码...} 2. 校验密码强度密码的强度必须是包含大小写字母和数字的组合,不能使用特殊...

寒青56阅读 8.5k评论 11

JavaScript有用的代码片段和trick
平时工作过程中可以用到的实用代码集棉。判断对象否为空 {代码...} 浮点数取整 {代码...} 注意:前三种方法只适用于32个位整数,对于负数的处理上和Math.floor是不同的。 {代码...} 生成6位数字验证码 {代码...} ...

jenemy48阅读 7.1k评论 12

从零搭建 Node.js 企业级 Web 服务器(十五):总结与展望
总结截止到本章 “从零搭建 Node.js 企业级 Web 服务器” 主题共计 16 章内容就更新完毕了,回顾第零章曾写道:搭建一个 Node.js 企业级 Web 服务器并非难事,只是必须做好几个关键事项这几件必须做好的关键事项就...

乌柏木75阅读 7.1k评论 16

再也不学AJAX了!(二)使用AJAX ① XMLHttpRequest
「再也不学 AJAX 了」是一个以 AJAX 为主题的系列文章,希望读者通过阅读本系列文章,能够对 AJAX 技术有更加深入的认识和理解,从此能够再也不用专门学习 AJAX。本篇文章为该系列的第二篇,最近更新于 2023 年 1...

libinfs42阅读 6.8k评论 12

封面图
从零搭建 Node.js 企业级 Web 服务器(一):接口与分层
分层规范从本章起,正式进入企业级 Web 服务器核心内容。通常,一块完整的业务逻辑是由视图层、控制层、服务层、模型层共同定义与实现的,如下图:从上至下,抽象层次逐渐加深。从下至上,业务细节逐渐清晰。视图...

乌柏木45阅读 8.6k评论 6

A simple way to solving problems is using tools like docker/Spring boot/React Native/React/Vue… T...

1.2k 声望
335 粉丝
宣传栏