Dart&Flutter基础学习日记

Dart语法

每段语气都需要逗号隔开

定义变量

Dart用两种定义变量的方式,第一种明确变量类型定义,第二种通过推导类型定义
通过数据类型定义变量,dart的数据类型有:
数字(int/double),字符串(String),布尔(Boolean),列表,集合(Set),映射(Map),符文。

一:使用变量类型定义

入口函数main

void main(List<String> args) {
  int firstFlutterCode = 12;
  print(firstFlutterCode);
}

二:类型推导定义

声明变量关键词:var、final、const

final:用于定义常量,可以通过计算或者函数进行赋值

const:用于什么常量,必须赋值,且值必须在编写时就确定,不可通过函数结果或者计算结果赋值

void main(List<String> args) {
  int firstFlutterCode = 12;
  var a = 12;
  const b = 'wosh';
  const ab = 12 / a; //这种方式不对,不可以这样
  final c = 12 / 3;
  final ac = 12 / a; //可以通过计算实现赋值
  print(firstFlutterCode);
  print(a);
  print(b);
  print(c);
  print(ab);
}

三:字符串拼接

通过${变量拼接}

void main(List<String> args) {
  final a = 12;
  final b = 'xiaozi';
  print("我的名字叫$b,今年有${a}岁");
}

如果是变量,可以省略{},如果是对象调动变量的形式,就不可以省略大括号。

四:集合相关

列表:

final list = ["12", '12'];

set集合:

final set = {'12', 12};

map映射:

final map = {'name': '小智', 'age': 13};

五:定义函数

void main(List<String> args) {
  print(sum(1, 2));
}
int sum(int a, int b) {
  return a + b;
}

函数定义有返回值必须明确放回类型,不能使用var,final,const定义,但是可以不写类型,可以推断出返回值类型,但是不推荐使用。

可选参数:

注意:可选参数必须给初始化值,如果不给,需要使用?

[参数1,参数2…]命名可选参数

void p(String name, [int? a, int sex = 0]) {}
p('小智', 12);

{参数1,参数2,…}

命名可选参数在调用的时候必须带参数名,可选参数必须带初始化值,如果不带,将会被报错。

void f(String name, {int? a, int b = 12}) {}
f('小智', a: 12);

注意:只有可选参数才能有形参默认值

函数作为函数的参数

void main(List<String> args) {
  p(f);
}
void p(Function foo) {
  foo();
}
void f() {
  print("aa");
}

参数作为匿名函数的时候:

  p(() {
    print("bbb");
  });
void p(Function foo) {
  foo();
}

箭头函数作为参数:

箭头函数:当函数体只有一行代码的时候可以使用

p(() => print('ccc'));
void p(Function foo) {foo();}

函数作为参数带参数:

void main(List<String> args) {
  p(fn);
}
void p(Function f) {
  f('12');
}
void fn(String a) {
  print(a);
}

这种写法我们使用的不多,我们一般直接将函数的返回值作为函数进行传参

void main(List<String> args) {
  p(f);
}
void p(void fn(int a, int b)) {
  fn(12, 23);
}
void f(int d, int c) {
  print("${d}+++${c}");
}

此时的阅读比较差,可以使用typedef定义函数参数

void main(List<String> args) {
  p(f);
}
typedef C = void Function(int a, int b);
void p(C c) {
  c(12, 23);
}
void f(int d, int c) {
  print("${d}+++${c}");
}

函数作为返回值

void main(List<String> args) {
  var a = demo();
  var b = a(12, 23);
  print(b);
}
demo() {
  return (num1, num2) {
    return num1 + num2;
  };
}

运算符

这部分值写和JavaScript不一样的运算符

整除:~/

3~\2:结果为1

赋值运算符:

??=:表示如果变量为null,赋值,不为null将不赋值。

void main(List<String> args) {
  var name = 'xiaozhi';
  name ??= '小红';
  print(name);
}

??:表示前面的变量不为null,就是用改变量,如果为null,将使用后面的值

void main(List<String> args) {
  var name = 'xiaozhi';
  var myName = name ?? '小丽';
  print(myName);
}

级联运算符

…类似JavaScript的链式调用

void main(List<String> args) {
    //一般情况下我是时这么调用的
  var p = Person();
  p.name='xiao';
  p.show();
  p.eat();
  //级联方式
  var p1 = Person()
    ..name = 'xiaozhi'
    ..eat()
    ..show();
}

class Person {
  var name;
  void show() {
    print("yifu");
  }
  void eat() {
    print("吃法");
  }
}

循环控制

if的条件必须是rue或者false,如果一个字符串为空或者为null的时候,不会将其转成boolean类型

for循环有两种,普通for循环和for…in循环

其他循环体和JavaScript一样的。

类和对象

使用class关键词定义

void main(List<String> args) {
  var p1 = Person('12');
  var p = Person.nameAndAge('12', '13');
  p.show();
}
class Person {
  var name;
  var age;
  //dart是没有构造方法重载的
 Person(this.name);
   //命名构造函数
  Person.nameAndAge(this.age, this.name);
  show() {
    print(name);
    print(age);
  }
}

我们可以使用构造函数和命名构造函数的方式实现方法重载

Object类型和dynamic类型

Object类型:通过多态实现的变量,编译期调用方法会报错

void main(List<String> args) {
  Object obj = '123';
  obj.substring(1);//报错
}

dynamic:表示的是动态类型,编译期调用方法不会报错,但是在运行阶段可能会报错,导致运行错误。

  dynamic obj = '123';
  obj.substring(1)

重写tostring方法

class Person {
  var name;
  var age;
  Person(this.name, this.age);
  String toString() {
    return "$name,$age";
  }
}

类的初始化列表

使用finale修饰的,不能再方法内进行赋值,可以通过以下方式赋值

class Person {
  final String name;
  final int age;
  Person(this.name, {int age}):this.age=age??10{
      //不支持该方式,使用以上方式
      //this.age=age
  };
}

多个初始化列表使用逗号隔开。

下面这种方式叫做构造函数重定向:

class Person {
  String name;
  int age;
  Person(String name) : this._personConstructer(name, 0);
  Person._personConstructer(this.name, this.age);
}

常量构造函数:

构造函数使用const修饰,成员变量使用final修饰,保证每次创建对象都保证只创建一个对象

class Person {
  final String name;
  final String age;
  const Person(this.name, this.age);
}

工厂构造函数

class Person {
  String name;
  String age;
  Person(this.name, this.age);
  static final Map<String, Person> _nameCache = {};
  static final Map<String, Person> _ageCache = {};
  factory Person.withName(String name) {
    if (_nameCache.containsKey(name)) {
      return _nameCache[name];
    } else {
      final p = Person(name, '0');
      _nameCache[name] = p;
      return p;
    }
  }
  factory Person.withAge(String age) {
    if (_nameCache.containsKey(age)) {
      return _ageCache[age];
    } else {
      final p = Person('defualt', age);
      _nameCache[age] = p;
      return p;
    }
  }
}

代码都是对的,不知道为什么报错,不知道是不是语法变了。艹!!!!!!

get/set

class Person {
  String name;
  set setName(String name) {
    this.name = name;
  }
  String get getName {
    return name;
  }
}

代码还是报错,我真的服气了,写的代码和学习的一模一样都能报错!!必须先初始化值!!!!

类的继承

在子类的初始化列表内调用父类构造函数

class Animal {
  String name;
  Animal(this.name);
}
class Person extends Animal {
  String name;
  String age;
  Person(this.name, this.age) : super(name);
}

抽象类

// 注意二: 抽象类不能实例化
abstract class Shape {
  String getInfo() {
    return "形状";
  }

  //factory Shape() {
  //  return Rectangle();
  //}
}

// 注意一:继承自抽象类后, 必须实现抽象类的抽象方法
class Rectangle extends Shape {
  @override
  int getArea() {
    return 100;
  }
}

隐式接口

Dart中没有哪一个关键字是来定义接口的,没有这些关键字interface/protocol,默认情况下所有的类都是隐式接口, Dart支持单继承,当将一个类当做接口使用时, 那么实现这个接口的类, 必须实现这个接口中所有方法

class Runner {
  void running() {

  }
}
class Flyer {
  void flying() {

  }
class Animal {
  void eating() {
    print("动物次东西");
  }
  void running() {
    print("running");
  }
}
class SuperMan extends Animal implements Runner, Flyer {
  @override
  void eating() {
    super.eating();
  }
  @override
  void flying() {
  }
}

mixin混入

使用mixin定义混入类,使用with引入混入

main(List<String> args) {
  final sm = SuperMan();
  sm.running();
  sm.flying();
}
mixin Runner {
  void running() {
    print("runner running");
  }
}
mixin Flyer {
  void flying() {
    print("flying");
  }
}
class Animal {
  void eating() {
    print("动物次东西");
  }
  void running() {
    print("animal running");
  }
}
class SuperMan extends Animal with Runner, Flyer {
  @override
  void eating() {
    super.eating();
  }
  void running() {
    print("SuperMan running");
  }
}

类属性和类方法

class Person {
  // 成员变量
  String name;
  // 静态属性(类属性)
  static String courseTime;
  // 对象方法
  void eating() {
    print("eating");
  }
  // 静态方法(类方法)
  static void gotoCourse() {
    print("去上课");
  }
}

枚举类型

main(List<String> args) {
  final color = Colors.red;
  switch (color) {
    case Colors.red:
      print("红色");
      break;
    case Colors.blue:
      print("蓝色");
      break;
    case Colors.green:
      print("绿色");
      break;
  }
  print(Colors.values);
  print(Colors.red.index);
}
enum Colors {
  red,
  blue,
  green
}

使用类库

使用系统库

系统的库: import ‘dart:库的名字’;

// import 'dart:io';
// import 'dart:isolate';
// import 'dart:async';
// import 'dart:math';
import 'dart:math';
main(List<String> args) {
  final num1 = 20;
  final num2 = 30;
  print(min(num1, num2));
}
使用自定义库

就是引入自己创建的dart文件,使用里面的方法和变量等

  • 1.补充一: as关键字给库起别名

  • 2.补充二: 默认情况下载导入一个库时, 导入这个库中所有的内容

    • show: 执行要导入的内容
    • hide: 隐藏某个要导入的内容, 导入其它内容
  • 3.公共的dart文件的抽取: export


// import 'utils/math_utils.dart' as mUtils;
// import "utils/math_utils.dart" show sum, mul;
// import "utils/math_utils.dart" hide mul;
// import 'utils/date_utils.dart';
import "utils/utils.dart";
main(List<String> args) {
  // print(sum(20, 30));
  print(sum(20, 30));
  // print(mul(20, 30));
  print(dateFormat());

  min(20, 30);
}

date_utils.dart文件:

String dateFormat() {
  return "2020-12-12";
}

math_utils.dart文件:

int sum(int num1, int num2) {
  return num1 + num2;
}
int mul(int num1, int num2) {
  return num1 * num2;
}
int min(int num1, int num2) {
  return num1 > num2 ? num2: num1;
}

提取公共代码到一个文件内:utils.dart(看以上代码)

export 'math_utils.dart';
export 'date_utils.dart';
使用第三方库

导入方式一样,只是地址为第三方地址

import 'package:http/http.dart' as http;
main(List<String> args) async {
  var url = 'http://123.207.32.32:8000/home/multidata';
  var response = await http.get(url);
  print('Response status: ${response.statusCode}');
  print('Response body: ${response.body}');
}

flutter环境配置

环境配置地址:https://flutter.cn/docs/get-started/install/windows

按照这个环境配置即可,如需使用其他模拟器,请自行百度。

创建项目

flutter create 项目名     ----创建项目指令
flutter run              ----运行项目

文件

.dart_tool:用于存放第三方dart库使用,记录第三方库的相关信息,只要下载会自动记录

android:flutter工程

ios:ios工程包

web:web项目工程

window:windows桌面应用工程

build:打包目录

lib:工程源代码存放处,主入口文件存放处。

test:测试代码存放处。

第一个flutter工程

将main中的代码全部删除,写入:

import 'package:flutter/cupertino.dart';
void main(List<String> args) {
  runApp(app)
}

入口需要执行一个全局函数runApp,用于运行项目,runApp函数接收一个类型为Widget的参数。

注意:在flutter中,万物皆是Widget

在flutter中,几乎所有的组件都实现了Widget(按照前端的说法,就是一个组件),属于Widget类型,此时我们的app参数可以写入:

import 'package:flutter/cupertino.dart';
void main(List<String> args) {
  var app = Text("hello wolrd ");
  runApp(app);
}

这时候会报一个错。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v49dFA6Q-1652173929950)(\img\fristerror.PNG)]

这时候会发现,这个错误是没有设置文字布局的排版方向,因为flutter考虑到在很多国家的文字排版都不一样,所以将排版方向设置为必填项目。

重启项目,结果为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kNJDXFC6-1652173929959)(\img\helloworld.PNG)]

此时第一个项目运行成功!

模板脚手架

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
void main(List<String> args) {
  var app = MaterialApp(
    home: Scaffold(
      appBar: AppBar(
        title:Text("我的第一个应用"),
      ) ,
      body: Center(
        child: Text(
          "任性的代码",
          style: TextStyle(
            color:Colors.amber,           
          )        
        ),
      ),
    ),
  );
  runApp(app);
}

MaterialApp:App的风格为Material提供的风格,Material中的组件定义了自己的默认样式,

Scaffold:脚手架,主要有appBar和body两个属性,appBar表示应用的上头,body应用的内容。

widget

widget:按照前端的说法widget就是一个个个的组件,也就说flutter可以按照组件化编程,但是和react或者vue不同的是,flutter的相关样式或者其他代码是写在一块的。注意:全部的widget都是无状态的,只能通过定义类的形式来定义、获取、修改相关的状态。

组件有个widget属性,表示父组件的实例

widget有两种:StatelessWidgetStatefulWidget两种

  • StatelessWidget: 没有状态改变的Widget,通常这种Widget仅仅是做一些展示工作而已;
  • StatefulWidget: 需要保存状态,并且可能出现状态改变的Widget;

StatelessWidget

void main(List<String> args) {
  var app = MaterialApp(
    home: AppHomePage(),
  );
  runApp(app);
}
class AppHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    //throw UnimplementedError();
    return Scaffold(
        appBar: AppBar(
          title: Text("我的"),
        ),
        body: AppBodyPage());
  }
}
class AppBodyPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Center(
      child: Text("任性的代码",
          style: TextStyle(
            color: Colors.amber,
          )),
    );
  }
}

StatefulWidget

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        debugShowCheckedModeBanner: false,
        home: HYHomePage()
    );
  }
}
class HYHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print("HYHomePage Build");
    return Scaffold(
        appBar: AppBar(
          title: Text("第一个Flutter程序"),
        ),
        body: HYContentBody()
    );
  }
}
// StatefullWidget: 继承自StatefulWidget的类(可以接收父Widget传过来的数据)/State类(状态)
// flag: 状态
// Stateful不能定义状态 -> 创建一个单独的类, 这个类负责维护状态
class HYContentBody extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return HYContentBodyState();
  }
}
class _HYContentBodyState extends State<HYContentBody> {
  var _flag = true;
  @override
  Widget build(BuildContext context) {
    print("HYContentBodyState Build");
    return Center(
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Checkbox(
            value: flag,
            onChanged: (value) {
              this.setState(() {
                flag = value;
              });
            },
          ),
          Text("同意协议", style: TextStyle(fontSize: 20))
        ],
      ),
    );
  }
}

StatefulWidget本身是不能用于定义状态的,而是通过其内部的createState函数创建一个状态,该状态需要继承一个State类,在State类中可以使用setState来实现对状态的改变。

注意:StateWidget用于管理状态的类和其状态最好加‘_’,加’_'之后该类只能被StateWidget类使用。

widget生命周期

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ysn5YaCT-1652173929960)(\img\shengming.PNG)]

对于StatelessWidget无状态组件,其生命周期只有:constructor>build>deactivate>dispose这条路径。

对于StatefulWidget有状态组件,生命周期完整过程如上图所示(图缺少最开始的调用的constructor)。

生命周期完整介绍:

https://www.jianshu.com/p/6ed6f7de01ff

didChangeDependencies():当State对象的依赖发生变化时会被调用;didChangeDependencies 方法调用后,组件的状态变为 dirty,立即调用 build 方法。

reassemble():此回调是专门为了开发调试而提供的,在热重载(hot reload)时会被调用,此回调在Release模式下永远不会被调用。

didUpdateWidget():在父widget重新构建子widget时,子widget的didUpdateWidget可能会被调用

eactivate():当State对象从树中被移除时,会调用此回调。在一些场景下,Flutter framework会将State对象重新插到树中,如包含此State对象的子树在树的一个位置移动到另一个位置时(可以通过GlobalKey来实现)。如果移除后没有重新插入到树中则紧接着会调用dispose()方法。

dispose():当State对象从树中被永久移除时调用;通常在此回调中释放资源。

TextWidget

普通文本:Text

文本使用的组件叫做Text,在相关的属性可以通过点击该组件查看源码

  const Text(
    String this.data, {
    Key? key,
    this.style,
    this.strutStyle,
    this.textAlign,
    this.textDirection,
    this.locale,
    this.softWrap,
    this.overflow,
    this.textScaleFactor,
    this.maxLines,
    this.semanticsLabel,
    this.textWidthBasis,
    this.textHeightBehavior,
  }) :
  • 控制文本布局的参数: 如文本对齐方式 textAlign、文本排版方向 textDirection,文本显示最大行数 maxLines、文本截断规则 overflow 等等,这些都是构造函数中的参数;
  • 控制文本样式的参数: 如字体名称 fontFamily、字体大小 fontSize、文本颜色 color、文本阴影 shadows 等等,这些参数被统一封装到了构造函数中的参数 style 中。
Text(text,
    textAlign: TextAlign.center,
    maxLines: 3,
    overflow:TextOverflow.ellipsis,
    style: TextStyle(
      color:Colors.green,
      fontSize:30,
      fontStyle: FontStyle.italic,
    )
 );

注意:在之前我们在runApp中直接使用Text需要设置文字方向,而在MaterialApp中不需要设置主要是MaterialApp已经有默认的文本方向了,而且,我们设置Text的文本方向不是问Text widget本身设置的而是为其RichText设置的,Text会渲染成一个RichText。

富文本Text.rich()

接收一个TextSpan类型的widget,在TextSpan内可以创建多个TextSpan

Text.rich(
      TextSpan(
        children: [
          TextSpan(text:'你好',style: TextStyle(color:Colors.red)),
          TextSpan(text: '好不好!', style: TextStyle(color: Colors.green)),
          TextSpan(text: '别呀!燕子!', style: TextStyle(color: Colors.black)),
        ]
      )
    );

Button Widget

RaisedButton:带有阴影的按钮
RaisedButton(
   child: Text("RaisedButton"),
   textColor: Colors.white,
   color: Colors.purple,
   onPressed: () => print("RaisedButton Click"),
),
FlatButton:扁平化按钮
FlatButton(
   child: Text("FlatButton"),
   color: Colors.orange,
   onPressed: () => print("FlatButton Click"),
),
OutlineButton:线框按钮
 OutlineButton(
   child: Text("OutlineButton"),
   onPressed: () => print("OutlineButton"),
),
FloatingActionButton:悬浮按钮

这个按钮可以和appBar和body同级,

自定义按钮:使用基础FlatButton组件自定义

    FlatButton(
      color: Colors.amberAccent,
      shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(8)
      ),
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: <Widget>[
          Icon(Icons.favorite, color: Colors.red,),
          Text("喜欢作者")
        ],
      ),
      onPressed: ()=>{},
    )
ButtonTheme:按钮风格,

定义按钮小部件的默认配置,

ButtonTheme(
   minWidth: 30,
   height: 10,
  child: FlatButton(
      padding: EdgeInsets.all(0),
      color: Colors.red,
      materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
      child: Text("Flat Button1"),
      textColor: Colors.white,
      onPressed: () {},
     ),
)

图标和字体图标

Icon(Icons.pets, size: 300, color: Colors.orange,);
Icon(IconData(0xe91d, fontFamily: 'MaterialIcons'), size: 300, color: Colors.orange,);
Text("\ue91d", style: TextStyle(fontSize: 100, color: Colors.orange, fontFamily: "MaterialIcons"),);

Image组件

  • Image.assets:加载本地资源图片;
  • Image.network:加载网络中的图片;
加载网络图片
return Center(
      child: Container(
        child: Image.network(
          "http://img0.dili360.com/ga/M01/48/3C/wKgBy1kj49qAMVd7ADKmuZ9jug8377.tub.jpg",
          alignment: Alignment.topCenter,
          repeat: ImageRepeat.repeatY,
          color: Colors.red,
          colorBlendMode: BlendMode.colorDodge,
        ),
        width: 300,
        height: 300,
        color: Colors.yellow,
      ),
    );
加载本地图片

需要现在pubspec.yaml配置路径,打开assets配置项。

return Center(
      child: Container(
        width: 300,
        height: 300,
        color: Colors.yellow,
        child: Image.asset("images/test.jpeg"),
      ),
    );

圆角图像

方式一:CircleAvatar
 return Center(
      child: CircleAvatar(
        radius: 100,
        backgroundImage: 		         NetworkImage("https://tva1.sinaimg.cn/large/006y8mN6gy1g7aa03bmfpj3069069mx8.jpg"),
        child: Container(
          alignment: Alignment(0, .5),
          width: 200,
          height: 200,
          child: Text("兵长利威尔")
        ),
      ),
    );
方式二:ClipOval
return Center(
      child: ClipOval(
        child: Image.network(
          "https://tva1.sinaimg.cn/large/006y8mN6gy1g7aa03bmfpj3069069mx8.jpg",
          width: 200,
          height: 200,
        ),
      ),
    );

圆角图片ClipRRect

 return Center(
      child: ClipRRect(
        borderRadius: BorderRadius.circular(10),
        child: Image.network(
          "https://tva1.sinaimg.cn/large/006y8mN6gy1g7aa03bmfpj3069069mx8.jpg",
          width: 200,
          height: 200,
        ),
      ),
    );

文本输入TextField

重要属性:

controller:数据绑定控制器

onChanged:数据改变时回调函数

decoration:TextField相关装饰

final mytest = TextEditingController();//控制变量,用于将变量绑定到输入框
Widget build(BuildContext context) {
    return Column(
      children: [
        TextField(
          controller: mytest,
          onChanged: (value) => {
              print(value)
          },
          decoration: InputDecoration(
              icon: Icon(Icons.people),
              labelText: '用户名',
              hintText: "请输入用户名",
              hoverColor: Colors.blue,
              labelStyle: TextStyle(
                color: Colors.red,
                fontSize: 20,
              )),
        ),
        FlatButton(
          child: Text("点击"),
          onPressed: () => {
            print(mytest.text)
          },
        )
      ],
    );
  }

flutter布局

SizedBox

用于设置两组件间间隙

children: <Widget>[
            TextField(.....),
            SizedBox(height: 10,),
            TextField(.....),
            SizedBox(height: 10,),
            Container(....)
          ],

单子布局

单子组件只能容纳一个组件,即属性child。

Align

该组件用于设置元素位置相关布局,其Center组件完全继承自该组件。

 Align(
        alignment: Alignment(1, 1),
        widthFactor: 5,
        heightFactor: 5,
        child: Icon(Icons.pets, size: 50)
    );
Padding

该组件可用于设置内边距

Padding(
      padding: EdgeInsets.only(
          bottom: 10
      ),
      child: Text("你好啊,李银河", 
      	style: TextStyle(fontSize: 30, backgroundColor: Colors.red),),
    );
Container

表示的是一个单子布局容器,该容器类似html的div标签,但容纳的组件只能是一个。

注意:属性color和decoration只能出现一个。

alignment:0.0为原点,距离边距为1,

Container(
      width: 200,
      height: 200,
      alignment: Alignment(0, 0),
      padding: EdgeInsets.all(20),
      margin: EdgeInsets.all(10),
      child: Text("Hello World"),
      decoration: BoxDecoration(
          color: Colors.red,
          border: Border.all(
              width: 5,
              color: Colors.purple
          ),
          boxShadow: [
            BoxShadow(color: Colors.orange, offset: Offset(10, 10), spreadRadius: 5, blurRadius: 10),
            BoxShadow(color: Colors.blue, offset: Offset(-10, 10), spreadRadius: 5, blurRadius: 10),
          ]
      ),
    );

多子布局

Flex布局

事实上,我们即将学习的Row组件和Column组件都继承自Flex组件。

  • Flex组件和Row、Column属性主要的区别就是多一个direction。
  • 当direction的值为Axis.horizontal的时候,则是Row。
  • 当direction的值为Axis.vertical的时候,则是Column。
Row

水平方向尽可能占据比较大的空间,水平方向也是希望包裹内容, 那么设置mainAxisSize = min
垂直方向包裹内容
MainAxisAlignment:主轴

start: 主轴的开始位置挨个摆放元素(默认值)

end: 主轴的结束位置挨个摆放元素

center: 主轴的中心点对齐

spaceBetween: 左右两边的间距为0, 其它元素之间平分间距

spaceAround: 左右两边的间距是其它元素之间的间距的一半

spaceEvenly: 所有的间距平分空间

CrossAxisAlignment:交叉轴

start: 交叉轴的起始位置对齐

end: 交叉轴的结束位置对齐

center: 中心点对齐(默认值)

baseline: 基线对齐(必须有文本的时候才起效果)

stretch: 先Row占据交叉轴尽可能大的空间, 将所有的子Widget交叉轴的高度, 拉伸到最大

Row(
   mainAxisAlignment: MainAxisAlignment.spaceBetween,
   children: <Widget>[
       Text(
          "进击的巨人挺不错的",
          style: TextStyle(fontSize: 20, color: Colors.white),
        ),
       IconButton(
          icon: Icon(
             Icons.favorite,
             color: _isFavor? Colors.red : Colors.white,
          ),
          onPressed: () {
             setState(() {
             _isFavor = !_isFavor;
         });
     },
)
columns

这个和Row类似,但是是垂直布局

Column(
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      crossAxisAlignment: CrossAxisAlignment.center,
      textBaseline: TextBaseline.alphabetic,
      verticalDirection: VerticalDirection.down,
      mainAxisSize: MainAxisSize.min,
      children: <Widget>[
        Container(
          width: 80,
          height: 60,
          color: Colors.red,
          child: Text(
            "Hellxo",
            style: TextStyle(fontSize: 20),
          ),
        ),
        Container(
          width: 120,
          height: 100,
          color: Colors.green,
          child: Text(
            "Woxrld",
            style: TextStyle(fontSize: 30),
          ),
        ),
        Container(
          width: 90,
          height: 80,
          color: Colors.blue,
          child: Text(
            "abxc",
            style: TextStyle(fontSize: 12),
          ),
        ),
        Container(
          width: 50,
          height: 120,
          color: Colors.orange,
          child: Text(
            "cxba",
            style: TextStyle(fontSize: 40),
          ),
        ),
      ],
    );
Stack

默认的大小是包裹内容的,就是组件如何重叠

alignment: 从什么位置开始排布所有的子Widget

fit: expand(很少) 将子元素拉伸到尽可能大

overflow: 超出部分如何处理

Stack(
      children: <Widget>[
        Image.asset("assets/images/juren.jpeg"),
        Positioned(
          left: 0,
          right: 0,
          bottom: 0,
          child: Container(
            padding: EdgeInsets.symmetric(horizontal: 8),
            color: Color.fromARGB(150, 0, 0, 0),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: <Widget>[
                Text(
                  "进击的巨人挺不错的",
                  style: TextStyle(fontSize: 20, color: Colors.white),
                ),
                IconButton(
                  icon: Icon(
                    Icons.favorite,
                    color: _isFavor? Colors.red : Colors.white,
                  ),
                  onPressed: () {
                    setState(() {
                      _isFavor = !_isFavor;
                    });
                  },
                )
              ],
            ),
          ),
        )
      ],
    );
Positioned

定位组件,

Positioned(
    left: 20,
    bottom: -50,
    child: Container(
        width: 150,
        height: 150,
        color: Colors.red,
)),

flutter滚动

ListView

ListView有三种方式可以实现

  • ListView():默认构造器
  • ListView.build:和默认方式用法没有什么差异,但是会增加首屏的渲染时间,
  • ListView.separated:可以生成列表项之间的分割器,它除了比ListView.builder多了一个separatorBuilder参数,该参数是一个分割器生成器。
ListView()
ListView(
      children: <Widget>[
        Padding(
          padding: const EdgeInsets.all(8.0),
          child: Text("人的一切痛苦,本质。", style: textStyle),
        ),
        Padding(
          padding: const EdgeInsets.all(8.0),
          child: Text("人活在世界上,不可以有偏差;。", style: textStyle),
        ),
        Padding(
          padding: const EdgeInsets.all(8.0),
          child: Text("我活在世上,遇见些有趣的事。", style: textStyle),
        )
      ],
    );
ListTile的使用

在开发中,我们经常见到一种列表,有一个图标或图片(Icon),有一个标题(Title),有一个子标题(Subtitle),还有尾部一个图标(Icon)

ListView(
      children: <Widget>[
        ListTile(
          leading: Icon(Icons.people, size: 36,),
          title: Text("联系人"),
          subtitle: Text("联系人信息"),
          trailing: Icon(Icons.arrow_forward_ios),
        ),
        ListTile(
          leading: Icon(Icons.email, size: 36,),
          title: Text("邮箱"),
          subtitle: Text("邮箱地址信息"),
          trailing: Icon(Icons.arrow_forward_ios),
        ),
        ListTile(
          leading: Icon(Icons.message, size: 36,),
          title: Text("消息"),
          subtitle: Text("消息详情信息"),
          trailing: Icon(Icons.arrow_forward_ios),
        ),
        ListTile(
          leading: Icon(Icons.map, size: 36,),
          title: Text("地址"),
          subtitle: Text("地址详情信息"),
          trailing: Icon(Icons.arrow_forward_ios),
        )
      ],
    );
垂直方向滚动

我们可以通过设置 scrollDirection 参数来控制视图的滚动方向。

我们通过下面的代码实现一个水平滚动的内容:

  • 这里需要注意,我们需要给Container设置width,否则它是没有宽度的,就不能正常显示。
  • 或者我们也可以给ListView设置一个itemExtent,该属性会设置滚动方向上每个item所占据的宽度。
 ListView(
      scrollDirection: Axis.horizontal,
      itemExtent: 200,
      children: <Widget>[
        Container(color: Colors.red, width: 200),
        Container(color: Colors.green, width: 200),
        Container(color: Colors.blue, width: 200),
        Container(color: Colors.purple, width: 200),
        Container(color: Colors.orange, width: 200),
      ],
    );
ListView.build

ListView.build适用于子Widget比较多的场景

该方法有两个重要参数:

  • itemBuilder:列表项创建的方法。当列表滚动到对应位置的时候,ListView会自动调用该方法来创建对应的子Widget。类型是IndexedWidgetBuilder,是一个函数类型。
  • itemCount:表示列表项的数量,如果为空,则表示ListView为无限列表。
 ListView.builder(
      itemCount: 100,
      itemExtent: 80,
      itemBuilder: (BuildContext context, int index) {
        return ListTile(title: Text("标题$index"), subtitle: Text("详情内容$index"));
      }
    );
ListView.separated

ListView.separated可以生成列表项之间的分割器,它除了比ListView.builder多了一个separatorBuilder参数,该参数是一个分割器生成器。

下面我们看一个例子:奇数行添加一条蓝色下划线,偶数行添加一条红色下划线

 ListView.separated(
      itemBuilder: (BuildContext context, int index) {
        return ListTile(
          leading: Icon(Icons.people),
          title: Text("联系人${index+1}"),
          subtitle: Text("联系人电话${index+1}"),
        );
      },
      separatorBuilder: (BuildContext context, int index) {
        return index % 2 == 0 ? redColor : blueColor;
      },
      itemCount: 100
    );
生成多个元素
List.generate(100, (index) {
      return Container(
        color: Colors.purple,
        alignment: Alignment(0, 0),
        child: Text("item$index", style: TextStyle(fontSize: 20, color: Colors.white)),
      );
    });

GridView组件

GridView用于展示多列的展示,在开发中也非常常见,比如直播App中的主播列表、电商中的商品列表等等。

GridView构造函数

重要属性:

  • SliverGridDelegateWithFixedCrossAxisCount:一列个数
  • SliverGridDelegateWithMaxCrossAxisExtent:设置每个元素的宽度,通过宽度计算出每列的个数。
class MyGridCountDemo extends StatelessWidget {
  List<Widget> getGridWidgets() {
    returnList.generate(100, (index) {
      return Container(
        color: Colors.purple,
        alignment: Alignment(0, 0),
        child: Text("item$index", style: TextStyle(fontSize: 20, color: Colors.white)),
      );
    });
  }
  @override
  Widget build(BuildContext context) {
    return GridView(
      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 3,
        mainAxisSpacing: 10,
        crossAxisSpacing: 10,
        childAspectRatio: 1.0
      ),
      children: getGridWidgets(),
    );
  }
}

-----------------------------------------------------分割线-----------------------------------------

class MyGridExtentDemo extends StatelessWidget {
  List<Widget> getGridWidgets() {
    returnList.generate(100, (index) {
      return Container(
        color: Colors.purple,
        alignment: Alignment(0, 0),
        child: Text("item$index", style: TextStyle(fontSize: 20, color: Colors.white)),
      );
    });
  }
  @override
  Widget build(BuildContext context) {
    return GridView(
      gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
        maxCrossAxisExtent: 150,
        mainAxisSpacing: 10,
        crossAxisSpacing: 10,
        childAspectRatio: 1.0
      ),
      children: getGridWidgets(),
    );
  }
}

两种方式也可以不设置delegate

可以分别使用:GridView.count构造函数GridView.extent构造函数实现相同的效果,这里不再赘述。

GridView.build

和ListView一样,使用构造函数会一次性创建所有的子Widget,会带来性能问题,所以我们可以使用GridView.build来交给GridView自己管理需要创建的子Widget。

class _GridViewBuildDemoState extends State<GridViewBuildDemo> {
  List<Anchor> anchors = [];
  @override
  void initState() {
    getAnchors().then((anchors) {
      setState(() {
        this.anchors = anchors;
      });
    });
    super.initState();
  }
  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: GridView.builder(
        shrinkWrap: true,
        physics: ClampingScrollPhysics(),
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 2,
          mainAxisSpacing: 10,
          crossAxisSpacing: 10,
          childAspectRatio: 1.2
        ),
        itemCount: anchors.length,
        itemBuilder: (BuildContext context, int index) {
          return Container(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: <Widget>[
                Image.network(anchors[index].imageUrl),
                SizedBox(height: 5),
                Text(anchors[index].nickname, style: TextStyle(fontSize: 16),),
                Text(anchors[index].roomName, maxLines: 1, overflow: TextOverflow.ellipsis,)
              ],
            ),
          );
        }
      ),
    );
  }
}

Slivers

我们考虑一个这样的布局:一个滑动的视图中包括一个标题视图(HeaderView),一个列表视图(ListView),一个网格视图(GridView)。

我们怎么可以让它们做到统一的滑动效果呢?使用前面的滚动是很难做到的。

Flutter中有一个可以完成这样滚动效果的Widget:CustomScrollView,可以统一管理多个滚动视图。

在CustomScrollView中,每一个独立的,可滚动的Widget被称之为Sliver。

补充:Sliver可以翻译成裂片、薄片,你可以将每一个独立的滚动视图当做一个小裂片。

Slivers的基本使用

因为我们需要把很多的Sliver放在一个CustomScrollView中,所以CustomScrollView有一个slivers属性,里面让我们放对应的一些Sliver:

  • SliverList:类似于我们之前使用过的ListView;
  • SliverFixedExtentList:类似于SliverList只是可以设置滚动的高度;
  • SliverGrid:类似于我们之前使用过的GridView;
  • SliverPadding:设置Sliver的内边距,因为可能要单独给Sliver设置内边距;
  • SliverAppBar:添加一个AppBar,通常用来作为CustomScrollView的HeaderView;
  • SliverSafeArea:设置内容显示在安全区域(比如不让齐刘海挡住我们的内容)

简单演示一下:SliverGrid+SliverPadding+SliverSafeArea的组合

class HomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return CustomScrollView(
      slivers: <Widget>[
        SliverSafeArea(
          sliver: SliverPadding(
            padding: EdgeInsets.all(8),
            sliver: SliverGrid(
              gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 2,
                crossAxisSpacing: 8,
                mainAxisSpacing: 8,
              ),
              delegate: SliverChildBuilderDelegate(
                (BuildContext context, int index) {
                  return Container(
                    alignment: Alignment(0, 0),
                    color: Colors.orange,
                    child: Text("item$index"),
                  );
                },
                childCount: 20
              ),
            ),
          ),
        )
      ],
    );
  }
}

Slivers的组合使用

使用官方的示例程序,将SliverAppBar+SliverGrid+SliverFixedExtentList做出如下界面:

class HomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return showCustomScrollView();
  }

  Widget showCustomScrollView() {
    returnnew CustomScrollView(
      slivers: <Widget>[
        const SliverAppBar(
          expandedHeight: 250.0,
          flexibleSpace: FlexibleSpaceBar(
            title: Text('Coderwhy Demo'),
            background: Image(
              image: NetworkImage(
                "https://tva1.sinaimg.cn/large/006y8mN6gy1g72j6nk1d4j30u00k0n0j.jpg",
              ),
              fit: BoxFit.cover,
            ),
          ),
        ),
        new SliverGrid(
          gridDelegate: new SliverGridDelegateWithMaxCrossAxisExtent(
            maxCrossAxisExtent: 200.0,
            mainAxisSpacing: 10.0,
            crossAxisSpacing: 10.0,
            childAspectRatio: 4.0,
          ),
          delegate: new SliverChildBuilderDelegate(
                (BuildContext context, int index) {
              returnnew Container(
                alignment: Alignment.center,
                color: Colors.teal[100 * (index % 9)],
                child: new Text('grid item $index'),
              );
            },
            childCount: 10,
          ),
        ),
        SliverFixedExtentList(
          itemExtent: 50.0,
          delegate: SliverChildBuilderDelegate(
                (BuildContext context, int index) {
              returnnew Container(
                alignment: Alignment.center,
                color: Colors.lightBlue[100 * (index % 9)],
                child: new Text('list item $index'),
              );
            },
            childCount: 20
          ),
        ),
      ],
    );
  }
}

监听滚动事件

对于滚动的视图,我们经常需要监听它的一些滚动事件,在监听到的时候去做对应的一些事情。

比如视图滚动到底部时,我们可能希望做上拉加载更多;

比如滚动到一定位置时显示一个回到顶部的按钮,点击回到顶部的按钮,回到顶部;

比如监听滚动什么时候开始,什么时候结束;

在Flutter中监听滚动相关的内容由两部分组成:ScrollController和ScrollNotification。

ScrollController

在Flutter中,Widget并不是最终渲染到屏幕上的元素(真正渲染的是RenderObject),因此通常这种监听事件以及相关的信息并不能直接从Widget中获取,而是必须通过对应的Widget的Controller来实现。

ListView、GridView的组件控制器是ScrollController,我们可以通过它来获取视图的滚动信息,并且可以调用里面的方法来更新视图的滚动位置。

另外,通常情况下,我们会根据滚动的位置来改变一些Widget的状态信息,所以ScrollController通常会和StatefulWidget一起来使用,并且会在其中控制它的初始化、监听、销毁等事件。

我们来做一个案例,当滚动到1000位置的时候,显示一个回到顶部的按钮:

  • jumpTo(double offset)animateTo(double offset,...):这两个方法用于跳转到指定的位置,它们不同之处在于,后者在跳转时会执行一个动画,而前者不会。
  • ScrollController间接继承自Listenable,我们可以根据ScrollController来监听滚动事件。
class MyHomePage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => MyHomePageState();
}

class MyHomePageState extends State<MyHomePage> {
  ScrollController _controller;
  bool _isShowTop = false;
  
  @override
  void initState() {
    // 初始化ScrollController
    _controller = ScrollController();
    
    // 监听滚动
    _controller.addListener(() {
      var tempSsShowTop = _controller.offset >= 1000;
      if (tempSsShowTop != _isShowTop) {
        setState(() {
          _isShowTop = tempSsShowTop;
        });
      }
    });
    
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("ListView展示"),
      ),
      body: ListView.builder(
        itemCount: 100,
        itemExtent: 60,
        controller: _controller,
        itemBuilder: (BuildContext context, int index) {
          return ListTile(title: Text("item$index"));
        }
      ),
      floatingActionButton: !_isShowTop ? null : FloatingActionButton(
        child: Icon(Icons.arrow_upward),
        onPressed: () {
          _controller.animateTo(0, duration: Duration(milliseconds: 1000), curve: Curves.ease);
        },
      ),
    );
  }
}
NotificationListener

如果我们希望监听什么时候开始滚动,什么时候结束滚动,这个时候我们可以通过NotificationListener

  • NotificationListener是一个Widget,模板参数T是想监听的通知类型,如果省略,则所有类型通知都会被监听,如果指定特定类型,则只有该类型的通知会被监听。
  • NotificationListener需要一个onNotification回调函数,用于实现监听处理逻辑。
  • 该回调可以返回一个布尔值,代表是否阻止该事件继续向上冒泡,如果为true时,则冒泡终止,事件停止向上传播,如果不返回或者返回值为false 时,则冒泡继续。

案例: 列表滚动, 并且在中间显示滚动进度

class MyHomeNotificationDemo extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => MyHomeNotificationDemoState();
}

class MyHomeNotificationDemoState extends State<MyHomeNotificationDemo> {
  int _progress = 0;

  @override
  Widget build(BuildContext context) {
    return NotificationListener(
      onNotification: (ScrollNotification notification) {
        // 1.判断监听事件的类型
        if (notification is ScrollStartNotification) {
          print("开始滚动.....");
        } elseif (notification is ScrollUpdateNotification) {
          // 当前滚动的位置和总长度
          final currentPixel = notification.metrics.pixels;
          final totalPixel = notification.metrics.maxScrollExtent;
          double progress = currentPixel / totalPixel;
          setState(() {
            _progress = (progress * 100).toInt();
          });
          print("正在滚动:${notification.metrics.pixels} - ${notification.metrics.maxScrollExtent}");
        } elseif (notification is ScrollEndNotification) {
          print("结束滚动....");
        }
        returnfalse;
      },
      child: Stack(
        alignment: Alignment(.9, .9),
        children: <Widget>[
          ListView.builder(
            itemCount: 100,
            itemExtent: 60,
            itemBuilder: (BuildContext context, int index) {
              return ListTile(title: Text("item$index"));
            }
          ),
          CircleAvatar(
            radius: 30,
            child: Text("$_progress%"),
            backgroundColor: Colors.black54,
          )
        ],
      ),
    );
  }
}

flutter异步处理和发网络请求

异步处理可以参考这个地址:

https://www.jianshu.com/p/c0e30769ea7e

flutter实现网络请求

在Flutter中常见的网络请求方式有三种:HttpClient、http库、dio库;

dio库

官方提供的HttpClient和http都可以正常的发送网络请求,但是对于现代的应用程序开发来说,我们通常要求的东西会更多:比如拦截器、取消请求、文件上传/下载、超时设置等等;

这个时候,我们可以使用一个在Flutter中非常流行的三方库:dio;

官网有对dio进行解释:

dio是一个强大的Dart Http请求库,支持Restful API、FormData、拦截器、请求取消、Cookie管理、文件上传/下载、超时、自定义适配器等…

使用dio三方库必然也需要先在pubspec中依赖它:

  dio:^3.0.1

代码演练:

import'package:dio/dio.dart';

void dioNetwork() async {
  // 1.创建Dio请求对象
  final dio = Dio();

  // 2.发送网络请求
  final response = await dio.get("http://123.207.32.32:8000/api/v1/recommend");

  // 3.打印请求结果
  if (response.statusCode == HttpStatus.ok) {
    print(response.data);
  } else {
    print("请求失败:${response.statusCode}");
  }
}
dio库的封装

http_config.dart

class HTTPConfig {
  staticconst baseURL = "https://httpbin.org";
  staticconst timeout = 5000;
}

http_request.dart

import'package:dio/dio.dart';
import'package:testflutter001/service/config.dart';

class HttpRequest {
  staticfinal BaseOptions options = BaseOptions(
      baseUrl: HTTPConfig.baseURL, connectTimeout: HTTPConfig.timeout);
  staticfinal Dio dio = Dio(options);

  static Future<T> request<T>(String url,
      {String method = 'get', Map<String, dynamic> params, Interceptor inter}) async {
    // 1.请求的单独配置
    final options = Options(method: method);

    // 2.添加第一个拦截器
    Interceptor dInter = InterceptorsWrapper(
      onRequest: (RequestOptions options) {
        // 1.在进行任何网络请求的时候, 可以添加一个loading显示

        // 2.很多页面的访问必须要求携带Token,那么就可以在这里判断是有Token

        // 3.对参数进行一些处理,比如序列化处理等
        print("拦截了请求");
        return options;
      },
      onResponse: (Response response) {
        print("拦截了响应");
        return response;
      },
      onError: (DioError error) {
        print("拦截了错误");
        return error;
      }
    );
    List<Interceptor> inters = [dInter];
    if (inter != null) {
      inters.add(inter);
    }
    dio.interceptors.addAll(inters);

    // 3.发送网络请求
    try {
      Response response = await dio.request<T>(url, queryParameters: params, options: options);
      return response.data;
    } on DioError catch(e) {
      return Future.error(e);
    }
  }
}

代码使用:

HttpRequest.request("https://httpbin.org/get", params: {"name": "why", 'age': 18}).then((res) {
  print(res);
});

HttpRequest.request("https://httpbin.org/post",
                    method: "post", params: {"name": "why", 'age': 18}).then((res) {
  print(res);
});

路由、导航和事件处理

组件封装

评分五星封装
// ignore_for_file: prefer_const_literals_to_create_immutables, prefer_const_constructors, use_key_in_widget_constructors, prefer_const_constructors_in_immutables
import 'dart:ffi';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
class YJBStarRating extends StatefulWidget {
  final double? rating; //得分
  final double maxRating; //满分
  final int count; //星星个数
  final double size; //大小
  final Color unsetColor;
  final Color selectColor;
  YJBStarRating(
      {@required this.rating,
      this.maxRating = 10,
      this.count = 5,
      this.size = 30,
      this.unsetColor = Colors.grey,
      this.selectColor = Colors.red});
  @override
  State<YJBStarRating> createState() => _YJBStarRatingState();
}
class _YJBStarRatingState extends State<YJBStarRating> {
  @override
  Widget build(BuildContext context) {
    return Stack(children: [
      Row(
        children: buildunsetStar(),
      ),
      Row(children: buildSelectStar())
    ]);
  }
  List<Widget> buildunsetStar() {
    return List.generate(widget.count, (index) {
      return Icon(
        Icons.star_border,
        color: widget.unsetColor,
        size: widget.size,
      );
    });
  }
  List<Widget> buildSelectStar() {
    List<Widget> starList = [];
    final markStar = widget.maxRating / widget.count; //每一个多少分
    final fullStar = (widget.rating! / markStar).floor(); //满的个数
    final double otherStar =
        ((widget.rating! / markStar) - fullStar) * widget.size;
    //满的个数
    for (int i = 0; i < fullStar; i++) {
      starList.add(Icon(
        Icons.star,
        color: widget.selectColor,
        size: widget.size,
      ));
    }
    //裁剪不满的
    starList.add(ClipRect(
        clipper: MyClip(otherStar),
        child: Icon(
          Icons.star,
          color: widget.selectColor,
          size: widget.size,
        )));
    return starList;
  }
}
class MyClip extends CustomClipper<Rect> {
  final double clipSize;
  MyClip(this.clipSize);
  @override
  Rect getClip(Size size) {
    return Rect.fromLTWH(0, 0, clipSize, size.height);
  }
  @override
  bool shouldReclip(MyClip oldClipper) {
    return false;
  }
}

虚线封装
// ignore_for_file: prefer_const_constructors, sized_box_for_whitespace
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
class YJBDashed extends StatelessWidget {
  final Axis axis; //方向
  final int count; //线点
  final double dashHeight; //高度
  final double dashWidth; //宽度
  final Color color; //颜色
  // ignore: use_key_in_widget_constructors
  YJBDashed(
      {@required this.axis = Axis.horizontal,
      this.count = 20,
      this.dashHeight = 2,
      this.color = Colors.grey,
      this.dashWidth = 4});
  @override
  Widget build(BuildContext context) {
    // ignore: avoid_unnecessary_containers
    return Flex(
        direction: axis,
        mainAxisAlignment:MainAxisAlignment.spaceBetween,
        children: List.generate(count, (index) {
          return SizedBox(
            width: dashWidth,
            height: dashHeight,
            child: DecoratedBox(
              decoration: BoxDecoration(color: color),
            ),
          );
        }));
  }
}

底部导航栏

底部导航:和body同级的一个属性:bottomNavigationBar

路由切换:body属性使用IndexedStack

import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import './home/home.dart';
import './mine/mine.dart';

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

  @override
  State<YJBMainPage> createState() => _YJBMainPageState();
}

class _YJBMainPageState extends State<YJBMainPage> {
  int tabIndex = 0;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: IndexedStack(
          index: tabIndex,
          // ignore: prefer_const_literals_to_create_immutables
          children: [YJBHmoe(), YJBMine()],
        ),
        bottomNavigationBar: BottomNavigationBar(
            currentIndex: tabIndex,
            onTap: (value) {
              // ignore: unnecessary_this
              setState(() {this.tabIndex = value;});
            },
            // ignore: prefer_const_literals_to_create_immutables
            items: [
              BottomBarItem(Icons.home,Colors.red,'HOME'),
              BottomBarItem(Icons.person, Colors.red, 'MINE'),
            ]));
  }
  // ignore: non_constant_identifier_names
  BottomNavigationBarItem BottomBarItem(iconType,color,text) {
    return BottomNavigationBarItem(
        icon: Icon(iconType),
        activeIcon: Icon(iconType,color: color,),
        label: text);
  }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值