위젯의 높이를 얻는 방법?


90

LayoutBuilder위젯의 높이를 얻는 방법을 이해하지 못합니다 .

특별한 스크롤 효과를 계산할 수 있도록 위젯 목록을 표시하고 높이를 가져와야합니다. 나는 패키지를 개발 중이고 다른 개발자는 위젯을 제공합니다 (나는 그것들을 제어하지 않습니다). LayoutBuilder를 사용하여 높이를 얻을 수 있다고 읽었습니다.

아주 간단한 경우에는 LayoutBuilder.builder에 위젯을 래핑하고 스택에 넣으려고했지만 항상 minHeight 0.0, 및 maxHeight INFINITY. LayoutBuilder를 오용하고 있습니까?

편집 : LayoutBuilder가 안되는 것 같습니다. 거의 해결책 인 CustomSingleChildLayout 을 찾았습니다 .

나는 그 델리게이트를 확장했고, getPositionForChild(Size size, Size childSize)메소드 에서 위젯의 높이를 얻을 수있었습니다 . 그러나 호출되는 첫 번째 메서드는 Size getSize(BoxConstraints constraints)제약 조건으로 이러한 CustomSingleChildLayouts를 ListView에 배치하기 때문에 0에서 INFINITY를 얻습니다.

내 문제는 SingleChildLayoutDelegate getSize가 뷰의 높이를 반환 해야하는 것처럼 작동한다는 것입니다. 나는 그 순간 아이의 키를 모른다. constraints.smallest (0, 높이 0) 또는 constraints.biggest (무한대 및 앱 충돌) 만 반환 할 수 있습니다.

문서에서는 다음과 같이 말합니다.

...하지만 부모의 크기는 자녀의 크기에 따라 달라질 수 없습니다.

그리고 그것은 이상한 한계입니다.


1
LayoutBuilder는 부모의 상자 제약 조건을 제공합니다. 아이의 크기를 원한다면 다른 전략이 필요합니다. 내가 지적 할 수있는 한 가지 예는 Wrap 위젯으로, 관련 RenderWrap 클래스에서 자식의 크기에 따라 레이아웃을 수행합니다. 이것은 build ()가 아니라 레이아웃 중에 발생합니다.
— Jonah Williams

@JonahWilliams 흠. 랩이 아이들을 배치하도록 설계된 위젯이기 때문에 어떻게 도와 줄 수 있는지 모르겠습니다 (웹의 플렉스 박스 그리드처럼 작동합니다). 높이를 찾는 데 필요한 자식 위젯이 하나 있습니다. 질문의 편집을 참조하십시오. CustomSingleChildLayout으로 문제를 거의 해결했지만 한계에 갇혔습니다.
— Gudin

원하는 것을 더 구체적으로 설명 할 수 있습니까? 여러 솔루션이 있습니다. 그러나 각각 다른 사용 사례가 있습니다.
— Rémi Rousselet

확실한. 패키지를 개발 중입니다. 사용자 / 개발자가 내 수업에 위젯을 제공합니다. 우리는 여기에서 new Text("hello")더 복잡한 것 까지 모든 위젯에 대해 이야기하고 있습니다. 이 위젯을 ListView에 놓고 스크롤 효과를 계산하려면 높이가 필요합니다. SingleChildLayoutDelegate가 수행하는 것과 마찬가지로 레이아웃 시간에 높이를 얻는 것이 좋습니다.
— Gudin

"스크롤 효과"란 무엇을 의미합니까? 예가 있습니까?
— Rémi Rousselet

답변:


159

화면에 위젯의 크기 / 위치를 얻으려면, 당신이 사용할 수있는 GlobalKey자사 얻기 위해 BuildContext다음을 찾을 수RenderBox 해당 위젯의 전역 위치와 렌더링 된 크기를 포함 할 특정 위젯 있습니다.

주의해야 할 사항은 하나뿐입니다. 위젯이 렌더링되지 않으면 해당 컨텍스트가 존재하지 않을 수 있습니다. 문제를 일으킬 수있는ListView위젯이 잠재적으로 표시되는 경우에만 렌더링되므로 가 발생할 수 있습니다.

또 다른 문제는 위젯이 아직 렌더링되지 않았기 때문에 호출 RenderBox중에 위젯을 가져올 수 없다는 build것입니다.


하지만 빌드하는 동안 크기가 필요합니다! 어떡해?

거기 캔 도움말 위젯 하나의 멋진는 다음과 같습니다 Overlay과OverlayEntry . 다른 모든 것 위에 위젯을 표시하는 데 사용됩니다 (스택과 유사).

그러나 가장 멋진 것은 그들이 다른 build흐름 에 있다는 것입니다 . 그들은 이후 에 지어 졌습니다. 일반 위젯 됩니다.

