flutter中获取元素的大小

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 中使用 `SingleChildScrollView` 撑开高度以解决内容高度不足的问题 在 Flutter 开发中,如果遇到 `SingleChildScrollView` 的子组件无法撑开其父容器的高度问题,通常是因为布局约束未被正确处理。以下是详细的解决方案: #### 1. 使用 `Expanded` 或 `Flexible` 当 `SingleChildScrollView` 嵌套在一个有限制的父级容器(如 `Column`)中时,可以通过将其包裹在 `Expanded` 或 `Flexible` 组件中来允许它扩展到可用空间。 ```dart Column( children: [ Expanded( child: SingleChildScrollView( child: YourContentWidget(), ), ), ], ); ``` 这种方式适用于 `SingleChildScrollView` 需要占据剩余空间的情况[^1]。 --- #### 2. 明确指定父容器的高度 通过显式设置父容器的高度可以有效解决问题。例如,在某些场景下可以直接利用 `Container` 并为其提供固定高度或动态计算的高度。 ```dart Container( height: calculateDynamicHeight(), // 动态计算高度逻辑 child: SingleChildScrollView( child: YourContentWidget(), ), ); ``` 对于动态高度的计算,可参考引用中的方法,比如通过 `GlobalKey` 和 `MediaQuery` 来获取特定组件的实际尺寸[^4]。 --- #### 3. 利用 `IntrinsicHeight` 自动适配内部子组件高度 `IntrinsicHeight` 是一种让父容器自动适应子组件实际高度的方式。需要注意的是,这种方法性能成本较高,因此仅适合简单场景。 ```dart IntrinsicHeight( child: Column( children: [ SingleChildScrollView( child: YourContentWidget(), ), ], ), ); ``` 此方式特别适合于需要精确控制列布局高度的情况下[^2]。 --- #### 4. 调整滚动视图的行为 有时,`SingleChildScrollView` 可能因为嵌套关系而受到不合理的约束影响。此时可通过调整其行为参数实现更灵活的效果。例如,启用 `shrinkWrap` 属性可以让滚动视图只占用必要的最小空间。 ```dart SingleChildScrollView( shrinkWrap: true, child: YourContentWidget(), ); ``` 这种配置尤其适用于列表或其他复杂结构下的局部区域滚动需求[^3]。 --- #### 5. 结合 `LayoutBuilder` 实现响应式设计 为了使应用更加智能化地应对不同设备屏幕大小的变化,推荐采用 `LayoutBuilder` 对当前上下文环境进行检测并作出相应调整。 ```dart LayoutBuilder(builder: (context, constraints) { return SingleChildScrollView( child: ConstrainedBox( constraints: BoxConstraints(minHeight: constraints.maxHeight), child: IntrinsicHeight( child: YourContentWidget(), ), ), ); }); ``` 以上代码片段展示了如何基于现有约束条件构建合适的 UI 构造[^1]。 --- ### 总结 针对 `SingleChildScrollView` 内容高度不足的问题,开发者可以根据具体项目需求选择合适的方法加以改进。无论是借助内置工具还是手动干预,都需注意保持良好的用户体验以及高效的运行效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值