目录

1. 基础 widget1.1 TextText各种参数多种效果合体实例:俩花活(艺术字)

1.2 Row, Columnrow示例主轴调整大小ExpandedSizedBoxSpacerIconImage实例:小黑子界面

1.3 Container旋转BoxDecoration

1.4 GridView实例:ikun 相册

1.5 ListView1.6 Stack

2. 进阶widget (Material widget)2.1 Card实例:鸡哥商品

2.2 ListTile

一开始没想写这么多的,但是这种开发类的知识很多都是具有连带性的,看了官网的几个基础 widget 后顺带也学习了其他几种常用 widget,这里按照 Row, Column, GridView, Container, ListView, Stack, Card, ListTile为主线整理。

在这之前原来想学先布局的,但是发现布局之前还是有很多的 widget 不熟悉,就尝试了几种基础的 widget,特此记录。

但是个人认为还是要知道一些布局的知识的,不然看起来还是相对吃力的,毕竟widget相对细节很多,布局对于app界面的设计会重要一点。

1. 基础 widget

widget在flutter中是一个非常重要的概念。Flutter 从 React 中吸取灵感,通过现代化框架创建出精美的组件。它的核心思想是用 widget 来构建你的 UI 界面。 Widget 描述了在当前的配置和状态下视图所应该呈现的样子。当 widget 的状态改变时,它会重新构建其描述(展示的 UI),框架则会对比前后变化的不同,以确定底层渲染树从一个状态转换到下一个状态所需的最小更改。

这么讲可能有点抽象,这里放一个例子HelloWorld:

import 'package:flutter/material.dart';

void main() {

// runApp(const MyApp());

runApp(

const Center(

child: Text(

'Hello, world!',

textDirection: TextDirection.ltr,

),

),

);

}

效果如下:

可以看出,这里的runApp持有传入的widget树,这会被作为根widget,这个树内包含一个子widget树Center,Center下包含它的子widget(Text)。

此外,widget最重要的工作之一就是提供build()方法,这可以用更低级的widget的特性来渲染自己的特性。widgets还分有状态和无状态两种形式,无状态的widgets内所有的属性都是final性质的,这意味着它的属性不可改变;相对的,有状态的widgets的属性就可以在其生命周期内改变。那怎么区分有状态和无状态?有状态一般有两个类:

StatefulWidget 类,这个类本身不变State 类,这个类本身存在于整个生命周期中

在flutter中,widget还是很丰富的,我们这里就挑最常用的几个讲,其他的详见Flutter官网。

1.1 Text

Text widget 可以用来在应用内创建带样式的文本。这里放上两个例子,第一个例子偏向于文本各个参数编辑,第二个是多行文字和渐变色。

Text各种参数

注释写的满详细了,图是丑了点,但是基本的几个点都用到了。

import 'package:flutter/material.dart';

void main() {

// runApp(const MyApp());

// 变量

const String _abc = "Clark";

// Text1

runApp(

Text(

// 文本; 文本加入变量

'Hello, world! \n Hello, $_abc!',

// 位置:left, right, center, justify, start, end

textAlign: TextAlign.justify,

// 处理溢出文本:clip(剪掉溢出文本), fade(透明化溢出文本), ellipsis(省略号表溢出), visible(容器外的也可以渲染)

overflow: TextOverflow.fade,

// 文字方向:ltr左到右,rtl右到左

textDirection: TextDirection.ltr,

// 文字风格,这个就很多了。

// FontWeight.bold:加粗

// FontStyle.italic:斜体

// color: Colors.black.withOpacity(0.6):颜色与透明度 => 注意此时不能使用const Text

// fontFamily: 'Raleway':字体

// height: 5:列高

// fontSize: 18:大小

style: TextStyle(

color: Colors.yellow.withOpacity(0.6),

fontWeight: FontWeight.bold,

fontStyle: FontStyle.italic,

fontFamily: 'Raleway',

height: 5,

fontSize: 30,

),

// 可以在默认字体下修改

// style: DefaultTextStyle.of(context).style.apply(fontSizeFactor: 2.0)

),

);

}

多种效果合体

为了在一句话中显示不同样式的文字。这里主要强调一个RichText。

import 'package:flutter/material.dart';

import 'package:english_words/english_words.dart';

void main() {

// Text2

runApp(

// 注意:RichText一定要一个textDirection

RichText(

textDirection: TextDirection.ltr,

textAlign: TextAlign.center,

text: TextSpan(

children: [

TextSpan(

text: "Hello Clark",

style: TextStyle(color: Colors.white.withOpacity(0.2), fontSize: 60),

),

TextSpan(

text: "!!!!!\n",

style: TextStyle(

color: Colors.yellow.withOpacity(0.6),

fontWeight: FontWeight.bold,

fontStyle: FontStyle.italic,

fontFamily: 'Raleway',

height: 5,

fontSize: 30,

),

),

TextSpan(

text: "1234567898765432345678\n",

style: TextStyle(color: Colors.white.withOpacity(1.0)),

),

],

),

),

);

}

效果如下:

实例:俩花活(艺术字)

这里有点点超纲,有用到Stack的知识,但是问题不大。这里的stack就是为了让这俩字体合到一起,大致思路是写一个粗一点的蓝字,然后再给它stark一个细一点的白字。

// Text3

runApp(

Stack(

alignment: Alignment.center,

children: [

// Stroked text as border.

Text(

textDirection: TextDirection.ltr,

'SCQ CXN FOREVER!!!!!',

style: TextStyle(

fontSize: 40,

foreground: Paint()

..style = PaintingStyle.stroke

..strokeWidth = 6

..color = Colors.blue[700]!,

),

),

// Solid text as fill.

Text(

textDirection: TextDirection.ltr,

'SCQ CXN FOREVER!!!!!',

style: TextStyle(

fontSize: 40,

color: Colors.grey[300],

),

),

],

)

);

