flutter学习(一)环境搭建及基础速通

环境搭建

看下面的链接 flutter环境搭建

runApp

runApp是内部flutter内部提供的函数,启动一个flutter应用就从调用这个函数开始(类比React的main.tsx)

dart 复制代码
void main() {
    runApp(const MyApp())
}

其中,MyApp是入口widget,那widget是什么?

widget

widget类似于React中的组件,React中皆有组件构成,flutter中皆由widget组成

Material库

Material库是google设计的一套设计风格库,比如文字排版、颜色、动画等,目的是为 安卓、IOS、鸿蒙等多个平台提供统一的交互和视觉体验

Material库是flutter自带的,不需要额外安装。并且Material包含很多拆箱可用的widget

基础组件

MaterialApp

整个flutter应用需要被MaterialApp包裹。它有一些常用属性

  • title: 窗口标题
  • theme: 应用主题
  • home: 窗口主题内容
dart 复制代码
import 'package:flutter/material.dart';

void main(List<String> args) {
  runApp(
    MaterialApp(
      title: "Flutter组件初体验",
      // ThemeData主题类
      theme: ThemeData(scaffoldBackgroundColor: Colors.blue),
      // Scaffold骨架组件
      home: Scaffold(),
    ),
  );
}

Scaffold

Scaffold是构建 Material Design 风格页面的核心布局组件,即为页面骨架组件。比较常用的几个属性如下

  • appBar: 页面顶部
  • body:页面内容区域
  • bottomNavigationBar: 底部导航栏
  • backgroundColor:设置整个 Scaffold 的背景颜色
  • floatingActionButton: 悬浮按钮,常用于触发页面的主要动作
  • ....
dart 复制代码
import 'package:flutter/material.dart';

void main(List<String> args) {
  runApp(
    MaterialApp(
      title: "Flutter组件初体验",
      home: Scaffold(
        appBar: AppBar(title: Text("头部区域")),
        body: Container(child: Center(child: Text("中部区域"))),
        bottomNavigationBar: Container(
          height: 80,
          child: Center(child: Text('底部区域')),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () {},
          backgroundColor: Colors.blue,
          child: Icon(Icons.add),
        ),
      ),
    ),
  );
}

其中 Text是显示文本的组件,Container是用来作为容器(类似div),设置高度以及child,FloatingActionButton是作为悬浮按钮容器的组件,Center是让组件水平、垂直居中的组件

无状态组件和有状态组件

无状态组件 StatelessWidget

无状态组件是单个类,用于静态内容展示,外观由配置参数决定,创建后内部状态不可变,生命周期只有build

dart 复制代码
import 'package:flutter/material.dart';

void main(List<String> args) {
  runApp(MainPage()); // Widget
}

// 无状态组件 -自定义组件中的一种
class MainPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return MaterialApp(
        title: "Flutter组件初体验-无状态组件",
        home: Scaffold(
          appBar: AppBar(
            title: Text("头部区域"),
          ),
          body: Container(
            child: Center(
              child: Text("中部区域"),
            ),
          ),
          bottomNavigationBar: Container(
            height: 80,
            child: Center(
              child: Text("底部区域"),
            ),
          ),
        ));
  }
}
  • 创建一个新的类,继承StatelessWidget类并重写build方法
  • build需要返回一个Widget
  • 纯展示型组件,无交互
  • 无状态组件在被创建或者父组件状态变化时,会重新调用build

有状态组件 StatefulWidget

交互式组件,包含状态创建、更新、销毁等,Widget本身和单独的State类相关联。生命周期包含 createState、initState、didChangeDependencies、didUpdateWidget、deactivate、dispose、build

dart 复制代码
import 'package:flutter/material.dart';

void main(List<String> args) {
  // runApp(MainPage()); // Widget
  runApp(MainPage());
}

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

  @override
  _MainPageState createState() {
    print("createState阶段执行");
    return _MainPageState();
  }
}

