likes
comments
collection
share

从零到应用:我的Flutter项目开发之旅

作者站长头像
站长
· 阅读数 58

前情提要

初次使用 Flutter 页面布局或者其他问题,多多少少可能都会有一点。如遇到并解决,会及时更新。

我经常去的几个网站330 多个组件使用介绍Flutter 入门介绍学习。这两个网站我个人感觉,前者偏向组件使用和语法介绍,而后者更适合新手来学习比较重要的概念。组件并不是很全。建议配合“食用”。

建议:我下文提到的任何组件 API 都可以通过以上两个网站去寻找学习!!!!!!!!官网个人觉得新人看起来稍有不妥,特别是我这种英语很垃圾的人,机翻根本看不懂。


先看蓝湖设计图

从零到应用:我的Flutter项目开发之旅

下面是我初次分解的结构。

从零到应用:我的Flutter项目开发之旅

我觉得前期页面结构基础不想好,画的时候会出现很多很多问题,有一些样式结构不生效,那么很有可能是你的页面结构有问题!亲身经历,改一下结构样式全生效了。

如果绘画页面有出现黑黄警告条纹的时候,那么代表你的像素溢出了。它不像 HTML,溢出了也很难发觉。而 Flutter 会警告你,这个时候你只需要打开 Android Studio 的 Flutter DevTools 功能。查看你的结构问题。基本改改高宽就能解决。再不济结构改变下。

从零到应用:我的Flutter项目开发之旅

全部代码

    return Container(
      decoration: BoxDecoration(
        color: Color(0xfff2f2f2),
      ),
      width: 345.w,
      // height: 389.h,
      child: Column(
        children: [
          Image.network(
            "http://yz-shigongli.oss-accelerate.aliyuncs.com/2022-03/1648094119154-7b280bbf63105a8e90299e2d79c8c6ee.jpeg",
            width: 345.w,
            height: 230.h,
            fit: BoxFit.cover,
          ),
          Container(
            padding: EdgeInsets.fromLTRB(15, 20, 0, 0),
            child: Row(
              crossAxisAlignment: CrossAxisAlignment.start,
              verticalDirection: VerticalDirection.up,
              children: <Widget>[
                Container(
                  child: Text(
                    item.name,
                    style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
                  ),
                ),
              ],
            ),
          ),
          Container(
            padding: const EdgeInsets.fromLTRB(19, 0, 0, 0),
            child: Row(
              crossAxisAlignment: CrossAxisAlignment.start,
              verticalDirection: VerticalDirection.up,
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: <Widget>[
                Container(
                  child: RichText(
                    text: const TextSpan(children: <InlineSpan>[
                      TextSpan(
                          text: '8555元/',
                          style: TextStyle(
                              color: Color(0xffce3800),
                              fontSize: 22,
                              fontWeight: FontWeight.bold)),
                      TextSpan(
                          text: '公顷',
                          style: TextStyle(
                              color: Color(0xffce3800),
                              fontSize: 10,
                              fontWeight: FontWeight.bold)),
                    ]),
                  ),
                ),
                Align(
                  widthFactor: 1.2,
                  heightFactor: 1.2,
                  alignment: Alignment(1.2, -5),
                  child: Container(
                    margin: EdgeInsets.only(right: 13),
                    width: 86.w, //+6
                    height: 44.h, //+10
                    child: ElevatedButton(
                      onPressed: () {
                        print('点我去预约${item.name}');
                      },
                      child: const Text(
                        "预约",
                        style: TextStyle(
                            fontSize: 19,
                            color: Colors.white,
                            fontWeight: FontWeight.bold),
                      ),
                      style: ButtonStyle(
                        backgroundColor:
                            MaterialStateProperty.all(const Color(0xFFFF703B)),
                        shape: MaterialStateProperty.all(
                            const RoundedRectangleBorder(
                                //这个0像素圆角style如果删掉的话,按钮会变成默认样式,自带圆角
                                borderRadius:
                                    BorderRadius.all(Radius.circular(0)))),
                      ),
                    ),
                  ),
                ),
              ],
            ),
          ),
// 地址信息 进入地图
          Container(
              padding: EdgeInsets.fromLTRB(19, 20, 0, 10),
              child: Row(
                children: [
                  Image.asset(
                    "images/gpsImgae.png",
                    width: 11.w,
                    height: 13.h,
                    fit: BoxFit.cover,
                  ),
                  Container(
                      padding: EdgeInsets.fromLTRB(5, 0, 0, 0),
                      width: 300.w,
                      child: Row(
                        mainAxisAlignment: MainAxisAlignment.spaceBetween,
                        children: [
                          Text(
                            '距离当前位置2.6公里',
                            style: TextStyle(
                                fontSize: 15, fontWeight: FontWeight.bold),
                          ),
                          GestureDetector(
                            child: Text(
                              '进入地图 >>',
                              style: TextStyle(
                                  fontSize: 15, fontWeight: FontWeight.bold),
                            ),
                            onTap: () {
                              print('进入地图');
                            },
                          )
                        ],
                      )),
                ],
              )),
        ],
      ),
    );

整体布局解释

这里面的结构布局还是有点乱的。基本就是使用 Container 包裹一个 Column 或者 Row,以我新手理解。垂直多元素布局使用 Column。想有 CSS 里面那种 Flex 布局的话使用 Row 就足够了。Flutter 里面也有 Flex 组件,但我用的少,感觉上面两个就够用了。下面是官方介绍。

Row

Row可以沿水平方向排列其子 Widget。

Column

Column可以在垂直方向排列其子组件。参数和Row一样,不同的是布局方向为垂直,主轴纵轴正好相反。

Container