这个也有点超纲,用到了ShaderMask,既然做到了,就在这里讲一下。

这个东西有点像ps里的蒙板,这里面有三种方法:LinearGradient, RadialGradient, SweepGradient。第一种是线性变化,顺着一个方向渐变颜色,可以横着也可以斜着;第二种是辐射式的变化,有点像中间淡色外边深色的那种;第三种是通过设置开始角度和结束角度,设置渐变范围,按照角度设置。

看上去这仨没啥用,实际上还是非常好用的,在图片处理的时候总有起效。

当然我们这里就是想做个渐变色,上述的一大堆操作都没啥用,就是用了个LinearGradient。

import 'package:flutter/material.dart';

void main() {

// Text4

runApp(

MyApp()

);

}

class MyApp extends StatelessWidget {

@override

Widget build(BuildContext context) {

return MaterialApp(

title: 'Love Story',

theme: ThemeData(

brightness: Brightness.dark,

primaryColor: Colors.blue,

),

home: Gredient(),

);

}

}

class Gredient extends StatelessWidget {

// const ({Key? key}) : super(key: key);

@override

Widget build(BuildContext context) {

return Scaffold(

appBar: AppBar(

title: Text('Love Story'),

),

body: Center(

child: ShaderMask(shaderCallback: (Rect bounds) {

return LinearGradient(

begin: Alignment.centerLeft,

end: Alignment.centerRight,

colors: [Colors.white, Color(0xFFFFBDE9)],

).createShader(Offset.zero & bounds.size);

},

child: Text(

"YWH MJT FOREVER!!!",

style: TextStyle(

color: Colors.white,

fontSize: 50

),

),

)

)

);

}

}

1.2 Row, Column

这两个 flex widgets 可以让你在水平 ( Row ) 和垂直( Column ) 方向创建灵活的布局。它是基于 web 的 flexbox 布局模型设计的。

这俩我们不会像Text那样详细尝试,毕竟是个布局相关的组件,细节没多少。主要以Row为主。

row示例

顾名思义,Row就是为了横向排列的,以下图为例,我们完成了三个板块的横向排列。

代码如下,内含注释:

import 'package:flutter/material.dart';

void main() {

runApp(MyApp());

}

class MyApp extends StatelessWidget {

@override

Widget build(BuildContext context) {

return MaterialApp(

title: 'Love Story',

theme: ThemeData(

// brightness: Brightness.dark,

primaryColor: Colors.yellowAccent,

),

home: TryRow(),

);

}

}

class TryRow extends StatelessWidget {

// const ({Key? key}) : super(key: key);

@override

Widget build(BuildContext context) {

return Scaffold(

appBar: AppBar(

title: Text('Love Story'),

),

// 主体部分展示Row

body: Center(

child: Row(

// 这个仍然适用,这里右到左,显示顺序与代码顺序颠倒

textDirection: TextDirection.rtl,

children: const [

Expanded(

child: Text('Deliver features faster', textAlign: TextAlign.center, style: TextStyle(fontSize: 20),),

),

Expanded(

child: Text('Craft beautiful UIs', textAlign: TextAlign.center),

),

// Expanded的好处是不会存在溢出的情况,溢出部分将自己调整

// 如果是一个简单的const将会导致溢出的文字之类的超出空间

Expanded(

child: FittedBox(

child: FlutterLogo(),

),

),

],

)

)

);

}

}

主轴

row是横向分布的,那所有的元素就是沿着一条横着的主轴排列的,默认情况下,它们将整个轴布满,而轴一般来说是跟屏幕同宽或者同长,跟上一个案例一样。但是可以通过mainAxisSize设置轴的长度、mainAxisAlignment设置对齐关系、使用CrossAxisAlignment设置children在横轴中的定位 。column同理。

具体用法见代码:

Row(

// max, min

mainAxisSize: MainAxisSize.max,

// start, end; children从主轴起点(终点)开始对其

// center: children设置到主轴中心

// spaceBetween: children间平分额外空间

// spaceEvenly: children间,第一个children前最后一个children后评分额外空间

// spaceAround: spaceEvenly种第一个和最后一个children少一半空间

mainAxisAlignment: MainAxisAlignment.spaceAround,

// 这个仍然适用,这里右到左,显示顺序与代码顺序颠倒

textDirection: TextDirection.rtl,

// start, end, center, stretch, baseline(仅限Text类,并要求 textBaseline 属性设置为 TextBaseline.alphabetic)

crossAxisAlignment: CrossAxisAlignment.end,

children: const [

Expanded(

child: Text('Deliver features faster', textAlign: TextAlign.center, style: TextStyle(fontSize: 20),),

),

Expanded(

child: Text('Craft beautiful UIs', textAlign: TextAlign.center),

),

// Expanded的好处是不会存在溢出的情况,溢出部分将自己调整

// 如果是一个简单的const将会导致溢出的文字之类的超出空间

Expanded(

child: FittedBox(

child: FlutterLogo(),

),

),

],

)

调整大小

一般来说是用flexible调整大小的,因为 widget 大小在设置flexible前不可变。

在losse时,方块大小取我们设定的50,出现tight则强制占据剩下的空间,若出现tight+flex的情况,则根据flex大小计算占据的空间。

Expanded

Expanded widget 能够包裹一个 widget 并强制其填满剩余空间,与 Flexible 非常相似。

效果如下:

SizedBox

这个可以用来调整大小,也可以用来创造空间。

