Flutter——Widget系列3、Material widgets之Card和ListTile、列表ListView、GridView和相對布局Stack

那個,Card常常做圓角背景,然后,Card和ListTile,是經(jīng)常一起玩,ListTile可以試下各種豐富的item效果,因此ListTile經(jīng)常和列表一起玩。
最后,說說九宮格和相對布局。

文中參閱了很多文章,感謝各位大佬。

不說了,開始吧。先來個Card

image.png

一、Card和ListTile

安卓里面,有CardView。Flutter為什么有Card,不言而喻了。

一.1、Card

Card的構(gòu)造函數(shù)

 * 卡片布局,相當于Android中的CardView
 * const Card({
    Key key,
    this.color,//背景色
    this.elevation,//陰影大小
    this.shape,//設置邊,可以設置圓角
    this.margin = const EdgeInsets.all(4.0),
    this.clipBehavior = Clip.none,
    this.child,
    this.semanticContainer = true,
    })
  • key 相當于id
  • color 顏色
  • elevation 陰影大小
  • shape 設置邊,可以設置圓角
  • margin 外邊距
  • clipBehavior 對Widget截取的行為,比如 Clip.antiAlias 指抗鋸齒
  • semanticContainer 語義容器? 默認為true

例子

一個簡單的例子,演示了邊框,陰影的。

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new CardSimple1()
    );
  }
}

class CardSimple1 extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Container(
      color: Color.fromARGB(255, 66, 165, 245),
      alignment: AlignmentDirectional(0.0, 0.0),
      child: Container(
        width: 200,
        height: 200,
        child: Card(
          color: Colors.red,
          // 普通的邊
          shape: Border.all(
              color: Colors.yellow,
              width: 5.0
          ),
          elevation: 20,// 陰影大小
          child: new Text("Card Widget"),
        ),
      )
    );
  }
}

image.png

看呢,是看到了,但是不是我們熟悉的原角CardView。

來個圓角的

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new CardSimple1()
    );
  }
}

class CardSimple1 extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Container(
      color: Color.fromARGB(255, 66, 165, 245),
      alignment: AlignmentDirectional(0.0, 0.0),
      child: Container(
        width: 200,
        height: 200,
        child: Card(
          color: Colors.red,

          //設置圓角
          shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0)),

          // 普通的邊
/*          shape: Border.all(
              color: Colors.yellow,
              width: 5.0
          ),*/
          elevation: 20,// 陰影大小
          child: new Text("Card Widget"),
        ),
      )
    );
  }
}

顯而易見,我們通過RoundedRectangleBorder實現(xiàn)Card的圓角。

而且,Card里面的元素,居然顯示在Card之外,這目前不知道怎么解決。

image.png

加上個抗鋸齒吧 clipBehavior

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: new CardSimple1());
  }
}

class CardSimple1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Container(
      color: Color.fromARGB(255, 66, 165, 245),
      alignment: AlignmentDirectional(0.0, 0.0),
      child: Card(
          //設置圓角
//          shape:
//              RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0)),
          color: Colors.purple,
          shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(20.0)),),
          // 普通的邊
/*          shape: Border.all(
              color: Colors.yellow,
              width: 5.0
          ),*/
          // 抗鋸齒
          clipBehavior: Clip.antiAlias,

          elevation: 20, // 陰影大小
          child: new Container(
            width: 200,
            height: 200,
            alignment: Alignment.center,
            child: new Text("Card Widget",style: TextStyle(color: Colors.white),),
          )),
    );
  }
}
image.png

指定角,實現(xiàn)圓角

shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.only(
                topLeft: Radius.circular(50.0),
                topRight: Radius.circular(50.0),
                bottomLeft: Radius.zero,
                bottomRight: Radius.zero),
          ),
image.png

一.2、ListTile

Card和ListTile,是經(jīng)常一起玩。

