【Flutter學習】頁面布局之寬高尺寸處理


一,概述  

  Flutter中擁有30多種預定義的布局widget,常用的有ContainerPaddingCenterFlexRowColumListViewGridView。按照《Flutter技術入門與實戰》上面來說的話,大概分為四類

  • 基礎布局組件Container(容器布局),Center(居中布局),Padding(填充布局),Align(對齊布局),Colum(垂直布局),Row(水平布局),Expanded(配合Colum,Row使用),FittedBox(縮放布局),Stack(堆疊布局),overflowBox(溢出父視圖容器)。
  • 寬高尺寸處理SizedBox(設置具體尺寸),ConstrainedBox(限定最大最小寬高布局),LimitedBox(限定最大寬高布局),AspectRatio(調整寬高比),FractionallySizedBox(百分比布局)
  • 列表和表格處理ListView(列表),GridView(網格),Table(表格)
  • 其它布局處理:Transform(矩陣轉換),Baseline(基准線布局),Offstage(控制是否顯示組件),Wrap(按寬高自動換行布局)

二,寬高尺寸處理

    • SizedBox(設置具體尺寸)
      • 介紹  
        比較常用的一個控件,設置具體尺寸。SizeBox組件是一個特定大小的盒子,這個組件強制它的chird有特定的寬度和高度,如果寬度和高度為null,則此組件將調整自身大小匹配該緯度中child的大小。
      • 布局行為

             SizedBox布局行為相對較簡單:

        • child不為null時,如果設置了寬高,則會強制把child尺寸調到此寬高;如果沒有設置寬高,則會根據child尺寸進行調整;
        • child為null時,如果設置了寬高,則自身尺寸調整到此寬高值,如果沒設置,則尺寸為0;
      • 繼承關系
        Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > SizedBox
      • 構造方法
        const SizedBox({ 
        Key key,
        this.width, //寬
        this.height, //高
        Widget child //子組件
        })
      • 參數解析

        width:寬度值,如果具體設置了,則強制child寬度為此值,如果沒設置,則根據child寬度調整自身寬度。

        height:同上。

    • ConstrainedBox(限定最大最小寬高布局)
      • 介紹
        這個控件的作用是添加額外的限制條件(constraints)到child上,本身挺簡單的,可以被一些控件替換使用。Flutter的布局控件體系,梳理着發現確實有點亂,感覺總體思想是缺啥就造啥
      • 布局行為
        ConstrainedBox的布局行為非常簡單,取決於設置的限制條件,而關於父子節點的限制因素生效優先級,可以查看之前的文章,在這里就不做具體敘述了。
      • 繼承關系
        Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > ConstrainedBox
        ConstrainedBox也是一個基礎的布局控件。
      • 構造函數
         包含了一個constraints屬性,且不能為null。
        ConstrainedBox({
         Key key,
         @required this.constraints,
         Widget child
        })
      • 參數解析
        constraints
            添加到child上的額外限制條件,其類型為BoxConstraints。BoxConstraints的作用是干啥的呢?其實很簡單,就是限制各種最大最小寬高。說到這里插一句,double.infinity在widget布局的時候是合法的,也就說,例如. 想最大的擴展寬度,可以將寬度值設為double.infinity。  

         LimitedBox

      (限定最大寬高布局)

 

                  介紹LimitedBox,通過字面意思,也可以猜測出這個控件的作用,是限制類型的控件。這種類型的控件前面也介紹了不少了,這個是對最大寬高進行限制的控件。

 

               布局行為從布局的角度講,LimitedBox是將child限制在其設定的最大寬高中的,但這個限定是有條件的。當LimitedBox最大寬度不受限制時,child的寬度就會受到這個最大寬度的限制,高度同理。
    • 繼承關系
      Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > LimitedBox
    • 構造函數
      const LimitedBox({
        Key key,
        this.maxWidth = double.infinity,
        this.maxHeight = double.infinity,
        Widget child,
      })
    • 參數解析 

      maxWidth:限定的最大寬度,默認值是double.infinity,不能為負數。

      maxHeight:同上。

  • AspectRatio(調整寬高比)
    • 介紹
      AspectRatio的作用是調整child到設置的寬高比,這種控件在其他移動端平台上一般都不會提供,Flutter之所以提供,我想最大的原因,可能就是自定義起來特別麻煩吧。
    • 布局行為

      AspectRatio的布局行為分為兩種情況:

      • AspectRatio首先會在布局限制條件允許的范圍內盡可能的擴展,widget的高度是由寬度和比率決定的,類似於BoxFit中的contain,按照固定比率去盡量占滿區域。
      • 如果在滿足所有限制條件過后無法找到一個可行的尺寸,AspectRatio最終將會去優先適應布局限制條件,而忽略所設置的比率。
    • 繼承關系
      Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > AspectRatio

      從繼承關系看,AspectRatio是基礎的布局控件。

    • 構造函數
      const AspectRatio({
      Key key,
      @required this.aspectRatio,
      Widget child
      }) 

      構造函數只包含了一個aspectRatio屬性,其中aspectRatio不能為null。

    • 參數解析 
      aspectRatio:aspectRatio是寬高比,最終可能不會根據這個值去布局,具體則要看綜合因素,外層是否允許按照這種比率進行布局,只是一個參考值。
      
  • FractionallySizedBox(百分比布局)
      • 介紹
        FractionallySizedBox控件會根據現有空間,來調整child的尺寸,所以說child就算設置了具體的尺寸數值,也不起作用。
      • 布局行為

    FractionallySizedBox的布局行為主要跟它的寬高因子兩個參數有關,當參數為null或者有具體數值的時候,布局表現不一樣。當然,還有一個輔助參數alignment,作為對齊方式進行布局。

      • 當設置了具體的寬高因子,具體的寬高則根據現有空間寬高 * 因子,有可能會超出父控件的范圍,當寬高因子大於1的時候;
      • 當沒有設置寬高因子,則填滿可用區域;
    • 繼承關系
      Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > FractionallySizedBox
    • 構造函數
      const FractionallySizedBox({
        Key key,
        this.alignment = Alignment.center,
        this.widthFactor,
        this.heightFactor,
        Widget child,
      })
    • 參數解析 

      alignment:對齊方式,不能為null。

      widthFactor:寬度因子,跟之前介紹的控件類似,寬度乘以這個值,就是最后的寬度。

      heightFactor:高度因子,用作計算最后實際高度的。

      其中widthFactorheightFactor都有一個規則

      • 如果不為null,那么實際的最大寬高度則為child的寬高乘以這個因子;
      • 如果為null,那么child的寬高則會盡量充滿整個區域。