그것은 하나의 멋진 의미 OverlayEntry를 가지고 있습니다 : 실제 위젯 트리의 위젯에 따라 크기를 가질 수 있습니다.


괜찮아. 그러나 OverlayEntry는 수동으로 다시 빌드 할 필요가 없습니까?

예, 그렇습니다. 그러나 알아 두어야 할 또 다른 사항이 있습니다. ScrollController, a에 전달 된 Scrollable,는 AnimationController.

당신이 결합 할 수있는 수단 AnimatedBuilderA를을 ScrollController, 그것은 스크롤에 자동으로 위젯을 재건 아름다운 영향을 미칠 것입니다. 이 상황에 딱 맞죠?


모든 것을 예제로 결합 :

다음 예에서는 내부 위젯을 따라 가며 ListView동일한 높이를 공유 하는 오버레이를 볼 수 있습니다 .

import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final controller = ScrollController();
  OverlayEntry sticky;
  GlobalKey stickyKey = GlobalKey();

  @override
  void initState() {
    if (sticky != null) {
      sticky.remove();
    }
    sticky = OverlayEntry(
      builder: (context) => stickyBuilder(context),
    );

    SchedulerBinding.instance.addPostFrameCallback((_) {
      Overlay.of(context).insert(sticky);
    });

    super.initState();
  }

  @override
  void dispose() {
    sticky.remove();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ListView.builder(
        controller: controller,
        itemBuilder: (context, index) {
          if (index == 6) {
            return Container(
              key: stickyKey,
              height: 100.0,
              color: Colors.green,
              child: const Text("I'm fat"),
            );
          }
          return ListTile(
            title: Text(
              'Hello $index',
              style: const TextStyle(color: Colors.white),
            ),
          );
        },
      ),
    );
  }

  Widget stickyBuilder(BuildContext context) {
    return AnimatedBuilder(
      animation: controller,
      builder: (_,Widget child) {
        final keyContext = stickyKey.currentContext;
        if (keyContext != null) {
          // widget is visible
          final box = keyContext.findRenderObject() as RenderBox;
          final pos = box.localToGlobal(Offset.zero);
          return Positioned(
            top: pos.dy + box.size.height,
            left: 50.0,
            right: 50.0,
            height: box.size.height,
            child: Material(
              child: Container(
                alignment: Alignment.center,
                color: Colors.purple,
                child: const Text("^ Nah I think you're okay"),
              ),
            ),
          );
        }
        return Container();
      },
    );
  }
}

노트 :

다른 화면으로 이동할 때 다음을 호출하지 않으면 고정 된 상태로 유지됩니다.

sticky.remove();

5
좋아, 드디어 테스트 할 시간이 생겼다. 그래서 실제로는 첫 번째 부분 만 필요했습니다. .NET과 함께 context.height 액세스를 사용할 수 있는지 몰랐습니다 GlobalKey. 좋은 대답입니다.
— Gudin apr

1
일정 바인더를 가져 오는 방법은 무엇입니까?
— siddhesh

1
나는 import 'package : flutter / scheduler.dart.'; 하지만 난 오류 목표를 가지고 URI가 존재하지 않습니다 @ 레미 - rousselet
— siddhesh

@ rémi-rousselet 높이를 ListView의 높이에 따라 제어하고 싶은 ListView 뒤에 위젯이있을 때 어떻게해야합니까?
— RoyalGriffin

2
Remi, 영리한 해결책과 훌륭한 설명에 감사드립니다. 질문이 있습니다. 눌 렸을 때 Recta의 ListItem 을 알고 싶다면 어떻게해야할까요? 모든 listItem에 대해 ListView.builder설정하는 것이 과잉 GlobalKey listItemKey = GlobalKey();일까요? +10000 개의 항목이 있다고 가정 해 보겠습니다. 성능 / 메모리 문제없이이를 관리 할 수있는 현명한 방법이 있습니까?
— aytunch

50

이것은 (내 생각에) 이것을 수행하는 가장 간단한 방법입니다.

다음을 프로젝트에 복사하여 붙여 넣으십시오.

UPDATE : 사용 RenderProxyBox하면 하위 및 하위 항목의 모든 재 빌드에서 호출되기 때문에 약간 더 정확한 구현이 가능합니다. 이는 항상 최상위 build () 메서드의 경우는 아닙니다.

참고 : Hixie에 의해 지적이 정확하게이 작업을 수행 할 수있는 효율적인 방법이 아니다 여기 . 그러나 가장 쉽습니다.

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

typedef void OnWidgetSizeChange(Size size);

class MeasureSizeRenderObject extends RenderProxyBox {
  Size oldSize;
  final OnWidgetSizeChange onChange;

  MeasureSizeRenderObject(this.onChange);

  @override
  void performLayout() {
    super.performLayout();

    Size newSize = child.size;
    if (oldSize == newSize) return;

    oldSize = newSize;
    WidgetsBinding.instance.addPostFrameCallback((_) {
      onChange(newSize);
    });
  }
}

class MeasureSize extends SingleChildRenderObjectWidget {
  final OnWidgetSizeChange onChange;

  const MeasureSize({
    Key key,
    @required this.onChange,
    @required Widget child,
  }) : super(key: key, child: child);

  @override
  RenderObject createRenderObject(BuildContext context) {
    return MeasureSizeRenderObject(onChange);
  }
}

그런 다음 크기를 측정하려는 위젯을 MeasureSize.


var myChildSize = Size.zero;

Widget build(BuildContext context) {
  return ...( 
    child: MeasureSize(
      onChange: (size) {
        setState(() {
          myChildSize = size;
        });
      },
      child: ...
    ),
  );
}

그래서 그래, 부모의 크기는 할 수 있습니다 당신은 충분히 열심히하려고하면 아이의 크기에 따라 달라집니다.


개인 일화- Align터무니없는 공간을 차지하는 것을 좋아하는와 같은 위젯의 크기를 제한하는 데 유용 합니다.


여기서 얻은 것은 훌륭합니다. 미친 설명은 없습니다. 작동하는 코드 만 있습니다. 감사!
— Danny Daglas

1
멋진 솔루션 형제. 이것을 펍 패키지로 만드십시오.
— Harsh Bhikadia

많은 높이와 너비 문제, 감사에 대한 아주 좋은 솔루션
— 알리레자 daryani

이것은 작동하지만 일부 장소에서는 사용하기가 어렵습니다. 예를 들어,에 PreferredSizeWidget, preferredSize당신은 쉬운 방법으로 높이를 설정할 수 없도록 한 번만 호출됩니다.
— user2233706 2010 년

안녕하세요, 빌드 ()가 재 빌드를 위해 호출되지 않는 경우를 지원하기 위해 구현을 업데이트했습니다. 이것이 더 정확하기를 바랍니다.
— Dev Aggarwal

6

내가 올바르게 이해했다면 임의의 위젯의 크기를 측정하고 해당 위젯을 다른 위젯으로 래핑 할 수 있습니다. 이 경우이 답변 의 방법 이 효과적입니다.

기본적으로 해결책은 첫 번째 프레임이 렌더링 된 후 호출되는 위젯 라이프 사이클에서 콜백을 바인딩하는 것 context.size입니다. 여기서. 문제는 상태 저장 위젯 내에서 측정하려는 위젯을 래핑해야한다는 것입니다. 그리고 build()그 안에 크기가 절대적으로 필요한 경우 두 번째 렌더링에서만 액세스 할 수 있습니다 (첫 번째 렌더링 이후에만 사용 가능).


1
- 감사 포인터에 대한 나의 완벽한 솔루션 체크 아웃 stackoverflow.com/a/60868972/7061265
— 데브 가르 왈

3

다음 LayoutBuilder은 위젯의 크기를 결정하는 데 사용할 수있는 방법에 대한 샘플입니다 .