ListTile的構(gòu)造函數(shù)

  const ListTile({
    Key key,
    this.leading,
    this.title,
    this.subtitle,
    this.trailing,
    this.isThreeLine = false,
    this.dense,
    this.contentPadding,
    this.enabled = true,
    this.onTap,
    this.onLongPress,
    this.selected = false,
  }) 
  • key 相當于毆打
  • leading 將圖像或圖標添加到列表的開頭。這通常是一個圖標。
  • title 標題
  • subtitle 子標題
  • trailing 設置拖尾將在列表的末尾放置一個圖像。這對于指示主-細節(jié)布局特別有用。(trailing本身是拖尾的意思)
  • isThreeLine = false 默認為false 3行,當列表標題、副標題,有需要更多的空間來容納長度超過一行的文本,可開啟
  • dense 讓文本變小 (dense本身是稠密的意思)
  • contentPadding 內(nèi)容的padding
  • enabled = true 可否點擊??赏ㄟ^將 enable 設置為 false,來禁止點擊事件
  • onTap 點擊
  • onLongPress 長按
  • selected = false 是否選中,默認為否 ,如果選中列表的 item 項,那么文本和圖標的顏色將成為主題的主顏色。

.
.
.

基本屬性先用起來

這4個,先一起來,leading、title、subtitle、trailing

leading
將圖像或圖標添加到列表的開頭。這通常是一個圖標。
title
標題
subtitle
子標題
trailing
設置拖尾將在列表的末尾放置一個圖像。這對于指示主-細節(jié)布局特別有用。(trailing本身是拖尾的意思)

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: new ListTileSimle());
  }
}


class ListTileSimle extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Container(
      color: Color.fromARGB(255, 66, 165, 245),
      alignment: AlignmentDirectional(0.0, 0.0),
      child: Card(

          color: Colors.white,
          shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(20.0)),),
          // 抗鋸齒
          clipBehavior: Clip.antiAlias,
          elevation: 20,
          // 陰影大小
          child: new Container(
            height: 100,
            alignment: Alignment.center,
            
            // 演示 ListTile
            child: new ListTile(
              title: new Text("海賊王"),
              subtitle: new Text("來自東海的路飛"),

              // item左側(cè)的圖像
              leading: new Icon(
                Icons.ac_unit,
                color: Colors.blue[500],
              ),

              // 列表尾部的圖標
              trailing: new Icon(Icons.chevron_right),

            ),
          )),
    );
  }
}
image.png
其他屬性的使用
  • isThreeLine = false 默認為false 3行,當列表標題、副標題,有需要更多的空間來容納長度超過一行的文本,可開啟
  • dense 讓文本變小 (dense本身是稠密的意思)
  • contentPadding 內(nèi)容的padding
  • enabled = true 可否點擊??赏ㄟ^將 enable 設置為 false,來禁止點擊事件
  • onTap 點擊
  • onLongPress 長按
  • selected = false 是否選中,默認為否 ,如果選中列表的 item 項,那么文本和圖標的顏色將成為主題的主顏色。
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: new ListTileSimle());
  }
}

class ListTileSimle extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Container(
      color: Color.fromARGB(255, 66, 165, 245),
      alignment: AlignmentDirectional(0.0, 0.0),
      child: Card(
          color: Colors.white,
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.all(Radius.circular(20.0)),
          ),
          // 抗鋸齒
          clipBehavior: Clip.antiAlias,
          elevation: 20,
          // 陰影大小
          child: new Container(
            height: 100,
            alignment: Alignment.center,

            // 演示 ListTile
            child: new ListTile(
              title: new Text("海賊王"),
              subtitle: new Text("來自東海的路飛"),

              // item左側(cè)的圖像
              leading: new Icon(
                Icons.ac_unit,
                color: Colors.blue[500],
              ),

              // 列表尾部的圖標
              trailing: new Icon(Icons.chevron_right),

              isThreeLine:true,

              dense: true, // 讓文本變小
              contentPadding:EdgeInsets.symmetric(horizontal: 20.0),

              selected: true,  // 如果選中列表的 item 項,那么文本和圖標的顏色將成為主題的主顏色。

              onTap: () { // 點擊會有水波紋效果
                // do something
              },
              onLongPress: (){
                // do something else
              },

            ),
          )),
    );
  }
}
image.png

.
.
.

二 、列表 ListView

ListView,做列表呀。

ListView的屬性

  ListView({
    Key key,
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
    ScrollController controller,
    bool primary,
    ScrollPhysics physics,
    bool shrinkWrap = false,
    EdgeInsetsGeometry padding,
    this.itemExtent,
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
    bool addSemanticIndexes = true,
    double cacheExtent,
    List<Widget> children = const <Widget>[],
    int semanticChildCount,
    DragStartBehavior dragStartBehavior = DragStartBehavior.down,
  }) 