三,常用方法

  • SizeBox(設置具體尺寸)
    /**
     * MySizeBox
     */
    class MySizeBox extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        // TODO: implement build
        return new Container(
          color: Colors.green,
          padding: const EdgeInsets.all(5.0),
          child: SizedBox(
            width: 200.0,
            height: 200.0,
            child: new Container(
              color: Colors.red,
              width: 100.0,
              height: 300.0,
            ),
          ),
        );
      }
    }

    效果圖



    源碼解析
    SizedBox內部是通過RenderConstrainedBox來實現的。具體的源碼就不解析了,總體思路是,根據寬高值算好一個constraints,然后強制應用到child上。

  • ConstrainedBox(限定最大最小寬高布局)
    /**
     * ConstrainedBox
     * 在一個寬高200.0的Container上添加一個約束最大最小寬高的ConstrainedBox,實際的顯示中,則是一個寬高為150.0的區域。
     */
    class MyConstrainedBox extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        // TODO: implement build
        return new ConstrainedBox(
          constraints: const BoxConstraints(
            minWidth: 100.0,
            minHeight: 100.0,
            maxWidth: 150.0,
            maxHeight: 150.0,
          ),
          child: new Container(
            width: 200.0,
            height: 200.0,
            color: Colors.red,
          ),
        );
      }
    }

    效果圖


    源碼解析
    @override
     RenderConstrainedBox createRenderObject(BuildContext context) {
     return new RenderConstrainedBox(additionalConstraints: constraints);
    }

    RenderConstrainedBox實現其繪制。其具體的布局表現分兩種情況:

    如果child不為null,則將限制條件加在child上;
    如果child為null,則會盡可能的縮小尺寸。
    具體代碼如下:

    @override
    void performLayout() {
    if (child != null) {
      child.layout(_additionalConstraints.enforce(constraints), parentUsesSize: true);
      size = child.size;
     } else {
      size = _additionalConstraints.enforce(constraints).constrain(Size.zero);
     }
    }

    使用場景

    需要添加額外的約束條件可以使用此控件,例如設置最小寬高,盡可能的占用區域等等。筆者在實際開發中使用的倒不是很多,倒不是說這個控件不好使用,而是好多約束因素是綜合的,例如需要額外的設置margin、padding屬性能,因此單獨再套個這個就顯得很繁瑣了。
  • LimitedBox(限定最大寬高布局)
    class MyLimitedBox extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        // TODO: implement build
        return new Row(
          children: <Widget>[
            new Container(
              color: Colors.red,
              width: 100.0,
            ),
            LimitedBox(
              maxWidth: 150.0,
              child: new Container(
                color: Colors.blue,
                width: 250.0,
              ),
            )
          ],
        );
      }
    }

    效果圖:


    源碼解析

    先不說其源碼,單純從其作用,前面介紹的SizedBox、ConstrainedBox都類似,都是通過強加到child的constraint,來達到相應的效果。

    我們直接看其計算constraint的代碼

    minWidth: constraints.minWidth,
    maxWidth: constraints.hasBoundedWidth ? constraints.maxWidth : constraints.constrainWidth(maxWidth),
    minHeight: constraints.minHeight,
    maxHeight: constraints.hasBoundedHeight ? constraints.maxHeight : constraints.constrainHeight(maxHeight)

    LimitedBox只是改變最大寬高的限定。具體的布局代碼如下:

    if (child != null) {
      child.layout(_limitConstraints(constraints), parentUsesSize: true);
      size = constraints.constrain(child.size);
    } else {
      size = _limitConstraints(constraints).constrain(Size.zero);
    }

    根據最大尺寸,限制child的布局,然后將自身調節到child的尺寸。

    使用場景
       是不可能清楚了,光是找例子,就花了不少時間。Flutter的一些冷門控件,真的是除了官方的文檔,啥材料都木有。
       谷歌說這個很有用,還是一臉懵逼。這種控件,也有其他的替代解決方案,LimitedBox可以達到的效果,ConstrainedBox都可以實現。

  • AspectRatio(調整寬高比)
    /**
     * AspectRatio
     * 定義了一個高度為200的區域,內部AspectRatio比率設置為1.5,最終AspectRatio的是寬300高200的一個區域。
     */
    class MyAspectRatio extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        // TODO: implement build
        return new Container(
          height: 200.0,
          child: new AspectRatio(
            aspectRatio: 1.5,
            child: new Container(
              color: Colors.red,
            ),
          ),
        );
      }
    }

    效果圖

    源碼解析
    @override
    RenderAspectRatio createRenderObject(BuildContext context) => new RenderAspectRatio(aspectRatio: aspectRatio);

    經過前面一些控件的解析,我想大家對這種構造應該不會再陌生了,繪制都是交由RenderObject去完成的,這里則是由RenderAspectRatio去完成具體的繪制工作。

    RenderAspectRatio的構造函數中會對aspectRatio做一些檢測(assert)

    aspectRatio不能為null;
    aspectRatio必須大於0;
    aspectRatio必須是有限的。

    接下來我們來看一下RenderAspectRatio的具體尺寸計算函數:

    (1)如果限制條件為isTight,則返回最小的尺寸(constraints.smallest);

    if (constraints.isTight)
    return constraints.smallest;

    (2)如果寬度為有限的值,則將高度設置為width / _aspectRatio。 如果寬度為無限,則將高度設為最大高度,寬度設為height * _aspectRatio;

    if (width.isFinite) {
      height = width / _aspectRatio;
    } else {
      height = constraints.maxHeight;
      width = height * _aspectRatio;
    }

    (3)接下來則是在限制范圍內調整寬高,總體思想則是寬度優先,大於最大值則設為最大值,小於最小值,則設為最小值。

    如果寬度大於最大寬度,則將其設為最大寬度,高度設為width / _aspectRatio;

    if (width > constraints.maxWidth) {
     width = constraints.maxWidth;
     height = width / _aspectRatio;
    }

    如果高度大於最大高度,則將其設為最大高度,寬度設為height * _aspectRatio;

    if (height > constraints.maxHeight) {
     height = constraints.maxHeight;
     width = height * _aspectRatio;
    }

    如果寬度小於最小寬度,則將其設為最小寬度,高度設為width / _aspectRatio;

    if (width < constraints.minWidth) {
     width = constraints.minWidth;
     height = width / _aspectRatio;
    }

    如果高度小於最小高度,則將其設為最小高度,寬度設為height * _aspectRatio。

    if (height < constraints.minHeight) {
     height = constraints.minHeight;
     width = height * _aspectRatio;
    }
  • FractionallySizedBox(百分比布局)
    /**
     * MyFractionallySizeBox
     */
    
    class  MyFractionallySizeBox extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        // TODO: implement build
        return new Container(
          color: Colors.blue,
          height: 150.0,
          width: 150.0,
          padding: const EdgeInsets.all(10.0),
          child: new FractionallySizedBox(
            alignment: Alignment.topLeft,
            widthFactor: 1.5,
            heightFactor: 0.5,
            child: new Container(
              color: Colors.red,
            ),
          ),
        );
      }
    }

    效果圖

    源碼解析
    FractionallySizedBox內部具體渲染是由RenderFractionallySizedOverflowBox來實現的,通過命名就可以看出,這個控件可能會Overflow。

    我們直接看實際計算尺寸的代碼

    double minWidth = constraints.minWidth;
    double maxWidth = constraints.maxWidth;
    if (_widthFactor != null) {
     final double width = maxWidth * _widthFactor;
     minWidth = width;
     maxWidth = width;
    }
    double minHeight = constraints.minHeight;
    double maxHeight = constraints.maxHeight;
    
    if (_heightFactor != null) {
     final double height = maxHeight * _heightFactor;
     minHeight = height;
     maxHeight = height;
    }

    源代碼中,根據寬高因子是否存在,來進行相對應的尺寸計算。

    使用場景
    當需要在一個區域里面取百分比尺寸的時候,可以使用這個,比方說,高度40%寬度70%的區域。當然,AspectRatio也可以達到近似的效果。

四,參考  

Flutter學習之認知基礎組件
Flutter布局


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM