Flutter之Widget大小與位置

大逗大人發表於2019-09-22

在某些需求下,我們需要獲取Widget的大小或位置資訊。但Widget物件本身沒有大小及位置資料,那麼想要拿到Widget的大小及位置資訊,就需要通過與Widget物件相關聯的RenderBox物件來獲取。

下面就開始來獲取Widget的大小與位置。

1、獲取RenderBox物件

要想獲取RenderBox物件,其實很簡單。只需要呼叫BuildContextfindRenderObject方法即可。程式碼如下。

//context是一個BuildContext物件
RenderBox renderBox = context.findRenderObject();
複製程式碼

但有時候,並不能順利的拿到BuildContext物件,那該怎麼辦尼?這時候就需要給Widget物件設定一個Key,然後根據這個Key來拿到BuildContext物件,從而獲取RenderBox物件,程式碼如下。

class MyHomePageState extends State<MyHomePage> {
  //定義一個key
  GlobalKey _key = GlobalKey();
  _getRenderBox() {
    //獲取`RenderBox`物件
    RenderBox renderBox = _key.currentContext.findRenderObject();
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Column(
        children: <Widget>[
          Flexible(
            flex: 2,
            child: Container(
              //設定key
              key: _key,
              color: Colors.red,
            ),
          ),
        ],
      ),
    );
  }
}
複製程式碼

但在使用的時候,我們會發現如果在initState方法通過上面的程式碼來獲取RenderBox物件是不會成功且報錯的。這是為什麼尼?其實在呼叫initState方法時,Widget還未完成渲染,也就不能獲取RenderBox物件。這時候只需要等待Widget渲染完畢即可。程式碼如下。

class MyHomePageState extends State<MyHomePage> {
  @override
  void initState() {
    //監聽Widget是否繪製完畢
    WidgetsBinding.instance.addPostFrameCallback(_getRenderBox);
    super.initState();
  }
  //定義一個key
  GlobalKey _key = GlobalKey();
  _getRenderBox(_) {
    //獲取`RenderBox`物件
    RenderBox renderBox = _key.currentContext.findRenderObject();
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Column(
        children: <Widget>[
          Flexible(
            flex: 2,
            child: Container(
              //設定key
              key: _key,
              color: Colors.red,
            ),
          ),
        ],
      ),
    );
  }
}
複製程式碼

2、獲取Widget的大小

拿到與Widget物件相關聯的RenderBox物件來後,就可以很方便的獲取Widget的大小。程式碼如下。

Size size = renderBox.size;
複製程式碼

下面來看一個示例。

class HomeState extends State<HomeWidget> {
  @override
  void initState() {
    //監聽Widget是否繪製完畢
    WidgetsBinding.instance.addPostFrameCallback(_afterLayout);
    super.initState();
  }

  void _afterLayout(_) {
    _getSizes();
  }

  _getSizes() {
    final RenderBox renderBoxRed = _keyRed.currentContext.findRenderObject();
    final sizeRed = renderBoxRed.size;
    //輸出背景為紅色的widget的寬高
    print("size of red:$sizeRed");
    final RenderBox renderBoxBlue = _keyBlue.currentContext.findRenderObject();
    final sizeBlue = renderBoxBlue.size;
    //輸出背景為藍色的widget的寬高
    print("size of Blue:$sizeBlue");
    final RenderBox renderBoxAmber =_keyAmber.currentContext.findRenderObject();
    final sizeAmber = renderBoxAmber.size;
    //輸出背景為的widget的寬高
    print("size of Amber:$sizeAmber");
  }

  GlobalKey _keyRed = GlobalKey();
  GlobalKey _keyBlue = GlobalKey();
  GlobalKey _keyAmber = GlobalKey();

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        Flexible(
          key: _keyRed,
          flex: 1,
          child: Container(
            color: Colors.red,
          ),
        ),
        Flexible(
          key: _keyBlue,
          child: Container(
            color: Colors.blue,
          ),
          flex: 2,
        ),
        Container(
          key: _keyAmber,
          width: 300,
          height: 200,
          color: Colors.amber,
        ),
      ],
    );
  }
}
複製程式碼

執行上面程式碼就可以把Widget的寬高獲取到,輸出資訊如下。

size of red:Size(411.4, 134.5)
size of Blue:Size(411.4, 269.0)
size of Amber:Size(300.0, 200.0)
複製程式碼

可以發現,獲取的寬高都具有裝置無關性。

3、獲取Widget的位置

獲取Widget的位置跟獲取其寬高一樣簡單,只需要呼叫不同API即可。如下。

Offset offset = renderBox.localToGlobal(Offset.zero);
複製程式碼

下面來看一個示例。

class HomeState extends State<HomeWidget> {
  @override
  void initState() {
    //監聽Widget是否繪製完畢
    WidgetsBinding.instance.addPostFrameCallback(_afterLayout);
    super.initState();
  }

  void _afterLayout(_) {
    _getPositions();
  }

  _getPositions() {
    final RenderBox renderBoxRed = _keyRed.currentContext.findRenderObject();
    final positionsRed = renderBoxRed.localToGlobal(Offset(0, 0));
    print("positions of red:$positionsRed");
    final RenderBox renderBoxBlue = _keyBlue.currentContext.findRenderObject();
    final positionsBlue = renderBoxBlue.localToGlobal(Offset(0, 0));
    print("positions of Blue:$positionsBlue");
    final RenderBox renderBoxAmber =
    _keyAmber.currentContext.findRenderObject();
    final positionsAmber = renderBoxAmber.localToGlobal(Offset(0, 0));
    print("positions of Amber:$positionsAmber");
  }

  GlobalKey _keyRed = GlobalKey();
  GlobalKey _keyBlue = GlobalKey();
  GlobalKey _keyAmber = GlobalKey();

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        Flexible(
          key: _keyRed,
          flex: 1,
          child: Container(
            color: Colors.red,
          ),
        ),
        Flexible(
          key: _keyBlue,
          child: Container(
            color: Colors.blue,
          ),
          flex: 2,
        ),
        //該Widget是居中顯示的
        Container(
          key: _keyAmber,
          width: 300,
          height: 200,
          color: Colors.amber,
        ),
      ],
    );
  }
}
複製程式碼

執行上面程式碼就可以把Widget的位置獲取到,輸出資訊如下。

positions of red:Offset(0.0, 80.0)
positions of Blue:Offset(0.0, 214.5)
positions of Amber:Offset(55.7, 483.4)
複製程式碼

FlutterAndroid一樣,都是以螢幕左上方為座標系的開始位置。而上面的輸出資訊就是Widget到座標系的開始位置的距離。如下圖所示。

Flutter之Widget大小與位置

4、總結

通過上面的程式碼,就可以獲取到Widget的大小及位置資訊,這在很多時候都是非常有用的。特別是當我們來自己實現一個自定義Widget的點選、手勢等事件時,就需要跟位置資訊來獲取事件在Widget中的位置。

【參考資料】

Flutter : Widget Size and Position

相關文章