常見屬性
  • scrollDirection
    Axis 設置滾動的方向,horizontal(水平)或vertical(垂直)
  • reverse
    bool 是否翻轉(zhuǎn)
  • itemExtent
    double 滾動方向子控件的長度,垂直方向即為高度,水平方向即為寬度
  • controller
    ScrollController 用來控制滾動位置及監(jiān)聽滾動事件
  • shrinkWrap
    bool 是否根據(jù)子widget的總長度來設置ListView的長度
  • padding
    EdgeInsetsGeometry 間距
  • children
    List 子控件
  • primary
    bool 當內(nèi)容不足以滾動時,是否支持滾動;對于iOS系統(tǒng)還有一個效果:當用戶點擊狀態(tài)欄時是否滑動到頂部。

  • physics
    ScrollPhysics:控制用戶滾動視圖的交互

    • AlwaysScrollableScrollPhysics:列表總是可滾動的。在iOS上會有回彈效果,在android上不會回彈。那么問題來了,如果primary設置為false(內(nèi)容不足時不滾動),且 physics設置為AlwaysScrollableScrollPhysics,列表是否可以滑動?答案是可以,感興趣的可以試一下
    • PageScrollPhysics:一般是給PageView控件用的滑動效果。如果listview設置的話在滑動到末尾時會有個比較大的彈起和回彈
    • ClampingScrollPhysics:滾動時沒有回彈效果,同android系統(tǒng)的listview效果
    • NeverScrollableScrollPhysics:就算內(nèi)容超過列表范圍也不會滑動
    • BouncingScrollPhysics:不論什么平臺都會有回彈效果
    • FixedExtentScrollPhysics:不適用于ListView,原因:需要指定scroller為 - FixedExtentScrollController,這個scroller只能用于ListWheelScrollViews
  • shrinkWrap: scroll view在滑動方向上的高度是否由內(nèi)容高度決定,false:則高度為滑動方向上的最大允許高度;如果在滑動方向上沒有設置約束,則這個字段必須設置為true,否則會報錯。

  • cacheExtent:可見區(qū)域的前后會有一定高度的空間去緩存子控件,當滑動時就可以迅速呈現(xiàn)

  • semanticChildCount:有含義的子控件的數(shù)量,如ListView會用children的長度,ListView.separated會用children長度的一半

用于構(gòu)造SliverChildListDelegate的屬性
  • addAutomaticKeepAlives:是否將子控件包裹在AutomaticKeepAlive控件內(nèi)
  • addRepaintBoundaries:true:是否將子控件包裹在 RepaintBoundary 控件內(nèi)。用于避免列表滾動時的重繪,如果子控件重繪開銷很小時,比如子控件就是個色塊或簡短的文字,把這個字段設置為false性能會更好
  • addSemanticIndexes:是否把子控件包裝在IndexedSemantics里,用來提供無障礙語義

來個簡單例子吧

Flutter ListView 用法詳解這個文章,根據(jù)構(gòu)造方法不同,列舉了幾個場景,看起來,比較恰當,本文也是這么來。

方式1 默認構(gòu)造函數(shù)(傳入 List children) 少數(shù)子View

適用場景:已知有限個Item的情況下

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    final title = 'list';
    return new MaterialApp(
      title: title,
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text(title),
        ),
        body: new Center(
          child: new ListViewSimle1(),
        ),
      ),
    );

  }
}

class ListViewSimle1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return new ListView(
      //控制方向 默認是垂直的
      scrollDirection: Axis.vertical,
      children: <Widget>[
        TileSimle(),
        TileSimle(),
        TileSimle(),
      ],
    );
  }
}

class TileSimle extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Container(
      color: Color.fromARGB(255, 66, 165, 245),
      alignment: AlignmentDirectional(0.0, 0.0),
      child: Card(
          color: Colors.white,
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.all(Radius.circular(20.0)),
          ),
          // 抗鋸齒
          clipBehavior: Clip.antiAlias,
          elevation: 20,
          // 陰影大小
          child: new Container(
            height: 100,
            alignment: Alignment.center,

            // 演示 ListTile
            child: new ListTile(
              title: new Text("海賊王"),
              subtitle: new Text("來自東海的路飛"),

              // item左側(cè)的圖像
              leading: new Icon(
                Icons.ac_unit,
                color: Colors.blue[500],
              ),

              // 列表尾部的圖標
              trailing: new Icon(Icons.chevron_right),
            ),
          )),
    );
  }
}
image.png

方式2 默認構(gòu)造函數(shù)(傳入 List children) 少數(shù)子View

適用場景:長列表時采用builder模式,能提高性能。不是把所有子控件都構(gòu)造出來,而是在控件viewport加上頭尾的cacheExtent這個范圍內(nèi)的子Item才會被構(gòu)造。在構(gòu)造時傳遞一個builder,按需加載是一個慣用模式,能提高加載性能。

就是類似安卓的ViewHolder。

簡單例子1
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    final title = 'list';
    return new MaterialApp(
      title: title,
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text(title),
        ),
        body: new Center(
//          child: new ListViewSimle1(),
          child: new ListView.builder(
              itemBuilder: (context, index) => Text("Item $index"),
              itemCount: 100),
        ),
      ),
    );

  }
}
image.png
構(gòu)造多種樣式的Item
abstract class ListItem {}

class HeadingItem implements ListItem {
  final String heading;

  HeadingItem(this.heading);
}

class MessageItem implements ListItem {
  final String sender;
  final String body;

  MessageItem(this.sender, this.body);
}

ListView.builder(
            itemBuilder: (context, index) {
              final item = items[index];

              if (item is HeadingItem) {
                return ListTile(
                  title: Text(
                    item.heading,
                    style: Theme.of(context).textTheme.headline,
                  ),
                );
              } else if (item is MessageItem) {
                return ListTile(
                  title: Text(item.sender),
                  subtitle: Text(item.body),
                );
              }
            },
            itemCount: items.length))

方式3 separated 帶分割線 dart

適用場景:列表中需要分割線時,可以自定義復雜的分割線

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    final title = 'list';
    return new MaterialApp(
      title: title,
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text(title),
        ),
        body: new Center(
//          child: new ListViewSimle1(),
          child: ListView.separated(
              itemBuilder: (context, index) {
                return Text("Item $index");
              },
              separatorBuilder: (context, index) {
                return Container(
                  color: Colors.grey,
                  height: 3,
                );
              },
              itemCount: 100)

        ),
      ),
    );

  }
}
image.png

方式4 custom 自定義

適用場景:上面幾種模式基本可以滿足業(yè)務需求,如果你還想做一些其它設置(如列表的最大滾動范圍)或獲取滑動時每次布局的子Item范圍,可以嘗試custom模式

  • 看看構(gòu)造函數(shù)
  const ListView.custom({
    Key key,
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
    ScrollController controller,
    bool primary,
    ScrollPhysics physics,
    bool shrinkWrap = false,
    EdgeInsetsGeometry padding,
    this.itemExtent,
    @required this.childrenDelegate,
    double cacheExtent,
    int semanticChildCount,
  }) 

發(fā)現(xiàn)有一個是必須復寫的 —— childrenDelegate
.
.

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    final title = 'list';
    return new MaterialApp(
      title: title,
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text(title),
        ),
        body: new Center(
//          child: new ListViewSimle1(),
          child: ListView.custom(childrenDelegate: CustomSliverChildDelegate())

        ),
      ),
    );

  }
}

class CustomSliverChildDelegate extends SliverChildDelegate {
  /// 根據(jù)index構(gòu)造child
  @override
  Widget build(BuildContext context, int index) {
    // KeepAlive將把所有子控件加入到cache,已輸入的TextField文字不會因滾動消失
    // 僅用于演示
    return KeepAlive(
        keepAlive: true,
        child: TextField(decoration: InputDecoration(hintText: '請輸入')));
  }

  /// 決定提供新的childDelegate時是否需要重新build。在調(diào)用此方法前會做類型檢查,不同類型時才會調(diào)用此方法,所以一般返回true。
  @override
  bool shouldRebuild(SliverChildDelegate oldDelegate) {
    return true;
  }

  /// 提高children的count,當無法精確知道時返回null。
  /// 當 build 返回 null時,它也將需要返回一個非null值
  @override
  int get estimatedChildCount => 100;

  /// 預計最大可滑動高度,如果設置的過小會導致部分child不可見,設置報錯
  @override
  double estimateMaxScrollOffset(int firstIndex, int lastIndex,
      double leadingScrollOffset, double trailingScrollOffset) {
    return 2500;
  }

  /// 完成layout后的回調(diào),可以通過該方法獲已完成布局的視圖樹包括哪些子控件
  @override
  void didFinishLayout(int firstIndex, int lastIndex) {
    print('didFinishLayout firstIndex=$firstIndex firstIndex=$lastIndex');
  }
}
image.png