Spacer

如果要造空位,Spacer比楼上更合适一点,它可以用flex,且放在children中效果优先级高于mainAxisAlignment。

Icon

插入图标的 widget,没什么注意点,会用就行。

Image

放图片的控件,还是非常重要的。

在放图片前先配置下。先创建assets文件夹,并补上俩子文件夹用于存放图标和图片;再将这俩文件夹写入pubspec.yaml里。

这边就介绍俩放图片的方法,一个是放网上图片的,一个是放项目图片的。

class TryImg extends StatelessWidget {

// const TryIcon({Key? key}) : super(key: key);

@override

Widget build(BuildContext context) {

return Column(

children: [

// 网图

Flexible(

child: Image.network(

'https://pic2.zhimg.com/80/v2-7091ef428b0b3cd8570f6851e0a4e811_720w.webp',

),

fit: FlexFit.loose

),

// 资源图

Flexible(

child: Image.asset(

'assets/images/18.jpg'

),

fit: FlexFit.loose

),

],

);

}

}

效果如下:

实例:小黑子界面

顺便学习下Scaffold。

也有另一个版本,更需要布局这方面的知识

代码如下

import 'package:flutter/material.dart';

void main() {

runApp(MyApp());

}

class MyApp extends StatelessWidget {

@override

Widget build(BuildContext context) {

return MaterialApp(

theme: ThemeData(

brightness: Brightness.dark,

),

home: Scaffold(

body: Center(

child: _Commodity()

),

// 悬浮符号

floatingActionButton:FloatingActionButton(

onPressed: (){

// 这里放点击后的事件

},

// 悬浮符号的符号

child: const Icon(Icons.send),

),

// 悬浮符号放中间

floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,

// 侧拉部分导航

drawer: Drawer(

child: SafeArea(

child:Column( //column widget

children: const [

ListTile(

leading: Icon(Icons.home),

title: Text("Home Page"),

subtitle: Text("Subtitle menu 1"),

),

ListTile(

leading: Icon(Icons.search),

title: Text("Search Page"),

subtitle: Text("Subtitle menu 1"),

),

//put more menu items here

],

),

),

),

// 底部导航

bottomNavigationBar: BottomNavigationBar( //bottom navigation bar on scaffold

items: const [ //items inside navigation bar

BottomNavigationBarItem(

icon: Icon(Icons.add),

label: "Button 1",

),

BottomNavigationBarItem(

icon: Icon(Icons.search),

label: "Button 2",

),

BottomNavigationBarItem(

icon: Icon(Icons.camera),

label: "Button 3",

),

// 要啥再写

],

),

),

);

}

}

class _Commodity extends StatefulWidget {

// const _Commodity({Key? key}) : super(key: key);

@override

State<_Commodity> createState() => _CommodityState();

}

class _CommodityState extends State<_Commodity> {

// 存图片 + 文字

final List goods = [];

// 商品名字体

final _GoodsNameFont = const TextStyle(fontSize: 18);

// 商品简介字体

final _GoodsIntroduceFont = const TextStyle(fontSize: 18);

@override

Widget build(BuildContext context) {

return Scaffold(

body: _buildFrame(),

);

}

Widget _buildFrame() {

final namelist = ["只因", "你", "太美", "迎面走来的你", "让我", "蠢蠢欲动", "就会", "爆炸"];

final introductionlist = ["小黑子露出鸡脚了吧,你食不食油饼", "你干嘛啊啊啊啊啊啊啊 哎呦!!!"];

return ListView.builder(

padding: const EdgeInsets.all(26.0),

// 对每一个商品都用itemBuilder

itemBuilder: (context, i) {

// 加分割线

if (i.isOdd) return const Divider();

i = i % (namelist.length * 2);

final index = i ~/ 2;

// 加货物

return _buildline(namelist[index], introductionlist[index % 2], index);

});

}

Widget _buildline(name, introduction, index) {

// 返回一个Card

// return Card(

// child: ListTile(

// leading: Image.asset("assets/images/"+ index.toString() +".jpg"),

// title: Text(name, style: _GoodsNameFont),

// subtitle: Text(introduction),

// trailing: Icon(Icons.more_vert),

// isThreeLine: true,

// ),

// );

// 也可以返回一个ListTile

return MyListTile(

thumbnail: Container(

child: Image.asset("assets/images/"+ index.toString() +".jpg"),

// 背景色

decoration: const BoxDecoration(color: Colors.pink),

),

// leading: Image.asset("assets/images/"+ index.toString() +".jpg"),

title: name,

subtitle: introduction,

author: 'Dash',

publishDate: 'Dec 28',

readDuration: '5 mins'

);

}

}

// 练布局用的,手写一个布局

class MyListTile extends StatelessWidget {

// 重构函数

const MyListTile({

super.key,

required this.thumbnail,

required this.title,

required this.subtitle,

required this.author,

required this.publishDate,

required this.readDuration,

});

final Widget thumbnail;

final String title;

final String subtitle;

final String author;

final String publishDate;

final String readDuration;

@override

Widget build(BuildContext context) {

// 给一个padding,用来处理容器和组件之间的距离的。

return Padding(

padding: const EdgeInsets.symmetric(vertical: 10.0),

// 定死每个栏目的高度

child: SizedBox(

height: 100,

// 先放方块(图片),再放文字堆叠

child: Row(

crossAxisAlignment: CrossAxisAlignment.start,

// 分两块,一块放图片,一块放_ArticleDescription => 那堆文字的合集

children: [

AspectRatio(

// 长宽比

aspectRatio: 1.0,

child: thumbnail,

),

Expanded(

child: Padding(

padding: const EdgeInsets.fromLTRB(20.0, 0.0, 2.0, 0.0),

child: _ArticleDescription(

title: title,

subtitle: subtitle,

author: author,

publishDate: publishDate,

readDuration: readDuration,

),

),

),

],

),

),

);

}

}

