위젯의 높이를 얻는 방법?


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것입니다.


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

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

그러나 가장 멋진 것은 그들이 다른 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.