參考:https://juejin.im/post/5cb1c9d5f265da037371777f

三 、列表 GridView

ListView都說完了,類似九宮格的GridView還會遠嗎

GridView的構(gòu)造函數(shù)

GridView({
    Key key,
    Axis scrollDirection = Axis.vertical, 
    bool reverse = false,  
    ScrollController controller,
    bool primary,
    ScrollPhysics physics,
    bool shrinkWrap = false, 
    EdgeInsetsGeometry padding,  
    @required this.gridDelegate, 
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
    bool addSemanticIndexes = true,
    double cacheExtent,
    List<Widget> children = const <Widget>[],
    int semanticChildCount,
  })

常見屬性

  • scrollDirection Axis 設置滾動的方向,horizontal(水平)或vertical(垂直)
  • reverse bool 是否翻轉(zhuǎn)
  • controller ScrollController 用來控制滾動位置及監(jiān)聽滾動事件
  • shrinkWrap bool 是否根據(jù)子widget的總長度來設置GridView的長度
  • padding EdgeInsetsGeometry 間距
  • gridDelegate SliverGridDelegate 控制子Widget如何進行布局
  • children List 子控件

其實其他屬性,已經(jīng)沒什么特別,需要重點看看的,也就是gridDelegate。
待會說。

GridView有好幾種使用方式。

GridView的幾種使用方式

方式1、GridView.count

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    final title = 'list';
    return new MaterialApp(
      title: title,
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text(title),
        ),
        body: new Center(
//          child: new ListViewSimle1(),
          child: GridView.count(
            // 方向,這行沒設置頁可以,默認就是垂直的
            scrollDirection:Axis.vertical,
            //水平子Widget之間間距
            crossAxisSpacing: 10.0,
            //垂直子Widget之間間距
            mainAxisSpacing: 30.0,
            //GridView內(nèi)邊距
            padding: EdgeInsets.all(10.0),
            //一行的Widget數(shù)量
            crossAxisCount: 2,
            //子Widget寬高比例
            childAspectRatio: 2.0,  // 比如2.0,就是 寬/高=2
            //子Widget列表
            children: getWidgetList(),
          ),
        ),
      ),
    );

  }
}


List<String> getDataList() {
  List<String> list = [];
  for (int i = 0; i < 100; i++) {
    list.add(i.toString());
  }
  return list;
}

List<Widget> getWidgetList() {
  return getDataList().map((item) => getItemContainer(item)).toList();
}

Widget getItemContainer(String item) {
  return Container(
    alignment: Alignment.center,
    child: Text(
      item,
      style: TextStyle(color: Colors.white, fontSize: 20),
    ),
    color: Colors.blue,
  );
}

如果設定了寬高比childAspectRatio,那么手動設定的寬高會失效

image.png

SliverGridDelegate的設置,直接在這種模式使用,但是我們結(jié)合build模式說,感覺合適一些。

方式2、GridView.build

這個,可以好好說下。

SliverGridDelegate,可以控制GridView的布局。

其中,有兩個實現(xiàn)類

  • SliverGridDelegateWithMaxCrossAxisExtent (創(chuàng)建一個具有交叉軸最大值的一個網(wǎng)格布局)
  • SliverGridDelegateWithFixedCrossAxisCount(設置交叉軸上子控件的個數(shù))

分開說

實現(xiàn)類1 SliverGridDelegateWithMaxCrossAxisExtent

MaxCrossAxis
創(chuàng)建一個具有交叉軸最大值的一個網(wǎng)格布局,元素的行/列數(shù)不是一定的,動態(tài)設定的

  • 對于SliverGridDelegateWithMaxCrossAxisExtent而言,水平方向元素個數(shù)不再固定
  • 其水平個數(shù)也就是有幾列,由 maxCrossAxisExtent 和屏幕的寬度以及padding和mainAxisSpacing等決定。

構(gòu)造方法

  const SliverGridDelegateWithMaxCrossAxisExtent({
    @required this.maxCrossAxisExtent, //子控件的最大寬度,實際寬度是根據(jù)交叉軸的值進行平分,也就是說最大寬度并不一定是實際寬度,很有可能子控件的實際寬度要小于設置的最大寬度
    this.mainAxisSpacing = 0.0, //主軸之間的間距
    this.crossAxisSpacing = 0.0,//交叉軸之間的間距
    this.childAspectRatio = 1.0,//子控件的寬高比
  }

記住,最大特點是元素的行/列數(shù)不是一定的,動態(tài)設定的

.
.

  • 當 maxCrossAxisExtent 為 50
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  List<String> datas = getDataList();
  @override
  Widget build(BuildContext context) {
    final title = 'list';
    return new MaterialApp(
      title: title,
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text(title),
        ),
        body: new Center(
//          child: new ListViewSimle1(),

          child: GridView.builder(
            // 方向,這行沒設置頁可以,默認就是垂直的
            scrollDirection:Axis.vertical,

            itemBuilder: (BuildContext context,int index){
              return getItemContainer(datas[index]);
            },

              // MaxCrossAxis 其水平個數(shù)也就是有幾列,由 maxCrossAxisExtent 和屏幕的寬度以及padding和mainAxisSpacing等決定。
              gridDelegate:SliverGridDelegateWithMaxCrossAxisExtent(
                //單個子Widget的水平最大寬度
                  maxCrossAxisExtent: 50,
                  //水平單個子Widget之間間距
                  mainAxisSpacing: 20.0,
                  //垂直單個子Widget之間間距
                  crossAxisSpacing: 10.0
              ),
          ),
        ),
      ),
    );

  }
}


List<String> getDataList() {
  List<String> list = [];
  for (int i = 0; i < 100; i++) {
    list.add(i.toString());
  }
  return list;
}

List<Widget> getWidgetList() {
  return getDataList().map((item) => getItemContainer(item)).toList();
}

Widget getItemContainer(String item) {
  return Container(
    alignment: Alignment.center,
    child: Text(
      item,
      style: TextStyle(color: Colors.white, fontSize: 20),
    ),
    color: Colors.blue,
  );
}
image.png

.
.

  • 當 maxCrossAxisExtent 為 100


    image.png

如果強制設置行/列數(shù)就不開心,怎么辦?比如指定為4行,可以試試:
maxCrossAxisExtent: MediaQuery.of(context).size.width/4
通過MediaQuery.of(context).size.width就可得到屏幕的寬度,除以4,就是子控件的最大值,這樣一來我們就可以確定要顯示的子控件的個數(shù)了

其實使用了 MaxCrossAxis 還制定數(shù)量,感覺就是不合適的,有病的。
想指定數(shù)量,那么應該用 SliverGridDelegateWithFixedCrossAxisCount,人家說了FixedCrossAxisCount.

實現(xiàn)類2 SliverGridDelegateWithMaxCrossAxisExtent

核心就是:crossAxisCount 指定數(shù)量

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  List<String> datas = getDataList();
  @override
  Widget build(BuildContext context) {
    final title = 'list';
    return new MaterialApp(
      title: title,
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text(title),
        ),
        body: new Center(
//          child: new ListViewSimle1(),

          child: GridView.builder(
            // 方向,這行沒設置頁可以,默認就是垂直的
            scrollDirection:Axis.vertical,

            itemBuilder: (BuildContext context,int index){
              return getItemContainer(datas[index]);
            },

              // FixedCrossAxisCount 指定 行/列的數(shù)量,就垂直方向來說,指定行數(shù)
              gridDelegate:SliverGridDelegateWithFixedCrossAxisCount(

                  //橫軸元素個數(shù): 3
                  crossAxisCount: 3,

                  //水平單個子Widget之間間距
                  mainAxisSpacing: 20.0,
                  //垂直單個子Widget之間間距
                  crossAxisSpacing: 10.0
              ),
          ),
        ),
      ),
    );

  }
}


List<String> getDataList() {
  List<String> list = [];
  for (int i = 0; i < 100; i++) {
    list.add(i.toString());
  }
  return list;
}

List<Widget> getWidgetList() {
  return getDataList().map((item) => getItemContainer(item)).toList();
}

Widget getItemContainer(String item) {
  return Container(
    alignment: Alignment.center,
    child: Text(
      item,
      style: TextStyle(color: Colors.white, fontSize: 20),
    ),
    color: Colors.blue,
  );
}

image.png

.
.

方式3、GridView.custom

