Flutter 初体验

Flutter 环境搭建

1. 安装Flutter SDK 配置环境变量

2. 配置两个用户环境变量

变量名: FLUTTER_STORAGE_BASE_URL

变量值: https://storage.flutter-io.cn

 变量名: PUB_HOSTED_URL

 变量值: https://pub.flutter-io.cn

3. 安装Android Studio 顺带安装Android SDK

脚手架组件

Scaffold

1. appBar: 显示在界面顶部的导航栏

2. body: 当前页面所显示的主要内容

3. bottomNavigationBar: 显示在底部的导航栏

无状态组件

// 无状态组件 必须要继承 StatelessWidget
class Component extends StatelessWidget {
  // 创建组件 创建一个方法
  @override
  Widget build(BuildContext context) {
    // 返回一个组件
    return Container(
      color: Colors.white,
      alignment: Alignment.center,
      child: Text('一个帅气的前端'),
    );
  }
}

效果如下

 

有状态组件:

1. 在组件内部需要维护可变的状态来重构界面的组件

2. 使用 setState() 方法改编状态的值来触发组件重新构建界面

3. 有状态组件也可以展示构造函数传入的数据

定义方式:状态组件包含两个类

一个类继承自 StatefulWidget , 作为页面结构的一部分 , 用作外部展示

一另一个类继承自State , 用于记录组件状态 , 并根据状态的变化 , 重新构建组件

// 对外作展示
class MyWidget extends StatefulWidget {
  const MyWidget({Key? key}) : super(key: key);

  @override
  State<MyWidget> createState() => _MyWidgetState();
}

// 控制逻辑
class _MyWidgetState extends State<MyWidget> {
  int index = 0;
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.white,
      child: TextButton(
        child: Text('一共点击$index了多少次'),
        onPressed: () {
          setState(() {
            index++;
          });
        },
      ),
    );
  }
}

生命周期

无状态组件

build() : 该方法是用来创建组件

状态组件

statefulWidget.createState() : 创建逻辑组件

initState() : 状态组件初始化

didChangeDependencies() : 依赖状态更新时调用

build() : 组件创建

addPostFrameCallback() : 组件渲染完成

deactivate() : 卸载前

dispose() : 卸载后

特殊

didUpdateWidget(): 父组件发生变化可以监听到 , 重新构建组件

// 对外作展示
class MyWidget extends StatefulWidget {
  const MyWidget({Key? key}) : super(key: key);

  @override
  State<MyWidget> createState() => _MyWidgetState();
}

// 控制逻辑
class _MyWidgetState extends State<MyWidget> {
  int index = 0;
  @override
  void initState() {
    //监听组件是否渲染完成
    SchedulerBinding.instance.addPostFrameCallback((timeStamp) {
    });
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.white,
      child: TextButton(
        child: Text('一共点击$index了多少次'),
        onPressed: () {
          setState(() {
            index++;
          });
        },
      ),
    );
  }
}

Container组件是一个用户绘制 , 定位 , 调整大小的组件

// 对外作展示
class MyWidget extends StatefulWidget {
  const MyWidget({Key? key}) : super(key: key);

  @override
  State<MyWidget> createState() => _MyWidgetState();
}

// 控制逻辑
class _MyWidgetState extends State<MyWidget> {
  int index = 0;
  @override
  void initState() {
    //监听组件是否渲染完成
    SchedulerBinding.instance.addPostFrameCallback((timeStamp) {});
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Flutter初体验')),
      body: Container(
          alignment: Alignment.center,
          color: Colors.white,
          child: Container(
            width: 200,
            height: 200,
            padding: const EdgeInsets.only(
                left: 25.0, right: 25.0, top: 30, bottom: 15),
            // 如果设置了decoration属性 , 那么Container组件的color就不能独立设置
            color: Colors.blue[100],
            child: Container(
              decoration: BoxDecoration(color: Colors.red[100]),
            ),
          )),
    );
  }
}

效果图如下

 布局

纵向布局

// 控制逻辑
class _MyWidgetState extends State<MyWidget> {
  int index = 0;
  @override
  void initState() {
    //监听组件是否渲染完成
    SchedulerBinding.instance.addPostFrameCallback((timeStamp) {});
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Flutter初体验')),
      body: Center(
        child: Container(
            width: 200,
            height: 300,
            padding: const EdgeInsets.only(
                left: 25.0, right: 25.0, top: 30, bottom: 15),
            // 如果设置了decoration属性 , 那么Container组件的color就不能独立设置
            color: Colors.blue[100],
            child: Column(
              // 主轴对其方式
              // mainAxisAlignment: MainAxisAlignment.start,
              // 副轴对齐方式
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
              /* 本地图片使用  要在pubspec.yaml 配置assets根路径
              /    assets:
              /      - assets/
              */
                Image.asset('assets/qyc.png'),
                const Padding(
                  padding: EdgeInsets.only(top: 5),
                  child: Text(
                    '犬夜叉',
                    style: TextStyle(color: Color.fromARGB(255, 231, 115, 156)),
                  ),
                ),
                const Padding(
                  padding: EdgeInsets.only(top: 5),
                  child: Text(
                    '戈薇',
                    style: TextStyle(color: Color.fromARGB(255, 231, 115, 156)),
                  ),
                ),
              ],
            )),
      ),
    );
  }
}

