Flutter初次探索

Visupervi Reborn發表於2021-07-16

1. MaterialApp

  • 首先在專案裡面使用的是MaterialApp。

MaterialApp 是一個方便的Widget, 它封裝了應用程式實現Material Design所需要的一些Widget,一般作為頂層widget使用 在MaterialApp裡面有Home(主頁)屬性 title(標題) color(顏色) Theme(主題) routes(路由)

  • MaterialApp中的屬性
// Scaffold元件是MaterialApp Design佈局結構的基本實現,此類提供了用於顯示
// drawer、snackbar和sheet的API
// Scaffold有下面幾個主要屬性
// appbar 顯示在介面頂部的一個AppBar
// body 當前介面所顯示的主要內容 Widget
// drawer 抽屜選單控制元件
// onGenerateRoute 路由傳值, 配置路由
複製程式碼

2. 路由的處理

  • 對路由統一管理

新建路由檔案,對路由進行統一攔截處理,主要是來處理路由攜帶的引數

//固定寫法
class RouterUtil{
  static Route<dynamic> ? onGenerateRoute (RouteSettings settings) {
    // 統一處理
    print("------------");
    final String? name = settings.name;
    final Function pageContentBuilder = routers[name] as Function;
    if (pageContentBuilder != null) {
      if (settings.arguments != null) {
        final Route route = MaterialPageRoute(
            builder: (context) =>
                pageContentBuilder(context, arguments: settings.arguments));
        return route;
      }else{
        final Route route = MaterialPageRoute(
            builder: (context) =>
                pageContentBuilder(context));
        return route;
      }
    }
  }
}
複製程式碼

3.元件

在flutter中萬物都是元件

  • 有狀態元件與無狀態元件

    StatefulWidget與StatelessWidget

import 'package:flutter/material.dart';
import 'package:flutterTanhua/pages/friends/components/RecommendList.dart';

class FansLike extends StatefulWidget {
  final arguments;
  final TabController ?tabController;
  const FansLike({this.tabController, this.arguments});

  _FansLikeState createState() => _FansLikeState(arguments: this.arguments);
}

class _FansLikeState extends State<FansLike>
    with SingleTickerProviderStateMixin {
  Map ? arguments;
  TabController ? tabController;
  _FansLikeState({this.tabController, this.arguments});

  @override
  void initState() {
    super.initState();
    tabController = TabController(length: 3, vsync: this);
  }
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.white,
        flexibleSpace: Container(
          decoration: BoxDecoration(
            gradient: LinearGradient(colors: [
              Colors.purple,
              Colors.deepOrange,
            ], begin: Alignment.centerLeft, end: Alignment.centerRight),
          ),
        ),
        title: TabBar(
          indicatorColor: Colors.white,
          indicatorSize: TabBarIndicatorSize.label, // 指示器是型別, label是這樣的,tab是沾滿整個tab的空間的
          isScrollable: true, // 是否可以滑動
          indicatorWeight: 3.0, // 指示器的高度/厚度
          unselectedLabelStyle: TextStyle(fontSize: 16), // 未選擇樣式
          labelStyle: TextStyle( fontSize: 24, height: 2), // 選擇的樣式
          tabs: [
            Tab(
              child: Text("互相關注", style: TextStyle(color: Colors.white),),
              // icon: Icon(Icons.recommend),
              // text: "推薦",
            ),

            Tab(
              // icon: Icon(Icons.directions_bike),
              child: Text("關注", style: TextStyle(color: Colors.white),),
            ),
            Tab(
              // icon: Icon(Icons.directions_bike),
              child: Text("粉絲", style: TextStyle(color: Colors.white),),
            ),
          ],
          controller: tabController,
        ),
      ),
      body: TabBarView(
        children: [
          Center(child:
          Container(
            padding: EdgeInsets.all(10),
            child: RecommendList(arguments: {"isIcon": "btn", "eachOther": "all"},),
          )),

          Center(child:
          Container(
            padding: EdgeInsets.all(10),
            child: RecommendList(arguments: {"isIcon": "btn", "eachOther": "like"},),
          )
          ),
          Center(child:
          Container(
            padding: EdgeInsets.all(10),
            child: RecommendList(arguments: {"isIcon": "btn", "eachOther": "fans"},),
          )
          ),
        ],
        controller: tabController,
      ),
    );
  }

  @override
  void dispose() {
    tabController!.dispose();
    super.dispose();
  }
}

