एक विजेट की ऊंचाई कैसे प्राप्त करें?


90

मुझे समझ में नहीं आता कि कैसे LayoutBuilderएक विजेट की ऊंचाई पाने के लिए उपयोग किया जाता है।

मुझे विजेट की सूची प्रदर्शित करने और उनकी ऊंचाई प्राप्त करने की आवश्यकता है ताकि मैं कुछ विशेष स्क्रॉल प्रभाव की गणना कर सकूं। मैं एक पैकेज विकसित कर रहा हूं और अन्य डेवलपर्स विजेट प्रदान करते हैं (मैं उन्हें नियंत्रित नहीं करता हूं)। मैंने पढ़ा कि ऊँचाई पाने के लिए LayoutBuilder का उपयोग किया जा सकता है।

बहुत ही साधारण मामले में, मैंने Widget को लेआउटब्यू.बिल्डर में लपेटने और स्टैक में डालने की कोशिश की, लेकिन मुझे हमेशा मिलता है minHeight 0.0, और maxHeight INFINITY। क्या मैं LayoutBuilder का दुरुपयोग कर रहा हूं?

संपादित करें : ऐसा लगता है कि LayoutBuilder एक नहीं है। मैंने CustomSingleChildLayout पाया जो लगभग एक समाधान है।

मैंने उस प्रतिनिधि को बढ़ाया, और मैं getPositionForChild(Size size, Size childSize)विधि में विजेट की ऊंचाई प्राप्त करने में सक्षम था । लेकिन, पहली विधि जिसे कहा जाता है वह है Size getSize(BoxConstraints constraints)और बाधाओं के रूप में, मुझे 0 से सूचना मिलती है क्योंकि मैं इन CustomSingleChildLayouts को एक ListView में रख रहा हूं।

मेरी समस्या यह है कि SingleChildLayoutDelegate इस getSizeतरह काम करता है जैसे किसी दृश्य की ऊंचाई को वापस करना हो। मैं उस पल में एक बच्चे की ऊंचाई नहीं जानता। मैं केवल constraints.smallest (जो कि 0 है, ऊँचाई 0 है), या constraints.biggest जो कि अनंत है और ऐप को क्रैश कर सकता है।

डॉक्स में यह भी कहते हैं:

... लेकिन माता-पिता का आकार बच्चे के आकार पर निर्भर नहीं कर सकता।

और यह एक अजीब सीमा है।


1
LayoutBuilder आपको माता-पिता के बॉक्स की कमी देगा। यदि आप बच्चे के आकार चाहते हैं तो आपको एक अलग रणनीति की आवश्यकता है। एक उदाहरण जिसे मैं रैप विजेट कह सकता हूं, वह संबंधित RenderWrap क्लास में बच्चों के आकार के आधार पर लेआउट करता है। यह लेआउट के दौरान होता है, हालांकि नहीं ()।
जोनाह विलियम्स

@ जोंहविलियम्स हम्म। मैं नहीं देखता कि कैसे लपेटें मुझे मदद कर सकती हैं क्योंकि यह विजेट बच्चों के लेआउट के लिए डिज़ाइन किया गया है (वेब ​​से फ्लेक्सबॉक्स ग्रिड की तरह काम करता है)। मेरे पास एक बच्चा विजेट है जिसकी मुझे ऊंचाई खोजने की आवश्यकता है। कृपया, प्रश्न में संपादित देखें। मैंने लगभग CustomSingleChildLayout के साथ समस्या को हल कर दिया, लेकिन इसकी सीमा पर अटक गया।
गुडिन

क्या आप बता सकते हैं कि आप विशेष रूप से क्या चाहते हैं? कई उपाय हैं। लेकिन प्रत्येक के उपयोग के मामले अलग हैं।
रमी रूसेलेट