class _MainPageState extends State<MainPage> {
  @override
  void initState() {
    print("initState阶段执行");
    // TODO: implement initState
    super.initState();
  }

  @override
  void didChangeDependencies() {
    print("didChangeDependencies阶段执行");
    // TODO: implement didChangeDependencies
    super.didChangeDependencies();
  }

  @override
  void didUpdateWidget(covariant MainPage oldWidget) {
    print("didUpdateWidget阶段执行");
    // TODO: implement didUpdateWidget
    super.didUpdateWidget(oldWidget);
  }

  @override
  void deactivate() {
    print("deactivate阶段执行");
    // TODO: implement deactivate
    super.deactivate();
  }

  @override
  void dispose() {
    print("dispose阶段执行");
    // TODO: implement dispose
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    print("build阶段执行");
    return Container(
      child: null,
    );
  }
}
  • 创建两个类,第一个继承StatefulWidget类,接受和定义参数,创建State对象
  • 第二个类继承State<第一个类类名>,负责管理状态与业务逻辑,并实现build方法(返回一个Widget)

有状态组件生命周期:

事件

通过 GestureDetector包裹组件

dart 复制代码
child: GestureDetector(
  onTap: () {
    print("轻触了该区域");
  },
  child: Text("中部区域"),
),

更多事件可以查看 GestureDetector

除了上述类包裹外,也可以使用专用组件触发事件

状态更新

在有状态组件中,在第二个类里面定义状态

我们以一个计数器来理解

dart 复制代码
import 'package:flutter/material.dart';

void main(List<String> args) {
  runApp(MainPage());
}


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

  @override
  _MainPageState createState() => _MainPageState();
}

class _MainPageState extends State<MainPage> {
  int count = 0;
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: Row(
            children: [
              TextButton(
                  onPressed: () {
                    setState(() {
                      count--;
                    });
                  },
                  child: Text("减")),
              Text(count.toString()),
              TextButton(
                  onPressed: () {
                    count += 1;
                    print(count);
                    setState(() {});
                  },
                  child: Text("加"))
            ],
          ),
        ),
      ),
    );
  }
}

你可以直接在 setState 里面写count的加减,也可以先写count加减,在通过 setState 去更新页面。setState会造成build重新执行

布局组件

Container

  • Container是个容器组件
  • 可通过多种方式定义大小,但有优先级限制
  • 优先级规则:明确宽高width、height > constraints约束 > 父组件约束 > 自适应组件大小
  • 通过decoration属性实现装饰效果,即一些样式效果,但是与 color 互斥
  • 布局通过 margin、paddingaligment 控制
  • 支持矩阵变化,如transform实现倾斜、旋转、平移

Center

  • Center是居中布局组件
  • 将子组件水平、垂直居中在父组件中
  • 不能设置宽高,Center的大小取决于父组件传递给他的约束
  • Center一般是去包裹一个具备宽高的子组件。比如包裹一个宽高固定的Container/SizeBox

Align

  • Align是控制组件对齐方式的组件。
  • Center是属于Align的一个特例,相当于一个将aligment属性居中的Aligen.center
  • alignment: 子组件在父组件中的对齐方式
  • widthFactor()宽度因子: Align的宽度是子组件的宽度乘以该属性
  • heightFactor()高度因子:Align的高度是子组件的宽度乘以该属性

Padding

  • 为组件添加内边距
  • Container组件也可以设置padding,单一需求用Padding,复杂的用Container

Column

  • 在垂直方向布局的组件(类似aligen-item)

Row

  • 水平方向布局的组件(类似justfiy-content)

Flex

  • Flex 跟css的flex类似,是Column和Row的组合

比如顶部底部高度固定,中间自适应

dart 复制代码
import 'package:flutter/material.dart';

void main(List<String> args) {
  // runApp(MainPage()); // Widget
  runApp(MainPage());
}

class MainPage extends StatelessWidget {
  const MainPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Container(
          color: Colors.amber,
          child: Flex(
            direction: Axis.vertical,
            children: [
              Container(color: Colors.blue, height: 100),
              Expanded(child: Container(color: Colors.blueGrey)),
              Container(color: Colors.red, height: 100),
            ],
          ),
        ),
      ),
    );
  }
}

Wrap

  • 流式布局组件,子组件在主轴上排列不下时,自动换行
  • 可以理解为 css中 flex 加了 flex-wrap:wrap

Stack/Positioned

其实 Stack/Position 类似于css子绝父相。 Stack 理解为父元素绝对定位,Position理解为子元素相对定位

Text/TextSpan

Image

TextField

滚动组件

SingleChildScrollView

dart 复制代码
import 'package:flutter/material.dart';
import 'dart:math' as math;

void main(List<String> args) {
  // runApp(MainPage()); // Widget
  runApp(MainPage());
}

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

  @override
  _MainPageState createState() => _MainPageState();
}

class _MainPageState extends State<MainPage> {
  ScrollController _controller = ScrollController(); //滚动条控制器
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: Scaffold(
            appBar: AppBar(
              title: Text("登录"),
            ),
            body: Stack(
              children: [
                SingleChildScrollView(
                  controller: _controller,
                  padding: EdgeInsets.all(20),
                  child: Column(
                    children: List.generate(100, (index) {
                      return Container(
                        margin: EdgeInsets.only(top: 10),
                        width: double.infinity,
                        color: Colors.blue,
                        height: 100,
                        child: Text("我是第${index + 1}个",
                            style:
                                TextStyle(color: Colors.white, fontSize: 30)),
                        alignment: Alignment.center,
                      );
                    }),
                  ),
                ),
                // 放置堆叠组件
                Positioned(
                  right: 10,
                  top: 10,
                  child: GestureDetector(
                      onTap: () {
                        // print("去底部");
                        // _controller.jumpTo(
                        //     _controller.position.maxScrollExtent); // 滚动到底部
                        _controller.animateTo(
                            _controller.position.maxScrollExtent,
                            duration: Duration(seconds: 1),
                            curve: Curves.easeIn);
                      },
                      child: Container(
                        decoration: BoxDecoration(
                            color: Colors.red,
                            borderRadius: BorderRadius.circular(40)),
                        width: 80,
                        height: 80,
                        alignment: Alignment.center,
                        child:
                            Text("去底部", style: TextStyle(color: Colors.white)),
                      )),
                ),
                Positioned(
                  right: 10,
                  bottom: 10,
                  child: GestureDetector(
                      onTap: () {
                        // print("去顶部");
                        // _controller.jumpTo(0);
                        _controller.animateTo(0,
                            duration: Duration(seconds: 1),
                            curve: Curves.bounceIn);
                      },
                      child: Container(
                        decoration: BoxDecoration(
                            color: Colors.red,
                            borderRadius: BorderRadius.circular(40)),
                        width: 80,
                        height: 80,
                        alignment: Alignment.center,
                        child:
                            Text("去顶部", style: TextStyle(color: Colors.white)),
                      )),
                )
              ],
            )));
  }
}

ListView

GridView

CustomScrollView

PageView

dart 复制代码
import 'package:flutter/material.dart';
import 'dart:math' as math;

void main(List<String> args) {
  // runApp(MainPage()); // Widget
  runApp(MainPage());
}

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

  @override
  _MainPageState createState() => _MainPageState();
}

class _MainPageState extends State<MainPage> {
  int _currentIndex = 0; // 当前激活索引
  PageController _controller = PageController();

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: Scaffold(
            appBar: AppBar(
              title: Text("登录"),
            ),
            body: CustomScrollView(
              slivers: [
                // 包裹普通Widget的东西
                SliverToBoxAdapter(
                    child: Stack(
                  children: [
                    Container(
                      color: Colors.blue,
                      alignment: Alignment.center,
                      height: 260,
                      child: PageView.builder(
                          controller: _controller,
                          itemCount: 10,
                          itemBuilder: (BuildContext context, int index) {
                            return Container(
                              alignment: Alignment.center,
                              child: Text("轮播图${index + 1}",
                                  style: TextStyle(
                                      color: Colors.white, fontSize: 20)),
                            );
                          }),
                    ),
                    Positioned(
                      bottom: 0,
                      left: 0,
                      right: 0,
                      height: 40,
                      child: Row(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: List.generate(10, (index) {
                          return GestureDetector(
                            onTap: () {
                              // 切换到具体的page
                              // _controller.jumpToPage(index);
                              _controller.animateToPage(index,
                                  duration: Duration(milliseconds: 300),
                                  curve: Curves.linear);
                              _currentIndex = index;
                              setState(() {});
                            },
                            child: Container(
                              margin: EdgeInsets.only(left: 10),
                              width: 10,
                              height: 10,
                              decoration: BoxDecoration(
                                  color: _currentIndex == index
                                      ? Colors.red
                                      : Colors.white,
                                  borderRadius: BorderRadius.circular(5)),
                            ),
                          );
                        }),
                      ),
                    )
                  ],
                )),
                SliverToBoxAdapter(
                    child: SizedBox(
                  height: 10,
                )),
                SliverPersistentHeader(
                  delegate: _StickyCategory(),
                  pinned: true, // 固定吸顶
                ),
                SliverToBoxAdapter(
                    child: SizedBox(
                  height: 10,
                )),
                SliverGrid.count(
                  crossAxisCount: 2,
                  mainAxisSpacing: 10,
                  crossAxisSpacing: 10,
                  children: List.generate(100, (index) {
                    return Container(
                      color: Colors.blue,
                      alignment: Alignment.center,
                      child: Text('列表项${index + 1}',
                          style: TextStyle(color: Colors.white, fontSize: 20)),
                    );
                  }),
                )
              ], 
            )));
  }
}

class _StickyCategory extends SliverPersistentHeaderDelegate {
  @override
  Widget build(
      BuildContext context, double shrinkOffset, bool overlapsContent) {
    // TODO: implement build
    return Container(
      color: Colors.white,
      child: ListView.builder(
          itemCount: 30,
          scrollDirection: Axis.horizontal,
          itemBuilder: (BuildContext context, int index) {
            return Container(
              width: 100,
              margin: EdgeInsets.symmetric(horizontal: 10),
              color: Colors.blue,
              alignment: Alignment.center,
              child:
                  Text('分类${index + 1}', style: TextStyle(color: Colors.white)),
            );
          }),
    );
  }

  @override
  // TODO: implement maxExtent
  double get maxExtent => 80; // 最大展开高度

  @override
  // TODO: implement minExtent
  double get minExtent => 40; // 最小折叠高度

  @override
  bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
    // TODO: implement shouldRebuild
    return false; // 不需要重建
  }
}

组件通信

父传子

子组件通过 final 关键字定义父组件传来的数据,因为属性是父组件决定的,子组件不能更改

子传父

网络请求DIO

dart 复制代码
import 'package:dio/dio.dart';

void main(List<String> args) {
  Dio().get("https://geek.itheima.net/v1_0/channels").then((res) {
    print(res);
  }).catchError((error) {});
}

通常是在 initState 中初始化状态

dart 复制代码
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';

void main(List<String> args) {
  runApp(MainPage());
}

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

  @override
  _MainPageState createState() => _MainPageState();
}

class _MainPageState extends State<MainPage> {
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    // 发起网络请求
    _getChannels(); // 获取频道数据
  }

  List<Map<String, dynamic>> _list = []; // 用来接收数据的
  void _getChannels() async {
    DioUtils util = DioUtils(); // 创建实例化对象
    Response<dynamic> result = await util.get("channels");
    Map<String, dynamic> res = result.data as Map<String, dynamic>;
    // print(res["data"]["channels"] as List<Map<String, dynamic>>);
    List data = res["data"]["channels"] as List;
    _list = data.cast<Map<String, dynamic>>() as List<Map<String, dynamic>>;
    // cast方法强制转化列表项的类型
    setState(() {}); // 执行方法 UI才会更新
    print(_list);
    // channels是一个后端支持前端跨域访问的接口 cors 支持任何的域名进行访问
    // www.baidu.com
    // localhost:60791
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text("频道管理")),
        body: GridView.extent(
          padding: EdgeInsets.all(10),
          maxCrossAxisExtent: 140,
          mainAxisSpacing: 10,
          crossAxisSpacing: 10,
          childAspectRatio: 3,
          children: List.generate(_list.length, (index) {
            return ChannelItem(item: _list[index]);
          }),
        ),
      ),
    );
  }
}