横向布局

import 'package:flutter/material.dart';

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

  @override
  State<MyWidget> createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: const Text('横向布局'),
        ),
        body: Center(
            child: Container(
          padding:
              const EdgeInsets.only(left: 10, top: 10, bottom: 10, right: 40),
          height: 100,
          color: Colors.blue[100],
          child: Row(
            // 主轴方式对齐
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Image.asset(
                'assets/qyc.png',
                width: 100,
                height: 100,
              ),
              const Text(
                '犬夜叉',
                style: TextStyle(color: Colors.pink),
              )
            ],
          ),
        )));
  }
}

 弹性布局

解决文字溢出问题

Expanded

        body: Center(
            child: Container(
          padding:
              const EdgeInsets.only(left: 10, top: 10, bottom: 10, right: 40),
          height: 100,
          color: Colors.blue[100],
          child: Row(
            // 主轴方式对齐
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Image.asset(
                'assets/qyc.png',
                width: 100,
                height: 100,
              ),
              const Expanded(
                  child: Text(
                '犬犬犬犬犬犬犬犬犬犬犬犬犬犬犬犬犬犬犬犬夜叉',
                style: TextStyle(color: Colors.pink),
              ))
            ],
          ),
        )));

 效果如下

 如果只是用Text组件的话 就会出现下面的情况

 Expanded有flex属性 类似css子盒子flex

定位

使用Stack组件搭配Positioned组件

import 'package:flutter/material.dart';

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

  @override
  State<MyWidget> createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: const Text('定位'),
        ),
        body: Center(
          child: Stack(alignment: Alignment.center, children: [
            Positioned(
              top: 20,
              child: Container(
                height: 100,
                width: 100,
                color: Colors.blue[100],
              ),
            ),
            Positioned(
                top: 0,
                child: Image.asset(
                  'assets/qyc.png',
                  width: 80,
                  height: 80,
                ))
          ]),
        ));
  }
}

 一个小小的案例

import 'package:flutter/material.dart';

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

  @override
  State<MyWidget> createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: const Text('商品列表'),
        ),
        body: Container(
          padding: const EdgeInsets.all(10),
          child: Column(children: [
            Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: const [
                  Text(
                    '2021-05-15 21:49:48',
                    style: TextStyle(color: Color(0xff666666), fontSize: 13),
                  ),
                  Text(
                    '代发货',
                    style: TextStyle(color: Color(0xffff9240), fontSize: 14),
                  )
                ]),
            Padding(
              padding: const EdgeInsets.only(top: 5),
              child:
                  Row(crossAxisAlignment: CrossAxisAlignment.start, children: [
                Image.asset(
                  'assets/qyc.png',
                  width: 86,
                  height: 86,
                ),
                Expanded(
                    child: Padding(
                  padding: const EdgeInsets.only(left: 10, right: 10),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Row(
                        mainAxisAlignment: MainAxisAlignment.spaceBetween,
                        children: const [
                          Expanded(
                              child: Text(
                            '犬犬犬犬犬犬犬犬犬犬犬犬犬犬犬犬犬犬犬夜叉',
                            style: TextStyle(
                                color: Color(0xff262626), fontSize: 13),
                          )),
                          Padding(
                            padding: EdgeInsets.only(left: 10),
                            child: Text('X 1',
                                style: TextStyle(
                                    color: Color(0xff262626), fontSize: 15)),
                          )
                        ],
                      ),
                      Padding(
                        padding: const EdgeInsets.only(top: 5),
                        child: Container(
                          decoration: BoxDecoration(
                              color: const Color(0xfff7f7f8),
                              borderRadius: BorderRadius.circular(2.0)),
                          padding: const EdgeInsets.only(
                              top: 3, right: 5, bottom: 3),
                          child: const Text('规格:粉色,小狗状',
                              style: TextStyle(
                                  color: Color(0xff888888), fontSize: 11)),
                        ),
                      ),
                      const Padding(
                        padding: EdgeInsets.only(top: 5),
                        child: Text(
                          '¥520',
                          style:
                              TextStyle(color: Color(0xff262626), fontSize: 14),
                        ),
                      ),
                      Padding(
                          padding: const EdgeInsets.only(top: 10),
                          child: Row(
                            mainAxisAlignment: MainAxisAlignment.end,
                            children: const [
                              Text(
                                '合计:  ¥520',
                                style: TextStyle(
                                    color: Color(0xff262626), fontSize: 16),
                              ),
                            ],
                          )),
                    ],
                  ),
                ))
              ]),
            )
          ]),
        ));
  }
}