ज़रूर। मैं एक पैकेज विकसित कर रहा हूं। उपयोगकर्ता / डेवलपर मेरी कक्षा को एक विजेट प्रदान करता है। हम यहां किसी भी विजेट के बारे में बात कर रहे हैं, new Text("hello")अधिक जटिल लोगों से। मैं इन विजेट्स को सूची दृश्य में रखता हूं, और मुझे कुछ स्क्रॉल प्रभाव की गणना करने के लिए उनकी ऊंचाई की आवश्यकता है। मैं लेआउट के समय ऊंचाई पाने के साथ ठीक हूं, जैसे कि SingleChildLayoutDelegate क्या कर रहा है।
गुदीन

"स्क्रॉल प्रभाव" से आपका क्या अभिप्राय है? आपके पास कोई उदाहरण है ?
रमी रौस्लेट

जवाबों:


159

स्क्रीन पर एक विजेट के आकार / स्थिति पाने के लिए आपको उपयोग कर सकते हैं GlobalKeyअपने पाने के लिए BuildContextतो लगता है RenderBoxकि विशिष्ट विजेट है, जो अपनी वैश्विक स्थिति और गाया आकार में शामिल होंगे की।

बस एक बात का ध्यान रखें: यदि विजेट प्रदान नहीं किया गया है तो यह संदर्भ मौजूद नहीं हो सकता है। जो ListViewविजेट के साथ एक समस्या का कारण बन सकते हैं, यदि वे संभावित रूप से दिखाई दे रहे हैं तो ही प्रदान किए जाते हैं।

एक और समस्या यह है कि आप कॉल के RenderBoxदौरान एक विजेट प्राप्त नहीं कर सकते buildक्योंकि विजेट अभी तक प्रदान नहीं किया गया है।


लेकिन मुझे निर्माण के दौरान आकार की आवश्यकता है! मैं क्या कर सकता हूँ?

एक शांत विजेट है जो मदद कर सकता है: Overlayऔर इसकी OverlayEntry। वे सब कुछ के ऊपर विगेट्स प्रदर्शित करने के लिए उपयोग किए जाते हैं (स्टैक के समान)।

लेकिन सबसे अच्छी बात यह है कि वे एक अलग buildप्रवाह पर हैं; वे नियमित विजेट के बाद निर्मित होते हैं ।

इसका एक सुपर कूल निहितार्थ है: OverlayEntryएक आकार हो सकता है जो वास्तविक विजेट ट्री के विजेट पर निर्भर करता है।


ठीक है। लेकिन OverlayEntry को मैन्युअल रूप से पुनर्निर्माण करने की आवश्यकता नहीं है?

हाँ, वो करते हैं। लेकिन, के बारे में पता होना करने के लिए एक और बात है: ScrollControllerकरने के लिए पारित Scrollable, के समान एक सुनने योग्य है AnimationController

जिसका अर्थ है कि आप एक के AnimatedBuilderसाथ गठबंधन कर सकते हैं 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
ठीक है, आखिरकार इसे परीक्षण करने का समय मिल गया। इसलिए, मुझे वास्तव में केवल पहले भाग की आवश्यकता थी। मुझे नहीं पता था कि मैं इसके साथ संदर्भ का उपयोग कर सकता हूं GlobalKey। बहुत बढ़िया जवाब।
गुद्दीन

1
अनुसूचक बांधने की मशीन आयात करने के लिए कैसे?
सिद्धेश

1
मैंने आयात 'पैकेज: स्पंदन / शेड्यूलर.डार्ट' की कोशिश की है; लेकिन मुझे त्रुटि लक्ष्य uri मौजूद नहीं है @ rémi-rousselet
siddhesh

@ rémi-rousselet जब मैं ListView जिनकी ऊंचाई के पीछे एक विजेट है, तो मैं यह काम कैसे करूं, मैं ListView की ऊंचाई के अनुसार नियंत्रण करना चाहता हूं?
रॉयलग्रिफिन

2
रेमी, चतुर समाधान और महान स्पष्टीकरण के लिए धन्यवाद। मेरा एक सवाल है। क्या होगा अगर हम Rectकिसी भी लिस्ट के बारे में जानना ListView.builderचाहते हैं जब उन्हें दबाया जाता है। यह GlobalKey listItemKey = GlobalKey();हर सूची के लिए सेट करने के लिए एक overkill होगा ? मान लीजिए कि मेरे पास +10000 आइटम हैं। क्या प्रदर्शन / स्मृति समस्याओं के बिना इसे प्रबंधित करने का एक चतुर तरीका है?
१unch

50

यह (मुझे लगता है) ऐसा करने का सबसे सीधा तरीका है।

अपने प्रोजेक्ट में निम्नलिखित को कॉपी-पेस्ट करें।

अद्यतन: का उपयोग कर RenderProxyBox थोड़ा और सही कार्यान्वयन में परिणामों करना, क्योंकि यह बच्चे और उसके वंश के हर पुनर्निर्माण पर कहा जाता है, जो हमेशा शीर्ष स्तर के निर्माण () विधि के लिए नहीं होता है।

नोट: यह बिल्कुल ऐसा करने का एक कुशल तरीका नहीं है, जैसा कि हिक्सी ने यहां बताया है । लेकिन यह सबसे आसान है।

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, जो अंतरिक्ष की एक बेतुकी राशि लेना पसंद करता है।


आपको यहां जो मिला है वह बहुत अच्छा है। कोई पागल व्याख्या नहीं .. बस कोड काम करता है। धन्यवाद!
डैनी डागलास

1
बहुत बढ़िया समाधान भाई। इसे पब पैकेज के रूप में बनाएं।
हर्ष भिकडिया

कई ऊंचाई और चौड़ाई के मुद्दों के लिए इसका बहुत अच्छा समाधान, धन्यवाद
alireza daryani

यह काम करता है, लेकिन कुछ स्थानों पर इसका उपयोग करना मुश्किल है। उदाहरण के लिए, एक में PreferredSizeWidget, preferredSizeकेवल एक बार कहा जाता है, इसलिए आप ऊंचाई को एक आसान तरीके से निर्धारित नहीं कर सकते।
user2233706

अरे, मैंने उन मामलों का समर्थन करने के लिए कार्यान्वयन को अपडेट किया है जहां बिल्ड () को पुनर्वित्त के लिए नहीं बुलाया जाता है। आशा है कि यह अधिक सही है।
देव अग्रवाल

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

गतिशील आकार के साथ काम नहीं करता है
रेनन कोल्हो

क्या आप "गतिशील आकार" का विस्तार कर सकते हैं जो इस मुद्दे का कारण बनता है? क्या आपके पास एक न्यूनतम रिप्रो है जिसे मैं जांच सकता हूं?
ओमैट

विजेट की चौड़ाई / ऊंचाई प्राप्त करने के लिए गतिशील रूप से आपको कॉल करने की आवश्यकता है .findRenderObejct()और फिर .sizeRenderBox box = widget.context.findRenderObject(); print(box.size);
रेनन कोएलो

आप विजेट की कुंजी के रूप में एक GlobalKey भी पास कर सकते हैं और फिर कॉल कर सकते हैं_myKey.currentContext.findRenderObject()
Renan Coelho

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यह उपकरण के ओरिएंटेशन का सम्मान करने के लिए


नमस्ते! यह शुरुआत में अच्छी तरह से काम करने के लिए दिखाई दिया, लेकिन मैंने एक चेतावनी की खोज की: आकार ने डिवाइस अभिविन्यास परिवर्तनों पर अपडेट नहीं किया। मुझे संदेह है कि एक स्टेटफुल विजेट के लिए राज्य डिवाइस रोटेशन पर पुनर्निवेशित नहीं होता है।
मटेरब

हाय, स्पंदन लेगो की तरह बहुत मॉड्यूलर है, बस उपरोक्त विजेट को OrientationBuilderकिसी भी अभिविन्यास का सम्मान करने के लिए शुरू होता है, मैं डिवाइस haha ​​का मतलब
Yadu

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(
          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>
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.