// 用来绘制每个频道的UI内容
class ChannelItem extends StatelessWidget {
  final Map<String, dynamic> item;
  const ChannelItem({Key? key, required this.item}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.blue,
      alignment: Alignment.center,
      child: Text(
        item["name"] ?? "空",
        style: TextStyle(color: Colors.white, fontSize: 14),
      ),
    );
  }
}

// 封装一个工具类
class DioUtils {
  final Dio _dio = Dio(); // 内部Dio实例对象
  DioUtils() {
    // 做些基本的操作
    // 配置基础地址 和超时时间
    // _dio.options.baseUrl = "https://geek.itheima.net/v1_0/";
    // _dio.options.connectTimeout = Duration(seconds: 10); // 连接超时
    // _dio.options.sendTimeout = Duration(seconds: 10); // 发送超时
    // _dio.options.receiveTimeout = Duration(seconds: 10); // 接收超时
    // 简写 ..连续赋值的写法
    _dio.options
      ..baseUrl = "https://geek.itheima.net/v1_0/"
      ..connectTimeout = Duration(seconds: 10)
      ..sendTimeout = Duration(seconds: 10)
      ..receiveTimeout = Duration(seconds: 10);

    // 拦截器
    _addInterceptor(); // 注册添加拦截器
  }
  void _addInterceptor() {
    _dio.interceptors.add(InterceptorsWrapper(
        // 请求拦截器
        onRequest: (context, handler) {
      //  handler.next(requestOptions) 放过请求
      // handler.reject(error) 拦截请求
      handler.next(context);
    },
        // 响应拦截器
        onResponse: (context, handler) {
      // http状态吗 2xx 成功 3 4 5
      // handler.reject(error)
      if (context.statusCode! >= 200 && context.statusCode! < 300) {
        handler.next(context); // 放过
        return;
      }
      // 说明出异常
      handler.reject(DioException(requestOptions: context.requestOptions));
      // 抛出异常
    },
        // 错误拦截器
        onError: (context, handler) {
      handler.reject(context); // 直接抛出异常
    }));
  }

  // 向外暴露一个get方法
  Future<Response<dynamic>> get(String url, {Map<String, dynamic>? params}) {
    return _dio.get(url, queryParameters: params);
  }
}

路由

相关推荐
MonkeyKing71552 小时前
Flutter状态管理实战:全局、局部、页面状态拆分指南
前端·flutter
MonkeyKing71554 小时前
Flutter异步状态统一处理实战:告别混乱,优雅管理请求与加载
flutter
MonkeyKing71554 小时前
Flutter项目结构与模块化、组件化、插件化
flutter
UnicornDev7 小时前
【Flutter x HarmonyOS 6】魔方计时APP——计时逻辑实现
flutter·华为·harmonyos·鸿蒙·鸿蒙系统
用户游民7 小时前
Flutter Widget、Element、RenderObject 关联以及实现原理
flutter
用户95421573334857 小时前
彻底告别 `.w/.h/.sp`!Flutter 屏幕适配的底层玩法,一次接入全局生效
flutter
liulian09167 小时前
Flutter for OpenHarmony 跨平台开发:密码生成器功能实战指南
flutter
可有道理7 小时前
Flutter 抽象类、接口与mixin
flutter
MonkeyKing71559 小时前
Flutter路由高级管理实战:守卫、深链、多栈与Tab路由全解析
flutter