与Flutter的第一次碰撞

977 阅读5分钟

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解决掉了。

6.源码地址

github.com/Visupervi/F…