效果图

滚动组件

ListView 构建方式

1. 默认构造函数

会一次性创建所有item 适用于item的情形

常用属性:

controller: 滚动控制器 , 用于监听列表滚动

shrinkWrap: 是否根据item得总长度来设置ListVirew的长度

Physics: 滚动效果, 比如:禁用滚动 , 滚动效果回弹等等

scrollDirection: 滚动方向

 ListView(
          children: [
            // *10
            Container(
              color: Colors.blue,
              height: 120,
            ),
          ],
        ));

2. 命名构造函数 builder

展示动态的长列表时

使用 itemBuilder 指定列表要展示的item

使用 itemCount 指定列表item的个数

 ListView.builder(
          itemCount: 10,
          itemBuilder: (BuildContext context, int index) {
            return Container(
              height: 100,
              color: Colors.pink[100],
            );
          },
        ));

3. 命名构造函数 separated

列表中需要分割线时 可以使用

separated 自定义分割线

 ListView.separated(
          separatorBuilder: (BuildContext context, int index) {
            return Container(
              height: 2,
              color: const Color(0xfff7f7f8),
            );
          },
          itemCount: 10,
          itemBuilder: (BuildContext context, int index) {
            return Container(
              height: 100,
              color: Colors.pink[100],
            );
          },
        ));

GridView 网格列表滚动

构建方式

默认构造函数

使用 gridDelegate 控制GridView子组件的布局方式

SliveGridDelegateWithFixedCrossAxisCount固定次轴子元素数量

SliverGridDelegateWithMaxCrossAxisExtent

固定次轴子元素的最大长度

固定次轴子元素数量

GridView(
          gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
              // 每行三个
              crossAxisCount: 3,
              // 主轴间距
              mainAxisSpacing: 5,
              // 次轴间距
              crossAxisSpacing: 5,
              // 次轴和主轴长度比列 宽高比
              childAspectRatio: 4 / 3),
          children: [
            Container(
              color: Colors.blue[100],
            ),
            Container(
              color: Colors.blue[100],
            ),
            Container(
              color: Colors.blue[100],
            ),
            Container(
              color: Colors.blue[100],
            ),
            Container(
              color: Colors.blue[100],
            ),
            Container(
              color: Colors.blue[100],
            ),
          ],
        ));

 固定次轴子元素的最大长度

 GridView(
          gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
              // 每个子元素宽度
              maxCrossAxisExtent: 120,
              // 主轴间距
              mainAxisSpacing: 5,
              // 次轴间距
              crossAxisSpacing: 5,
              // 次轴和主轴长度比列 宽高比
              childAspectRatio: 4 / 3),
          children: [
            Container(
              color: Colors.blue[100],
            ),
            Container(
              color: Colors.blue[100],
            ),
            Container(
              color: Colors.blue[100],
            ),
            Container(
              color: Colors.blue[100],
            ),
            Container(
              color: Colors.blue[100],
            ),
            Container(
              color: Colors.blue[100],
            ),
          ],
        ));

 设置宽度后, 每行能放多少放多少

builder 构造函数

用于动态创建grid滚动列表

               GridView.builder(
            gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 3,
                mainAxisSpacing: 5,
                crossAxisSpacing: 5,
                childAspectRatio: 4 / 3),
            // 指定个数
            itemCount: 50,
            itemBuilder: (BuildContext context, int index) {
              return Container(height: 120, color: Colors.blue[100]);
            }));

命名构造函数 count 和 extent 

对上面两种布局的封装

          GridView.count(
            crossAxisCount: 3,
            mainAxisSpacing: 5,
            crossAxisSpacing: 5,
            childAspectRatio: 4 / 3,
            children: [
              Container(
                color: Colors.blue[100],
              )
            ]));

CustomScrollView 

相当于一个容易 把多个滚动组件放在一个盒子里 成为一个滚动组件 不然是独立的

        CustomScrollView(
          // 放子组件
          slivers: [
            // 网格组件
            SliverGrid.count(
              crossAxisCount: 3,
              mainAxisSpacing: 5,
              crossAxisSpacing: 5,
              childAspectRatio: 4 / 3,
              children: [],
            ),
            // 列表组件 delegate类似itemBuilder
            SliverList(
                delegate: SliverChildBuilderDelegate(
              childCount: 10,
              (BuildContext context, int index) {
                return Container(
                  padding: const EdgeInsets.only(bottom: 3),
                  height: 100,
                  child: Container(height: 120, color: Colors.blue[100]),
                );
              },
            ))
          ],
        ));

动画组件


class MyWidget extends StatefulWidget {
  const MyWidget({Key? key}) : super(key: key);
  @override
  State<MyWidget> createState() => _MyWidgetState();
}

// with SingleTickerProviderStateMixin 保证状态组件可以监听屏幕刷新
class _MyWidgetState extends State<MyWidget>
    with SingleTickerProviderStateMixin {
  // 动画控制器
  late AnimationController _animationController;
  // 动画对象
  late Animation<Offset> _animation;

  @override
  void initState() {
    // 创建动画控制器
    // vsync: 监听屏幕刷新 避免屏幕外动画
    _animationController =
        AnimationController(vsync: this, duration: const Duration(seconds: 10));
    // 创建动画对象
    // Tween 专门定义起始和结束动画
    _animation = Tween<Offset>(begin: Offset.zero, end: const Offset(1, -1))
        .animate(_animationController);

    // 启动动画
    _animationController.fling();
    super.initState();
  }

  @override
  void dispose() {
    // 卸载动画
    _animationController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: const Text('商品列表'),
        ),
        body: Center(
          child: SlideTransition(
            position: _animation,
            child: Container(
              width: 80,
              height: 80,
              color: Colors.pink[100],
            ),
          ),
        ));
  }
}

dio 插件 网络请求

https://pub.flutter-io.cn/

reques.dart

import 'package:dio/dio.dart';

class RequestsManager {
  // 声明Dio
  Dio? _dio;
  // 私有静态属性
  static RequestsManager? _instance;
  // 私有命名构造函数
  RequestsManager._initManager() {
    // 配置请求
    BaseOptions baseOptions = BaseOptions(
        // 连接超时时间
        connectTimeout: 5000,
        // 响应超时时间
        receiveTimeout: 3000,
        // 根路径
        baseUrl: 'https://pcapi-xiaotuxian-front.itheima.net/');
    _dio = Dio(baseOptions);
    // 请求拦截器
    _dio!.interceptors.add(InterceptorsWrapper(onRequest: (
      RequestOptions options,
      RequestInterceptorHandler handler,
    ) {
      // 发送请求前的拦截操作
      // 设置请求头信息
      options.headers = {
        'Authorization': '',
        'source-client': 'app',
      };
      return handler.next(options);
    },
        // 接收响应前拦截参数
        onResponse: (response, handler) {
      return handler.next(response);
    },
        // 捕获异常信息
        onError: (DioError e, handler) {
      return handler.next(e);
    }));
  }
  // 创建单例对象并向外界提供单例对象的方法
  factory RequestsManager() {
    // 判断单例对象是否存在
    _instance ??= RequestsManager._initManager();
    return _instance!;
  }

  // 处理请求公共方法 低耦合
  handleRequest(
    String path,
    String method, {
    data,
    Map<String, dynamic>? queryParmeters,
  }) {
    return _dio!.request(path,
        data: data,
        queryParameters: queryParmeters,
        options: Options(method: method));
  }
}

home_request.dart

import 'package:dio/dio.dart';
import 'package:erabbit_app_flutter/models/home_models.dart';
import 'package:erabbit_app_flutter/service/requests.dart';

class HomeApi {
  // 获取首页数据
  static Future<HomeModel> homeFetch() async {
    Response response =
        await RequestsManager().handleRequest('home/index', 'get');
    dynamic ret = response.data['result'];
    // 将result字段对应的首页网络数据(字典类型)传给首页总模型
    HomeModel homeModel = HomeModel.fromJson(ret);
    return homeModel;
  }
}

home.dart

// 轮播图数据
  List<ImageBannersModel>? imageBanners;
// 分类商品
  List<CategoryGridsModel>? categoryGrids;
  // 获取数据
  void _loadData() async {
    try {
      HomeModel homeModel = await HomeApi.homeFetch();
      setState(() {
        imageBanners = homeModel.imageBanners;
        categoryGrids = homeModel.categoryGrids;
        categoryGrids!.addAll(categoryGrids!.sublist(1, 6));
      });
      debugPrint('$homeModel');
    } catch (e) {
      debugPrint('#e');
    }
  }

点击组件

         GestureDetector(
              child: Image.asset('assets/home_login.png'),
              onTap: () {
                // 逻辑
              },
            )

判断设备 设置状态栏

 // 判断设备
  if (Platform.isAndroid) {
    // 设置状态栏透明
    SystemChrome.setSystemUIOverlayStyle(
        const SystemUiOverlayStyle(statusBarColor: Colors.transparent));
  }

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值