LayoutBuilder위젯은 상위 위젯의 제약 조건을 결정할 수 있기 때문에 사용 사례 중 하나는 하위 위젯이 상위의 크기에 맞게 조정되도록하는 것입니다.

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  var dimension = 40.0;

  increaseWidgetSize() {
    setState(() {
      dimension += 20;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(children: <Widget>[
          Text('Dimension: $dimension'),
          Container(
            color: Colors.teal,
            alignment: Alignment.center,
            height: dimension,
            width: dimension,
            // LayoutBuilder inherits its parent widget's dimension. In this case, the Container in teal
            child: LayoutBuilder(builder: (context, constraints) {
              debugPrint('Max height: ${constraints.maxHeight}, max width: ${constraints.maxWidth}');
              return Container(); // create function here to adapt to the parent widget's constraints
            }),
          ),
        ]),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: increaseWidgetSize,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

데모

데모

로그

I/flutter (26712): Max height: 40.0, max width: 40.0
I/flutter (26712): Max height: 60.0, max width: 60.0
I/flutter (26712): Max height: 80.0, max width: 80.0
I/flutter (26712): Max height: 100.0, max width: 100.0

동적 크기가 작동하지 않습니다
— 르낭 코엘류에게

문제를 일으키는 "동적 크기"에 대해 자세히 설명해 주시겠습니까? 내가 확인할 수있는 최소한의 재현이 있습니까?
— Omatt 2012

위젯의 폭을 얻을 수 / 높이를 동적으로 호출 할 필요가 .findRenderObejct()다음과 .size. RenderBox box = widget.context.findRenderObject(); print(box.size);
— Renan Coelho

당신은 또한 위젯의 핵심으로 GlobalKey을 통과 한 후 호출 할 수 있습니다_myKey.currentContext.findRenderObject()
— 르낭 코엘류에게

1

그것에 대한 위젯을 드릴게요


class SizeProviderWidget extends StatefulWidget {
  final Widget child;
  final Function(Size) onChildSize;

  const SizeProviderWidget({Key key, this.onChildSize, this.child})
      : super(key: key);
  @override
  _SizeProviderWidgetState createState() => _SizeProviderWidgetState();
}

class _SizeProviderWidgetState extends State<SizeProviderWidget> {

  @override
  void initState() {
    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
      widget.onChildSize(context.size);
    });
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return widget.child;
  }
}

여기서 일어나는 일은 어떤 위젯에서든 위젯이 빌드되면 크기 변수를 사용할 수 있으므로 위젯이 빌드 된 직후 호출 될 함수를 위의 위젯에 전달하여 사용할 수있는 빌드 된 위젯의 크기를 사용합니다.

SizeProviderWidget(
 child:MyWidget(),//the widget we want the size of,
 onChildSize:(size){
  //the size of the rendered MyWidget() is available here
 }
)

편집은 그냥 포장 SizeProviderWidget과를 OrientationBuilder이 장치의 방향을 존중하기 위해


안녕! 이것은 처음에는 잘 작동하는 것처럼 보였지만 한 가지주의 사항을 발견했습니다. 장치 방향 변경시 크기가 업데이트되지 않았습니다. 상태 저장 위젯의 상태가 장치 회전에서 다시 초기화되지 않기 때문이라고 생각합니다.
— mattorb

안녕하세요, flutter는 레고처럼 모듈 식입니다. 위의 위젯을 OrientationBuilder모든 방향을 존중하기 시작 하는 것으로 감싸
— 세요

0

findRenderObject()RenderBox그린 위젯의 크기를 제공하는 데 사용 되는를 반환하며 위젯 트리가 빌드 된 후 호출되어야하므로 일부 콜백 메커니즘 또는 addPostFrameCallback()콜백 과 함께 사용해야합니다 .

class SizeWidget extends StatefulWidget {
  @override
  _SizeWidgetState createState() => _SizeWidgetState();
}

class _SizeWidgetState extends State<SizeWidget> {
  final GlobalKey _textKey = GlobalKey();
  Size textSize;

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) => getSizeAndPosition());
  }

  getSizeAndPosition() {
    RenderBox _cardBox = _textKey.currentContext.findRenderObject();
    textSize = _cardBox.size;
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Size Position"),
      ),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: <Widget>[
          Text(
            "Currern Size of Text",
            key: _textKey,
            textAlign: TextAlign.center,
            style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
          ),
          SizedBox(
            height: 20,
          ),
          Text(
            "Size - $textSize",
            textAlign: TextAlign.center,
          ),
        ],
      ),
    );
  }
}

산출:

여기에 이미지 설명 입력


_textKey.currentContext.size충분하다
— 르낭 코엘류

0

패키지 사용 : z_tools . 단계:

1. 메인 파일 변경

void main() async {
  runZoned(
    () => runApp(
      CalculateWidgetAppContainer(
        child: Center(
          child: LocalizedApp(delegate, MyApp()),
        ),
      ),
    ),
    onError: (Object obj, StackTrace stack) {
      print('global exception: obj = $obj;\nstack = $stack');
    },
  );
}

2. 기능에서 사용

_Cell(
    title: 'cal: Column-min',
    callback: () async {
        Widget widget1 = Column(
        mainAxisSize: MainAxisSize.min,
        children: [
            Container(
            width: 100,
            height: 30,
            color: Colors.blue,
            ),
            Container(
            height: 20.0,
            width: 30,
            ),
            Text('111'),
        ],
        );
        // size = Size(100.0, 66.0)
        print('size = ${await getWidgetSize(widget1)}');
    },
),

0

가장 쉬운 방법은 런타임에 자식의 크기를 계산하는 위젯 인 MeasuredSize 를 사용 하는 것입니다.

다음과 같이 사용할 수 있습니다.

MeasuredSize(
          onChange: (Size size) {
            setState(() {
              print(size);
            });
          },
          child: Text(
            '$_counter',
            style: Theme.of(context).textTheme.headline4,
          ),
        );

여기에서 찾을 수 있습니다. https://pub.dev/packages/measured_size


-2
<p>@override
<br/>
  Widget build(BuildContext context) {<br/>
   &nbsp;&nbsp; &nbsp;return Container(<br/>
     &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; width: MediaQuery.of(context).size.width,<br/>
     &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; height: MediaQuery.of(context).size.height,<br/>
    &nbsp;&nbsp;&nbsp; &nbsp;&nbsp; color: Colors.blueAccent,<br/>
   &nbsp;&nbsp; &nbsp;);<br/>
  }</p>
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.