感觉Container像是一个万能组件,什么都可以。这边我说一下他指定边框线条的样式写法。(百度了好多才找到!其他都是写四条边框的。而我需要的是单独一条边框。)

参考资料视频:ninghao.net/video/6441#…

代码

//首先定义一个Container 然后给他加上decoration (修饰器属性)

Container(
  decoration: const BoxDecoration(
      border: Border(
        //同理,需要上边框就写top
          bottom:
              BorderSide(width: 0.5, color: Color(0x20000000)
                  ))),

  child:<省略>
)

预约按钮

预约按钮这边也是费劲了心思,一开始我选择用padding,发现会撑起其他元素,后来又选择用margin,还是一样的问题,会顶起其他元素。苦思冥想,选择使用StackPositioned组件,奈何我页面布局写的太拉。Positioned总是出问题,有时候还没效果。最后!我选择了使用Align,因为他正好适合我这种只想简单的调整一个子元素在父元素中的位置

 Align(
  widthFactor: 2,
  heightFactor: 2,
  alignment: Alignment(2,0.0),
  child: FlutterLogo(
    size: 60,
  ),
)

Alignment他有两个属性 x、y,分别表示在水平和垂直方向的偏移。

注意:widthFactor,heightFactor 这两个参数一定要大于 1.0 哪怕 1.1 也好,因为Alignment是要和他们俩相乘的(Alignment.xchildWidth/2+childWidth/2, Alignment.ychildHeight/2+childHeight/2) 这是他的公式。


进入地图文字点击

这个布局很简单了就,我上面提到Row他能支持多种布局。详情请移步至官网 API。我这里用到的是crossAxisAlignment: CrossAxisAlignment.start, 就是两个元素,一左一右布局。Text 文本点击我用的是GestureDetector 组件。它是手势识别的组件,可以识别点击、双击、长按事件、拖动、缩放等手势。这里我用到了点击。

  GestureDetector(
    child: Text(
      '进入地图 >>',
    style: TextStyle(
      fontSize: 15, fontWeight: FontWeight.bold),
    ),
    onTap: () {
      print('进入地图');
     },
    )

##列表上下刷新加载的使用

简介

这个是一个上拉加载,下拉刷新的列表。本来是想自己写的,但是遇到很多难题,时间不太够。还是选择使用第三方组件 flutter_easyrefresh 组件只是提供了刷新加载的功能和 UI,至于一次几页,一页多少条这个自己定义。 使用非常简单,单纯记一下语法。

引入方法

pubspec.yaml文件里面添加如下代码,然后执行 Pub get。

flutter_easyrefresh: ^0.0.0 #版本请移步上述地址。懒人可用2.2.1

页面使用

创建一个 Dart 文件,创建一个StatefulWidget 类。import 插件

import 'package:flutter_easyrefresh/easy_refresh.dart';

然后实例化一个_controller用来触发刷新和加载动作。

  EasyRefreshController _controller = EasyRefreshController();

剩下就是自由发挥的地方了。根据业务需求来写,我这边只简单的用到了刷新加载,所以我定义俩数组和其余参数就可以。代码如下:

  final jobList = Rx<List<Job>>([]);
  final newJobList = Rx<List<Job>>([]);
  var pageIndex = 1; //页数
  var count = 10; //每页10条
  late  int maxSum;//最多条数
  Map<String, dynamic> map = {'pageNum': 1};

  void getNewData() {
    pageIndex = 1;
    map['pageNum'] = pageIndex;
    requestXXXX(map).then((value) {
      maxSum=value['total'];
      List<Job> list = [];
      value['records'].forEach((item) {
        list.add(Job.fromJson(item));
      });
      jobList(list);
      newJobList(list);//首次获取刷新,我把最新的数据拿出去,等待加载的时候合并。
    });
  }

  void getMoreData() {
    pageIndex++;
    map['pageNum'] = pageIndex;
    requestXXXX(map).then((value) {
      maxSum=value['total'];
      List<Job> list = [];
      List<Job> Newlist = [];
      value['records'].forEach((item) {
        list.add(Job.fromJson(item));
      });
      newJobList.value.addAll(list);//拿到加载的数据,和我之前第一次刷新的数据合并下。
      Newlist.addAll(newJobList.value);//存放到局部变量。
      jobList(Newlist);//这里用的是GTEX 要覆盖整个数组,才能触发widget的Obx刷新。

    });
  }

  int _count = 0;

Widge 里使用

这里建议先看一下官方文档的使用教程,他有三种方法,我这个是比较基础的。写的有点乱。对着官网写法看,会比较容易些。

官方写法:

 // 方式一
  EasyRefresh(
    child: ScrollView(),
    onRefresh: () async{
      ....
    },
    onLoad: () async {
      ....
    },
  )


我的写法:

EasyRefresh(
        controller: _controller,
        firstRefresh: true,
        onRefresh: () async {
          await Future.delayed(Duration(seconds: 2), () {
            print("下拉刷新-----");
            getNewData();
            _count = jobList.value.length;
            print("最新条数" + _count.toString());
            _controller.resetLoadState();
          });
        },
        onLoad: () async {
          await Future.delayed(Duration(seconds: 2), () {
            print("上拉加载-----");
            getMoreData();
            _count = jobList.value.length;
            print("加载更多条数" + _count.toString());

            _controller.finishLoad(noMore: _count >= maxSum); //这是用来防止多次上拉加载的。
          });
        },
        child: Obx(
          () => Column(
              children: 这里直接map你的数据。
              ),
        ));

关于国际化的问题,官方原话:不提供自带国际化支持,请自行设置 ClassicalHeader 和 ClassicalFooter 中需要展示的文字。

本文同步我的技术文档

转载自:https://juejin.cn/post/7254032949230633021
评论
请登录