複製程式碼
class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false, // 是否顯示debugger
      theme: ThemeData(
        primarySwatch: Colors.deepPurple,
      ),
      home: Tabs(),
      // 路由傳值, 配置路由
      onGenerateRoute: RouterUtil.onGenerateRoute,
      // routes: routers,
    );
  }
}
複製程式碼
import 'dart:math';
import 'package:flutter/material.dart';
import '../../../data/SwiperData.dart';
class MySwiper extends StatefulWidget {
  final arguments;
  const MySwiper({this.arguments}) ;
  _MySwiperState createState() => _MySwiperState(arguments: this.arguments);
}

class _MySwiperState extends State<MySwiper> {
  var currentPage = images.length - 1.0;
  PageController ? controller;
  Map arguments;
  _MySwiperState({ required this.arguments});
  @override
  void initState() {
    super.initState();
    controller = PageController(initialPage: images.length - 1);
    // print(controller);
    controller!.addListener(() {
      setState(() {
        currentPage = controller!.page!;
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    print(arguments);
    return Scaffold(
      backgroundColor: Colors.transparent,
      body: Center(
        child: Stack(
          children: <Widget>[
            // 兩者堆疊在一起。通過PageView滑動的Controller來控制當前顯示的page
            CardScrollWidget(currentPage),
            Positioned.fill(
              child: PageView.builder(
                itemCount: images.length,
                controller: controller,
                reverse: true,
                itemBuilder: (context, index) {
                  return Container();
                },
              ),
            )
          ],
        ),
      ),
    );
  }
}

class CardScrollWidget extends StatelessWidget {
  final currentPage;
  final padding = 20.0;
  final verticalInset = 20.0;
  CardScrollWidget(this.currentPage);

  @override
  Widget build(BuildContext context) {
    return AspectRatio(
      aspectRatio: (12.0 / 16.0) * 1.2,
      child: LayoutBuilder(
        builder: (context, contraints) {
          var width = contraints.maxWidth;
          var height = contraints.maxHeight;
          var safeWidth = width - 2 * padding;
          var safeHeight = height - 2 * padding;
          var heightOfPrimaryCard = safeHeight;
          var widthOfPrimaryCard = heightOfPrimaryCard * 12 / 16;
          var primaryCardLeft = safeWidth - widthOfPrimaryCard;
          var horizontalInset = primaryCardLeft / 2;
          List<Widget> cardList = [];
          for (int i = 0; i < images.length; i++) {
            var leftPage = i - currentPage;
            bool isOnRight = leftPage > 0;
            var start = padding +
                max(
                    primaryCardLeft -
                        horizontalInset * -leftPage * (isOnRight ? 15 : 1),
                    0);
            var cardItem = Positioned.directional(
                top: padding + verticalInset * max(-leftPage, 0.0),
                bottom: padding + verticalInset * max(-leftPage, 0.0) ,
                start: start,
                textDirection: TextDirection.rtl,
                child: ClipRRect(
                  borderRadius: BorderRadius.circular(16.0),
                  child: Container(
                    decoration: BoxDecoration(color: Colors.white, boxShadow: [
                      BoxShadow(
                          color: Colors.black12,
                          offset: Offset(3.0, 6.0),
                          blurRadius: 10.0)
                    ]),
                    child: AspectRatio(
                      aspectRatio: 12 / 16,
                      child: Stack(
                        fit: StackFit.expand,
                        children: <Widget>[
                          Align(
                            child: Image.network("${images[i]["header"]}", fit: BoxFit.cover,),
                          ),
                          Align(
                            alignment: Alignment.bottomLeft,
                            child: Column(
                              mainAxisSize: MainAxisSize.min,
                              crossAxisAlignment: CrossAxisAlignment.start,
                              children: <Widget>[
                                // 設定標題
                                Padding(
                                  padding: EdgeInsets.symmetric(
                                      horizontal: 16, vertical: 8),
                                  child: Column(
                                    children: <Widget>[
                                      Text(
                                        images[i]["nick_name"],
                                        style: TextStyle(
                                          color: Colors.black,
                                          fontSize: 18,
                                        ),
                                      ),
                                      Text("${images[i]["marry"]}${images[i]["degree"]} | 年齡相仿", textAlign: TextAlign.left),
                                      Padding(
                                        padding:
                                        EdgeInsets.only(left: 12, top: 10),
                                        child: Container(
                                          padding: EdgeInsets.symmetric(
                                              horizontal: 22.0, vertical: 6.0),
                                          decoration: BoxDecoration(
                                              color: Colors.purpleAccent,
                                              borderRadius:
                                              BorderRadius.circular(20.0)),
                                          child: Text("點選檢視",
                                              style: TextStyle(color: Colors.white)),
                                        ),
                                      )
                                    ],
                                  )
                                ),
                                SizedBox(
                                  height: 10,
                                ),
                              ],
                            ),
                          )
                        ],
                      ),
                    ),
                  ),
                ));
            cardList.add(cardItem);
          }
          return Stack(
            children: cardList,
          );
        },
      ),
    );
  }
}

複製程式碼
  • 通用頭部元件

    因為有些樣式需要自定義,感覺使用AppBar有些侷限,索性直接去掉這個,在body裡面自定義並下沉

    /// 自定義app頭部, 預設返回上一頁
    /// 引數 title:header顯示的文字
    import 'package:flutter/material.dart';
    
    class CommonHeader extends StatefulWidget {
      final title;
    
      const CommonHeader({this.title});
    
      _CommonHeaderState createState() => _CommonHeaderState(title: this.title);
    }
    
    class _CommonHeaderState extends State<CommonHeader> {
      Map title;
    
      _CommonHeaderState({required this.title});
    
      @override
      Widget build(BuildContext context) {
        // TODO: implement build
        return Container(
            constraints: BoxConstraints(maxHeight: 80),
            width: double.infinity,
            alignment: Alignment.center,
            decoration: BoxDecoration(
                image: DecorationImage(
                    image: new ExactAssetImage("images/headbg.png"),
                    fit: BoxFit.cover)),
            child: Stack(
              alignment: Alignment.center,
              children: <Widget>[
                Positioned(
                  left: 10,
                  bottom: 15,
                  child: GestureDetector(
                      onTap: () {
                        Navigator.pop(context, true);
                      },
                      child: Container(
                        alignment: Alignment.center,
                        child: Row(
                          children: <Widget>[
                            Icon(
                              Icons.arrow_back_ios,
                              color: Colors.white,
                            )
                          ],
                        ),
                      ),
                    )),
                Positioned(
                  bottom: 0,
                  child: Container(
                    constraints: BoxConstraints(maxHeight: 50),
                    alignment: Alignment.center,
                    child: Text(
                      "${title["title"]}",
                      style: TextStyle(
                          color: Colors.white,
                          fontSize: 22,
                          fontWeight: FontWeight.w500),
                    ),
                  ),
                )
              ],
            ));
      }
    }
    
    複製程式碼
  • button元件

    /// 漸變按鈕元件
    /// 引數:wh: 寬度 double
    /// 引數 ht: 高度 double
    /// 引數 src: 按鈕背景圖片,用來設定按鈕漸變色
    /// 引數 text: 按鈕需要顯示的文字
    
    import 'package:flutter/material.dart';
    
    typedef OnPressedChangeState();
    class LineGradientButton extends StatefulWidget {
      final OnPressedChangeState ? onPressedChangeState;
      final arguments;
      LineGradientButton(this.onPressedChangeState, {this.arguments});
    
      _LineGradientButtonState createState() => _LineGradientButtonState(this.onPressedChangeState, arguments:this.arguments);
    }
    
    class _LineGradientButtonState extends State<LineGradientButton> {
      Map ? arguments;
      OnPressedChangeState ? onPressedChangeState;
      _LineGradientButtonState(this.onPressedChangeState, {this.arguments});
    
      @override
      Widget build(BuildContext context) {
        // TODO: implement build
        print(arguments!["src"]);
        return GestureDetector(
          child: Container(
            width: arguments !["wd"],
            height: arguments !["ht"],
            // constraints: BoxConstraints(),
            alignment: Alignment.center,
            decoration: BoxDecoration(
                borderRadius: BorderRadius.circular(10),
                image: DecorationImage(
                    image: new ExactAssetImage(arguments!["src"]),
                    fit: BoxFit.cover)),
            child: Text("${arguments!["text"]}", style: TextStyle(color: Colors.white, fontSize: 18),),
          ),
          onTap: onPressedChangeState,
        );
      }
    }
    
    複製程式碼

image-20210716133857829.png

4.沉浸式頭部

image-20210716133857829
/// 交友頁面頂部元件
/// Align控制元件即對齊控制元件,能將子控制元件所指定方式對齊,並根據子控制元件的大小調整自己的大小。
/// Expanded元件是flutter中使用率很高的一個元件,它可以動態調整child元件沿主軸的尺寸,比如填充剩餘空間,比如設定尺寸比例。它常常和Row或Column組合起來使用。
import 'package:flutter/material.dart';

class Header extends StatefulWidget {
  const Header({Key? key}) : super(key: key);

  _HeaderState createState() => _HeaderState();
}

class _HeaderState extends State<Header> {
  // 建立Icon圖示
  List <Widget> _createIcon(){
    Color color;
    String title = "";
    List<Widget> tempList = [SizedBox(width: 50,)];
    for(int i = 0; i < 3; i++){
      switch (i) {
        case 0:
          color = Colors.red;
          title = "探花";
          break;
        case 1:
          title = "搜附近";
          color = Colors.blue;
          break;
        default:
          title = "測靈魂";
          color = Colors.deepOrangeAccent;
          break;
      }
      tempList.add(Expanded(
        child: GestureDetector(
          child: Column(
              children: <Widget>[
                SizedBox(height: 50,),
                Align(
                  child: Container(
                      width: 60,
                      constraints: BoxConstraints(maxHeight: 60),
                      decoration: BoxDecoration(
                          borderRadius: BorderRadius.circular(30),
                          color: color
                      ),
                      // color: Colors.red,
                      child: Align(
                        child: Container(
                          width: 40,
                          constraints: BoxConstraints(maxHeight: 40),
                          decoration: BoxDecoration(
                              borderRadius: BorderRadius.circular(20),
                              image: DecorationImage(
                                  image: new ExactAssetImage("images/${i}.png"),
                                  fit: BoxFit.cover)),
                        ),
                      )
                  ),
                ),
                Text("${title}", style: TextStyle(color: Colors.white),)
              ],
            ),
          onTap: (){
            switch (i) {
              case 0:
                Navigator.pushNamed(context, "/searchFlower");
                break;
              case 1:
                Navigator.pushNamed(context, "/searchNear");
                break;
              case 2:
                Navigator.pushNamed(context, "/testSoul");
                break;
              }

            },
          )
      ));
    }
    tempList.add(SizedBox(width: 50,));
    return tempList;
  }
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return
      Container(
        constraints: BoxConstraints(maxHeight: 160),
          width: double.infinity,
          decoration: BoxDecoration(
            image: DecorationImage(
              image: new ExactAssetImage("images/img.png"),
              fit: BoxFit.cover)),
          child: Row(
            children: this._createIcon()
          ),
        );
  }
}

複製程式碼

5.遇到的問題

  1. Container 巢狀 Container 時,明明指定了子元件的寬高,為什麼不起作用 ?

    ​ 這是因為 Container 的寬高計算機制造成的,因為 Container 在計算寬高的時候,不僅需要考慮 width 和 height 屬性,還要遵循父元件的尺寸約束,即 BoxConstraints 。

    ​ BoxConstraints 有四個屬性,分別為 minWidth、maxWidth、minHeight、maxHeight。預設情況下,minWidth 和 maxWidth 的預設值為螢幕寬度,minHeight 和 maxHeight 的預設值為螢幕高度。

    ​ 父元件通過設定 BoxConstraints 來約束子元件的最小和最大尺寸,如果子元件的 width 和 height 不在父元件 Constraints 限制的範圍內,則子元件的尺寸會被強制設定為符合父元件 Constraints 約束的值。

    ​ 給子元件設定的寬高都為 50 ,而父元件約束的最小寬高分別為螢幕寬度和高度,子元件的寬高不滿足父元件的約束,所以當我們給子元件設定了寬高時,並沒有起到作用,所以子元件會充滿父元件。

    ​ 解決的方式有多種,其中最簡單的就是在子元件外層套 Center 元件,檢視 Center 元件的原始碼可知,被 Center 元件包裹的子元件,該子元件將不再受父元件的尺寸約束。Center 元件又是繼承自 Align 元件的,所以用 Align 元件巢狀子元件也是可以的。

  2. Null check operator used on a null value

​ 這個主要是我在定義引數的時候允許為空,但是在使用的時候沒有判斷是否為空造成。

  1. RenderFlex children have non-zero flex but incoming height constraints are unbounded.

    ​ 原因是ListView垂直方向的計算是包裹子View的,也就是說子View必須有一個明確的高度,或者儘可能小的高度,而不能是無限高。 ​ Row是橫向排列,在Row中使用Expanded是填充水平方向的剩餘空間,這和ListView的這一特性沒有衝突,可以使用。

    而Column是豎直排列,在Column中使用Expanded是填充豎直方向的剩餘空間,這將和ListView的這一特性發生衝突,因為ListView將無法計算自己的子View的高度。

    ​ 這個主要是我使用了ListView但是又把他當作是Container 的中的Colum元件去使用,所以我給這個ListView包了一層Expand解決掉了。

相關文章