這個也沒什么特別的,之前ListView也有說過了,類似

  • 構(gòu)造函數(shù)
  const GridView.custom({
    Key key,
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
    ScrollController controller,
    bool primary,
    ScrollPhysics physics,
    bool shrinkWrap = false,
    EdgeInsetsGeometry padding,
    @required this.gridDelegate,
    @required this.childrenDelegate,
    double cacheExtent,
    int semanticChildCount,
    DragStartBehavior dragStartBehavior = DragStartBehavior.down,
  }) 

看看構(gòu)造函數(shù),有兩個必須復寫的,簡單示例下


import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  List<String> datas = getDataList();
  @override
  Widget build(BuildContext context) {
    final title = 'list';
    return new MaterialApp(
      title: title,
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text(title),
        ),
        body: new Center(
//          child: new ListViewSimle1(),

          child: GridView.custom(
              gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 3, mainAxisSpacing: 10.0, crossAxisSpacing: 20.0, ),
              childrenDelegate: SliverChildBuilderDelegate((context, position) {
                return getItemContainer(datas[position]);
              }, childCount: datas.length))
        ),
      ),
    );

  }
}


List<String> getDataList() {
  List<String> list = [];
  for (int i = 0; i < 100; i++) {
    list.add(i.toString());
  }
  return list;
}

List<Widget> getWidgetList() {
  return getDataList().map((item) => getItemContainer(item)).toList();
}

Widget getItemContainer(String item) {
  return Container(
    alignment: Alignment.center,
    child: Text(
      item,
      style: TextStyle(color: Colors.white, fontSize: 20),
    ),
    color: Colors.blue,
  );
}

image.png

參考:
https://blog.csdn.net/yuzhiqiang_1993/article/details/87968234
Flutter GridView

四、相對布局Stack

這貨。堆疊的意思,跟安卓比,類似于相對布局,誰出現(xiàn)的晚,誰就可以出現(xiàn)在上方,覆蓋別人。

看看構(gòu)造函數(shù)

  Stack({
    Key key,
    this.alignment = AlignmentDirectional.topStart,
    this.textDirection,
    this.fit = StackFit.loose,
    this.overflow = Overflow.clip,
    List<Widget> children = const <Widget>[],
  })
  • alignment:對齊方式,默認是左上角(topStart)。
  • textDirection:文本的方向,絕大部分不需要處理。
  • fit:定義如何設置non-positioned節(jié)點尺寸,默認為loose。
    其中StackFit有如下幾種:
    • loose:子節(jié)點寬松的取值,可以從min到max的尺寸;
    • expand:子節(jié)點盡可能的占用空間,取max尺寸;
    • passthrough:不改變子節(jié)點的約束條件。
  • overflow:超過的部分是否裁剪掉(clipped)。

布局行為

Stack的布局行為,根據(jù)child是positioned還是non-positioned來區(qū)分。

  • 對于positioned的子節(jié)點,它們的位置會根據(jù)所設置的top、bottom、right以及l(fā)eft屬性來確定,這幾個值都是相對于Stack的左上角;
  • 對于non-positioned的子節(jié)點,它們會根據(jù)Stack的aligment(左上角)來設置位置。

對于繪制child的順序,則是第一個child被繪制在最底端,后面的依次在前一個child的上面。調(diào)整先后出現(xiàn),可以改變展示順序。

例子

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  List<String> datas = getDataList();
  @override
  Widget build(BuildContext context) {
    final title = 'list';
    return new MaterialApp(
      title: title,
      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> {
  @override
  Widget build(BuildContext context) {
    //關(guān)鍵代碼
    var stack = new Stack(
      alignment: const Alignment(0.0, 0.6),  //分析 2
      children: [
        new CircleAvatar(   //分析 3
          backgroundImage: new AssetImage('images/lake.jpg'),
          radius: 100.0,
        ),
        new Container(   //分析 4
          decoration: new BoxDecoration(
            color: Colors.black45,
          ),
          child: new Text(
            '添加水印',
            style: new TextStyle(
              fontSize: 20.0,
              fontWeight: FontWeight.bold,
              color: Colors.white,
            ),
          ),
        ),
      ],
    );
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
        elevation: 5.0,
      ),
      body: Center(  //分析 1
        child: stack,
      ),
    );
  }
}
image.png

關(guān)于Stack,還有一個IndexedStack,這里就不展開了。

參考:
Flutter 布局(八)- Stack、IndexedStack、GridView詳解
http://m.itdecent.cn/p/f1b8fbe5cda0

.
.
.
END

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容