// 那一堆文字的合集

class _ArticleDescription extends StatelessWidget {

const _ArticleDescription({

required this.title,

required this.subtitle,

required this.author,

required this.publishDate,

required this.readDuration,

});

final String title;

final String subtitle;

final String author;

final String publishDate;

final String readDuration;

@override

Widget build(BuildContext context) {

return Column(

crossAxisAlignment: CrossAxisAlignment.start,

children: [

Expanded(

child: Column(

crossAxisAlignment: CrossAxisAlignment.start,

children: [

Text(

title,

maxLines: 2,

overflow: TextOverflow.ellipsis,

style: const TextStyle(

fontWeight: FontWeight.bold,

fontSize: 18,

),

),

// 放一小段空白

const Padding(padding: EdgeInsets.only(bottom: 2.0)),

Text(

subtitle,

maxLines: 2,

overflow: TextOverflow.ellipsis,

style: const TextStyle(

fontSize: 14.0,

color: Colors.white70,

),

),

],

),

),

Expanded(

child: Column(

crossAxisAlignment: CrossAxisAlignment.start,

mainAxisAlignment: MainAxisAlignment.end,

children: [

Text(

author,

style: const TextStyle(

fontSize: 12.0,

color: Colors.white70,

),

),

Text(

'$publishDate - $readDuration',

style: const TextStyle(

fontSize: 12.0,

color: Colors.white70,

),

),

],

),

),

],

);

}

}

1.3 Container

Container 这里跟 bootstrap 里面真的很像,在布局方面,个人感觉官网的这个图讲的还是比较清楚的,其实跟 html 大差不差,都是在调这些东西。

import 'package:flutter/material.dart';

void main() {

runApp(MyApp());

}

class MyApp extends StatelessWidget {

@override

Widget build(BuildContext context) {

return MaterialApp(

title: 'Love Story',

theme: ThemeData(

brightness: Brightness.dark,

// primaryColor: Colors.yellowAccent,

),

home: const Scaffold(

body: layout(),

)

);

}

}

class layout extends StatelessWidget {

const layout({Key? key}) : super(key: key);

@override

Widget build(BuildContext context) {

return _buildImageColumn();

}

}

// Container框架

Widget _buildImageColumn() {

return Container(

height: 350,

decoration: const BoxDecoration(

color: Colors.white30,

),

child: Column(

children: [

_buildImageRow(1),

_buildImageRow(3),

],

),

);

}

Widget _buildDecoratedImage(int imageIndex) => Expanded(

child: Container(

decoration: BoxDecoration(

border: Border.all(width: 10, color: Colors.white70),

borderRadius: const BorderRadius.all(Radius.circular(8)),

),

margin: const EdgeInsets.all(4),

child: Image.asset('assets/images/$imageIndex.jpg'),

),

);

Widget _buildImageRow(int imageIndex) => Row(

children: [

_buildDecoratedImage(imageIndex),

_buildDecoratedImage(imageIndex + 1),

],

);

强调几个特点:

在没有限制的情况下,Container 大小会尽可能大。以上图为例,如果没有 height 的限制,这个 Container 将布满屏幕。Container 需要遵守对齐方式,根据子项调整大小,尊重宽度、高度和约束,扩展以适合父项,尽可能小。如果 widget 没有子项且没有对齐方式,但提供了高度、宽度或约束,Container 会尝试尽可能小,给定这些约束和父约束的组合。

旋转

最基础的操作,顺便提一下 Theme.of(context).textTheme.headline n! 和 transform。

代码如下

import 'package:flutter/material.dart';

void main() {

runApp(MyApp());

}

class MyApp extends StatelessWidget {

@override

Widget build(BuildContext context) {

return MaterialApp(

title: 'Love Story',

theme: ThemeData(

brightness: Brightness.dark,

// primaryColor: Colors.yellowAccent,

),

home: const Scaffold(

body: layout(),

)

);

}

}

class layout extends StatelessWidget {

const layout({Key? key}) : super(key: key);

@override

Widget build(BuildContext context) {

return _buildInline(context);

}

}

// 斜体 Container

Widget _buildInline(BuildContext context){

return Center(

child: Container(

// 调用主题标题 Theme.of(context).textTheme.headline4!

// 限制大小

constraints: BoxConstraints.expand(

height: Theme.of(context).textTheme.headline4!.fontSize! * 1.1 + 200.0,

),

padding: const EdgeInsets.all(8.0),

color: Colors.blue[600],

// 内容居中

alignment: Alignment.center,

// 旋转效果

transform: Matrix4.rotationZ(0.1),

child: Text('Hello World',

style: Theme.of(context)

.textTheme

.headline4!

.copyWith(color: Colors.white)),

)

);

}

BoxDecoration

这个就是外面的那个壳子。

import 'package:flutter/material.dart';

void main() {

runApp(MyApp());

}

class MyApp extends StatelessWidget {

@override

Widget build(BuildContext context) {

return MaterialApp(

title: 'Love Story',

theme: ThemeData(

brightness: Brightness.dark,

// primaryColor: Colors.yellowAccent,

),

home: const Scaffold(

body: layout(),

)

);

}

}

class layout extends StatelessWidget {

const layout({Key? key}) : super(key: key);

@override

Widget build(BuildContext context) {

return _buildBox(context);

}

}

// box例子

Widget _buildBox(BuildContext context){

return Center(

child: Container(

height: Theme.of(context).textTheme.headline1!.fontSize! * 3.5,

decoration: BoxDecoration(

color: const Color(0xff7c94b6),

image: const DecorationImage(

// 这里跟正常的image操作有点不一样

image: AssetImage("assets/images/4.jpg"),

// BoxFit.fill:充满父容器,所以会变形、拉伸

// BoxFit.contain:尽可能大,保持图片分辨率,适应宽度或长度,会有留白

// BoxFit.fitWidth:图片填满宽度,高度可能会被截断

// BoxFit.fitHeight:图片填满高度,宽度可能会被截断

// BoxFit.cover:充满容器,可能会被截断

fit: BoxFit.fitHeight,

),

border: Border.all(

width: 8,

),

borderRadius: BorderRadius.circular(12),

boxShadow: const [

// 这几个参数自己搞,我是真没感觉,会搞就好了

BoxShadow(

color: Colors.purple,

offset: Offset(5.0, 5.0),

blurRadius: 10.0,

spreadRadius: 2.0,

),

BoxShadow(

color: Colors.white,

offset: Offset(100.0, 50.0),

blurRadius: 200.0,

spreadRadius: 10.0,

),

],

),

)

);

}

1.4 GridView

这个倒是跟正常安卓程序的网格布局大差不差。它将 widget 作为二维列表展示,提供两个预制的列表,或者你可以自定义网格。当检测到内容太长而无法适应渲染盒时,它就会自动支持滚动。最经典的网格布局的案例就是相册了:

几个注意点:

内容超出 height 时会产生滚动效果。一般来说这个是由二维行列表组成的,这个时候 Table 和 DataTable 就比较常用了。

实例:ikun 相册

尝试一个相册实例,注意图片的标题操作和网格操作的几个参数,不熟悉面向对象编程的读者还可以注意下面向对象的类创建与使用。这段代码是我从 gallery 上扒下来的,稍作修改。类似的样式可以复现。

import 'package:flutter/material.dart';

void main() {

runApp(MyApp());

}

class MyApp extends StatelessWidget {

@override

Widget build(BuildContext context) {

return MaterialApp(

title: 'Love Story',

theme: ThemeData(

brightness: Brightness.dark,

// primaryColor: Colors.yellowAccent,

),

home: const Scaffold(

body: GridGallery(type: GridListDemoType.header),

)

);

}

}

// 一共三种模式可供选择

enum GridListDemoType {

imageOnly,

header,

footer,

}

// 网格

class GridGallery extends StatelessWidget {

// const GridGallery({Key? key}) : super(key: key);

const GridGallery({super.key, required this.type});

final String Title = "你干嘛啊啊啊啊啊~~~";

final String Subtitle = "小鸡子露出黑脚了吧,你食不食油饼,我抱井惹";

final GridListDemoType type;

// 存一个类的列表

List<_Photo> _photos(BuildContext context) {

return [

_Photo(assetName: "assets/images/1.jpg", title: Title, subtitle: Subtitle),

_Photo(assetName: "assets/images/2.jpg", title: Title, subtitle: Subtitle),

_Photo(assetName: "assets/images/3.jpg", title: Title, subtitle: Subtitle),

_Photo(assetName: "assets/images/4.jpg", title: Title, subtitle: Subtitle),

_Photo(assetName: "assets/images/5.jpg", title: Title, subtitle: Subtitle),

_Photo(assetName: "assets/images/6.jpg", title: Title, subtitle: Subtitle),

_Photo(assetName: "assets/images/7.jpg", title: Title, subtitle: Subtitle),

_Photo(assetName: "assets/images/8.jpg", title: Title, subtitle: Subtitle),

_Photo(assetName: "assets/images/9.jpg", title: Title, subtitle: Subtitle),

_Photo(assetName: "assets/images/10.jpg", title: Title, subtitle: Subtitle),

_Photo(assetName: "assets/images/11.jpg", title: Title, subtitle: Subtitle),

_Photo(assetName: "assets/images/0.jpg", title: Title, subtitle: Subtitle),

];

}

@override

Widget build(BuildContext context) {

return Scaffold(

appBar: AppBar(

automaticallyImplyLeading: false,

title: const Text("< 我家giegie"),

),

body: GridView.count(

restorationId: 'grid_view_demo_grid_offset',

//

crossAxisCount: 2,

mainAxisSpacing: 8,

crossAxisSpacing: 8,

// 格子之间的padding

padding: const EdgeInsets.all(8),

// 圆角

childAspectRatio: 1,

// 一整个输出

children: _photos(context).map((photo) {

return _GridDemoPhotoItem(

photo: photo,

tileStyle: type,

);

}).toList(),

),

bottomNavigationBar: BottomNavigationBar( //bottom navigation bar on scaffold

items: const [

BottomNavigationBarItem(

icon: Icon(Icons.collections),

label: "图库",

),

BottomNavigationBarItem(

icon: Icon(Icons.collections_bookmark),

label: "为您推荐",

),

BottomNavigationBarItem(

icon: Icon(Icons.photo_library),

label: "相簿",

),

BottomNavigationBarItem(

icon: Icon(Icons.search),

label: "搜索",

),

],

// 被选中图标颜色

fixedColor: Colors.greenAccent,

// 底部背景色

backgroundColor: Colors.white30,

// 显示所有图标的label

showUnselectedLabels: true,

// 未选中文字颜色

unselectedItemColor:Colors.white70,

// 阴影长度

elevation: 20,

)

);

}

}

// 定义类

class _Photo {

_Photo({

required this.assetName,

required this.title,

required this.subtitle,

});

final String assetName;

final String title;

final String subtitle;

}

// 细节操作:调整图片大小适应窗口

class _GridTitleText extends StatelessWidget {

// 初始化

const _GridTitleText(this.text);

final String text;

@override

Widget build(BuildContext context) {

return FittedBox(

// 适应大小

fit: BoxFit.scaleDown,

// 对齐方向:

// bottomCenter bottomEnd bottomStart 底部居中,底部结尾,底部开始

// center centerEnd centerStart

// topCenter topEnd topStart

alignment: AlignmentDirectional.centerStart,

child: Text(text),

);

}

}

class _GridDemoPhotoItem extends StatelessWidget {

const _GridDemoPhotoItem({

required this.photo,

required this.tileStyle,

});

final _Photo photo;

final GridListDemoType tileStyle;

@override

Widget build(BuildContext context) {

final Widget image = Semantics(

label: '${photo.title} ${photo.subtitle}',

child: Material(

shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)),

clipBehavior: Clip.antiAlias,

child: Image.asset(

photo.assetName,

// package: 'flutter_gallery_assets',

fit: BoxFit.cover,

),

),

);

switch (tileStyle) {

case GridListDemoType.imageOnly:

return image;

case GridListDemoType.header:

return GridTile(

header: Material(

color: Colors.transparent,

shape: const RoundedRectangleBorder(

borderRadius: BorderRadius.vertical(top: Radius.circular(4)),

),

clipBehavior: Clip.antiAlias,

child: GridTileBar(

title: _GridTitleText(photo.title),

backgroundColor: Colors.black45,

),

),

child: image,

);

case GridListDemoType.footer:

return GridTile(

footer: Material(

color: Colors.transparent,

shape: const RoundedRectangleBorder(

borderRadius: BorderRadius.vertical(bottom: Radius.circular(4)),

),

clipBehavior: Clip.antiAlias,

child: GridTileBar(

backgroundColor: Colors.black45,

title: _GridTitleText(photo.title),

subtitle: _GridTitleText(photo.subtitle),

),

),

child: image,

);

}

}

}

1.5 ListView

有点像被定义好的下拉列表,好用的。一般常与 Divider 分割线联用。

布局那篇博客涉及了此部分内容,这里不重复,放一个例子:

代码如下

import 'package:flutter/material.dart';

void main() {

runApp(MyApp());

}

class MyApp extends StatelessWidget {

@override

Widget build(BuildContext context) {

return MaterialApp(

title: 'Love Story',

theme: ThemeData(

brightness: Brightness.light,

// primaryColor: Colors.yellowAccent,

),

home: ColumnListView(),

);

}

}

class ColumnListView extends StatelessWidget {

const ColumnListView({Key? key}) : super(key: key);

@override

Widget build(BuildContext context) {

final List entries = ['A', 'B', 'C', 'D', 'E', 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'];

final List colorCodes = [600, 500, 400, 300, 200, 100];

return Scaffold(

body: Column(

children: [

Column(

children: [

Text("1111111111111111111111111111111111111111111111111111111111111111"),

Text("222222"),

],

),

ListView.separated(

// 这个参数绝对不能省略

shrinkWrap: true,

padding: const EdgeInsets.all(8),

// 自带的迭代器,可以实现循环类似的功能

itemBuilder: (BuildContext context, int index) {

return Container(

height: 50,

color: Colors.amber[colorCodes[index]],

child: Center(child: Text('Entry ${entries[index]}')),

);

},

// 分割符号

separatorBuilder: (BuildContext context, int index) => const Divider(),

// 提前告诉ListView下有几个items

itemCount: entries.length

),

]

),

);

}

}

1.6 Stack

堆叠。顾名思义,就是把几个控件放到一起,有点像PS里各个图层叠到一起,因此有些妙用。

最经典的案例就是头像,在图片上套一个圆形的边框就好了:

几个注意点:

Stack 是定死的,不能像之前 ListView, Container 那样可以在内容数据溢出时可滚动。可以剪切掉超出渲染框的子项,所以才有上面圆形头像的操作。

简单实现一个:

import 'package:flutter/material.dart';

void main() {

runApp(MyApp());

}

class MyApp extends StatelessWidget {

@override

Widget build(BuildContext context) {

return MaterialApp(

title: 'Love Story',

theme: ThemeData(

brightness: Brightness.dark,

// primaryColor: Colors.yellowAccent,

),

home: const Scaffold(

// body: GridGallery(type: GridListDemoType.header),

body: layout(),

)

);

}

}

class layout extends StatelessWidget {

const layout({Key? key}) : super(key: key);

@override

Widget build(BuildContext context) {

return _buildStack();

}

}

// 头像

Widget _buildStack() {

return Center(

child: Stack(

alignment: const Alignment(0.6, 0.6),

children: [

const CircleAvatar(

backgroundImage: AssetImage('assets/images/2.jpg'),

radius: 100,

),

Container(

decoration: const BoxDecoration(

color: Colors.black45,

),

child: const Text(

'Olsen',

style: TextStyle(

fontSize: 20,

fontWeight: FontWeight.bold,

color: Colors.white,

),

),

),

],

)

);

}

2. 进阶widget (Material widget)

2.1 Card

有一说一这玩意还是被好用的。

注意点:

默认情况下 Card 是无限小的。Card 可识别为单个包含的单元。Card 可以独立存在,而不依赖于周围的元素作为上下文。一个 Card 不能与另一个 Card 合并,也不能分成多个 Card 。

这边尝试探索下 Card 的几个参数,做一个列表,因为可能会在之后的大作业中用到:

import 'package:flutter/material.dart';

void main() {

runApp(MyApp());

}

class MyApp extends StatelessWidget {

@override

Widget build(BuildContext context) {

return MaterialApp(

title: 'Love Story',

theme: ThemeData(

brightness: Brightness.dark,

// primaryColor: Colors.yellowAccent,

),

home: const Scaffold(

// body: GridGallery(type: GridListDemoType.header),

body: layout(),

)

);

}

}

class layout extends StatelessWidget {

const layout({Key? key}) : super(key: key);

@override

Widget build(BuildContext context) {

return _buildCard2();

}

}

// Card

Widget _buildCard2() {

return Center(

child: MaterialApp(

theme: ThemeData(

colorSchemeSeed: const Color(0xff6750a4), useMaterial3: true),

home: Scaffold(

appBar: AppBar(title: const Text('Card Examples')),

body: Column(

children: const [

Spacer(),

ElevatedCardExample(),

FilledCardExample(),

OutlinedCardExample(),

MyCardExample(),

Spacer(),

],

),

),

),

);

}

class ElevatedCardExample extends StatelessWidget {

const ElevatedCardExample({super.key});

@override

Widget build(BuildContext context) {

return const Center(

child: Card(

// Card 没大小,要靠里面的东西撑起来

child: SizedBox(

width: 300,

height: 75,

child: Center(child: Text('Elevated Card')),

),

),

);

}

}

class FilledCardExample extends StatelessWidget {

const FilledCardExample({super.key});

@override

Widget build(BuildContext context) {

return Center(

child: Card(

// 阴影

elevation: 1,

color: Theme.of(context).colorScheme.surfaceVariant,

child: const SizedBox(

width: 300,

height: 75,

child: Center(child: Text('Filled Card')),

),

),

);

}

}

class OutlinedCardExample extends StatelessWidget {

const OutlinedCardExample({super.key});

@override

Widget build(BuildContext context) {

return Center(

child: Card(

elevation: 0,

// 边框

shape: RoundedRectangleBorder(

side: BorderSide(

color: Theme.of(context).colorScheme.outline,

),

// 圆角

borderRadius: const BorderRadius.all(Radius.circular(12)),

),

child: const SizedBox(

width: 300,

height: 75,

child: Center(child: Text('Outlined Card')),

),

),

);

}

}

class MyCardExample extends StatelessWidget {

const MyCardExample({Key? key}) : super(key: key);

@override

Widget build(BuildContext context) {

return Center(

child: Card(

child: SizedBox(

height: 500,

width: 300,

child: Column(

children: [

Padding(

padding: EdgeInsets.fromLTRB(5, 5, 5, 0),

child: Container(

width: 280,

height: 190,

// child: Image.asset("assets/images/3.jpg"),

decoration: BoxDecoration(

image: DecorationImage(image: AssetImage('assets/images/3.jpg')),

borderRadius:const BorderRadius.all(Radius.circular(10))

)

),

),

Column(

mainAxisAlignment: MainAxisAlignment.start,

crossAxisAlignment: CrossAxisAlignment.start,

children: [

Padding(

padding: EdgeInsets.fromLTRB(20, 20, 6, 0),

child: Text("Headline", style: TextStyle(fontSize: Theme.of(context).textTheme.headline4!.fontSize))

),

Padding(

padding: EdgeInsets.fromLTRB(20, 10, 6, 0),

child: Text("SubHead", style: TextStyle(fontSize: Theme.of(context).textTheme.headline6!.fontSize))

),

Padding(

padding: EdgeInsets.fromLTRB(20, 10, 6, 0),

child: Text("Supporting text Supporting text Supporting text Supporting text Supporting text", style: TextStyle(fontSize: Theme.of(context).textTheme.bodyText1!.fontSize))

),

SizedBox(

height: 30,

),

ButtonBar(

children: [

FloatingActionButton.extended(

onPressed: () {},

backgroundColor: Colors.purple.shade100,

icon: const Icon(Icons.save),

label: const Text("Buy"),

),

FloatingActionButton.extended(

onPressed: () {},

backgroundColor: Colors.purple.shade100,

icon: const Icon(Icons.save),

label: const Text("Save"),

)

]

)

],

),

],

),

)

),

);

}

}

实例:鸡哥商品

图片 + 介绍卡片,也不错,可能会在大作业中用到,商品介绍之类的里面可能会有用,加个图片轮播就变成淘宝复现了。

import 'package:flutter/material.dart';

// 图片轮播的包

import 'package:flutter_swiper/flutter_swiper.dart';

void main() {

runApp(MyApp());

}

class MyApp extends StatelessWidget {

@override

Widget build(BuildContext context) {

return MaterialApp(

title: 'Love Story',

theme: ThemeData(

brightness: Brightness.dark,

// primaryColor: Colors.yellowAccent,

),

home: const Scaffold(

// body: GridGallery(type: GridListDemoType.header),

body: layout(),

)

);

}

}

class layout extends StatelessWidget {

const layout({Key? key}) : super(key: key);

@override

Widget build(BuildContext context) {

return _buildCard2();

}

}

// Card

Widget _buildCard2() {

return Center(

child: MaterialApp(

theme: ThemeData(

colorSchemeSeed: const Color(0xff6750a4), useMaterial3: true),

home: Scaffold(

appBar: AppBar(title: const Text('Card Examples')),

body: Column(

children: [

Spacer(),

ElevatedCardExample(),

FilledCardExample(),

OutlinedCardExample(),

MyCardExample(),

Spacer(),

],

),

),

),

);

}

class ElevatedCardExample extends StatelessWidget {

const ElevatedCardExample({super.key});

@override

Widget build(BuildContext context) {

return const Center(

child: Card(

// Card 没大小,要靠里面的东西撑起来

child: SizedBox(

width: 300,

height: 75,

child: Center(child: Text('Elevated Card')),

),

),

);

}

}

class FilledCardExample extends StatelessWidget {

const FilledCardExample({super.key});

@override

Widget build(BuildContext context) {

return Center(

child: Card(

// 阴影

elevation: 1,

color: Theme.of(context).colorScheme.surfaceVariant,

child: const SizedBox(

width: 300,

height: 75,

child: Center(child: Text('Filled Card')),

),

),

);

}

}

class OutlinedCardExample extends StatelessWidget {

const OutlinedCardExample({super.key});

@override

Widget build(BuildContext context) {

return Center(

child: Card(

elevation: 0,

// 边框

shape: RoundedRectangleBorder(

side: BorderSide(

color: Theme.of(context).colorScheme.outline,

),

// 圆角

borderRadius: const BorderRadius.all(Radius.circular(12)),

),

child: const SizedBox(

width: 300,

height: 75,

child: Center(child: Text('Outlined Card')),

),

),

);

}

}

class MyCardExample extends StatelessWidget {

// const MyCardExample({Key? key}) : super(key: key);

List imageList = [

{

"asset":"assets/images/8.jpg"

},

{

"asset":"assets/images/2.jpg"

},

{

"asset":"assets/images/3.jpg"

},

{

"asset":"assets/images/4.jpg"

}

];

@override

Widget build(BuildContext context) {

return Center(

child: Card(

shape: RoundedRectangleBorder(//设置圆角

borderRadius: BorderRadius.circular(5),

),

child: SizedBox(

height: 500,

width: 300,

child: Column(

children: [

Padding(

padding: EdgeInsets.fromLTRB(5, 5, 5, 0),

child: Container(

width: 300,

height: 190,

// child: Image.asset("assets/images/3.jpg"),

child: Container(

decoration: const BoxDecoration(

color: Colors.deepPurple,

borderRadius: BorderRadius.only(

topLeft: Radius.circular(5), topRight: Radius.circular(5)),

),

child: Swiper(

itemBuilder: (BuildContext context, int index){

return Image.asset(imageList[index]["asset"], fit: BoxFit.fill,);

},

itemCount: imageList.length,

pagination: SwiperPagination(),

control: SwiperControl(),

loop: true,

autoplay: true,

),

),

// decoration: BoxDecoration(

// image: DecorationImage(image: AssetImage('assets/images/3.jpg')),

// borderRadius:const BorderRadius.all(Radius.circular(10))

// )

),

),

Column(

mainAxisAlignment: MainAxisAlignment.start,

crossAxisAlignment: CrossAxisAlignment.start,

children: [

Padding(

padding: EdgeInsets.fromLTRB(0, 20, 6, 0),

child: ListTile(

leading: CircleAvatar(

backgroundImage: AssetImage("assets/images/ji.jpg"),

),

title: Text("只因你们太美", style: TextStyle(fontSize: Theme.of(context).textTheme.headline4!.fontSize)),

)

),

Padding(

padding: EdgeInsets.fromLTRB(20, 10, 6, 0),

child: Text("就会爆炸", style: TextStyle(fontSize: Theme.of(context).textTheme.headline6!.fontSize))

),

Padding(

padding: EdgeInsets.fromLTRB(20, 10, 6, 0),

child: Text("只因你实在是太美 baby 只因你太美 baby 迎面走来的你让我如此蠢蠢欲动 这种感觉我从未有", style: TextStyle(fontSize: Theme.of(context).textTheme.bodyText1!.fontSize))

),

// SizedBox(

// height: 30,

// ),

Align(

alignment: Alignment.bottomRight,

child:

ButtonBar(

children: [

FloatingActionButton.extended(

onPressed: () {},

backgroundColor: Colors.purple.shade100,

icon: const Icon(Icons.save),

label: const Text("Buy"),

),

FloatingActionButton.extended(

onPressed: () {},

backgroundColor: Colors.purple.shade100,

icon: const Icon(Icons.save),

label: const Text("Save"),

)

]

)

)

],

),

],

),

)

),

);

}

}

2.2 ListTile

这个已经被多次使用了,最经典的案例就是个人名片,因为这个自带 heading 放头像, title, subtitle, text 这种可以放不同层级内容的部分。

代码如下

import 'package:flutter/material.dart';

void main() {

runApp(MyApp());

}

class MyApp extends StatelessWidget {

@override

Widget build(BuildContext context) {

return MaterialApp(

title: 'Love Story',

theme: ThemeData(

brightness: Brightness.dark,

// primaryColor: Colors.yellowAccent,

),

home: const Scaffold(

// body: GridGallery(type: GridListDemoType.header),

body: layout(),

)

);

}

}

class layout extends StatelessWidget {

const layout({Key? key}) : super(key: key);

@override

Widget build(BuildContext context) {

return _buildCard();

}

}

// Card

Widget _buildCard() {

return Center(

child: SizedBox(

height: 210,

child: Card(

child: Column(

children: [

ListTile(

title: const Text(

'1625 Main Street',

style: TextStyle(fontWeight: FontWeight.w500),

),

subtitle: const Text('My City, CA 99984'),

leading: Icon(

Icons.restaurant_menu,

color: Colors.blue[500],

),

),

const Divider(),

ListTile(

title: const Text(

'(408) 555-1212',

style: TextStyle(fontWeight: FontWeight.w500),

),

leading: Icon(

Icons.contact_phone,

color: Colors.blue[500],

),

),

ListTile(

title: const Text('costa@example.com'),

leading: Icon(

Icons.contact_mail,

color: Colors.blue[500],

),

),

],

),

),

)

);

}

参考阅读

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: